]> git.evergreen-ils.org Git - Evergreen.git/blob - Open-ILS/src/sql/Pg/1.6.1-2.0-upgrade-db.sql
Enable "maintain control numbers" and "record ID as TCN" behavior by default
[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 -- ARG! VIM! '
15
16 ALTER TABLE permission.grp_perm_map        DROP CONSTRAINT grp_perm_map_perm_fkey;
17 ALTER TABLE permission.usr_perm_map        DROP CONSTRAINT usr_perm_map_perm_fkey;
18 ALTER TABLE permission.usr_object_perm_map DROP CONSTRAINT usr_object_perm_map_perm_fkey;
19 ALTER TABLE booking.resource_type          DROP CONSTRAINT brt_name_or_record_once_per_owner;
20 ALTER TABLE booking.resource_type          DROP CONSTRAINT brt_name_once_per_owner;
21
22 \qecho Beginning the transaction now
23
24 BEGIN;
25
26 UPDATE biblio.record_entry SET marc = '<record xmlns="http://www.loc.gov/MARC21/slim"/>' WHERE id = -1;
27
28 -- Highest-numbered individual upgrade script incorporated herein:
29
30 INSERT INTO config.upgrade_log (version) VALUES ('0461');
31
32 -- Remove some uses of the connectby() function from the tablefunc contrib module
33 CREATE OR REPLACE FUNCTION actor.org_unit_descendants( INT, INT ) RETURNS SETOF actor.org_unit AS $$
34     WITH RECURSIVE descendant_depth AS (
35         SELECT  ou.id,
36                 ou.parent_ou,
37                 out.depth
38           FROM  actor.org_unit ou
39                 JOIN actor.org_unit_type out ON (out.id = ou.ou_type)
40                 JOIN anscestor_depth ad ON (ad.id = ou.id)
41           WHERE ad.depth = $2
42             UNION ALL
43         SELECT  ou.id,
44                 ou.parent_ou,
45                 out.depth
46           FROM  actor.org_unit ou
47                 JOIN actor.org_unit_type out ON (out.id = ou.ou_type)
48                 JOIN descendant_depth ot ON (ot.id = ou.parent_ou)
49     ), anscestor_depth AS (
50         SELECT  ou.id,
51                 ou.parent_ou,
52                 out.depth
53           FROM  actor.org_unit ou
54                 JOIN actor.org_unit_type out ON (out.id = ou.ou_type)
55           WHERE ou.id = $1
56             UNION ALL
57         SELECT  ou.id,
58                 ou.parent_ou,
59                 out.depth
60           FROM  actor.org_unit ou
61                 JOIN actor.org_unit_type out ON (out.id = ou.ou_type)
62                 JOIN anscestor_depth ot ON (ot.parent_ou = ou.id)
63     ) SELECT ou.* FROM actor.org_unit ou JOIN descendant_depth USING (id);
64 $$ LANGUAGE SQL;
65
66 CREATE OR REPLACE FUNCTION actor.org_unit_descendants( INT ) RETURNS SETOF actor.org_unit AS $$
67     WITH RECURSIVE descendant_depth AS (
68         SELECT  ou.id,
69                 ou.parent_ou,
70                 out.depth
71           FROM  actor.org_unit ou
72                 JOIN actor.org_unit_type out ON (out.id = ou.ou_type)
73           WHERE ou.id = $1
74             UNION ALL
75         SELECT  ou.id,
76                 ou.parent_ou,
77                 out.depth
78           FROM  actor.org_unit ou
79                 JOIN actor.org_unit_type out ON (out.id = ou.ou_type)
80                 JOIN descendant_depth ot ON (ot.id = ou.parent_ou)
81     ) SELECT ou.* FROM actor.org_unit ou JOIN descendant_depth USING (id);
82 $$ LANGUAGE SQL;
83
84 CREATE OR REPLACE FUNCTION actor.org_unit_ancestors( INT ) RETURNS SETOF actor.org_unit AS $$
85     WITH RECURSIVE anscestor_depth AS (
86         SELECT  ou.id,
87                 ou.parent_ou
88           FROM  actor.org_unit ou
89           WHERE ou.id = $1
90             UNION ALL
91         SELECT  ou.id,
92                 ou.parent_ou
93           FROM  actor.org_unit ou
94                 JOIN anscestor_depth ot ON (ot.parent_ou = ou.id)
95     ) SELECT ou.* FROM actor.org_unit ou JOIN anscestor_depth USING (id);
96 $$ LANGUAGE SQL;
97
98 -- Support merge template buckets
99 INSERT INTO container.biblio_record_entry_bucket_type (code,label) VALUES ('template_merge','Template Merge Container');
100
101 -- Recreate one of the constraints that we just dropped,
102 -- under a different name:
103
104 ALTER TABLE booking.resource_type
105         ADD CONSTRAINT brt_name_and_record_once_per_owner UNIQUE(owner, name, record);
106
107 -- Now upgrade permission.perm_list.  This is fairly complicated.
108
109 -- Add ON UPDATE CASCADE to some foreign keys so that, when we renumber the
110 -- permissions, the dependents will follow and stay in sync:
111
112 ALTER TABLE permission.grp_perm_map ADD CONSTRAINT grp_perm_map_perm_fkey FOREIGN KEY (perm)
113     REFERENCES permission.perm_list (id) ON UPDATE CASCADE ON DELETE RESTRICT DEFERRABLE INITIALLY DEFERRED;
114
115 ALTER TABLE permission.usr_perm_map ADD CONSTRAINT usr_perm_map_perm_fkey FOREIGN KEY (perm)
116     REFERENCES permission.perm_list (id) ON UPDATE CASCADE ON DELETE RESTRICT DEFERRABLE INITIALLY DEFERRED;
117
118 ALTER TABLE permission.usr_object_perm_map ADD CONSTRAINT usr_object_perm_map_perm_fkey FOREIGN KEY (perm)
119     REFERENCES permission.perm_list (id) ON UPDATE CASCADE ON DELETE RESTRICT DEFERRABLE INITIALLY DEFERRED;
120
121 UPDATE permission.perm_list
122     SET code = 'UPDATE_ORG_UNIT_SETTING.credit.payments.allow'
123     WHERE code = 'UPDATE_ORG_UNIT_SETTING.global.credit.allow';
124
125 -- The following UPDATES were originally in an individual upgrade script, but should
126 -- no longer be necessary now that the foreign key has an ON UPDATE CASCADE clause.
127 -- We retain the UPDATES here, commented out, as historical relics.
128
129 -- UPDATE permission.grp_perm_map SET perm = perm + 1000 WHERE perm NOT IN ( SELECT id FROM permission.perm_list );
130 -- UPDATE permission.usr_perm_map SET perm = perm + 1000 WHERE perm NOT IN ( SELECT id FROM permission.perm_list );
131
132 -- Spelling correction
133 UPDATE permission.perm_list SET code = 'ADMIN_RECURRING_FINE_RULE' WHERE code = 'ADMIN_RECURING_FINE_RULE';
134
135 -- Now we engage in a Great Renumbering of the permissions in permission.perm_list,
136 -- in order to clean up accumulated cruft.
137
138 -- The first step is to establish some triggers so that, when we change the id of a permission,
139 -- the associated translations are updated accordingly.
140
141 CREATE OR REPLACE FUNCTION oils_i18n_update_apply(old_ident TEXT, new_ident TEXT, hint TEXT) RETURNS VOID AS $_$
142 BEGIN
143
144     EXECUTE $$
145         UPDATE  config.i18n_core
146           SET   identity_value = $$ || quote_literal( new_ident ) || $$ 
147           WHERE fq_field LIKE '$$ || hint || $$.%' 
148                 AND identity_value = $$ || quote_literal( old_ident ) || $$;$$;
149
150     RETURN;
151
152 END;
153 $_$ LANGUAGE PLPGSQL;
154
155 CREATE OR REPLACE FUNCTION oils_i18n_id_tracking(/* hint */) RETURNS TRIGGER AS $_$
156 BEGIN
157     PERFORM oils_i18n_update_apply( OLD.id::TEXT, NEW.id::TEXT, TG_ARGV[0]::TEXT );
158     RETURN NEW;
159 END;
160 $_$ LANGUAGE PLPGSQL;
161
162 CREATE OR REPLACE FUNCTION oils_i18n_code_tracking(/* hint */) RETURNS TRIGGER AS $_$
163 BEGIN
164     PERFORM oils_i18n_update_apply( OLD.code::TEXT, NEW.code::TEXT, TG_ARGV[0]::TEXT );
165     RETURN NEW;
166 END;
167 $_$ LANGUAGE PLPGSQL;
168
169
170 CREATE TRIGGER maintain_perm_i18n_tgr
171     AFTER UPDATE ON permission.perm_list
172     FOR EACH ROW EXECUTE PROCEDURE oils_i18n_id_tracking('ppl');
173
174 -- Next, create a new table as a convenience for sloshing data back and forth,
175 -- and for recording which permission went where.  It looks just like
176 -- permission.perm_list, but with two extra columns: one for the old id, and one to
177 -- distinguish between predefined permissions and non-predefined permissions.
178
179 -- This table is, in effect, a temporary table, because we can drop it once the
180 -- upgrade is complete.  It is not technically temporary as far as PostgreSQL is
181 -- concerned, because we don't want it to disappear at the end of the session.
182 -- We keep it around so that we have a map showing the old id and the new id for
183 -- each permission.  However there is no IDL entry for it, nor is it defined
184 -- in the base sql files.
185
186 CREATE TABLE permission.temp_perm (
187         id          INT        PRIMARY KEY,
188         code        TEXT       UNIQUE,
189         description TEXT,
190         old_id      INT,
191         predefined  BOOL       NOT NULL DEFAULT TRUE
192 );
193
194 -- Populate the temp table with a definitive set of predefined permissions,
195 -- hard-coding the ids.
196
197 -- The first set of permissions is derived from the database, as loaded in a
198 -- loaded 1.6.1 database, plus a few changes previously applied in this upgrade
199 -- script.  The second set is derived from the IDL -- permissions that are referenced
200 -- in <permacrud> elements but not defined in the database.
201
202 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( -1, 'EVERYTHING',
203      '' );
204 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 1, 'OPAC_LOGIN',
205      'Allow a user to log in to the OPAC' );
206 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 2, 'STAFF_LOGIN',
207      'Allow a user to log in to the staff client' );
208 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 3, 'MR_HOLDS',
209      'Allow a user to create a metarecord holds' );
210 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 4, 'TITLE_HOLDS',
211      'Allow a user to place a hold at the title level' );
212 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 5, 'VOLUME_HOLDS',
213      'Allow a user to place a volume level hold' );
214 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 6, 'COPY_HOLDS',
215      'Allow a user to place a hold on a specific copy' );
216 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 7, 'REQUEST_HOLDS',
217      '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)' );
218 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 8, 'REQUEST_HOLDS_OVERRIDE',
219      '* no longer applicable' );
220 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 9, 'VIEW_HOLD',
221      'Allow a user to view another user''s holds' );
222 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 10, 'DELETE_HOLDS',
223      '* no longer applicable' );
224 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 11, 'UPDATE_HOLD',
225      'Allow a user to update another user''s hold' );
226 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 12, 'RENEW_CIRC',
227      'Allow a user to renew items' );
228 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 13, 'VIEW_USER_FINES_SUMMARY',
229      'Allow a user to view bill details' );
230 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 14, 'VIEW_USER_TRANSACTIONS',
231      'Allow a user to see another user''s grocery or circulation transactions in the Bills Interface; duplicate of VIEW_TRANSACTION' );
232 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 15, 'UPDATE_MARC',
233      'Allow a user to edit a MARC record' );
234 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 16, 'CREATE_MARC',
235      'Allow a user to create new MARC records' );
236 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 17, 'IMPORT_MARC',
237      'Allow a user to import a MARC record via the Z39.50 interface' );
238 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 18, 'CREATE_VOLUME',
239      'Allow a user to create a volume' );
240 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 19, 'UPDATE_VOLUME',
241      '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.' );
242 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 20, 'DELETE_VOLUME',
243      'Allow a user to delete a volume' );
244 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 21, 'CREATE_COPY',
245      'Allow a user to create a new copy object' );
246 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 22, 'UPDATE_COPY',
247      'Allow a user to edit a copy' );
248 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 23, 'DELETE_COPY',
249      'Allow a user to delete a copy' );
250 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 24, 'RENEW_HOLD_OVERRIDE',
251      'Allow a user to continue to renew an item even if it is required for a hold' );
252 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 25, 'CREATE_USER',
253      'Allow a user to create another user' );
254 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 26, 'UPDATE_USER',
255      'Allow a user to edit a user''s record' );
256 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 27, 'DELETE_USER',
257      'Allow a user to mark a user as deleted' );
258 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 28, 'VIEW_USER',
259      'Allow a user to view another user''s Patron Record' );
260 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 29, 'COPY_CHECKIN',
261      'Allow a user to check in a copy' );
262 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 30, 'CREATE_TRANSIT',
263      'Allow a user to place an item in transit' );
264 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 31, 'VIEW_PERMISSION',
265      'Allow a user to view user permissions within the user permissions editor' );
266 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 32, 'CHECKIN_BYPASS_HOLD_FULFILL',
267      '* no longer applicable' );
268 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 33, 'CREATE_PAYMENT',
269      'Allow a user to record payments in the Billing Interface' );
270 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 34, 'SET_CIRC_LOST',
271      'Allow a user to mark an item as ''lost''' );
272 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 35, 'SET_CIRC_MISSING',
273      'Allow a user to mark an item as ''missing''' );
274 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 36, 'SET_CIRC_CLAIMS_RETURNED',
275      'Allow a user to mark an item as ''claims returned''' );
276 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 37, 'CREATE_TRANSACTION',
277      'Allow a user to create a new billable transaction' );
278 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 38, 'VIEW_TRANSACTION',
279      'Allow a user may view another user''s transactions' );
280 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 39, 'CREATE_BILL',
281      'Allow a user to create a new bill on a transaction' );
282 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 40, 'VIEW_CONTAINER',
283      'Allow a user to view another user''s containers (buckets)' );
284 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 41, 'CREATE_CONTAINER',
285      'Allow a user to create a new container for another user' );
286 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 42, 'UPDATE_ORG_UNIT',
287      'Allow a user to change the settings for an organization unit' );
288 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 43, 'VIEW_CIRCULATIONS',
289      'Allow a user to see what another user has checked out' );
290 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 44, 'DELETE_CONTAINER',
291      'Allow a user to delete another user''s container' );
292 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 45, 'CREATE_CONTAINER_ITEM',
293      'Allow a user to create a container item for another user' );
294 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 46, 'CREATE_USER_GROUP_LINK',
295      'Allow a user to add other users to permission groups' );
296 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 47, 'REMOVE_USER_GROUP_LINK',
297      'Allow a user to remove other users from permission groups' );
298 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 48, 'VIEW_PERM_GROUPS',
299      'Allow a user to view other users'' permission groups' );
300 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 49, 'VIEW_PERMIT_CHECKOUT',
301      'Allow a user to determine whether another user can check out an item' );
302 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 50, 'UPDATE_BATCH_COPY',
303      'Allow a user to edit copies in batch' );
304 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 51, 'CREATE_PATRON_STAT_CAT',
305      'User may create a new patron statistical category' );
306 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 52, 'CREATE_COPY_STAT_CAT',
307      'User may create a copy statistical category' );
308 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 53, 'CREATE_PATRON_STAT_CAT_ENTRY',
309      'User may create an entry in a patron statistical category' );
310 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 54, 'CREATE_COPY_STAT_CAT_ENTRY',
311      'User may create an entry in a copy statistical category' );
312 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 55, 'UPDATE_PATRON_STAT_CAT',
313      'User may update a patron statistical category' );
314 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 56, 'UPDATE_COPY_STAT_CAT',
315      'User may update a copy statistical category' );
316 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 57, 'UPDATE_PATRON_STAT_CAT_ENTRY',
317      'User may update an entry in a patron statistical category' );
318 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 58, 'UPDATE_COPY_STAT_CAT_ENTRY',
319      'User may update an entry in a copy statistical category' );
320 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 59, 'CREATE_PATRON_STAT_CAT_ENTRY_MAP',
321      'User may link another user to an entry in a statistical category' );
322 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 60, 'CREATE_COPY_STAT_CAT_ENTRY_MAP',
323      'User may link a copy to an entry in a statistical category' );
324 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 61, 'DELETE_PATRON_STAT_CAT',
325      'User may delete a patron statistical category' );
326 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 62, 'DELETE_COPY_STAT_CAT',
327      'User may delete a copy statistical category' );
328 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 63, 'DELETE_PATRON_STAT_CAT_ENTRY',
329      'User may delete an entry from a patron statistical category' );
330 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 64, 'DELETE_COPY_STAT_CAT_ENTRY',
331      'User may delete an entry from a copy statistical category' );
332 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 65, 'DELETE_PATRON_STAT_CAT_ENTRY_MAP',
333      'User may delete a patron statistical category entry map' );
334 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 66, 'DELETE_COPY_STAT_CAT_ENTRY_MAP',
335      'User may delete a copy statistical category entry map' );
336 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 67, 'CREATE_NON_CAT_TYPE',
337      'Allow a user to create a new non-cataloged item type' );
338 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 68, 'UPDATE_NON_CAT_TYPE',
339      'Allow a user to update a non-cataloged item type' );
340 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 69, 'CREATE_IN_HOUSE_USE',
341      'Allow a user to create a new in-house-use ' );
342 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 70, 'COPY_CHECKOUT',
343      'Allow a user to check out a copy' );
344 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 71, 'CREATE_COPY_LOCATION',
345      'Allow a user to create a new copy location' );
346 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 72, 'UPDATE_COPY_LOCATION',
347      'Allow a user to update a copy location' );
348 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 73, 'DELETE_COPY_LOCATION',
349      'Allow a user to delete a copy location' );
350 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 74, 'CREATE_COPY_TRANSIT',
351      'Allow a user to create a transit_copy object for transiting a copy' );
352 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 75, 'COPY_TRANSIT_RECEIVE',
353      'Allow a user to close out a transit on a copy' );
354 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 76, 'VIEW_HOLD_PERMIT',
355      'Allow a user to see if another user has permission to place a hold on a given copy' );
356 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 77, 'VIEW_COPY_CHECKOUT_HISTORY',
357      'Allow a user to view which users have checked out a given copy' );
358 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 78, 'REMOTE_Z3950_QUERY',
359      'Allow a user to perform Z39.50 queries against remote servers' );
360 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 79, 'REGISTER_WORKSTATION',
361      'Allow a user to register a new workstation' );
362 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 80, 'VIEW_COPY_NOTES',
363      'Allow a user to view all notes attached to a copy' );
364 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 81, 'VIEW_VOLUME_NOTES',
365      'Allow a user to view all notes attached to a volume' );
366 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 82, 'VIEW_TITLE_NOTES',
367      'Allow a user to view all notes attached to a title' );
368 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 83, 'CREATE_COPY_NOTE',
369      'Allow a user to create a new copy note' );
370 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 84, 'CREATE_VOLUME_NOTE',
371      'Allow a user to create a new volume note' );
372 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 85, 'CREATE_TITLE_NOTE',
373      'Allow a user to create a new title note' );
374 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 86, 'DELETE_COPY_NOTE',
375      'Allow a user to delete another user''s copy notes' );
376 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 87, 'DELETE_VOLUME_NOTE',
377      'Allow a user to delete another user''s volume note' );
378 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 88, 'DELETE_TITLE_NOTE',
379      'Allow a user to delete another user''s title note' );
380 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 89, 'UPDATE_CONTAINER',
381      'Allow a user to update another user''s container' );
382 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 90, 'CREATE_MY_CONTAINER',
383      'Allow a user to create a container for themselves' );
384 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 91, 'VIEW_HOLD_NOTIFICATION',
385      'Allow a user to view notifications attached to a hold' );
386 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 92, 'CREATE_HOLD_NOTIFICATION',
387      'Allow a user to create new hold notifications' );
388 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 93, 'UPDATE_ORG_SETTING',
389      'Allow a user to update an organization unit setting' );
390 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 94, 'OFFLINE_UPLOAD',
391      'Allow a user to upload an offline script' );
392 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 95, 'OFFLINE_VIEW',
393      'Allow a user to view uploaded offline script information' );
394 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 96, 'OFFLINE_EXECUTE',
395      'Allow a user to execute an offline script batch' );
396 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 97, 'CIRC_OVERRIDE_DUE_DATE',
397      'Allow a user to change the due date on an item to any date' );
398 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 98, 'CIRC_PERMIT_OVERRIDE',
399      'Allow a user to bypass the circulation permit call for check out' );
400 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 99, 'COPY_IS_REFERENCE.override',
401      'Allow a user to override the copy_is_reference event' );
402 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 100, 'VOID_BILLING',
403      'Allow a user to void a bill' );
404 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 101, 'CIRC_CLAIMS_RETURNED.override',
405      'Allow a user to check in or check out an item that has a status of ''claims returned''' );
406 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 102, 'COPY_BAD_STATUS.override',
407      'Allow a user to check out an item in a non-circulatable status' );
408 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 103, 'COPY_ALERT_MESSAGE.override',
409      'Allow a user to check in/out an item that has an alert message' );
410 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 104, 'COPY_STATUS_LOST.override',
411      'Allow a user to remove the lost status from a copy' );
412 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 105, 'COPY_STATUS_MISSING.override',
413      'Allow a user to change the missing status on a copy' );
414 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 106, 'ABORT_TRANSIT',
415      'Allow a user to abort a copy transit if the user is at the transit destination or source' );
416 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 107, 'ABORT_REMOTE_TRANSIT',
417      'Allow a user to abort a copy transit if the user is not at the transit source or dest' );
418 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 108, 'VIEW_ZIP_DATA',
419      'Allow a user to query the ZIP code data method' );
420 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 109, 'CANCEL_HOLDS',
421      'Allow a user to cancel holds' );
422 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 110, 'CREATE_DUPLICATE_HOLDS',
423      'Allow a user to create duplicate holds (two or more holds on the same title)' );
424 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 111, 'actor.org_unit.closed_date.delete',
425      'Allow a user to remove a closed date interval for a given location' );
426 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 112, 'actor.org_unit.closed_date.update',
427      'Allow a user to update a closed date interval for a given location' );
428 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 113, 'actor.org_unit.closed_date.create',
429      'Allow a user to create a new closed date for a location' );
430 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 114, 'DELETE_NON_CAT_TYPE',
431      'Allow a user to delete a non cataloged type' );
432 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 115, 'money.collections_tracker.create',
433      'Allow a user to put someone into collections' );
434 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 116, 'money.collections_tracker.delete',
435      'Allow a user to remove someone from collections' );
436 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 117, 'BAR_PATRON',
437      'Allow a user to bar a patron' );
438 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 118, 'UNBAR_PATRON',
439      'Allow a user to un-bar a patron' );
440 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 119, 'DELETE_WORKSTATION',
441      'Allow a user to remove an existing workstation so a new one can replace it' );
442 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 120, 'group_application.user',
443      'Allow a user to add/remove users to/from the "User" group' );
444 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 121, 'group_application.user.patron',
445      'Allow a user to add/remove users to/from the "Patron" group' );
446 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 122, 'group_application.user.staff',
447      'Allow a user to add/remove users to/from the "Staff" group' );
448 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 123, 'group_application.user.staff.circ',
449      'Allow a user to add/remove users to/from the "Circulator" group' );
450 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 124, 'group_application.user.staff.cat',
451      'Allow a user to add/remove users to/from the "Cataloger" group' );
452 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 125, 'group_application.user.staff.admin.global_admin',
453      'Allow a user to add/remove users to/from the "GlobalAdmin" group' );
454 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 126, 'group_application.user.staff.admin.local_admin',
455      'Allow a user to add/remove users to/from the "LocalAdmin" group' );
456 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 127, 'group_application.user.staff.admin.lib_manager',
457      'Allow a user to add/remove users to/from the "LibraryManager" group' );
458 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 128, 'group_application.user.staff.cat.cat1',
459      'Allow a user to add/remove users to/from the "Cat1" group' );
460 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 129, 'group_application.user.staff.supercat',
461      'Allow a user to add/remove users to/from the "Supercat" group' );
462 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 130, 'group_application.user.sip_client',
463      'Allow a user to add/remove users to/from the "SIP-Client" group' );
464 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 131, 'group_application.user.vendor',
465      'Allow a user to add/remove users to/from the "Vendor" group' );
466 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 132, 'ITEM_AGE_PROTECTED.override',
467      'Allow a user to place a hold on an age-protected item' );
468 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 133, 'MAX_RENEWALS_REACHED.override',
469      'Allow a user to renew an item past the maximum renewal count' );
470 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 134, 'PATRON_EXCEEDS_CHECKOUT_COUNT.override',
471      'Allow staff to override checkout count failure' );
472 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 135, 'PATRON_EXCEEDS_OVERDUE_COUNT.override',
473      'Allow staff to override overdue count failure' );
474 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 136, 'PATRON_EXCEEDS_FINES.override',
475      'Allow staff to override fine amount checkout failure' );
476 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 137, 'CIRC_EXCEEDS_COPY_RANGE.override',
477      'Allow staff to override circulation copy range failure' );
478 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 138, 'ITEM_ON_HOLDS_SHELF.override',
479      'Allow staff to override item on holds shelf failure' );
480 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 139, 'COPY_NOT_AVAILABLE.override',
481      'Allow staff to force checkout of Missing/Lost type items' );
482 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 140, 'HOLD_EXISTS.override',
483      'Allow a user to place multiple holds on a single title' );
484 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 141, 'RUN_REPORTS',
485      'Allow a user to run reports' );
486 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 142, 'SHARE_REPORT_FOLDER',
487      'Allow a user to share report his own folders' );
488 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 143, 'VIEW_REPORT_OUTPUT',
489      'Allow a user to view report output' );
490 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 144, 'COPY_CIRC_NOT_ALLOWED.override',
491      'Allow a user to checkout an item that is marked as non-circ' );
492 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 145, 'DELETE_CONTAINER_ITEM',
493      'Allow a user to delete an item out of another user''s container' );
494 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 146, 'ASSIGN_WORK_ORG_UNIT',
495      'Allow a staff member to define where another staff member has their permissions' );
496 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 147, 'CREATE_FUNDING_SOURCE',
497      'Allow a user to create a new funding source' );
498 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 148, 'DELETE_FUNDING_SOURCE',
499      'Allow a user to delete a funding source' );
500 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 149, 'VIEW_FUNDING_SOURCE',
501      'Allow a user to view a funding source' );
502 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 150, 'UPDATE_FUNDING_SOURCE',
503      'Allow a user to update a funding source' );
504 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 151, 'CREATE_FUND',
505      'Allow a user to create a new fund' );
506 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 152, 'DELETE_FUND',
507      'Allow a user to delete a fund' );
508 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 153, 'VIEW_FUND',
509      'Allow a user to view a fund' );
510 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 154, 'UPDATE_FUND',
511      'Allow a user to update a fund' );
512 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 155, 'CREATE_FUND_ALLOCATION',
513      'Allow a user to create a new fund allocation' );
514 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 156, 'DELETE_FUND_ALLOCATION',
515      'Allow a user to delete a fund allocation' );
516 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 157, 'VIEW_FUND_ALLOCATION',
517      'Allow a user to view a fund allocation' );
518 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 158, 'UPDATE_FUND_ALLOCATION',
519      'Allow a user to update a fund allocation' );
520 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 159, 'GENERAL_ACQ',
521      'Lowest level permission required to access the ACQ interface' );
522 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 160, 'CREATE_PROVIDER',
523      'Allow a user to create a new provider' );
524 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 161, 'DELETE_PROVIDER',
525      'Allow a user to delate a provider' );
526 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 162, 'VIEW_PROVIDER',
527      'Allow a user to view a provider' );
528 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 163, 'UPDATE_PROVIDER',
529      'Allow a user to update a provider' );
530 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 164, 'ADMIN_FUNDING_SOURCE',
531      'Allow a user to create/view/update/delete a funding source' );
532 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 165, 'ADMIN_FUND',
533      '(Deprecated) Allow a user to create/view/update/delete a fund' );
534 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 166, 'MANAGE_FUNDING_SOURCE',
535      'Allow a user to view/credit/debit a funding source' );
536 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 167, 'MANAGE_FUND',
537      'Allow a user to view/credit/debit a fund' );
538 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 168, 'CREATE_PICKLIST',
539      'Allows a user to create a picklist' );
540 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 169, 'ADMIN_PROVIDER',
541      'Allow a user to create/view/update/delete a provider' );
542 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 170, 'MANAGE_PROVIDER',
543      'Allow a user to view and purchase from a provider' );
544 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 171, 'VIEW_PICKLIST',
545      'Allow a user to view another users picklist' );
546 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 172, 'DELETE_RECORD',
547      'Allow a staff member to directly remove a bibliographic record' );
548 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 173, 'ADMIN_CURRENCY_TYPE',
549      'Allow a user to create/view/update/delete a currency_type' );
550 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 174, 'MARK_BAD_DEBT',
551      'Allow a user to mark a transaction as bad (unrecoverable) debt' );
552 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 175, 'VIEW_BILLING_TYPE',
553      'Allow a user to view billing types' );
554 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 176, 'MARK_ITEM_AVAILABLE',
555      'Allow a user to mark an item status as ''available''' );
556 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 177, 'MARK_ITEM_CHECKED_OUT',
557      'Allow a user to mark an item status as ''checked out''' );
558 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 178, 'MARK_ITEM_BINDERY',
559      'Allow a user to mark an item status as ''bindery''' );
560 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 179, 'MARK_ITEM_LOST',
561      'Allow a user to mark an item status as ''lost''' );
562 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 180, 'MARK_ITEM_MISSING',
563      'Allow a user to mark an item status as ''missing''' );
564 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 181, 'MARK_ITEM_IN_PROCESS',
565      'Allow a user to mark an item status as ''in process''' );
566 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 182, 'MARK_ITEM_IN_TRANSIT',
567      'Allow a user to mark an item status as ''in transit''' );
568 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 183, 'MARK_ITEM_RESHELVING',
569      'Allow a user to mark an item status as ''reshelving''' );
570 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 184, 'MARK_ITEM_ON_HOLDS_SHELF',
571      'Allow a user to mark an item status as ''on holds shelf''' );
572 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 185, 'MARK_ITEM_ON_ORDER',
573      'Allow a user to mark an item status as ''on order''' );
574 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 186, 'MARK_ITEM_ILL',
575      'Allow a user to mark an item status as ''inter-library loan''' );
576 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 187, 'group_application.user.staff.acq',
577      'Allows a user to add/remove/edit users in the "ACQ" group' );
578 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 188, 'CREATE_PURCHASE_ORDER',
579      'Allows a user to create a purchase order' );
580 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 189, 'VIEW_PURCHASE_ORDER',
581      'Allows a user to view a purchase order' );
582 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 190, 'IMPORT_ACQ_LINEITEM_BIB_RECORD',
583      'Allows a user to import a bib record from the acq staging area (on-order record) into the ILS bib data set' );
584 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 191, 'RECEIVE_PURCHASE_ORDER',
585      'Allows a user to mark a purchase order, lineitem, or individual copy as received' );
586 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 192, 'VIEW_ORG_SETTINGS',
587      'Allows a user to view all org settings at the specified level' );
588 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 193, 'CREATE_MFHD_RECORD',
589      'Allows a user to create a new MFHD record' );
590 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 194, 'UPDATE_MFHD_RECORD',
591      'Allows a user to update an MFHD record' );
592 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 195, 'DELETE_MFHD_RECORD',
593      'Allows a user to delete an MFHD record' );
594 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 196, 'ADMIN_ACQ_FUND',
595      'Allow a user to create/view/update/delete a fund' );
596 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 197, 'group_application.user.staff.acq_admin',
597      'Allows a user to add/remove/edit users in the "Acquisitions Administrators" group' );
598 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 198, 'SET_CIRC_CLAIMS_RETURNED.override',
599      'Allows staff to override the max claims returned value for a patron' );
600 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 199, 'UPDATE_PATRON_CLAIM_RETURN_COUNT',
601      'Allows staff to manually change a patron''s claims returned count' );
602 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 200, 'UPDATE_BILL_NOTE',
603      'Allows staff to edit the note for a bill on a transaction' );
604 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 201, 'UPDATE_PAYMENT_NOTE',
605      'Allows staff to edit the note for a payment on a transaction' );
606 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 202, 'UPDATE_PATRON_CLAIM_NEVER_CHECKED_OUT_COUNT',
607      'Allows staff to manually change a patron''s claims never checkout out count' );
608 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 203, 'ADMIN_COPY_LOCATION_ORDER',
609      'Allow a user to create/view/update/delete a copy location order' );
610 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 204, 'ASSIGN_GROUP_PERM',
611      '' );
612 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 205, 'CREATE_AUDIENCE',
613      '' );
614 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 206, 'CREATE_BIB_LEVEL',
615      '' );
616 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 207, 'CREATE_CIRC_DURATION',
617      '' );
618 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 208, 'CREATE_CIRC_MOD',
619      '' );
620 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 209, 'CREATE_COPY_STATUS',
621      '' );
622 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 210, 'CREATE_HOURS_OF_OPERATION',
623      '' );
624 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 211, 'CREATE_ITEM_FORM',
625      '' );
626 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 212, 'CREATE_ITEM_TYPE',
627      '' );
628 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 213, 'CREATE_LANGUAGE',
629      '' );
630 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 214, 'CREATE_LASSO',
631      '' );
632 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 215, 'CREATE_LASSO_MAP',
633      '' );
634 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 216, 'CREATE_LIT_FORM',
635      '' );
636 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 217, 'CREATE_METABIB_FIELD',
637      '' );
638 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 218, 'CREATE_NET_ACCESS_LEVEL',
639      '' );
640 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 219, 'CREATE_ORG_ADDRESS',
641      '' );
642 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 220, 'CREATE_ORG_TYPE',
643      '' );
644 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 221, 'CREATE_ORG_UNIT',
645      '' );
646 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 222, 'CREATE_ORG_UNIT_CLOSING',
647      '' );
648 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 223, 'CREATE_PERM',
649      '' );
650 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 224, 'CREATE_RELEVANCE_ADJUSTMENT',
651      '' );
652 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 225, 'CREATE_SURVEY',
653      '' );
654 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 226, 'CREATE_VR_FORMAT',
655      '' );
656 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 227, 'CREATE_XML_TRANSFORM',
657      '' );
658 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 228, 'DELETE_AUDIENCE',
659      '' );
660 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 229, 'DELETE_BIB_LEVEL',
661      '' );
662 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 230, 'DELETE_CIRC_DURATION',
663      '' );
664 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 231, 'DELETE_CIRC_MOD',
665      '' );
666 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 232, 'DELETE_COPY_STATUS',
667      '' );
668 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 233, 'DELETE_HOURS_OF_OPERATION',
669      '' );
670 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 234, 'DELETE_ITEM_FORM',
671      '' );
672 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 235, 'DELETE_ITEM_TYPE',
673      '' );
674 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 236, 'DELETE_LANGUAGE',
675      '' );
676 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 237, 'DELETE_LASSO',
677      '' );
678 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 238, 'DELETE_LASSO_MAP',
679      '' );
680 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 239, 'DELETE_LIT_FORM',
681      '' );
682 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 240, 'DELETE_METABIB_FIELD',
683      '' );
684 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 241, 'DELETE_NET_ACCESS_LEVEL',
685      '' );
686 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 242, 'DELETE_ORG_ADDRESS',
687      '' );
688 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 243, 'DELETE_ORG_TYPE',
689      '' );
690 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 244, 'DELETE_ORG_UNIT',
691      '' );
692 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 245, 'DELETE_ORG_UNIT_CLOSING',
693      '' );
694 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 246, 'DELETE_PERM',
695      '' );
696 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 247, 'DELETE_RELEVANCE_ADJUSTMENT',
697      '' );
698 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 248, 'DELETE_SURVEY',
699      '' );
700 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 249, 'DELETE_TRANSIT',
701      '' );
702 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 250, 'DELETE_VR_FORMAT',
703      '' );
704 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 251, 'DELETE_XML_TRANSFORM',
705      '' );
706 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 252, 'REMOVE_GROUP_PERM',
707      '' );
708 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 253, 'TRANSIT_COPY',
709      '' );
710 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 254, 'UPDATE_AUDIENCE',
711      '' );
712 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 255, 'UPDATE_BIB_LEVEL',
713      '' );
714 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 256, 'UPDATE_CIRC_DURATION',
715      '' );
716 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 257, 'UPDATE_CIRC_MOD',
717      '' );
718 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 258, 'UPDATE_COPY_NOTE',
719      '' );
720 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 259, 'UPDATE_COPY_STATUS',
721      '' );
722 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 260, 'UPDATE_GROUP_PERM',
723      '' );
724 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 261, 'UPDATE_HOURS_OF_OPERATION',
725      '' );
726 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 262, 'UPDATE_ITEM_FORM',
727      '' );
728 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 263, 'UPDATE_ITEM_TYPE',
729      '' );
730 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 264, 'UPDATE_LANGUAGE',
731      '' );
732 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 265, 'UPDATE_LASSO',
733      '' );
734 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 266, 'UPDATE_LASSO_MAP',
735      '' );
736 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 267, 'UPDATE_LIT_FORM',
737      '' );
738 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 268, 'UPDATE_METABIB_FIELD',
739      '' );
740 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 269, 'UPDATE_NET_ACCESS_LEVEL',
741      '' );
742 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 270, 'UPDATE_ORG_ADDRESS',
743      '' );
744 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 271, 'UPDATE_ORG_TYPE',
745      '' );
746 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 272, 'UPDATE_ORG_UNIT_CLOSING',
747      '' );
748 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 273, 'UPDATE_PERM',
749      '' );
750 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 274, 'UPDATE_RELEVANCE_ADJUSTMENT',
751      '' );
752 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 275, 'UPDATE_SURVEY',
753      '' );
754 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 276, 'UPDATE_TRANSIT',
755      '' );
756 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 277, 'UPDATE_VOLUME_NOTE',
757      '' );
758 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 278, 'UPDATE_VR_FORMAT',
759      '' );
760 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 279, 'UPDATE_XML_TRANSFORM',
761      '' );
762 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 280, 'MERGE_BIB_RECORDS',
763      '' );
764 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 281, 'UPDATE_PICKUP_LIB_FROM_HOLDS_SHELF',
765      '' );
766 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 282, 'CREATE_ACQ_FUNDING_SOURCE',
767      '' );
768 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 283, 'CREATE_AUTHORITY_IMPORT_IMPORT_FIELD_DEF',
769      '' );
770 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 284, 'CREATE_AUTHORITY_IMPORT_QUEUE',
771      '' );
772 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 285, 'CREATE_AUTHORITY_RECORD_NOTE',
773      '' );
774 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 286, 'CREATE_BIB_IMPORT_FIELD_DEF',
775      '' );
776 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 287, 'CREATE_BIB_IMPORT_QUEUE',
777      '' );
778 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 288, 'CREATE_LOCALE',
779      '' );
780 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 289, 'CREATE_MARC_CODE',
781      '' );
782 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 290, 'CREATE_TRANSLATION',
783      '' );
784 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 291, 'DELETE_ACQ_FUNDING_SOURCE',
785      '' );
786 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 292, 'DELETE_AUTHORITY_IMPORT_IMPORT_FIELD_DEF',
787      '' );
788 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 293, 'DELETE_AUTHORITY_IMPORT_QUEUE',
789      '' );
790 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 294, 'DELETE_AUTHORITY_RECORD_NOTE',
791      '' );
792 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 295, 'DELETE_BIB_IMPORT_IMPORT_FIELD_DEF',
793      '' );
794 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 296, 'DELETE_BIB_IMPORT_QUEUE',
795      '' );
796 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 297, 'DELETE_LOCALE',
797      '' );
798 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 298, 'DELETE_MARC_CODE',
799      '' );
800 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 299, 'DELETE_TRANSLATION',
801      '' );
802 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 300, 'UPDATE_ACQ_FUNDING_SOURCE',
803      '' );
804 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 301, 'UPDATE_AUTHORITY_IMPORT_IMPORT_FIELD_DEF',
805      '' );
806 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 302, 'UPDATE_AUTHORITY_IMPORT_QUEUE',
807      '' );
808 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 303, 'UPDATE_AUTHORITY_RECORD_NOTE',
809      '' );
810 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 304, 'UPDATE_BIB_IMPORT_IMPORT_FIELD_DEF',
811      '' );
812 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 305, 'UPDATE_BIB_IMPORT_QUEUE',
813      '' );
814 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 306, 'UPDATE_LOCALE',
815      '' );
816 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 307, 'UPDATE_MARC_CODE',
817      '' );
818 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 308, 'UPDATE_TRANSLATION',
819      '' );
820 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 309, 'VIEW_ACQ_FUNDING_SOURCE',
821      '' );
822 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 310, 'VIEW_AUTHORITY_RECORD_NOTES',
823      '' );
824 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 311, 'CREATE_IMPORT_ITEM',
825      '' );
826 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 312, 'CREATE_IMPORT_ITEM_ATTR_DEF',
827      '' );
828 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 313, 'CREATE_IMPORT_TRASH_FIELD',
829      '' );
830 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 314, 'DELETE_IMPORT_ITEM',
831      '' );
832 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 315, 'DELETE_IMPORT_ITEM_ATTR_DEF',
833      '' );
834 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 316, 'DELETE_IMPORT_TRASH_FIELD',
835      '' );
836 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 317, 'UPDATE_IMPORT_ITEM',
837      '' );
838 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 318, 'UPDATE_IMPORT_ITEM_ATTR_DEF',
839      '' );
840 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 319, 'UPDATE_IMPORT_TRASH_FIELD',
841      '' );
842 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 320, 'UPDATE_ORG_UNIT_SETTING_ALL',
843      '' );
844 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 321, 'UPDATE_ORG_UNIT_SETTING.circ.lost_materials_processing_fee',
845      '' );
846 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 322, 'UPDATE_ORG_UNIT_SETTING.cat.default_item_price',
847      '' );
848 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 323, 'UPDATE_ORG_UNIT_SETTING.auth.opac_timeout',
849      '' );
850 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 324, 'UPDATE_ORG_UNIT_SETTING.auth.staff_timeout',
851      '' );
852 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 325, 'UPDATE_ORG_UNIT_SETTING.org.bounced_emails',
853      '' );
854 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 326, 'UPDATE_ORG_UNIT_SETTING.circ.hold_expire_alert_interval',
855      '' );
856 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 327, 'UPDATE_ORG_UNIT_SETTING.circ.hold_expire_interval',
857      '' );
858 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 328, 'UPDATE_ORG_UNIT_SETTING.credit.payments.allow',
859      '' );
860 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 329, 'UPDATE_ORG_UNIT_SETTING.circ.void_overdue_on_lost',
861      '' );
862 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 330, 'UPDATE_ORG_UNIT_SETTING.circ.hold_stalling.soft',
863      '' );
864 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 331, 'UPDATE_ORG_UNIT_SETTING.circ.hold_boundary.hard',
865      '' );
866 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 332, 'UPDATE_ORG_UNIT_SETTING.circ.hold_boundary.soft',
867      '' );
868 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 333, 'UPDATE_ORG_UNIT_SETTING.opac.barcode_regex',
869      '' );
870 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 334, 'UPDATE_ORG_UNIT_SETTING.global.password_regex',
871      '' );
872 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 335, 'UPDATE_ORG_UNIT_SETTING.circ.item_checkout_history.max',
873      '' );
874 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 336, 'UPDATE_ORG_UNIT_SETTING.circ.reshelving_complete.interval',
875      '' );
876 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 337, 'UPDATE_ORG_UNIT_SETTING.circ.selfcheck.patron_login_timeout',
877      '' );
878 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 338, 'UPDATE_ORG_UNIT_SETTING.circ.selfcheck.alert_on_checkout_event',
879      '' );
880 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 339, 'UPDATE_ORG_UNIT_SETTING.circ.selfcheck.require_patron_password',
881      '' );
882 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 340, 'UPDATE_ORG_UNIT_SETTING.global.juvenile_age_threshold',
883      '' );
884 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 341, 'UPDATE_ORG_UNIT_SETTING.cat.bib.keep_on_empty',
885      '' );
886 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 342, 'UPDATE_ORG_UNIT_SETTING.cat.bib.alert_on_empty',
887      '' );
888 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 343, 'UPDATE_ORG_UNIT_SETTING.patron.password.use_phone',
889      '' );
890 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 344, 'HOLD_ITEM_CHECKED_OUT.override',
891      'Allows a user to place a hold on an item that they already have checked out' );
892 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 345, 'ADMIN_ACQ_CANCEL_CAUSE',
893      'Allow a user to create/update/delete reasons for order cancellations' );
894 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 346, 'ACQ_XFER_MANUAL_DFUND_AMOUNT',
895      'Allow a user to transfer different amounts of money out of one fund and into another' );
896 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 347, 'OVERRIDE_HOLD_HAS_LOCAL_COPY',
897      'Allow a user to override the circ.holds.hold_has_copy_at.block setting' );
898 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 348, 'UPDATE_PICKUP_LIB_FROM_TRANSIT',
899      'Allow a user to change the pickup and transit destination for a captured hold item already in transit' );
900 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 349, 'COPY_NEEDED_FOR_HOLD.override',
901      'Allow a user to force renewal of an item that could fulfill a hold request' );
902 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 350, 'MERGE_AUTH_RECORDS',
903      'Allow a user to merge authority records together' );
904 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 351, 'ALLOW_ALT_TCN',
905      'Allows staff to import a record using an alternate TCN to avoid conflicts' );
906 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 352, 'ADMIN_TRIGGER_EVENT_DEF',
907      'Allow a user to administer trigger event definitions' );
908 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 353, 'ADMIN_TRIGGER_CLEANUP',
909      'Allow a user to create, delete, and update trigger cleanup entries' );
910 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 354, 'CREATE_TRIGGER_CLEANUP',
911      'Allow a user to create trigger cleanup entries' );
912 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 355, 'DELETE_TRIGGER_CLEANUP',
913      'Allow a user to delete trigger cleanup entries' );
914 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 356, 'UPDATE_TRIGGER_CLEANUP',
915      'Allow a user to update trigger cleanup entries' );
916 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 357, 'CREATE_TRIGGER_EVENT_DEF',
917      'Allow a user to create trigger event definitions' );
918 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 358, 'DELETE_TRIGGER_EVENT_DEF',
919      'Allow a user to delete trigger event definitions' );
920 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 359, 'UPDATE_TRIGGER_EVENT_DEF',
921      'Allow a user to update trigger event definitions' );
922 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 360, 'VIEW_TRIGGER_EVENT_DEF',
923      'Allow a user to view trigger event definitions' );
924 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 361, 'ADMIN_TRIGGER_HOOK',
925      'Allow a user to create, update, and delete trigger hooks' );
926 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 362, 'CREATE_TRIGGER_HOOK',
927      'Allow a user to create trigger hooks' );
928 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 363, 'DELETE_TRIGGER_HOOK',
929      'Allow a user to delete trigger hooks' );
930 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 364, 'UPDATE_TRIGGER_HOOK',
931      'Allow a user to update trigger hooks' );
932 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 365, 'ADMIN_TRIGGER_REACTOR',
933      'Allow a user to create, update, and delete trigger reactors' );
934 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 366, 'CREATE_TRIGGER_REACTOR',
935      'Allow a user to create trigger reactors' );
936 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 367, 'DELETE_TRIGGER_REACTOR',
937      'Allow a user to delete trigger reactors' );
938 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 368, 'UPDATE_TRIGGER_REACTOR',
939      'Allow a user to update trigger reactors' );
940 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 369, 'ADMIN_TRIGGER_TEMPLATE_OUTPUT',
941      'Allow a user to delete trigger template output' );
942 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 370, 'DELETE_TRIGGER_TEMPLATE_OUTPUT',
943      'Allow a user to delete trigger template output' );
944 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 371, 'ADMIN_TRIGGER_VALIDATOR',
945      'Allow a user to create, update, and delete trigger validators' );
946 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 372, 'CREATE_TRIGGER_VALIDATOR',
947      'Allow a user to create trigger validators' );
948 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 373, 'DELETE_TRIGGER_VALIDATOR',
949      'Allow a user to delete trigger validators' );
950 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 374, 'UPDATE_TRIGGER_VALIDATOR',
951      'Allow a user to update trigger validators' );
952 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 376, 'ADMIN_BOOKING_RESOURCE',
953      'Enables the user to create/update/delete booking resources' );
954 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 377, 'ADMIN_BOOKING_RESOURCE_TYPE',
955      'Enables the user to create/update/delete booking resource types' );
956 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 378, 'ADMIN_BOOKING_RESOURCE_ATTR',
957      'Enables the user to create/update/delete booking resource attributes' );
958 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 379, 'ADMIN_BOOKING_RESOURCE_ATTR_MAP',
959      'Enables the user to create/update/delete booking resource attribute maps' );
960 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 380, 'ADMIN_BOOKING_RESOURCE_ATTR_VALUE',
961      'Enables the user to create/update/delete booking resource attribute values' );
962 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 381, 'ADMIN_BOOKING_RESERVATION',
963      'Enables the user to create/update/delete booking reservations' );
964 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 382, 'ADMIN_BOOKING_RESERVATION_ATTR_VALUE_MAP',
965      'Enables the user to create/update/delete booking reservation attribute value maps' );
966 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 383, 'RETRIEVE_RESERVATION_PULL_LIST',
967      'Allows a user to retrieve a booking reservation pull list' );
968 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 384, 'CAPTURE_RESERVATION',
969      'Allows a user to capture booking reservations' );
970 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 385, 'UPDATE_RECORD',
971      '' );
972 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 386, 'UPDATE_ORG_UNIT_SETTING.circ.block_renews_for_holds',
973      '' );
974 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 387, 'MERGE_USERS',
975      'Allows user records to be merged' );
976 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 388, 'ISSUANCE_HOLDS',
977      'Allow a user to place holds on serials issuances' );
978 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 389, 'VIEW_CREDIT_CARD_PROCESSING',
979      'View org unit settings related to credit card processing' );
980 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 390, 'ADMIN_CREDIT_CARD_PROCESSING',
981      'Update org unit settings related to credit card processing' );
982 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 391, 'ADMIN_SERIAL_CAPTION_PATTERN',
983         'Create/update/delete serial caption and pattern objects' );
984 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 392, 'ADMIN_SERIAL_SUBSCRIPTION',
985         'Create/update/delete serial subscription objects' );
986 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 393, 'ADMIN_SERIAL_DISTRIBUTION',
987         'Create/update/delete serial distribution objects' );
988 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 394, 'ADMIN_SERIAL_STREAM',
989         'Create/update/delete serial stream objects' );
990 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 395, 'RECEIVE_SERIAL',
991         'Receive serial items' );
992
993 -- Now for the permissions from the IDL.  We don't have descriptions for them.
994
995 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 396, 'ADMIN_ACQ_CLAIM' );
996 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 397, 'ADMIN_ACQ_CLAIM_EVENT_TYPE' );
997 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 398, 'ADMIN_ACQ_CLAIM_TYPE' );
998 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 399, 'ADMIN_ACQ_DISTRIB_FORMULA' );
999 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 400, 'ADMIN_ACQ_FISCAL_YEAR' );
1000 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 401, 'ADMIN_ACQ_FUND_ALLOCATION_PERCENT' );
1001 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 402, 'ADMIN_ACQ_FUND_TAG' );
1002 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 403, 'ADMIN_ACQ_LINEITEM_ALERT_TEXT' );
1003 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 404, 'ADMIN_AGE_PROTECT_RULE' );
1004 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 405, 'ADMIN_ASSET_COPY_TEMPLATE' );
1005 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 406, 'ADMIN_BOOKING_RESERVATION_ATTR_MAP' );
1006 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 407, 'ADMIN_CIRC_MATRIX_MATCHPOINT' );
1007 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 408, 'ADMIN_CIRC_MOD' );
1008 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 409, 'ADMIN_CLAIM_POLICY' );
1009 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 410, 'ADMIN_CONFIG_REMOTE_ACCOUNT' );
1010 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 411, 'ADMIN_FIELD_DOC' );
1011 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 412, 'ADMIN_GLOBAL_FLAG' );
1012 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 413, 'ADMIN_GROUP_PENALTY_THRESHOLD' );
1013 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 414, 'ADMIN_HOLD_CANCEL_CAUSE' );
1014 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 415, 'ADMIN_HOLD_MATRIX_MATCHPOINT' );
1015 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 416, 'ADMIN_IDENT_TYPE' );
1016 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 417, 'ADMIN_IMPORT_ITEM_ATTR_DEF' );
1017 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 418, 'ADMIN_INDEX_NORMALIZER' );
1018 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 419, 'ADMIN_INVOICE' );
1019 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 420, 'ADMIN_INVOICE_METHOD' );
1020 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 421, 'ADMIN_INVOICE_PAYMENT_METHOD' );
1021 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 422, 'ADMIN_LINEITEM_MARC_ATTR_DEF' );
1022 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 423, 'ADMIN_MARC_CODE' );
1023 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 424, 'ADMIN_MAX_FINE_RULE' );
1024 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 425, 'ADMIN_MERGE_PROFILE' );
1025 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 426, 'ADMIN_ORG_UNIT_SETTING_TYPE' );
1026 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 427, 'ADMIN_RECURRING_FINE_RULE' );
1027 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 428, 'ADMIN_STANDING_PENALTY' );
1028 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 429, 'ADMIN_SURVEY' );
1029 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 430, 'ADMIN_USER_REQUEST_TYPE' );
1030 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 431, 'ADMIN_USER_SETTING_GROUP' );
1031 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 432, 'ADMIN_USER_SETTING_TYPE' );
1032 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 433, 'ADMIN_Z3950_SOURCE' );
1033 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 434, 'CREATE_BIB_BTYPE' );
1034 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 435, 'CREATE_BIBLIO_FINGERPRINT' );
1035 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 436, 'CREATE_BIB_SOURCE' );
1036 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 437, 'CREATE_BILLING_TYPE' );
1037 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 438, 'CREATE_CN_BTYPE' );
1038 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 439, 'CREATE_COPY_BTYPE' );
1039 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 440, 'CREATE_INVOICE' );
1040 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 441, 'CREATE_INVOICE_ITEM_TYPE' );
1041 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 442, 'CREATE_INVOICE_METHOD' );
1042 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 443, 'CREATE_MERGE_PROFILE' );
1043 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 444, 'CREATE_METABIB_CLASS' );
1044 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 445, 'CREATE_METABIB_SEARCH_ALIAS' );
1045 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 446, 'CREATE_USER_BTYPE' );
1046 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 447, 'DELETE_BIB_BTYPE' );
1047 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 448, 'DELETE_BIBLIO_FINGERPRINT' );
1048 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 449, 'DELETE_BIB_SOURCE' );
1049 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 450, 'DELETE_BILLING_TYPE' );
1050 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 451, 'DELETE_CN_BTYPE' );
1051 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 452, 'DELETE_COPY_BTYPE' );
1052 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 453, 'DELETE_INVOICE_ITEM_TYPE' );
1053 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 454, 'DELETE_INVOICE_METHOD' );
1054 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 455, 'DELETE_MERGE_PROFILE' );
1055 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 456, 'DELETE_METABIB_CLASS' );
1056 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 457, 'DELETE_METABIB_SEARCH_ALIAS' );
1057 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 458, 'DELETE_USER_BTYPE' );
1058 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 459, 'MANAGE_CLAIM' );
1059 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 460, 'UPDATE_BIB_BTYPE' );
1060 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 461, 'UPDATE_BIBLIO_FINGERPRINT' );
1061 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 462, 'UPDATE_BIB_SOURCE' );
1062 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 463, 'UPDATE_BILLING_TYPE' );
1063 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 464, 'UPDATE_CN_BTYPE' );
1064 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 465, 'UPDATE_COPY_BTYPE' );
1065 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 466, 'UPDATE_INVOICE_ITEM_TYPE' );
1066 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 467, 'UPDATE_INVOICE_METHOD' );
1067 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 468, 'UPDATE_MERGE_PROFILE' );
1068 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 469, 'UPDATE_METABIB_CLASS' );
1069 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 470, 'UPDATE_METABIB_SEARCH_ALIAS' );
1070 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 471, 'UPDATE_USER_BTYPE' );
1071 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 472, 'user_request.create' );
1072 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 473, 'user_request.delete' );
1073 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 474, 'user_request.update' );
1074 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 475, 'user_request.view' );
1075 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 476, 'VIEW_ACQ_FUND_ALLOCATION_PERCENT' );
1076 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 477, 'VIEW_CIRC_MATRIX_MATCHPOINT' );
1077 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 478, 'VIEW_CLAIM' );
1078 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 479, 'VIEW_GROUP_PENALTY_THRESHOLD' );
1079 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 480, 'VIEW_HOLD_MATRIX_MATCHPOINT' );
1080 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 481, 'VIEW_INVOICE' );
1081 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 482, 'VIEW_MERGE_PROFILE' );
1082 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 483, 'VIEW_SERIAL_SUBSCRIPTION' );
1083 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 484, 'VIEW_STANDING_PENALTY' );
1084
1085 -- For every permission in the temp_perm table that has a matching
1086 -- permission in the real table: record the original id.
1087
1088 UPDATE permission.temp_perm AS tp
1089 SET old_id =
1090         (
1091                 SELECT id
1092                 FROM permission.perm_list AS ppl
1093                 WHERE ppl.code = tp.code
1094         )
1095 WHERE code IN ( SELECT code FROM permission.perm_list );
1096
1097 -- Start juggling ids.
1098
1099 -- If any permissions have negative ids (with the special exception of -1),
1100 -- we need to move them into the positive range in order to avoid duplicate
1101 -- key problems (since we are going to use the negative range as a temporary
1102 -- staging area).
1103
1104 -- First, move any predefined permissions that have negative ids (again with
1105 -- the special exception of -1).  Temporarily give them positive ids based on
1106 -- the sequence.
1107
1108 UPDATE permission.perm_list
1109 SET id = NEXTVAL('permission.perm_list_id_seq'::regclass)
1110 WHERE id < -1
1111   AND code IN (SELECT code FROM permission.temp_perm);
1112
1113 -- Identify any non-predefined permissions whose ids are either negative
1114 -- or within the range (0-1000) reserved for predefined permissions.
1115 -- Assign them ids above 1000, based on the sequence.  Record the new
1116 -- ids in the temp_perm table.
1117
1118 INSERT INTO permission.temp_perm ( id, code, description, old_id, predefined )
1119 (
1120         SELECT NEXTVAL('permission.perm_list_id_seq'::regclass),
1121                 code, description, id, false
1122         FROM permission.perm_list
1123         WHERE  ( id < -1 OR id BETWEEN 0 AND 1000 )
1124         AND code NOT IN (SELECT code FROM permission.temp_perm)
1125 );
1126
1127 -- Now update the ids of those non-predefined permissions, using the
1128 -- values assigned in the previous step.
1129
1130 UPDATE permission.perm_list AS ppl
1131 SET id = (
1132                 SELECT id
1133                 FROM permission.temp_perm AS tp
1134                 WHERE tp.code = ppl.code
1135         )
1136 WHERE id IN ( SELECT old_id FROM permission.temp_perm WHERE NOT predefined );
1137
1138 -- Now the negative ids have been eliminated, except for -1.  Move all the
1139 -- predefined permissions temporarily into the negative range.
1140
1141 UPDATE permission.perm_list
1142 SET id = -1 - id
1143 WHERE id <> -1
1144 AND code IN ( SELECT code from permission.temp_perm WHERE predefined );
1145
1146 -- Apply the final ids to the existing predefined permissions.
1147
1148 UPDATE permission.perm_list AS ppl
1149 SET id =
1150         (
1151                 SELECT id
1152                 FROM permission.temp_perm AS tp
1153                 WHERE tp.code = ppl.code
1154         )
1155 WHERE
1156         id <> -1
1157         AND ppl.code IN
1158         (
1159                 SELECT code from permission.temp_perm
1160                 WHERE predefined
1161                 AND old_id IS NOT NULL
1162         );
1163
1164 -- If there are any predefined permissions that don't exist yet in
1165 -- permission.perm_list, insert them now.
1166
1167 INSERT INTO permission.perm_list ( id, code, description )
1168 (
1169         SELECT id, code, description
1170         FROM permission.temp_perm
1171         WHERE old_id IS NULL
1172 );
1173
1174 -- Reset the sequence to the lowest feasible value.  This may or may not
1175 -- accomplish anything, but it will do no harm.
1176
1177 SELECT SETVAL('permission.perm_list_id_seq'::TEXT, GREATEST( 
1178         (SELECT MAX(id) FROM permission.perm_list), 1000 ));
1179
1180 -- If any permission lacks a description, use the code as a description.
1181 -- It's better than nothing.
1182
1183 UPDATE permission.perm_list
1184 SET description = code
1185 WHERE description IS NULL
1186    OR description = '';
1187
1188 -- Thus endeth the Great Renumbering.
1189
1190 -- Having massaged the permissions, massage the way they are assigned, by inserting
1191 -- rows into permission.grp_perm_map.  Some of these permissions may have already
1192 -- been assigned, so we insert the rows only if they aren't already there.
1193
1194 -- for backwards compat, give everyone the permission
1195 INSERT INTO permission.grp_perm_map (grp, perm, depth, grantable)
1196     SELECT 1, id, 0, false FROM permission.perm_list AS perm
1197         WHERE code = 'HOLD_ITEM_CHECKED_OUT.override'
1198                 AND NOT EXISTS (
1199                         SELECT 1
1200                         FROM permission.grp_perm_map AS map
1201                         WHERE
1202                                 grp = 1
1203                                 AND map.perm = perm.id
1204                 );
1205
1206 -- Add trigger administration permissions to the Local System Administrator group.
1207 INSERT INTO permission.grp_perm_map (grp, perm, depth, grantable)
1208     SELECT 10, id, 1, false FROM permission.perm_list AS perm
1209     WHERE (
1210                 perm.code LIKE 'ADMIN_TRIGGER%'
1211         OR perm.code LIKE 'CREATE_TRIGGER%'
1212         OR perm.code LIKE 'DELETE_TRIGGER%'
1213         OR perm.code LIKE 'UPDATE_TRIGGER%'
1214         ) AND NOT EXISTS (
1215                 SELECT 1
1216                 FROM permission.grp_perm_map AS map
1217                 WHERE
1218                         grp = 10
1219                         AND map.perm = perm.id
1220         );
1221
1222 -- View trigger permissions are required at a consortial level for initial setup
1223 -- (as before, only if the row doesn't already exist)
1224 INSERT INTO permission.grp_perm_map (grp, perm, depth, grantable)
1225     SELECT 10, id, 0, false FROM permission.perm_list AS perm
1226         WHERE code LIKE 'VIEW_TRIGGER%'
1227                 AND NOT EXISTS (
1228                         SELECT 1
1229                         FROM permission.grp_perm_map AS map
1230                         WHERE
1231                                 grp = 10
1232                                 AND map.perm = perm.id
1233                 );
1234
1235 -- Permission for merging auth records may already be defined,
1236 -- so add it only if it isn't there.
1237 INSERT INTO permission.grp_perm_map (grp, perm, depth, grantable)
1238     SELECT 4, id, 1, false FROM permission.perm_list AS perm
1239         WHERE code = 'MERGE_AUTH_RECORDS'
1240                 AND NOT EXISTS (
1241                         SELECT 1
1242                         FROM permission.grp_perm_map AS map
1243                         WHERE
1244                                 grp = 4
1245                                 AND map.perm = perm.id
1246                 );
1247
1248 -- Create a reference table as parent to both
1249 -- config.org_unit_setting_type and config_usr_setting_type
1250
1251 CREATE TABLE config.settings_group (
1252     name    TEXT PRIMARY KEY,
1253     label   TEXT UNIQUE NOT NULL -- I18N
1254 );
1255
1256 -- org_unit setting types
1257 CREATE TABLE config.org_unit_setting_type (
1258     name            TEXT    PRIMARY KEY,
1259     label           TEXT    UNIQUE NOT NULL,
1260     grp             TEXT    REFERENCES config.settings_group (name),
1261     description     TEXT,
1262     datatype        TEXT    NOT NULL DEFAULT 'string',
1263     fm_class        TEXT,
1264     view_perm       INT,
1265     update_perm     INT,
1266     --
1267     -- define valid datatypes
1268     --
1269     CONSTRAINT coust_valid_datatype CHECK ( datatype IN
1270     ( 'bool', 'integer', 'float', 'currency', 'interval',
1271       'date', 'string', 'object', 'array', 'link' ) ),
1272     --
1273     -- fm_class is meaningful only for 'link' datatype
1274     --
1275     CONSTRAINT coust_no_empty_link CHECK
1276     ( ( datatype =  'link' AND fm_class IS NOT NULL ) OR
1277       ( datatype <> 'link' AND fm_class IS NULL ) ),
1278         CONSTRAINT view_perm_fkey FOREIGN KEY (view_perm) REFERENCES permission.perm_list (id)
1279                 ON UPDATE CASCADE
1280                 ON DELETE RESTRICT
1281                 DEFERRABLE INITIALLY DEFERRED,
1282         CONSTRAINT update_perm_fkey FOREIGN KEY (update_perm) REFERENCES permission.perm_list (id)
1283                 ON UPDATE CASCADE
1284                 DEFERRABLE INITIALLY DEFERRED
1285 );
1286
1287 CREATE TABLE config.usr_setting_type (
1288
1289     name TEXT PRIMARY KEY,
1290     opac_visible BOOL NOT NULL DEFAULT FALSE,
1291     label TEXT UNIQUE NOT NULL,
1292     description TEXT,
1293     grp             TEXT    REFERENCES config.settings_group (name),
1294     datatype TEXT NOT NULL DEFAULT 'string',
1295     fm_class TEXT,
1296
1297     --
1298     -- define valid datatypes
1299     --
1300     CONSTRAINT coust_valid_datatype CHECK ( datatype IN
1301     ( 'bool', 'integer', 'float', 'currency', 'interval',
1302         'date', 'string', 'object', 'array', 'link' ) ),
1303
1304     --
1305     -- fm_class is meaningful only for 'link' datatype
1306     --
1307     CONSTRAINT coust_no_empty_link CHECK
1308     ( ( datatype = 'link' AND fm_class IS NOT NULL ) OR
1309         ( datatype <> 'link' AND fm_class IS NULL ) )
1310
1311 );
1312
1313 --------------------------------------
1314 -- Seed data for org_unit_setting_type
1315 --------------------------------------
1316
1317 INSERT into config.org_unit_setting_type
1318 ( name, label, description, datatype ) VALUES
1319
1320 ( 'auth.opac_timeout',
1321   'OPAC Inactivity Timeout (in seconds)',
1322   null,
1323   'integer' ),
1324
1325 ( 'auth.staff_timeout',
1326   'Staff Login Inactivity Timeout (in seconds)',
1327   null,
1328   'integer' ),
1329
1330 ( 'circ.lost_materials_processing_fee',
1331   'Lost Materials Processing Fee',
1332   null,
1333   'currency' ),
1334
1335 ( 'cat.default_item_price',
1336   'Default Item Price',
1337   null,
1338   'currency' ),
1339
1340 ( 'org.bounced_emails',
1341   'Sending email address for patron notices',
1342   null,
1343   'string' ),
1344
1345 ( 'circ.hold_expire_alert_interval',
1346   'Holds: Expire Alert Interval',
1347   'Amount of time before a hold expires at which point the patron should be alerted',
1348   'interval' ),
1349
1350 ( 'circ.hold_expire_interval',
1351   'Holds: Expire Interval',
1352   'Amount of time after a hold is placed before the hold expires.  Example "100 days"',
1353   'interval' ),
1354
1355 ( 'credit.payments.allow',
1356   'Allow Credit Card Payments',
1357   'If enabled, patrons will be able to pay fines accrued at this location via credit card',
1358   'bool' ),
1359
1360 ( 'global.default_locale',
1361   'Global Default Locale',
1362   null,
1363   'string' ),
1364
1365 ( 'circ.void_overdue_on_lost',
1366   'Void overdue fines when items are marked lost',
1367   null,
1368   'bool' ),
1369
1370 ( 'circ.hold_stalling.soft',
1371   'Holds: Soft stalling interval',
1372   'How long to wait before allowing remote items to be opportunistically captured for a hold.  Example "5 days"',
1373   'interval' ),
1374
1375 ( 'circ.hold_stalling_hard',
1376   'Holds: Hard stalling interval',
1377   '',
1378   'interval' ),
1379
1380 ( 'circ.hold_boundary.hard',
1381   'Holds: Hard boundary',
1382   null,
1383   'integer' ),
1384
1385 ( 'circ.hold_boundary.soft',
1386   'Holds: Soft boundary',
1387   null,
1388   'integer' ),
1389
1390 ( 'opac.barcode_regex',
1391   'Patron barcode format',
1392   'Regular expression defining the patron barcode format',
1393   'string' ),
1394
1395 ( 'global.password_regex',
1396   'Password format',
1397   'Regular expression defining the password format',
1398   'string' ),
1399
1400 ( 'circ.item_checkout_history.max',
1401   'Maximum previous checkouts displayed',
1402   'This is the maximum number of previous circulations the staff client will display when investigating item details',
1403   'integer' ),
1404
1405 ( 'circ.reshelving_complete.interval',
1406   'Change reshelving status interval',
1407   'Amount of time to wait before changing an item from "reshelving" status to "available".  Examples: "1 day", "6 hours"',
1408   'interval' ),
1409
1410 ( 'circ.holds.default_estimated_wait_interval',
1411   'Holds: Default Estimated Wait',
1412   '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.',
1413   'interval' ),
1414
1415 ( 'circ.holds.min_estimated_wait_interval',
1416   'Holds: Minimum Estimated Wait',
1417   '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.',
1418   'interval' ),
1419
1420 ( 'circ.selfcheck.patron_login_timeout',
1421   'Selfcheck: Patron Login Timeout (in seconds)',
1422   'Number of seconds of inactivity before the patron is logged out of the selfcheck interface',
1423   'integer' ),
1424
1425 ( 'circ.selfcheck.alert.popup',
1426   'Selfcheck: Pop-up alert for errors',
1427   'If true, checkout/renewal errors will cause a pop-up window in addition to the on-screen message',
1428   'bool' ),
1429
1430 ( 'circ.selfcheck.require_patron_password',
1431   'Selfcheck: Require patron password',
1432   'If true, patrons will be required to enter their password in addition to their username/barcode to log into the selfcheck interface',
1433   'bool' ),
1434
1435 ( 'global.juvenile_age_threshold',
1436   'Juvenile Age Threshold',
1437   'The age at which a user is no long considered a juvenile.  For example, "18 years".',
1438   'interval' ),
1439
1440 ( 'cat.bib.keep_on_empty',
1441   'Retain empty bib records',
1442   'Retain a bib record even when all attached copies are deleted',
1443   'bool' ),
1444
1445 ( 'cat.bib.alert_on_empty',
1446   'Alert on empty bib records',
1447   'Alert staff when the last copy for a record is being deleted',
1448   'bool' ),
1449
1450 ( 'patron.password.use_phone',
1451   'Patron: password from phone #',
1452   'Use the last 4 digits of the patrons phone number as the default password when creating new users',
1453   'bool' ),
1454
1455 ( 'circ.charge_on_damaged',
1456   'Charge item price when marked damaged',
1457   'Charge item price when marked damaged',
1458   'bool' ),
1459
1460 ( 'circ.charge_lost_on_zero',
1461   'Charge lost on zero',
1462   '',
1463   'bool' ),
1464
1465 ( 'circ.damaged_item_processing_fee',
1466   'Charge processing fee for damaged items',
1467   'Charge processing fee for damaged items',
1468   'currency' ),
1469
1470 ( 'circ.void_lost_on_checkin',
1471   'Circ: Void lost item billing when returned',
1472   'Void lost item billing when returned',
1473   'bool' ),
1474
1475 ( 'circ.max_accept_return_of_lost',
1476   'Circ: Void lost max interval',
1477   'Items that have been lost this long will not result in voided billings when returned.  E.g. ''6 months''',
1478   'interval' ),
1479
1480 ( 'circ.void_lost_proc_fee_on_checkin',
1481   'Circ: Void processing fee on lost item return',
1482   'Void processing fee when lost item returned',
1483   'bool' ),
1484
1485 ( 'circ.restore_overdue_on_lost_return',
1486   'Circ: Restore overdues on lost item return',
1487   'Restore overdue fines on lost item return',
1488   'bool' ),
1489
1490 ( 'circ.lost_immediately_available',
1491   'Circ: Lost items usable on checkin',
1492   'Lost items are usable on checkin instead of going ''home'' first',
1493   'bool' ),
1494
1495 ( 'circ.holds_fifo',
1496   'Holds: FIFO',
1497   'Force holds to a more strict First-In, First-Out capture',
1498   'bool' ),
1499
1500 ( 'opac.allow_pending_address',
1501   'OPAC: Allow pending addresses',
1502   'If enabled, patrons can create and edit existing addresses.  Addresses are kept in a pending state until staff approves the changes',
1503   'bool' ),
1504
1505 ( 'ui.circ.show_billing_tab_on_bills',
1506   'Show billing tab first when bills are present',
1507   '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',
1508   'bool' ),
1509
1510 ( 'ui.general.idle_timeout',
1511     'GUI: Idle timeout',
1512     '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).',
1513     'integer' ),
1514
1515 ( 'ui.circ.in_house_use.entry_cap',
1516   'GUI: Record In-House Use: Maximum # of uses allowed per entry.',
1517   'The # of uses entry in the Record In-House Use interface may not exceed the value of this setting.',
1518   'integer' ),
1519
1520 ( 'ui.circ.in_house_use.entry_warn',
1521   'GUI: Record In-House Use: # of uses threshold for Are You Sure? dialog.',
1522   'In the Record In-House Use interface, a submission attempt will warn if the # of uses field exceeds the value of this setting.',
1523   'integer' ),
1524
1525 ( 'acq.default_circ_modifier',
1526   'Default circulation modifier',
1527   null,
1528   'string' ),
1529
1530 ( 'acq.tmp_barcode_prefix',
1531   'Temporary barcode prefix',
1532   null,
1533   'string' ),
1534
1535 ( 'acq.tmp_callnumber_prefix',
1536   'Temporary call number prefix',
1537   null,
1538   'string' ),
1539
1540 ( 'ui.circ.patron_summary.horizontal',
1541   'Patron circulation summary is horizontal',
1542   null,
1543   'bool' ),
1544
1545 ( 'ui.staff.require_initials',
1546   oils_i18n_gettext('ui.staff.require_initials', 'GUI: Require staff initials for entry/edit of item/patron/penalty notes/messages.', 'coust', 'label'),
1547   oils_i18n_gettext('ui.staff.require_initials', 'Appends staff initials and edit date into note content.', 'coust', 'description'),
1548   'bool' ),
1549
1550 ( 'ui.general.button_bar',
1551   'Button bar',
1552   null,
1553   'bool' ),
1554
1555 ( 'circ.hold_shelf_status_delay',
1556   'Hold Shelf Status Delay',
1557   '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.',
1558   'interval' ),
1559
1560 ( 'circ.patron_invalid_address_apply_penalty',
1561   'Invalid patron address penalty',
1562   'When set, if a patron address is set to invalid, a penalty is applied.',
1563   'bool' ),
1564
1565 ( 'circ.checkout_fills_related_hold',
1566   'Checkout Fills Related Hold',
1567   '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',
1568   'bool'),
1569
1570 ( 'circ.selfcheck.auto_override_checkout_events',
1571   'Selfcheck override events list',
1572   'List of checkout/renewal events that the selfcheck interface should automatically override instead instead of alerting and stopping the transaction',
1573   'array' ),
1574
1575 ( 'circ.staff_client.do_not_auto_attempt_print',
1576   'Disable Automatic Print Attempt Type List',
1577   '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).',
1578   'array' ),
1579
1580 ( 'ui.patron.default_inet_access_level',
1581   'Default level of patrons'' internet access',
1582   null,
1583   'integer' ),
1584
1585 ( 'circ.max_patron_claim_return_count',
1586     'Max Patron Claims Returned Count',
1587     'When this count is exceeded, a staff override is required to mark the item as claims returned',
1588     'integer' ),
1589
1590 ( 'circ.obscure_dob',
1591     'Obscure the Date of Birth field',
1592     '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.',
1593     'bool' ),
1594
1595 ( 'circ.auto_hide_patron_summary',
1596     'GUI: Toggle off the patron summary sidebar after first view.',
1597     'When true, the patron summary sidebar will collapse after a new patron sub-interface is selected.',
1598     'bool' ),
1599
1600 ( 'credit.processor.default',
1601     'Credit card processing: Name default credit processor',
1602     'This can be "AuthorizeNet", "PayPal" (for the Website Payment Pro API), or "PayflowPro".',
1603     'string' ),
1604
1605 ( 'credit.processor.authorizenet.enabled',
1606     'Credit card processing: AuthorizeNet enabled',
1607     '',
1608     'bool' ),
1609
1610 ( 'credit.processor.authorizenet.login',
1611     'Credit card processing: AuthorizeNet login',
1612     '',
1613     'string' ),
1614
1615 ( 'credit.processor.authorizenet.password',
1616     'Credit card processing: AuthorizeNet password',
1617     '',
1618     'string' ),
1619
1620 ( 'credit.processor.authorizenet.server',
1621     'Credit card processing: AuthorizeNet server',
1622     'Required if using a developer/test account with AuthorizeNet',
1623     'string' ),
1624
1625 ( 'credit.processor.authorizenet.testmode',
1626     'Credit card processing: AuthorizeNet test mode',
1627     '',
1628     'bool' ),
1629
1630 ( 'credit.processor.paypal.enabled',
1631     'Credit card processing: PayPal enabled',
1632     '',
1633     'bool' ),
1634 ( 'credit.processor.paypal.login',
1635     'Credit card processing: PayPal login',
1636     '',
1637     'string' ),
1638 ( 'credit.processor.paypal.password',
1639     'Credit card processing: PayPal password',
1640     '',
1641     'string' ),
1642 ( 'credit.processor.paypal.signature',
1643     'Credit card processing: PayPal signature',
1644     '',
1645     'string' ),
1646 ( 'credit.processor.paypal.testmode',
1647     'Credit card processing: PayPal test mode',
1648     '',
1649     'bool' ),
1650
1651 ( 'ui.admin.work_log.max_entries',
1652     oils_i18n_gettext('ui.admin.work_log.max_entries', 'GUI: Work Log: Maximum Actions Logged', 'coust', 'label'),
1653     oils_i18n_gettext('ui.admin.work_log.max_entries', 'Maximum entries for "Most Recent Staff Actions" section of the Work Log interface.', 'coust', 'description'),
1654   'interval' ),
1655
1656 ( 'ui.admin.patron_log.max_entries',
1657     oils_i18n_gettext('ui.admin.patron_log.max_entries', 'GUI: Work Log: Maximum Patrons Logged', 'coust', 'label'),
1658     oils_i18n_gettext('ui.admin.patron_log.max_entries', 'Maximum entries for "Most Recently Affected Patrons..." section of the Work Log interface.', 'coust', 'description'),
1659   'interval' ),
1660
1661 ( 'lib.courier_code',
1662     oils_i18n_gettext('lib.courier_code', 'Courier Code', 'coust', 'label'),
1663     oils_i18n_gettext('lib.courier_code', 'Courier Code for the library.  Available in transit slip templates as the %courier_code% macro.', 'coust', 'description'),
1664     'string'),
1665
1666 ( 'circ.block_renews_for_holds',
1667     oils_i18n_gettext('circ.block_renews_for_holds', 'Holds: Block Renewal of Items Needed for Holds', 'coust', 'label'),
1668     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'),
1669     'bool' ),
1670
1671 ( 'circ.password_reset_request_per_user_limit',
1672     oils_i18n_gettext('circ.password_reset_request_per_user_limit', 'Circulation: Maximum concurrently active self-serve password reset requests per user', 'coust', 'label'),
1673     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'),
1674     'string'),
1675
1676 ( 'circ.password_reset_request_time_to_live',
1677     oils_i18n_gettext('circ.password_reset_request_time_to_live', 'Circulation: Self-serve password reset request time-to-live', 'coust', 'label'),
1678     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'),
1679     'string'),
1680
1681 ( 'circ.password_reset_request_throttle',
1682     oils_i18n_gettext('circ.password_reset_request_throttle', 'Circulation: Maximum concurrently active self-serve password reset requests', 'coust', 'label'),
1683     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'),
1684     'string')
1685 ;
1686
1687 INSERT INTO config.org_unit_setting_type ( name, label, description, datatype ) VALUES (
1688         'ui.circ.suppress_checkin_popups',
1689         oils_i18n_gettext(
1690             'ui.circ.suppress_checkin_popups', 
1691             'Circ: Suppress popup-dialogs during check-in.', 
1692             'coust', 
1693             'label'),
1694         oils_i18n_gettext(
1695             'ui.circ.suppress_checkin_popups', 
1696             'Circ: Suppress popup-dialogs during check-in.', 
1697             'coust', 
1698             'description'),
1699         'bool'
1700 );
1701
1702 INSERT INTO config.org_unit_setting_type ( name, label, description, datatype ) VALUES (
1703         'format.date',
1704         oils_i18n_gettext(
1705             'format.date',
1706             'GUI: Format Dates with this pattern.', 
1707             'coust', 
1708             'label'),
1709         oils_i18n_gettext(
1710             'format.date',
1711             'GUI: Format Dates with this pattern (examples: "yyyy-MM-dd" for "2010-04-26", "MMM d, yyyy" for "Apr 26, 2010")', 
1712             'coust', 
1713             'description'),
1714         'string'
1715 ), (
1716         'format.time',
1717         oils_i18n_gettext(
1718             'format.time',
1719             'GUI: Format Times with this pattern.', 
1720             'coust', 
1721             'label'),
1722         oils_i18n_gettext(
1723             'format.time',
1724             '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")', 
1725             'coust', 
1726             'description'),
1727         'string'
1728 );
1729
1730 INSERT INTO config.org_unit_setting_type ( name, label, description, datatype ) VALUES (
1731         'cat.bib.delete_on_no_copy_via_acq_lineitem_cancel',
1732         oils_i18n_gettext(
1733             'cat.bib.delete_on_no_copy_via_acq_lineitem_cancel',
1734             'CAT: Delete bib if all copies are deleted via Acquisitions lineitem cancellation.', 
1735             'coust', 
1736             'label'),
1737         oils_i18n_gettext(
1738             'cat.bib.delete_on_no_copy_via_acq_lineitem_cancel',
1739             'CAT: Delete bib if all copies are deleted via Acquisitions lineitem cancellation.', 
1740             'coust', 
1741             'description'),
1742         'bool'
1743 );
1744
1745 INSERT INTO config.org_unit_setting_type ( name, label, description, datatype ) VALUES (
1746         'url.remote_column_settings',
1747         oils_i18n_gettext(
1748             'url.remote_column_settings',
1749             'GUI: URL for remote directory containing list column settings.', 
1750             'coust', 
1751             'label'),
1752         oils_i18n_gettext(
1753             'url.remote_column_settings',
1754             '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.', 
1755             'coust', 
1756             'description'),
1757         'string'
1758 );
1759
1760 INSERT INTO config.org_unit_setting_type ( name, label, description, datatype ) VALUES (
1761         'gui.disable_local_save_columns',
1762         oils_i18n_gettext(
1763             'gui.disable_local_save_columns',
1764             'GUI: Disable the ability to save list column configurations locally.', 
1765             'coust', 
1766             'label'),
1767         oils_i18n_gettext(
1768             'gui.disable_local_save_columns',
1769             '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.', 
1770             'coust', 
1771             'description'),
1772         'bool'
1773 );
1774
1775 INSERT INTO config.org_unit_setting_type ( name, label, description, datatype ) VALUES (
1776         'circ.password_reset_request_requires_matching_email',
1777         oils_i18n_gettext(
1778             'circ.password_reset_request_requires_matching_email',
1779             'Circulation: Require matching email address for password reset requests', 
1780             'coust', 
1781             'label'),
1782         oils_i18n_gettext(
1783             'circ.password_reset_request_requires_matching_email',
1784             'Circulation: Require matching email address for password reset requests', 
1785             'coust', 
1786             'description'),
1787         'bool'
1788 );
1789
1790 INSERT INTO config.org_unit_setting_type ( name, label, description, datatype ) VALUES (
1791         'circ.holds.expired_patron_block',
1792         oils_i18n_gettext(
1793             'circ.holds.expired_patron_block',
1794             'Circulation: Block hold request if hold recipient privileges have expired', 
1795             'coust', 
1796             'label'),
1797         oils_i18n_gettext(
1798             'circ.holds.expired_patron_block',
1799             'Circulation: Block hold request if hold recipient privileges have expired', 
1800             'coust', 
1801             'description'),
1802         'bool'
1803 );
1804
1805 INSERT INTO config.org_unit_setting_type
1806     (name, label, description, datatype) VALUES (
1807         'circ.booking_reservation.default_elbow_room',
1808         oils_i18n_gettext(
1809             'circ.booking_reservation.default_elbow_room',
1810             'Booking: Elbow room',
1811             'coust',
1812             'label'
1813         ),
1814         oils_i18n_gettext(
1815             'circ.booking_reservation.default_elbow_room',
1816             '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.',
1817             'coust',
1818             'label'
1819         ),
1820         'interval'
1821     );
1822
1823 -- Org_unit_setting_type(s) that need an fm_class:
1824 INSERT into config.org_unit_setting_type
1825 ( name, label, description, datatype, fm_class ) VALUES
1826 ( 'acq.default_copy_location',
1827   'Default copy location',
1828   null,
1829   'link',
1830   'acpl' );
1831
1832 INSERT INTO config.org_unit_setting_type (name, label, description, datatype) VALUES (
1833     'circ.holds.org_unit_target_weight',
1834     'Holds: Org Unit Target Weight',
1835     '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.',
1836     'integer'
1837 );
1838
1839 INSERT INTO config.org_unit_setting_type (name, label, description, datatype) VALUES (
1840     'circ.holds.target_holds_by_org_unit_weight',
1841     'Holds: Use weight-based hold targeting',
1842     'Use library weight based hold targeting',
1843     'bool'
1844 );
1845
1846 INSERT INTO config.org_unit_setting_type (name, label, description, datatype) VALUES (
1847     'circ.holds.max_org_unit_target_loops',
1848     'Holds: Maximum library target attempts',
1849     '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',
1850     'integer'
1851 );
1852
1853
1854 -- Org setting for overriding the circ lib of a precat copy
1855 INSERT INTO config.org_unit_setting_type (name, label, description, datatype) VALUES (
1856     'circ.pre_cat_copy_circ_lib',
1857     'Pre-cat Item Circ Lib',
1858     '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',
1859     'string'
1860 );
1861
1862 -- Circ auto-renew interval setting
1863 INSERT INTO config.org_unit_setting_type (name, label, description, datatype) VALUES (
1864     'circ.checkout_auto_renew_age',
1865     'Checkout auto renew age',
1866     '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',
1867     'interval'
1868 );
1869
1870 -- Setting for behind the desk hold pickups
1871 INSERT INTO config.org_unit_setting_type (name, label, description, datatype) VALUES (
1872     'circ.holds.behind_desk_pickup_supported',
1873     'Holds: Behind Desk Pickup Supported',
1874     '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',
1875     'bool'
1876 );
1877
1878 INSERT INTO config.org_unit_setting_type ( name, label, description, datatype ) VALUES (
1879         'acq.holds.allow_holds_from_purchase_request',
1880         oils_i18n_gettext(
1881             'acq.holds.allow_holds_from_purchase_request', 
1882             'Allows patrons to create automatic holds from purchase requests.', 
1883             'coust', 
1884             'label'),
1885         oils_i18n_gettext(
1886             'acq.holds.allow_holds_from_purchase_request', 
1887             'Allows patrons to create automatic holds from purchase requests.', 
1888             'coust', 
1889             'description'),
1890         'bool'
1891 );
1892
1893 INSERT INTO config.org_unit_setting_type (name, label, description, datatype) VALUES (
1894     'circ.holds.target_skip_me',
1895     'Skip For Hold Targeting',
1896     'When true, don''t target any copies at this org unit for holds',
1897     'bool'
1898 );
1899
1900 -- claims returned mark item missing 
1901 INSERT INTO
1902     config.org_unit_setting_type ( name, label, description, datatype )
1903     VALUES (
1904         'circ.claim_return.mark_missing',
1905         'Claim Return: Mark copy as missing', 
1906         'When a circ is marked as claims-returned, also mark the copy as missing',
1907         'bool'
1908     );
1909
1910 -- claims never checked out mark item missing 
1911 INSERT INTO
1912     config.org_unit_setting_type ( name, label, description, datatype )
1913     VALUES (
1914         'circ.claim_never_checked_out.mark_missing',
1915         'Claim Never Checked Out: Mark copy as missing', 
1916         'When a circ is marked as claims-never-checked-out, mark the copy as missing',
1917         'bool'
1918     );
1919
1920 -- mark damaged void overdue setting
1921 INSERT INTO
1922     config.org_unit_setting_type ( name, label, description, datatype )
1923     VALUES (
1924         'circ.damaged.void_ovedue',
1925         'Mark item damaged voids overdues',
1926         'When an item is marked damaged, overdue fines on the most recent circulation are voided.',
1927         'bool'
1928     );
1929
1930 -- hold cancel display limits
1931 INSERT INTO config.org_unit_setting_type ( name, label, description, datatype )
1932     VALUES (
1933         'circ.holds.canceled.display_count',
1934         'Holds: Canceled holds display count',
1935         'How many canceled holds to show in patron holds interfaces',
1936         'integer'
1937     );
1938
1939 INSERT INTO config.org_unit_setting_type ( name, label, description, datatype )
1940     VALUES (
1941         'circ.holds.canceled.display_age',
1942         'Holds: Canceled holds display age',
1943         'Show all canceled holds that were canceled within this amount of time',
1944         'interval'
1945     );
1946
1947 INSERT INTO config.org_unit_setting_type ( name, label, description, datatype )
1948     VALUES (
1949         'circ.holds.uncancel.reset_request_time',
1950         'Holds: Reset request time on un-cancel',
1951         'When a hold is uncanceled, reset the request time to push it to the end of the queue',
1952         'bool'
1953     );
1954
1955 INSERT INTO config.org_unit_setting_type (name, label, description, datatype)
1956     VALUES (
1957         'circ.holds.default_shelf_expire_interval',
1958         'Default hold shelf expire interval',
1959         '',
1960         'interval'
1961 );
1962
1963 INSERT INTO config.org_unit_setting_type (name, label, description, datatype, fm_class)
1964     VALUES (
1965         'circ.claim_return.copy_status', 
1966         'Claim Return Copy Status', 
1967         'Claims returned copies are put into this status.  Default is to leave the copy in the Checked Out status',
1968         'link', 
1969         'ccs' 
1970     );
1971
1972 INSERT INTO config.org_unit_setting_type ( name, label, description, datatype ) 
1973     VALUES ( 
1974         'circ.max_fine.cap_at_price',
1975         oils_i18n_gettext('circ.max_fine.cap_at_price', 'Circ: Cap Max Fine at Item Price', 'coust', 'label'),
1976         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'),
1977         'bool' 
1978     );
1979
1980 INSERT INTO config.org_unit_setting_type ( name, label, description, datatype, fm_class ) 
1981     VALUES ( 
1982         'circ.holds.clear_shelf.copy_status',
1983         oils_i18n_gettext('circ.holds.clear_shelf.copy_status', 'Holds: Clear shelf copy status', 'coust', 'label'),
1984         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'),
1985         'link',
1986         'ccs'
1987     );
1988
1989 INSERT INTO config.org_unit_setting_type ( name, label, description, datatype )
1990     VALUES ( 
1991         'circ.selfcheck.workstation_required',
1992         oils_i18n_gettext('circ.selfcheck.workstation_required', 'Selfcheck: Workstation Required', 'coust', 'label'),
1993         oils_i18n_gettext('circ.selfcheck.workstation_required', 'All selfcheck stations must use a workstation', 'coust', 'description'),
1994         'bool'
1995     ), (
1996         'circ.selfcheck.patron_password_required',
1997         oils_i18n_gettext('circ.selfcheck.patron_password_required', 'Selfcheck: Require Patron Password', 'coust', 'label'),
1998         oils_i18n_gettext('circ.selfcheck.patron_password_required', 'Patron must log in with barcode and password at selfcheck station', 'coust', 'description'),
1999         'bool'
2000     );
2001
2002 INSERT INTO config.org_unit_setting_type ( name, label, description, datatype )
2003     VALUES ( 
2004         'circ.selfcheck.alert.sound',
2005         oils_i18n_gettext('circ.selfcheck.alert.sound', 'Selfcheck: Audio Alerts', 'coust', 'label'),
2006         oils_i18n_gettext('circ.selfcheck.alert.sound', 'Use audio alerts for selfcheck events', 'coust', 'description'),
2007         'bool'
2008     );
2009
2010 INSERT INTO
2011     config.org_unit_setting_type (name, label, description, datatype)
2012     VALUES (
2013         'notice.telephony.callfile_lines',
2014         'Telephony: Arbitrary line(s) to include in each notice callfile',
2015         $$
2016         This overrides lines from opensrf.xml.
2017         Line(s) must be valid for your target server and platform
2018         (e.g. Asterisk 1.4).
2019         $$,
2020         'string'
2021     );
2022
2023 INSERT INTO config.org_unit_setting_type ( name, label, description, datatype )
2024     VALUES ( 
2025         'circ.offline.username_allowed',
2026         oils_i18n_gettext('circ.offline.username_allowed', 'Offline: Patron Usernames Allowed', 'coust', 'label'),
2027         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'),
2028         'bool'
2029     );
2030
2031 INSERT INTO config.org_unit_setting_type ( name, label, description, datatype )
2032 VALUES (
2033     'acq.fund.balance_limit.warn',
2034     oils_i18n_gettext('acq.fund.balance_limit.warn', 'Fund Spending Limit for Warning', 'coust', 'label'),
2035     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'),
2036     'integer'
2037 );
2038
2039 INSERT INTO config.org_unit_setting_type ( name, label, description, datatype )
2040 VALUES (
2041     'acq.fund.balance_limit.block',
2042     oils_i18n_gettext('acq.fund.balance_limit.block', 'Fund Spending Limit for Block', 'coust', 'label'),
2043     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'),
2044     'integer'
2045 );
2046
2047 INSERT INTO config.org_unit_setting_type ( name, label, description, datatype )
2048     VALUES (
2049         'circ.holds.hold_has_copy_at.alert',
2050         oils_i18n_gettext('circ.holds.hold_has_copy_at.alert', 'Holds: Has Local Copy Alert', 'coust', 'label'),
2051         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'),
2052         'bool'
2053     ),(
2054         'circ.holds.hold_has_copy_at.block',
2055         oils_i18n_gettext('circ.holds.hold_has_copy_at.block', 'Holds: Has Local Copy Block', 'coust', 'label'),
2056         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'),
2057         'bool'
2058     );
2059
2060 INSERT INTO config.org_unit_setting_type ( name, label, description, datatype )
2061 VALUES (
2062     'auth.persistent_login_interval',
2063     oils_i18n_gettext('auth.persistent_login_interval', 'Persistent Login Duration', 'coust', 'label'),
2064     oils_i18n_gettext('auth.persistent_login_interval', 'How long a persistent login lasts.  E.g. ''2 weeks''', 'coust', 'description'),
2065     'interval'
2066 );
2067
2068 INSERT INTO config.org_unit_setting_type ( name, label, description, datatype ) VALUES (
2069         'cat.marc_control_number_identifier',
2070         oils_i18n_gettext(
2071             'cat.marc_control_number_identifier', 
2072             'Cat: Defines the control number identifier used in 003 and 035 fields.', 
2073             'coust', 
2074             'label'),
2075         oils_i18n_gettext(
2076             'cat.marc_control_number_identifier', 
2077             'Cat: Defines the control number identifier used in 003 and 035 fields.', 
2078             'coust', 
2079             'description'),
2080         'string'
2081 );
2082
2083 INSERT INTO config.org_unit_setting_type (name, label, description, datatype) 
2084     VALUES (
2085         'circ.selfcheck.block_checkout_on_copy_status',
2086         oils_i18n_gettext(
2087             'circ.selfcheck.block_checkout_on_copy_status',
2088             'Selfcheck: Block copy checkout status',
2089             'coust',
2090             'label'
2091         ),
2092         oils_i18n_gettext(
2093             'circ.selfcheck.block_checkout_on_copy_status',
2094             'List of copy status IDs that will block checkout even if the generic COPY_NOT_AVAILABLE event is overridden',
2095             'coust',
2096             'description'
2097         ),
2098         'array'
2099     );
2100
2101 INSERT INTO config.org_unit_setting_type ( name, label, description, datatype, fm_class )
2102 VALUES (
2103     'serial.prev_issuance_copy_location',
2104     oils_i18n_gettext(
2105         'serial.prev_issuance_copy_location',
2106         'Serials: Previous Issuance Copy Location',
2107         'coust',
2108         'label'
2109     ),
2110     oils_i18n_gettext(
2111         'serial.prev_issuance_copy_location',
2112         'When a serial issuance is received, copies (units) of the previous issuance will be automatically moved into the configured shelving location',
2113         'coust',
2114         'descripton'
2115         ),
2116     'link',
2117     'acpl'
2118 );
2119
2120 INSERT INTO config.org_unit_setting_type ( name, label, description, datatype, fm_class )
2121 VALUES (
2122     'cat.default_classification_scheme',
2123     oils_i18n_gettext(
2124         'cat.default_classification_scheme',
2125         'Cataloging: Default Classification Scheme',
2126         'coust',
2127         'label'
2128     ),
2129     oils_i18n_gettext(
2130         'cat.default_classification_scheme',
2131         'Defines the default classification scheme for new call numbers: 1 = Generic; 2 = Dewey; 3 = LC',
2132         'coust',
2133         'descripton'
2134         ),
2135     'link',
2136     'acnc'
2137 );
2138
2139 INSERT INTO config.org_unit_setting_type ( name, label, description, datatype ) VALUES (
2140         'opac.org_unit_hiding.depth',
2141         oils_i18n_gettext(
2142             'opac.org_unit_hiding.depth',
2143             'OPAC: Org Unit Hiding Depth', 
2144             'coust', 
2145             'label'),
2146         oils_i18n_gettext(
2147             'opac.org_unit_hiding.depth',
2148             '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.', 
2149             'coust', 
2150             'description'),
2151         'integer'
2152 );
2153
2154 INSERT INTO config.org_unit_setting_type ( name, label, description, datatype )
2155     VALUES ( 
2156         'circ.holds.clear_shelf.no_capture_holds',
2157         oils_i18n_gettext('circ.holds.clear_shelf.no_capture_holds', 'Holds: Bypass hold capture during clear shelf process', 'coust', 'label'),
2158         oils_i18n_gettext('circ.holds.clear_shelf.no_capture_holds', 'During the clear shelf process, avoid capturing new holds on cleared items.', 'coust', 'description'),
2159         'bool'
2160     );
2161
2162 INSERT INTO config.org_unit_setting_type (name, label, description, datatype) VALUES (
2163     'circ.booking_reservation.stop_circ',
2164     'Disallow circulation of items when they are on booking reserve and that reserve overlaps with the checkout period',
2165     '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.',
2166     'bool'
2167 );
2168
2169 ---------------------------------
2170 -- Seed data for usr_setting_type
2171 ----------------------------------
2172
2173 INSERT INTO config.usr_setting_type (name,opac_visible,label,description,datatype)
2174     VALUES ('opac.default_font', TRUE, 'OPAC Font Size', 'OPAC Font Size', 'string');
2175
2176 INSERT INTO config.usr_setting_type (name,opac_visible,label,description,datatype)
2177     VALUES ('opac.default_search_depth', TRUE, 'OPAC Search Depth', 'OPAC Search Depth', 'integer');
2178
2179 INSERT INTO config.usr_setting_type (name,opac_visible,label,description,datatype)
2180     VALUES ('opac.default_search_location', TRUE, 'OPAC Search Location', 'OPAC Search Location', 'integer');
2181
2182 INSERT INTO config.usr_setting_type (name,opac_visible,label,description,datatype)
2183     VALUES ('opac.hits_per_page', TRUE, 'Hits per Page', 'Hits per Page', 'string');
2184
2185 INSERT INTO config.usr_setting_type (name,opac_visible,label,description,datatype)
2186     VALUES ('opac.hold_notify', TRUE, 'Hold Notification Format', 'Hold Notification Format', 'string');
2187
2188 INSERT INTO config.usr_setting_type (name,opac_visible,label,description,datatype)
2189     VALUES ('staff_client.catalog.record_view.default', TRUE, 'Default Record View', 'Default Record View', 'string');
2190
2191 INSERT INTO config.usr_setting_type (name,opac_visible,label,description,datatype)
2192     VALUES ('staff_client.copy_editor.templates', TRUE, 'Copy Editor Template', 'Copy Editor Template', 'object');
2193
2194 INSERT INTO config.usr_setting_type (name,opac_visible,label,description,datatype)
2195     VALUES ('circ.holds_behind_desk', FALSE, 'Hold is behind Circ Desk', 'Hold is behind Circ Desk', 'bool');
2196
2197 INSERT INTO config.usr_setting_type (name,opac_visible,label,description,datatype)
2198     VALUES (
2199         'history.circ.retention_age',
2200         TRUE,
2201         oils_i18n_gettext('history.circ.retention_age','Historical Circulation Retention Age','cust','label'),
2202         oils_i18n_gettext('history.circ.retention_age','Historical Circulation Retention Age','cust','description'),
2203         'interval'
2204     ),(
2205         'history.circ.retention_start',
2206         FALSE,
2207         oils_i18n_gettext('history.circ.retention_start','Historical Circulation Retention Start Date','cust','label'),
2208         oils_i18n_gettext('history.circ.retention_start','Historical Circulation Retention Start Date','cust','description'),
2209         'date'
2210     );
2211
2212 INSERT INTO config.usr_setting_type (name,opac_visible,label,description,datatype)
2213     VALUES (
2214         'history.hold.retention_age',
2215         TRUE,
2216         oils_i18n_gettext('history.hold.retention_age','Historical Hold Retention Age','cust','label'),
2217         oils_i18n_gettext('history.hold.retention_age','Historical Hold Retention Age','cust','description'),
2218         'interval'
2219     ),(
2220         'history.hold.retention_start',
2221         TRUE,
2222         oils_i18n_gettext('history.hold.retention_start','Historical Hold Retention Start Date','cust','label'),
2223         oils_i18n_gettext('history.hold.retention_start','Historical Hold Retention Start Date','cust','description'),
2224         'interval'
2225     ),(
2226         'history.hold.retention_count',
2227         TRUE,
2228         oils_i18n_gettext('history.hold.retention_count','Historical Hold Retention Count','cust','label'),
2229         oils_i18n_gettext('history.hold.retention_count','Historical Hold Retention Count','cust','description'),
2230         'integer'
2231     );
2232
2233 INSERT INTO config.usr_setting_type (name, opac_visible, label, description, datatype)
2234     VALUES (
2235         'opac.default_sort',
2236         TRUE,
2237         oils_i18n_gettext(
2238             'opac.default_sort',
2239             'OPAC Default Search Sort',
2240             'cust',
2241             'label'
2242         ),
2243         oils_i18n_gettext(
2244             'opac.default_sort',
2245             'OPAC Default Search Sort',
2246             'cust',
2247             'description'
2248         ),
2249         'string'
2250     );
2251
2252 INSERT INTO config.org_unit_setting_type ( name, label, description, datatype, fm_class ) VALUES (
2253         'circ.missing_pieces.copy_status',
2254         oils_i18n_gettext(
2255             'circ.missing_pieces.copy_status',
2256             'Circulation: Item Status for Missing Pieces',
2257             'coust',
2258             'label'),
2259         oils_i18n_gettext(
2260             'circ.missing_pieces.copy_status',
2261             '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.',
2262             'coust',
2263             'description'),
2264         'link',
2265         'ccs'
2266 );
2267
2268 INSERT INTO config.org_unit_setting_type ( name, label, description, datatype ) VALUES (
2269         'circ.do_not_tally_claims_returned',
2270         oils_i18n_gettext(
2271             'circ.do_not_tally_claims_returned',
2272             'Circulation: Do not include outstanding Claims Returned circulations in lump sum tallies in Patron Display.',
2273             'coust',
2274             'label'),
2275         oils_i18n_gettext(
2276             'circ.do_not_tally_claims_returned',
2277             '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.',
2278             'coust',
2279             'description'),
2280         'bool'
2281 );
2282
2283 INSERT INTO config.org_unit_setting_type (name, label, description, datatype)
2284     VALUES
2285         ('cat.label.font.size',
2286             oils_i18n_gettext('cat.label.font.size',
2287                 'Cataloging: Spine and pocket label font size', 'coust', 'label'),
2288             oils_i18n_gettext('cat.label.font.size',
2289                 'Set the default font size for spine and pocket labels', 'coust', 'description'),
2290             'integer'
2291         )
2292         ,('cat.label.font.family',
2293             oils_i18n_gettext('cat.label.font.family',
2294                 'Cataloging: Spine and pocket label font family', 'coust', 'label'),
2295             oils_i18n_gettext('cat.label.font.family',
2296                 '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".',
2297                 'coust', 'description'),
2298             'string'
2299         )
2300         ,('cat.spine.line.width',
2301             oils_i18n_gettext('cat.spine.line.width',
2302                 'Cataloging: Spine label line width', 'coust', 'label'),
2303             oils_i18n_gettext('cat.spine.line.width',
2304                 'Set the default line width for spine labels in number of characters. This specifies the boundary at which lines must be wrapped.',
2305                 'coust', 'description'),
2306             'integer'
2307         )
2308         ,('cat.spine.line.height',
2309             oils_i18n_gettext('cat.spine.line.height',
2310                 'Cataloging: Spine label maximum lines', 'coust', 'label'),
2311             oils_i18n_gettext('cat.spine.line.height',
2312                 'Set the default maximum number of lines for spine labels.',
2313                 'coust', 'description'),
2314             'integer'
2315         )
2316         ,('cat.spine.line.margin',
2317             oils_i18n_gettext('cat.spine.line.margin',
2318                 'Cataloging: Spine label left margin', 'coust', 'label'),
2319             oils_i18n_gettext('cat.spine.line.margin',
2320                 'Set the left margin for spine labels in number of characters.',
2321                 'coust', 'description'),
2322             'integer'
2323         )
2324 ;
2325
2326 INSERT INTO config.org_unit_setting_type (name, label, description, datatype)
2327     VALUES
2328         ('cat.label.font.weight',
2329             oils_i18n_gettext('cat.label.font.weight',
2330                 'Cataloging: Spine and pocket label font weight', 'coust', 'label'),
2331             oils_i18n_gettext('cat.label.font.weight',
2332                 'Set the preferred font weight for spine and pocket labels. You can specify "normal", "bold", "bolder", or "lighter".',
2333                 'coust', 'description'),
2334             'string'
2335         )
2336 ;
2337
2338 INSERT INTO config.org_unit_setting_type ( name, label, description, datatype ) VALUES (
2339         'circ.patron_edit.clone.copy_address',
2340         oils_i18n_gettext(
2341             'circ.patron_edit.clone.copy_address',
2342             'Patron Registration: Cloned patrons get address copy',
2343             'coust',
2344             'label'
2345         ),
2346         oils_i18n_gettext(
2347             'circ.patron_edit.clone.copy_address',
2348             'In the Patron editor, copy addresses from the cloned user instead of linking directly to the address',
2349             'coust',
2350             'description'
2351         ),
2352         'bool'
2353 );
2354
2355 INSERT INTO config.org_unit_setting_type ( name, label, description, datatype, fm_class ) VALUES (
2356         'ui.patron.default_ident_type',
2357         oils_i18n_gettext(
2358             'ui.patron.default_ident_type',
2359             'GUI: Default Ident Type for Patron Registration',
2360             'coust',
2361             'label'),
2362         oils_i18n_gettext(
2363             'ui.patron.default_ident_type',
2364             'This is the default Ident Type for new users in the patron editor.',
2365             'coust',
2366             'description'),
2367         'link',
2368         'cit'
2369 );
2370
2371 INSERT INTO config.org_unit_setting_type ( name, label, description, datatype ) VALUES (
2372         'ui.patron.default_country',
2373         oils_i18n_gettext(
2374             'ui.patron.default_country',
2375             'GUI: Default Country for New Addresses in Patron Editor',
2376             'coust',
2377             'label'),
2378         oils_i18n_gettext(
2379             'ui.patron.default_country',
2380             'This is the default Country for new addresses in the patron editor.',
2381             'coust',
2382             'description'),
2383         'string'
2384 );
2385
2386 INSERT INTO config.org_unit_setting_type ( name, label, description, datatype ) VALUES (
2387         'ui.patron.registration.require_address',
2388         oils_i18n_gettext(
2389             'ui.patron.registration.require_address',
2390             'GUI: Require at least one address for Patron Registration',
2391             'coust',
2392             'label'),
2393         oils_i18n_gettext(
2394             'ui.patron.registration.require_address',
2395             'Enforces a requirement for having at least one address for a patron during registration.',
2396             'coust',
2397             'description'),
2398         'bool'
2399 );
2400
2401 INSERT INTO config.org_unit_setting_type (
2402     name, label, description, datatype
2403 ) VALUES
2404     ('credit.processor.payflowpro.enabled',
2405         'Credit card processing: Enable PayflowPro payments',
2406         'This is NOT the same thing as the settings labeled with just "PayPal."',
2407         'bool'
2408     ),
2409     ('credit.processor.payflowpro.login',
2410         'Credit card processing: PayflowPro login/merchant ID',
2411         'Often the same thing as the PayPal manager login',
2412         'string'
2413     ),
2414     ('credit.processor.payflowpro.password',
2415         'Credit card processing: PayflowPro password',
2416         'PayflowPro password',
2417         'string'
2418     ),
2419     ('credit.processor.payflowpro.testmode',
2420         'Credit card processing: PayflowPro test mode',
2421         'Do not really process transactions, but stay in test mode - uses pilot-payflowpro.paypal.com instead of the usual host',
2422         'bool'
2423     ),
2424     ('credit.processor.payflowpro.vendor',
2425         'Credit card processing: PayflowPro vendor',
2426         'Often the same thing as the login',
2427         'string'
2428     ),
2429     ('credit.processor.payflowpro.partner',
2430         'Credit card processing: PayflowPro partner',
2431         'Often "PayPal" or "VeriSign", sometimes others',
2432         'string'
2433     );
2434
2435 -- Patch the name of an old selfcheck setting
2436 UPDATE actor.org_unit_setting
2437     SET name = 'circ.selfcheck.alert.popup'
2438     WHERE name = 'circ.selfcheck.alert_on_checkout_event';
2439
2440 -- Rename certain existing org_unit settings, if present,
2441 -- and make sure their values are JSON
2442 UPDATE actor.org_unit_setting SET
2443     name = 'circ.holds.default_estimated_wait_interval',
2444     --
2445     -- The value column should be JSON.  The old value should be a number,
2446     -- but it may or may not be quoted.  The following CASE behaves
2447     -- differently depending on whether value is quoted.  It is simplistic,
2448     -- and will be defeated by leading or trailing white space, or various
2449     -- malformations.
2450     --
2451     value = CASE WHEN SUBSTR( value, 1, 1 ) = '"'
2452                 THEN '"' || SUBSTR( value, 2, LENGTH(value) - 2 ) || ' days"'
2453                 ELSE '"' || value || ' days"'
2454             END
2455 WHERE name = 'circ.hold_estimate_wait_interval';
2456
2457 -- Create types for existing org unit settings
2458 -- not otherwise accounted for
2459
2460 INSERT INTO config.org_unit_setting_type(
2461  name,
2462  label,
2463  description
2464 )
2465 SELECT DISTINCT
2466         name,
2467         name,
2468         'FIXME'
2469 FROM
2470         actor.org_unit_setting
2471 WHERE
2472         name NOT IN (
2473                 SELECT name
2474                 FROM config.org_unit_setting_type
2475         );
2476
2477 -- Add foreign key to org_unit_setting
2478
2479 ALTER TABLE actor.org_unit_setting
2480         ADD FOREIGN KEY (name) REFERENCES config.org_unit_setting_type (name)
2481                 DEFERRABLE INITIALLY DEFERRED;
2482
2483 -- Create types for existing user settings
2484 -- not otherwise accounted for
2485
2486 INSERT INTO config.usr_setting_type (
2487         name,
2488         label,
2489         description
2490 )
2491 SELECT DISTINCT
2492         name,
2493         name,
2494         'FIXME'
2495 FROM
2496         actor.usr_setting
2497 WHERE
2498         name NOT IN (
2499                 SELECT name
2500                 FROM config.usr_setting_type
2501         );
2502
2503 -- Add foreign key to user_setting_type
2504
2505 ALTER TABLE actor.usr_setting
2506         ADD FOREIGN KEY (name) REFERENCES config.usr_setting_type (name)
2507                 ON DELETE CASCADE ON UPDATE CASCADE
2508                 DEFERRABLE INITIALLY DEFERRED;
2509
2510 INSERT INTO actor.org_unit_setting (org_unit, name, value) VALUES
2511     (1, 'cat.spine.line.margin', 0)
2512     ,(1, 'cat.spine.line.height', 9)
2513     ,(1, 'cat.spine.line.width', 8)
2514     ,(1, 'cat.label.font.family', '"monospace"')
2515     ,(1, 'cat.label.font.size', 10)
2516     ,(1, 'cat.label.font.weight', '"normal"')
2517 ;
2518
2519 ALTER TABLE action_trigger.event_definition ADD COLUMN granularity TEXT;
2520 ALTER TABLE action_trigger.event ADD COLUMN async_output BIGINT REFERENCES action_trigger.event_output (id);
2521 ALTER TABLE action_trigger.event_definition ADD COLUMN usr_field TEXT;
2522 ALTER TABLE action_trigger.event_definition ADD COLUMN opt_in_setting TEXT REFERENCES config.usr_setting_type (name) DEFERRABLE INITIALLY DEFERRED;
2523
2524 CREATE OR REPLACE FUNCTION is_json( TEXT ) RETURNS BOOL AS $f$
2525     use JSON::XS;
2526     my $json = shift();
2527     eval { JSON::XS->new->allow_nonref->decode( $json ) };
2528     return $@ ? 0 : 1;
2529 $f$ LANGUAGE PLPERLU;
2530
2531 ALTER TABLE action_trigger.event ADD COLUMN user_data TEXT CHECK (user_data IS NULL OR is_json( user_data ));
2532
2533 INSERT INTO action_trigger.hook (key,core_type,description) VALUES (
2534     'hold_request.cancel.expire_no_target',
2535     'ahr',
2536     'A hold is cancelled because no copies were found'
2537 );
2538
2539 INSERT INTO action_trigger.hook (key,core_type,description) VALUES (
2540     'hold_request.cancel.expire_holds_shelf',
2541     'ahr',
2542     'A hold is cancelled because it was on the holds shelf too long'
2543 );
2544
2545 INSERT INTO action_trigger.hook (key,core_type,description) VALUES (
2546     'hold_request.cancel.staff',
2547     'ahr',
2548     'A hold is cancelled because it was cancelled by staff'
2549 );
2550
2551 INSERT INTO action_trigger.hook (key,core_type,description) VALUES (
2552     'hold_request.cancel.patron',
2553     'ahr',
2554     'A hold is cancelled by the patron'
2555 );
2556
2557 -- Fix typos in descriptions
2558 UPDATE action_trigger.hook SET description = 'A hold is successfully placed' WHERE key = 'hold_request.success';
2559 UPDATE action_trigger.hook SET description = 'A hold is attempted but not successfully placed' WHERE key = 'hold_request.failure';
2560
2561 -- Add a hook for renewals
2562 INSERT INTO action_trigger.hook (key,core_type,description) VALUES ('renewal','circ','Item renewed to user');
2563
2564 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');
2565
2566 -- Sample Pre-due Notice --
2567
2568 INSERT INTO action_trigger.event_definition (id, active, owner, name, hook, validator, reactor, delay, delay_field, group_field, template) 
2569     VALUES (6, 'f', 1, '3 Day Courtesy Notice', 'checkout.due', 'CircIsOpen', 'SendEmail', '-3 days', 'due_date', 'usr', 
2570 $$
2571 [%- USE date -%]
2572 [%- user = target.0.usr -%]
2573 To: [%- params.recipient_email || user.email %]
2574 From: [%- params.sender_email || default_sender %]
2575 Subject: Courtesy Notice
2576
2577 Dear [% user.family_name %], [% user.first_given_name %]
2578 As a reminder, the following items are due in 3 days.
2579
2580 [% FOR circ IN target %]
2581     Title: [% circ.target_copy.call_number.record.simple_record.title %] 
2582     Barcode: [% circ.target_copy.barcode %] 
2583     Due: [% date.format(helpers.format_date(circ.due_date), '%Y-%m-%d') %]
2584     Item Cost: [% helpers.get_copy_price(circ.target_copy) %]
2585     Library: [% circ.circ_lib.name %]
2586     Library Phone: [% circ.circ_lib.phone %]
2587 [% END %]
2588
2589 $$);
2590
2591 INSERT INTO action_trigger.environment (event_def, path) VALUES 
2592     (6, 'target_copy.call_number.record.simple_record'),
2593     (6, 'usr'),
2594     (6, 'circ_lib.billing_address');
2595
2596 INSERT INTO action_trigger.event_params (event_def, param, value) VALUES
2597     (6, 'max_delay_age', '"1 day"');
2598
2599 -- also add the max delay age to the default overdue notice event def
2600 INSERT INTO action_trigger.event_params (event_def, param, value) VALUES
2601     (1, 'max_delay_age', '"1 day"');
2602   
2603 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');
2604
2605 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.');
2606
2607 INSERT INTO action_trigger.hook (
2608         key,
2609         core_type,
2610         description,
2611         passive
2612     ) VALUES (
2613         'hold_request.shelf_expires_soon',
2614         'ahr',
2615         'A hold on the shelf will expire there soon.',
2616         TRUE
2617     );
2618
2619 INSERT INTO action_trigger.event_definition (
2620         id,
2621         active,
2622         owner,
2623         name,
2624         hook,
2625         validator,
2626         reactor,
2627         delay,
2628         delay_field,
2629         group_field,
2630         template
2631     ) VALUES (
2632         7,
2633         FALSE,
2634         1,
2635         'Hold Expires from Shelf Soon',
2636         'hold_request.shelf_expires_soon',
2637         'HoldIsAvailable',
2638         'SendEmail',
2639         '- 1 DAY',
2640         'shelf_expire_time',
2641         'usr',
2642 $$
2643 [%- USE date -%]
2644 [%- user = target.0.usr -%]
2645 To: [%- params.recipient_email || user.email %]
2646 From: [%- params.sender_email || default_sender %]
2647 Subject: Hold Available Notification
2648
2649 Dear [% user.family_name %], [% user.first_given_name %]
2650 You requested holds on the following item(s), which are available for
2651 pickup, but these holds will soon expire.
2652
2653 [% FOR hold IN target %]
2654     [%- data = helpers.get_copy_bib_basics(hold.current_copy.id) -%]
2655     Title: [% data.title %]
2656     Author: [% data.author %]
2657     Library: [% hold.pickup_lib.name %]
2658 [% END %]
2659 $$
2660     );
2661
2662 INSERT INTO action_trigger.environment (
2663         event_def,
2664         path
2665     ) VALUES
2666     ( 7, 'current_copy'),
2667     ( 7, 'pickup_lib.billing_address'),
2668     ( 7, 'usr');
2669
2670 INSERT INTO action_trigger.hook (
2671         key,
2672         core_type,
2673         description,
2674         passive
2675     ) VALUES (
2676         'hold_request.long_wait',
2677         'ahr',
2678         'A patron has been waiting on a hold to be fulfilled for a long time.',
2679         TRUE
2680     );
2681
2682 INSERT INTO action_trigger.event_definition (
2683         id,
2684         active,
2685         owner,
2686         name,
2687         hook,
2688         validator,
2689         reactor,
2690         delay,
2691         delay_field,
2692         group_field,
2693         template
2694     ) VALUES (
2695         9,
2696         FALSE,
2697         1,
2698         'Hold waiting for pickup for long time',
2699         'hold_request.long_wait',
2700         'NOOP_True',
2701         'SendEmail',
2702         '6 MONTHS',
2703         'request_time',
2704         'usr',
2705 $$
2706 [%- USE date -%]
2707 [%- user = target.0.usr -%]
2708 To: [%- params.recipient_email || user.email %]
2709 From: [%- params.sender_email || default_sender %]
2710 Subject: Long Wait Hold Notification
2711
2712 Dear [% user.family_name %], [% user.first_given_name %]
2713
2714 You requested hold(s) on the following item(s), but unfortunately
2715 we have not been able to fulfill your request after a considerable
2716 length of time.  If you would still like to receive these items,
2717 no action is required.
2718
2719 [% FOR hold IN target %]
2720     Title: [% hold.bib_rec.bib_record.simple_record.title %]
2721     Author: [% hold.bib_rec.bib_record.simple_record.author %]
2722 [% END %]
2723 $$
2724 );
2725
2726 INSERT INTO action_trigger.environment (
2727         event_def,
2728         path
2729     ) VALUES
2730     (9, 'pickup_lib'),
2731     (9, 'usr'),
2732     (9, 'bib_rec.bib_record.simple_record');
2733
2734 INSERT INTO action_trigger.hook (key, core_type, description, passive) 
2735     VALUES (
2736         'format.selfcheck.checkout',
2737         'circ',
2738         'Formats circ objects for self-checkout receipt',
2739         TRUE
2740     );
2741
2742 INSERT INTO action_trigger.event_definition (id, active, owner, name, hook, validator, reactor, group_field, granularity, template )
2743     VALUES (
2744         10,
2745         TRUE,
2746         1,
2747         'Self-Checkout Receipt',
2748         'format.selfcheck.checkout',
2749         'NOOP_True',
2750         'ProcessTemplate',
2751         'usr',
2752         'print-on-demand',
2753 $$
2754 [%- USE date -%]
2755 [%- SET user = target.0.usr -%]
2756 [%- SET lib = target.0.circ_lib -%]
2757 [%- SET lib_addr = target.0.circ_lib.billing_address -%]
2758 [%- SET hours = lib.hours_of_operation -%]
2759 <div>
2760     <style> li { padding: 8px; margin 5px; }</style>
2761     <div>[% date.format %]</div>
2762     <div>[% lib.name %]</div>
2763     <div>[% lib_addr.street1 %] [% lib_addr.street2 %]</div>
2764     <div>[% lib_addr.city %], [% lib_addr.state %] [% lb_addr.post_code %]</div>
2765     <div>[% lib.phone %]</div>
2766     <br/>
2767
2768     [% user.family_name %], [% user.first_given_name %]
2769     <ol>
2770     [% FOR circ IN target %]
2771         [%-
2772             SET idx = loop.count - 1;
2773             SET udata =  user_data.$idx
2774         -%]
2775         <li>
2776             <div>[% helpers.get_copy_bib_basics(circ.target_copy.id).title %]</div>
2777             <div>Barcode: [% circ.target_copy.barcode %]</div>
2778             [% IF udata.renewal_failure %]
2779                 <div style='color:red;'>Renewal Failed</div>
2780             [% ELSE %]
2781                 <div>Due Date: [% date.format(helpers.format_date(circ.due_date), '%Y-%m-%d') %]</div>
2782             [% END %]
2783         </li>
2784     [% END %]
2785     </ol>
2786     
2787     <div>
2788         Library Hours
2789         [%- BLOCK format_time; date.format(time _ ' 1/1/1000', format='%I:%M %p'); END -%]
2790         <div>
2791             Monday 
2792             [% PROCESS format_time time = hours.dow_0_open %] 
2793             [% PROCESS format_time time = hours.dow_0_close %] 
2794         </div>
2795         <div>
2796             Tuesday 
2797             [% PROCESS format_time time = hours.dow_1_open %] 
2798             [% PROCESS format_time time = hours.dow_1_close %] 
2799         </div>
2800         <div>
2801             Wednesday 
2802             [% PROCESS format_time time = hours.dow_2_open %] 
2803             [% PROCESS format_time time = hours.dow_2_close %] 
2804         </div>
2805         <div>
2806             Thursday
2807             [% PROCESS format_time time = hours.dow_3_open %] 
2808             [% PROCESS format_time time = hours.dow_3_close %] 
2809         </div>
2810         <div>
2811             Friday
2812             [% PROCESS format_time time = hours.dow_4_open %] 
2813             [% PROCESS format_time time = hours.dow_4_close %] 
2814         </div>
2815         <div>
2816             Saturday
2817             [% PROCESS format_time time = hours.dow_5_open %] 
2818             [% PROCESS format_time time = hours.dow_5_close %] 
2819         </div>
2820         <div>
2821             Sunday 
2822             [% PROCESS format_time time = hours.dow_6_open %] 
2823             [% PROCESS format_time time = hours.dow_6_close %] 
2824         </div>
2825     </div>
2826 </div>
2827 $$
2828 );
2829
2830 INSERT INTO action_trigger.environment ( event_def, path) VALUES
2831     ( 10, 'target_copy'),
2832     ( 10, 'circ_lib.billing_address'),
2833     ( 10, 'circ_lib.hours_of_operation'),
2834     ( 10, 'usr');
2835
2836 INSERT INTO action_trigger.hook (key, core_type, description, passive) 
2837     VALUES (
2838         'format.selfcheck.items_out',
2839         'circ',
2840         'Formats items out for self-checkout receipt',
2841         TRUE
2842     );
2843
2844 INSERT INTO action_trigger.event_definition (id, active, owner, name, hook, validator, reactor, group_field, granularity, template )
2845     VALUES (
2846         11,
2847         TRUE,
2848         1,
2849         'Self-Checkout Items Out Receipt',
2850         'format.selfcheck.items_out',
2851         'NOOP_True',
2852         'ProcessTemplate',
2853         'usr',
2854         'print-on-demand',
2855 $$
2856 [%- USE date -%]
2857 [%- SET user = target.0.usr -%]
2858 <div>
2859     <style> li { padding: 8px; margin 5px; }</style>
2860     <div>[% date.format %]</div>
2861     <br/>
2862
2863     [% user.family_name %], [% user.first_given_name %]
2864     <ol>
2865     [% FOR circ IN target %]
2866         <li>
2867             <div>[% helpers.get_copy_bib_basics(circ.target_copy.id).title %]</div>
2868             <div>Barcode: [% circ.target_copy.barcode %]</div>
2869             <div>Due Date: [% date.format(helpers.format_date(circ.due_date), '%Y-%m-%d') %]</div>
2870         </li>
2871     [% END %]
2872     </ol>
2873 </div>
2874 $$
2875 );
2876
2877
2878 INSERT INTO action_trigger.environment ( event_def, path) VALUES
2879     ( 11, 'target_copy'),
2880     ( 11, 'circ_lib.billing_address'),
2881     ( 11, 'circ_lib.hours_of_operation'),
2882     ( 11, 'usr');
2883
2884 INSERT INTO action_trigger.hook (key, core_type, description, passive) 
2885     VALUES (
2886         'format.selfcheck.holds',
2887         'ahr',
2888         'Formats holds for self-checkout receipt',
2889         TRUE
2890     );
2891
2892 INSERT INTO action_trigger.event_definition (id, active, owner, name, hook, validator, reactor, group_field, granularity, template )
2893     VALUES (
2894         12,
2895         TRUE,
2896         1,
2897         'Self-Checkout Holds Receipt',
2898         'format.selfcheck.holds',
2899         'NOOP_True',
2900         'ProcessTemplate',
2901         'usr',
2902         'print-on-demand',
2903 $$
2904 [%- USE date -%]
2905 [%- SET user = target.0.usr -%]
2906 <div>
2907     <style> li { padding: 8px; margin 5px; }</style>
2908     <div>[% date.format %]</div>
2909     <br/>
2910
2911     [% user.family_name %], [% user.first_given_name %]
2912     <ol>
2913     [% FOR hold IN target %]
2914         [%-
2915             SET idx = loop.count - 1;
2916             SET udata =  user_data.$idx
2917         -%]
2918         <li>
2919             <div>Title: [% hold.bib_rec.bib_record.simple_record.title %]</div>
2920             <div>Author: [% hold.bib_rec.bib_record.simple_record.author %]</div>
2921             <div>Pickup Location: [% hold.pickup_lib.name %]</div>
2922             <div>Status: 
2923                 [%- IF udata.ready -%]
2924                     Ready for pickup
2925                 [% ELSE %]
2926                     #[% udata.queue_position %] of [% udata.potential_copies %] copies.
2927                 [% END %]
2928             </div>
2929         </li>
2930     [% END %]
2931     </ol>
2932 </div>
2933 $$
2934 );
2935
2936
2937 INSERT INTO action_trigger.environment ( event_def, path) VALUES
2938     ( 12, 'bib_rec.bib_record.simple_record'),
2939     ( 12, 'pickup_lib'),
2940     ( 12, 'usr');
2941
2942 INSERT INTO action_trigger.hook (key, core_type, description, passive) 
2943     VALUES (
2944         'format.selfcheck.fines',
2945         'au',
2946         'Formats fines for self-checkout receipt',
2947         TRUE
2948     );
2949
2950 INSERT INTO action_trigger.event_definition (id, active, owner, name, hook, validator, reactor, granularity, template )
2951     VALUES (
2952         13,
2953         TRUE,
2954         1,
2955         'Self-Checkout Fines Receipt',
2956         'format.selfcheck.fines',
2957         'NOOP_True',
2958         'ProcessTemplate',
2959         'print-on-demand',
2960 $$
2961 [%- USE date -%]
2962 [%- SET user = target -%]
2963 <div>
2964     <style> li { padding: 8px; margin 5px; }</style>
2965     <div>[% date.format %]</div>
2966     <br/>
2967
2968     [% user.family_name %], [% user.first_given_name %]
2969     <ol>
2970     [% FOR xact IN user.open_billable_transactions_summary %]
2971         <li>
2972             <div>Details: 
2973                 [% IF xact.xact_type == 'circulation' %]
2974                     [%- helpers.get_copy_bib_basics(xact.circulation.target_copy).title -%]
2975                 [% ELSE %]
2976                     [%- xact.last_billing_type -%]
2977                 [% END %]
2978             </div>
2979             <div>Total Billed: [% xact.total_owed %]</div>
2980             <div>Total Paid: [% xact.total_paid %]</div>
2981             <div>Balance Owed : [% xact.balance_owed %]</div>
2982         </li>
2983     [% END %]
2984     </ol>
2985 </div>
2986 $$
2987 );
2988
2989 INSERT INTO action_trigger.environment ( event_def, path) VALUES
2990     ( 13, 'open_billable_transactions_summary.circulation' );
2991
2992 INSERT INTO action_trigger.reactor (module,description) VALUES
2993 (   'SendFile',
2994     oils_i18n_gettext(
2995         'SendFile',
2996         '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.',
2997         'atreact',
2998         'description'
2999     )
3000 );
3001
3002 INSERT INTO action_trigger.hook (key, core_type, description, passive) 
3003     VALUES (
3004         'format.acqli.html',
3005         'jub',
3006         'Formats lineitem worksheet for titles received',
3007         TRUE
3008     );
3009
3010 INSERT INTO action_trigger.event_definition (id, active, owner, name, hook, validator, reactor, granularity, template)
3011     VALUES (
3012         14,
3013         TRUE,
3014         1,
3015         'Lineitem Worksheet',
3016         'format.acqli.html',
3017         'NOOP_True',
3018         'ProcessTemplate',
3019         'print-on-demand',
3020 $$
3021 [%- USE date -%]
3022 [%- SET li = target; -%]
3023 <div class="wrapper">
3024     <div class="summary" style='font-size:110%; font-weight:bold;'>
3025
3026         <div>Title: [% helpers.get_li_attr("title", "", li.attributes) %]</div>
3027         <div>Author: [% helpers.get_li_attr("author", "", li.attributes) %]</div>
3028         <div class="count">Item Count: [% li.lineitem_details.size %]</div>
3029         <div class="lineid">Lineitem ID: [% li.id %]</div>
3030
3031         [% IF li.distribution_formulas.size > 0 %]
3032             [% SET forms = [] %]
3033             [% FOREACH form IN li.distribution_formulas; forms.push(form.formula.name); END %]
3034             <div>Distribution Formulas: [% forms.join(',') %]</div>
3035         [% END %]
3036
3037         [% IF li.lineitem_notes.size > 0 %]
3038             Lineitem Notes:
3039             <ul>
3040                 [%- FOR note IN li.lineitem_notes -%]
3041                     <li>
3042                     [% IF note.alert_text %]
3043                         [% note.alert_text.code -%] 
3044                         [% IF note.value -%]
3045                             : [% note.value %]
3046                         [% END %]
3047                     [% ELSE %]
3048                         [% note.value -%] 
3049                     [% END %]
3050                     </li>
3051                 [% END %]
3052             </ul>
3053         [% END %]
3054     </div>
3055     <br/>
3056     <table>
3057         <thead>
3058             <tr>
3059                 <th>Branch</th>
3060                 <th>Barcode</th>
3061                 <th>Call Number</th>
3062                 <th>Fund</th>
3063                 <th>Recd.</th>
3064                 <th>Notes</th>
3065             </tr>
3066         </thead>
3067         <tbody>
3068         [% FOREACH detail IN li.lineitem_details.sort('owning_lib') %]
3069             [% 
3070                 IF copy.eg_copy_id;
3071                     SET copy = copy.eg_copy_id;
3072                     SET cn_label = copy.call_number.label;
3073                 ELSE; 
3074                     SET copy = detail; 
3075                     SET cn_label = detail.cn_label;
3076                 END 
3077             %]
3078             <tr>
3079                 <!-- acq.lineitem_detail.id = [%- detail.id -%] -->
3080                 <td style='padding:5px;'>[% detail.owning_lib.shortname %]</td>
3081                 <td style='padding:5px;'>[% IF copy.barcode   %]<span class="barcode"  >[% detail.barcode   %]</span>[% END %]</td>
3082                 <td style='padding:5px;'>[% IF cn_label %]<span class="cn_label" >[% cn_label  %]</span>[% END %]</td>
3083                 <td style='padding:5px;'>[% IF detail.fund %]<span class="fund">[% detail.fund.code %] ([% detail.fund.year %])</span>[% END %]</td>
3084                 <td style='padding:5px;'>[% IF detail.recv_time %]<span class="recv_time">[% detail.recv_time %]</span>[% END %]</td>
3085                 <td style='padding:5px;'>[% detail.note %]</td>
3086             </tr>
3087         [% END %]
3088         </tbody>
3089     </table>
3090 </div>
3091 $$
3092 );
3093
3094
3095 INSERT INTO action_trigger.environment (event_def, path) VALUES
3096     ( 14, 'attributes' ),
3097     ( 14, 'lineitem_details' ),
3098     ( 14, 'lineitem_details.owning_lib' ),
3099     ( 14, 'lineitem_notes' )
3100 ;
3101
3102 INSERT INTO action_trigger.hook (key,core_type,description,passive) VALUES (
3103         'aur.ordered',
3104         'aur', 
3105         oils_i18n_gettext(
3106             'aur.ordered',
3107             'A patron acquisition request has been marked On-Order.',
3108             'ath',
3109             'description'
3110         ), 
3111         TRUE
3112     ), (
3113         'aur.received', 
3114         'aur', 
3115         oils_i18n_gettext(
3116             'aur.received', 
3117             'A patron acquisition request has been marked Received.',
3118             'ath',
3119             'description'
3120         ),
3121         TRUE
3122     ), (
3123         'aur.cancelled',
3124         'aur',
3125         oils_i18n_gettext(
3126             'aur.cancelled',
3127             'A patron acquisition request has been marked Cancelled.',
3128             'ath',
3129             'description'
3130         ),
3131         TRUE
3132     )
3133 ;
3134
3135 INSERT INTO action_trigger.validator (module,description) VALUES (
3136         'Acq::UserRequestOrdered',
3137         oils_i18n_gettext(
3138             'Acq::UserRequestOrdered',
3139             'Tests to see if the corresponding Line Item has a state of "on-order".',
3140             'atval',
3141             'description'
3142         )
3143     ), (
3144         'Acq::UserRequestReceived',
3145         oils_i18n_gettext(
3146             'Acq::UserRequestReceived',
3147             'Tests to see if the corresponding Line Item has a state of "received".',
3148             'atval',
3149             'description'
3150         )
3151     ), (
3152         'Acq::UserRequestCancelled',
3153         oils_i18n_gettext(
3154             'Acq::UserRequestCancelled',
3155             'Tests to see if the corresponding Line Item has a state of "cancelled".',
3156             'atval',
3157             'description'
3158         )
3159     )
3160 ;
3161
3162 -- What was event_definition #15 in v1.6.1 will be recreated as #20.  This
3163 -- renumbering requires some juggling:
3164 --
3165 -- 1. Update any child rows to point to #20.  These updates will temporarily
3166 -- violate foreign key constraints, but that's okay as long as we create
3167 -- #20 before committing.
3168 --
3169 -- 2. Delete the old #15.
3170 --
3171 -- 3. Insert the new #15.
3172 --
3173 -- 4. Insert #20.
3174 --
3175 -- We could combine steps 2 and 3 into a single update, but that would create
3176 -- additional opportunities for typos, since we already have the insert from
3177 -- an upgrade script.
3178
3179 UPDATE action_trigger.environment
3180 SET event_def = 20
3181 WHERE event_def = 15;
3182
3183 UPDATE action_trigger.event
3184 SET event_def = 20
3185 WHERE event_def = 15;
3186
3187 UPDATE action_trigger.event_params
3188 SET event_def = 20
3189 WHERE event_def = 15;
3190
3191 DELETE FROM action_trigger.event_definition
3192 WHERE id = 15;
3193
3194 INSERT INTO action_trigger.event_definition (
3195         id,
3196         active,
3197         owner,
3198         name,
3199         hook,
3200         validator,
3201         reactor,
3202         template
3203     ) VALUES (
3204         15,
3205         FALSE,
3206         1,
3207         'Email Notice: Patron Acquisition Request marked On-Order.',
3208         'aur.ordered',
3209         'Acq::UserRequestOrdered',
3210         'SendEmail',
3211 $$
3212 [%- USE date -%]
3213 [%- SET li = target.lineitem; -%]
3214 [%- SET user = target.usr -%]
3215 [%- SET title = helpers.get_li_attr("title", "", li.attributes) -%]
3216 [%- SET author = helpers.get_li_attr("author", "", li.attributes) -%]
3217 [%- SET edition = helpers.get_li_attr("edition", "", li.attributes) -%]
3218 [%- SET isbn = helpers.get_li_attr("isbn", "", li.attributes) -%]
3219 [%- SET publisher = helpers.get_li_attr("publisher", "", li.attributes) -%]
3220 [%- SET pubdate = helpers.get_li_attr("pubdate", "", li.attributes) -%]
3221
3222 To: [%- params.recipient_email || user.email %]
3223 From: [%- params.sender_email || default_sender %]
3224 Subject: Acquisition Request Notification
3225
3226 Dear [% user.family_name %], [% user.first_given_name %]
3227 Our records indicate the following acquisition request has been placed on order.
3228
3229 Title: [% title %]
3230 [% IF author %]Author: [% author %][% END %]
3231 [% IF edition %]Edition: [% edition %][% END %]
3232 [% IF isbn %]ISBN: [% isbn %][% END %]
3233 [% IF publisher %]Publisher: [% publisher %][% END %]
3234 [% IF pubdate %]Publication Date: [% pubdate %][% END %]
3235 Lineitem ID: [% li.id %]
3236 $$
3237     ), (
3238         16,
3239         FALSE,
3240         1,
3241         'Email Notice: Patron Acquisition Request marked Received.',
3242         'aur.received',
3243         'Acq::UserRequestReceived',
3244         'SendEmail',
3245 $$
3246 [%- USE date -%]
3247 [%- SET li = target.lineitem; -%]
3248 [%- SET user = target.usr -%]
3249 [%- SET title = helpers.get_li_attr("title", "", li.attributes) %]
3250 [%- SET author = helpers.get_li_attr("author", "", li.attributes) %]
3251 [%- SET edition = helpers.get_li_attr("edition", "", li.attributes) %]
3252 [%- SET isbn = helpers.get_li_attr("isbn", "", li.attributes) %]
3253 [%- SET publisher = helpers.get_li_attr("publisher", "", li.attributes) -%]
3254 [%- SET pubdate = helpers.get_li_attr("pubdate", "", li.attributes) -%]
3255
3256 To: [%- params.recipient_email || user.email %]
3257 From: [%- params.sender_email || default_sender %]
3258 Subject: Acquisition Request Notification
3259
3260 Dear [% user.family_name %], [% user.first_given_name %]
3261 Our records indicate the materials for the following acquisition request have been received.
3262
3263 Title: [% title %]
3264 [% IF author %]Author: [% author %][% END %]
3265 [% IF edition %]Edition: [% edition %][% END %]
3266 [% IF isbn %]ISBN: [% isbn %][% END %]
3267 [% IF publisher %]Publisher: [% publisher %][% END %]
3268 [% IF pubdate %]Publication Date: [% pubdate %][% END %]
3269 Lineitem ID: [% li.id %]
3270 $$
3271     ), (
3272         17,
3273         FALSE,
3274         1,
3275         'Email Notice: Patron Acquisition Request marked Cancelled.',
3276         'aur.cancelled',
3277         'Acq::UserRequestCancelled',
3278         'SendEmail',
3279 $$
3280 [%- USE date -%]
3281 [%- SET li = target.lineitem; -%]
3282 [%- SET user = target.usr -%]
3283 [%- SET title = helpers.get_li_attr("title", "", li.attributes) %]
3284 [%- SET author = helpers.get_li_attr("author", "", li.attributes) %]
3285 [%- SET edition = helpers.get_li_attr("edition", "", li.attributes) %]
3286 [%- SET isbn = helpers.get_li_attr("isbn", "", li.attributes) %]
3287 [%- SET publisher = helpers.get_li_attr("publisher", "", li.attributes) -%]
3288 [%- SET pubdate = helpers.get_li_attr("pubdate", "", li.attributes) -%]
3289
3290 To: [%- params.recipient_email || user.email %]
3291 From: [%- params.sender_email || default_sender %]
3292 Subject: Acquisition Request Notification
3293
3294 Dear [% user.family_name %], [% user.first_given_name %]
3295 Our records indicate the following acquisition request has been cancelled.
3296
3297 Title: [% title %]
3298 [% IF author %]Author: [% author %][% END %]
3299 [% IF edition %]Edition: [% edition %][% END %]
3300 [% IF isbn %]ISBN: [% isbn %][% END %]
3301 [% IF publisher %]Publisher: [% publisher %][% END %]
3302 [% IF pubdate %]Publication Date: [% pubdate %][% END %]
3303 Lineitem ID: [% li.id %]
3304 $$
3305     );
3306
3307 INSERT INTO action_trigger.environment (
3308         event_def,
3309         path
3310     ) VALUES 
3311         ( 15, 'lineitem' ),
3312         ( 15, 'lineitem.attributes' ),
3313         ( 15, 'usr' ),
3314
3315         ( 16, 'lineitem' ),
3316         ( 16, 'lineitem.attributes' ),
3317         ( 16, 'usr' ),
3318
3319         ( 17, 'lineitem' ),
3320         ( 17, 'lineitem.attributes' ),
3321         ( 17, 'usr' )
3322     ;
3323
3324 INSERT INTO action_trigger.event_definition
3325 (id, active, owner, name, hook, validator, reactor, cleanup_success, cleanup_failure, delay, delay_field, group_field, template) VALUES
3326 (23, true, 1, 'PO JEDI', 'acqpo.activated', 'Acq::PurchaseOrderEDIRequired', 'GeneratePurchaseOrderJEDI', NULL, NULL, '00:05:00', NULL, NULL,
3327 $$
3328 [%- USE date -%]
3329 [%# start JEDI document 
3330   # Vendor specific kludges:
3331   # BT      - vendcode goes to NAD/BY *suffix*  w/ 91 qualifier
3332   # INGRAM  - vendcode goes to NAD/BY *segment* w/ 91 qualifier (separately)
3333   # BRODART - vendcode goes to FTX segment (lineitem level)
3334 -%]
3335 [%- 
3336 IF target.provider.edi_default.vendcode && target.provider.code == 'BRODART';
3337     xtra_ftx = target.provider.edi_default.vendcode;
3338 END;
3339 -%]
3340 [%- BLOCK big_block -%]
3341 {
3342    "recipient":"[% target.provider.san %]",
3343    "sender":"[% target.ordering_agency.mailing_address.san %]",
3344    "body": [{
3345      "ORDERS":[ "order", {
3346         "po_number":[% target.id %],
3347         "date":"[% date.format(date.now, '%Y%m%d') %]",
3348         "buyer":[
3349             [%   IF   target.provider.edi_default.vendcode && (target.provider.code == 'BT' || target.provider.name.match('(?i)^BAKER & TAYLOR'))  -%]
3350                 {"id-qualifier": 91, "id":"[% target.ordering_agency.mailing_address.san _ ' ' _ target.provider.edi_default.vendcode %]"}
3351             [%- ELSIF target.provider.edi_default.vendcode && target.provider.code == 'INGRAM' -%]
3352                 {"id":"[% target.ordering_agency.mailing_address.san %]"},
3353                 {"id-qualifier": 91, "id":"[% target.provider.edi_default.vendcode %]"}
3354             [%- ELSE -%]
3355                 {"id":"[% target.ordering_agency.mailing_address.san %]"}
3356             [%- END -%]
3357         ],
3358         "vendor":[
3359             [%- # target.provider.name (target.provider.id) -%]
3360             "[% target.provider.san %]",
3361             {"id-qualifier": 92, "id":"[% target.provider.id %]"}
3362         ],
3363         "currency":"[% target.provider.currency_type %]",
3364                 
3365         "items":[
3366         [%- FOR li IN target.lineitems %]
3367         {
3368             "line_index":"[% li.id %]",
3369             "identifiers":[   [%-# li.isbns = helpers.get_li_isbns(li.attributes) %]
3370             [% FOR isbn IN helpers.get_li_isbns(li.attributes) -%]
3371                 [% IF isbn.length == 13 -%]
3372                 {"id-qualifier":"EN","id":"[% isbn %]"},
3373                 [% ELSE -%]
3374                 {"id-qualifier":"IB","id":"[% isbn %]"},
3375                 [%- END %]
3376             [% END %]
3377                 {"id-qualifier":"IN","id":"[% li.id %]"}
3378             ],
3379             "price":[% li.estimated_unit_price || '0.00' %],
3380             "desc":[
3381                 {"BTI":"[% helpers.get_li_attr('title',     '', li.attributes) %]"},
3382                 {"BPU":"[% helpers.get_li_attr('publisher', '', li.attributes) %]"},
3383                 {"BPD":"[% helpers.get_li_attr('pubdate',   '', li.attributes) %]"},
3384                 {"BPH":"[% helpers.get_li_attr('pagination','', li.attributes) %]"}
3385             ],
3386             [%- ftx_vals = []; 
3387                 FOR note IN li.lineitem_notes; 
3388                     NEXT UNLESS note.vendor_public == 't'; 
3389                     ftx_vals.push(note.value); 
3390                 END; 
3391                 IF xtra_ftx;           ftx_vals.unshift(xtra_ftx); END; 
3392                 IF ftx_vals.size == 0; ftx_vals.unshift('');       END;  # BT needs FTX+LIN for every LI, even if it is an empty one
3393             -%]
3394
3395             "free-text":[ 
3396                 [% FOR note IN ftx_vals -%] "[% note %]"[% UNLESS loop.last %], [% END %][% END %] 
3397             ],            
3398             "quantity":[% li.lineitem_details.size %]
3399         }[% UNLESS loop.last %],[% END %]
3400         [%-# TODO: lineitem details (later) -%]
3401         [% END %]
3402         ],
3403         "line_items":[% target.lineitems.size %]
3404      }]  [%# close ORDERS array %]
3405    }]    [%# close  body  array %]
3406 }
3407 [% END %]
3408 [% tempo = PROCESS big_block; helpers.escape_json(tempo) %]
3409
3410 $$);
3411
3412 INSERT INTO action_trigger.environment (event_def, path) VALUES 
3413   (23, 'lineitems.attributes'), 
3414   (23, 'lineitems.lineitem_details'), 
3415   (23, 'lineitems.lineitem_notes'), 
3416   (23, 'ordering_agency.mailing_address'), 
3417   (23, 'provider');
3418
3419 UPDATE action_trigger.event_definition SET template = 
3420 $$
3421 [%- USE date -%]
3422 [%-
3423     # find a lineitem attribute by name and optional type
3424     BLOCK get_li_attr;
3425         FOR attr IN li.attributes;
3426             IF attr.attr_name == attr_name;
3427                 IF !attr_type OR attr_type == attr.attr_type;
3428                     attr.attr_value;
3429                     LAST;
3430                 END;
3431             END;
3432         END;
3433     END
3434 -%]
3435
3436 <h2>Purchase Order [% target.id %]</h2>
3437 <br/>
3438 date <b>[% date.format(date.now, '%Y%m%d') %]</b>
3439 <br/>
3440
3441 <style>
3442     table td { padding:5px; border:1px solid #aaa;}
3443     table { width:95%; border-collapse:collapse; }
3444     #vendor-notes { padding:5px; border:1px solid #aaa; }
3445 </style>
3446 <table id='vendor-table'>
3447   <tr>
3448     <td valign='top'>Vendor</td>
3449     <td>
3450       <div>[% target.provider.name %]</div>
3451       <div>[% target.provider.addresses.0.street1 %]</div>
3452       <div>[% target.provider.addresses.0.street2 %]</div>
3453       <div>[% target.provider.addresses.0.city %]</div>
3454       <div>[% target.provider.addresses.0.state %]</div>
3455       <div>[% target.provider.addresses.0.country %]</div>
3456       <div>[% target.provider.addresses.0.post_code %]</div>
3457     </td>
3458     <td valign='top'>Ship to / Bill to</td>
3459     <td>
3460       <div>[% target.ordering_agency.name %]</div>
3461       <div>[% target.ordering_agency.billing_address.street1 %]</div>
3462       <div>[% target.ordering_agency.billing_address.street2 %]</div>
3463       <div>[% target.ordering_agency.billing_address.city %]</div>
3464       <div>[% target.ordering_agency.billing_address.state %]</div>
3465       <div>[% target.ordering_agency.billing_address.country %]</div>
3466       <div>[% target.ordering_agency.billing_address.post_code %]</div>
3467     </td>
3468   </tr>
3469 </table>
3470
3471 <br/><br/>
3472 <fieldset id='vendor-notes'>
3473     <legend>Notes to the Vendor</legend>
3474     <ul>
3475     [% FOR note IN target.notes %]
3476         [% IF note.vendor_public == 't' %]
3477             <li>[% note.value %]</li>
3478         [% END %]
3479     [% END %]
3480     </ul>
3481 </fieldset>
3482 <br/><br/>
3483
3484 <table>
3485   <thead>
3486     <tr>
3487       <th>PO#</th>
3488       <th>ISBN or Item #</th>
3489       <th>Title</th>
3490       <th>Quantity</th>
3491       <th>Unit Price</th>
3492       <th>Line Total</th>
3493       <th>Notes</th>
3494     </tr>
3495   </thead>
3496   <tbody>
3497
3498   [% subtotal = 0 %]
3499   [% FOR li IN target.lineitems %]
3500
3501   <tr>
3502     [% count = li.lineitem_details.size %]
3503     [% price = li.estimated_unit_price %]
3504     [% litotal = (price * count) %]
3505     [% subtotal = subtotal + litotal %]
3506     [% isbn = PROCESS get_li_attr attr_name = 'isbn' %]
3507     [% ident = PROCESS get_li_attr attr_name = 'identifier' %]
3508
3509     <td>[% target.id %]</td>
3510     <td>[% isbn || ident %]</td>
3511     <td>[% PROCESS get_li_attr attr_name = 'title' %]</td>
3512     <td>[% count %]</td>
3513     <td>[% price %]</td>
3514     <td>[% litotal %]</td>
3515     <td>
3516         <ul>
3517         [% FOR note IN li.lineitem_notes %]
3518             [% IF note.vendor_public == 't' %]
3519                 <li>[% note.value %]</li>
3520             [% END %]
3521         [% END %]
3522         </ul>
3523     </td>
3524   </tr>
3525   [% END %]
3526   <tr>
3527     <td/><td/><td/><td/>
3528     <td>Subtotal</td>
3529     <td>[% subtotal %]</td>
3530   </tr>
3531   </tbody>
3532 </table>
3533
3534 <br/>
3535
3536 Total Line Item Count: [% target.lineitems.size %]
3537 $$
3538 WHERE id = 4;
3539
3540 INSERT INTO action_trigger.environment (event_def, path) VALUES 
3541     (4, 'lineitems.lineitem_notes'),
3542     (4, 'notes');
3543
3544 INSERT INTO action_trigger.environment (event_def, path) VALUES
3545     ( 14, 'lineitem_notes.alert_text' ),
3546     ( 14, 'distribution_formulas.formula' ),
3547     ( 14, 'lineitem_details.fund' ),
3548     ( 14, 'lineitem_details.eg_copy_id' ),
3549     ( 14, 'lineitem_details.eg_copy_id.call_number' )
3550 ;
3551
3552 INSERT INTO action_trigger.hook (key,core_type,description,passive) VALUES (
3553         'aur.created',
3554         'aur',
3555         oils_i18n_gettext(
3556             'aur.created',
3557             'A patron has made an acquisitions request.',
3558             'ath',
3559             'description'
3560         ),
3561         TRUE
3562     ), (
3563         'aur.rejected',
3564         'aur',
3565         oils_i18n_gettext(
3566             'aur.rejected',
3567             'A patron acquisition request has been rejected.',
3568             'ath',
3569             'description'
3570         ),
3571         TRUE
3572     )
3573 ;
3574
3575 INSERT INTO action_trigger.event_definition (
3576         id,
3577         active,
3578         owner,
3579         name,
3580         hook,
3581         validator,
3582         reactor,
3583         template
3584     ) VALUES (
3585         18,
3586         FALSE,
3587         1,
3588         'Email Notice: Acquisition Request created.',
3589         'aur.created',
3590         'NOOP_True',
3591         'SendEmail',
3592 $$
3593 [%- USE date -%]
3594 [%- SET user = target.usr -%]
3595 [%- SET title = target.title -%]
3596 [%- SET author = target.author -%]
3597 [%- SET isxn = target.isxn -%]
3598 [%- SET publisher = target.publisher -%]
3599 [%- SET pubdate = target.pubdate -%]
3600
3601 To: [%- params.recipient_email || user.email %]
3602 From: [%- params.sender_email || default_sender %]
3603 Subject: Acquisition Request Notification
3604
3605 Dear [% user.family_name %], [% user.first_given_name %]
3606 Our records indicate that you have made the following acquisition request:
3607
3608 Title: [% title %]
3609 [% IF author %]Author: [% author %][% END %]
3610 [% IF edition %]Edition: [% edition %][% END %]
3611 [% IF isbn %]ISXN: [% isxn %][% END %]
3612 [% IF publisher %]Publisher: [% publisher %][% END %]
3613 [% IF pubdate %]Publication Date: [% pubdate %][% END %]
3614 $$
3615     ), (
3616         19,
3617         FALSE,
3618         1,
3619         'Email Notice: Acquisition Request Rejected.',
3620         'aur.rejected',
3621         'NOOP_True',
3622         'SendEmail',
3623 $$
3624 [%- USE date -%]
3625 [%- SET user = target.usr -%]
3626 [%- SET title = target.title -%]
3627 [%- SET author = target.author -%]
3628 [%- SET isxn = target.isxn -%]
3629 [%- SET publisher = target.publisher -%]
3630 [%- SET pubdate = target.pubdate -%]
3631 [%- SET cancel_reason = target.cancel_reason.description -%]
3632
3633 To: [%- params.recipient_email || user.email %]
3634 From: [%- params.sender_email || default_sender %]
3635 Subject: Acquisition Request Notification
3636
3637 Dear [% user.family_name %], [% user.first_given_name %]
3638 Our records indicate the following acquisition request has been rejected for this reason: [% cancel_reason %]
3639
3640 Title: [% title %]
3641 [% IF author %]Author: [% author %][% END %]
3642 [% IF edition %]Edition: [% edition %][% END %]
3643 [% IF isbn %]ISBN: [% isbn %][% END %]
3644 [% IF publisher %]Publisher: [% publisher %][% END %]
3645 [% IF pubdate %]Publication Date: [% pubdate %][% END %]
3646 $$
3647     );
3648
3649 INSERT INTO action_trigger.environment (
3650         event_def,
3651         path
3652     ) VALUES 
3653         ( 18, 'usr' ),
3654         ( 19, 'usr' ),
3655         ( 19, 'cancel_reason' )
3656     ;
3657
3658 INSERT INTO action_trigger.hook (key, core_type, description, passive)
3659     VALUES (
3660         'format.acqcle.html',
3661         'acqcle',
3662         'Formats claim events into a voucher',
3663         TRUE
3664     );
3665
3666 INSERT INTO action_trigger.event_definition (
3667         id, active, owner, name, hook, group_field,
3668         validator, reactor, granularity, template
3669     ) VALUES (
3670         21,
3671         TRUE,
3672         1,
3673         'Claim Voucher',
3674         'format.acqcle.html',
3675         'claim',
3676         'NOOP_True',
3677         'ProcessTemplate',
3678         'print-on-demand',
3679 $$
3680 [%- USE date -%]
3681 [%- SET claim = target.0.claim -%]
3682 <!-- This will need refined/prettified. -->
3683 <div class="acq-claim-voucher">
3684     <h2>Claim: [% claim.id %] ([% claim.type.code %])</h2>
3685     <h3>Against: [%- helpers.get_li_attr("title", "", claim.lineitem_detail.lineitem.attributes) -%]</h3>
3686     <ul>
3687         [% FOR event IN target %]
3688         <li>
3689             Event type: [% event.type.code %]
3690             [% IF event.type.library_initiated %](Library initiated)[% END %]
3691             <br />
3692             Event date: [% event.event_date %]<br />
3693             Order date: [% event.claim.lineitem_detail.lineitem.purchase_order.order_date %]<br />
3694             Expected receive date: [% event.claim.lineitem_detail.lineitem.expected_recv_time %]<br />
3695             Initiated by: [% event.creator.family_name %], [% event.creator.first_given_name %] [% event.creator.second_given_name %]<br />
3696             Barcode: [% event.claim.lineitem_detail.barcode %]; Fund:
3697             [% event.claim.lineitem_detail.fund.code %]
3698             ([% event.claim.lineitem_detail.fund.year %])
3699         </li>
3700         [% END %]
3701     </ul>
3702 </div>
3703 $$
3704 );
3705
3706 INSERT INTO action_trigger.environment (event_def, path) VALUES
3707     (21, 'claim'),
3708     (21, 'claim.type'),
3709     (21, 'claim.lineitem_detail'),
3710     (21, 'claim.lineitem_detail.fund'),
3711     (21, 'claim.lineitem_detail.lineitem.attributes'),
3712     (21, 'claim.lineitem_detail.lineitem.purchase_order'),
3713     (21, 'creator'),
3714     (21, 'type')
3715 ;
3716
3717 INSERT INTO action_trigger.hook (key, core_type, description, passive)
3718     VALUES (
3719         'format.acqinv.html',
3720         'acqinv',
3721         'Formats invoices into a voucher',
3722         TRUE
3723     );
3724
3725 INSERT INTO action_trigger.event_definition (
3726         id, active, owner, name, hook,
3727         validator, reactor, granularity, template
3728     ) VALUES (
3729         22,
3730         TRUE,
3731         1,
3732         'Invoice',
3733         'format.acqinv.html',
3734         'NOOP_True',
3735         'ProcessTemplate',
3736         'print-on-demand',
3737 $$
3738 [% FILTER collapse %]
3739 [%- SET invoice = target -%]
3740 <!-- This lacks totals, info about funds (for invoice entries,
3741     funds are per-LID!), and general refinement -->
3742 <div class="acq-invoice-voucher">
3743     <h1>Invoice</h1>
3744     <div>
3745         <strong>No.</strong> [% invoice.inv_ident %]
3746         [% IF invoice.inv_type %]
3747             / <strong>Type:</strong>[% invoice.inv_type %]
3748         [% END %]
3749     </div>
3750     <div>
3751         <dl>
3752             [% BLOCK ent_with_address %]
3753             <dt>[% ent_label %]: [% ent.name %] ([% ent.code %])</dt>
3754             <dd>
3755                 [% IF ent.addresses.0 %]
3756                     [% SET addr = ent.addresses.0 %]
3757                     [% addr.street1 %]<br />
3758                     [% IF addr.street2 %][% addr.street2 %]<br />[% END %]
3759                     [% addr.city %],
3760                     [% IF addr.county %] [% addr.county %], [% END %]
3761                     [% IF addr.state %] [% addr.state %] [% END %]
3762                     [% IF addr.post_code %][% addr.post_code %][% END %]<br />
3763                     [% IF addr.country %] [% addr.country %] [% END %]
3764                 [% END %]
3765                 <p>
3766                     [% IF ent.phone %] Phone: [% ent.phone %]<br />[% END %]
3767                     [% IF ent.fax_phone %] Fax: [% ent.fax_phone %]<br />[% END %]
3768                     [% IF ent.url %] URL: [% ent.url %]<br />[% END %]
3769                     [% IF ent.email %] E-mail: [% ent.email %] [% END %]
3770                 </p>
3771             </dd>
3772             [% END %]
3773             [% INCLUDE ent_with_address
3774                 ent = invoice.provider
3775                 ent_label = "Provider" %]
3776             [% INCLUDE ent_with_address
3777                 ent = invoice.shipper
3778                 ent_label = "Shipper" %]
3779             <dt>Receiver</dt>
3780             <dd>
3781                 [% invoice.receiver.name %] ([% invoice.receiver.shortname %])
3782             </dd>
3783             <dt>Received</dt>
3784             <dd>
3785                 [% helpers.format_date(invoice.recv_date) %] by
3786                 [% invoice.recv_method %]
3787             </dd>
3788             [% IF invoice.note %]
3789                 <dt>Note</dt>
3790                 <dd>
3791                     [% invoice.note %]
3792                 </dd>
3793             [% END %]
3794         </dl>
3795     </div>
3796     <ul>
3797         [% FOR entry IN invoice.entries %]
3798             <li>
3799                 [% IF entry.lineitem %]
3800                     Title: [% helpers.get_li_attr(
3801                         "title", "", entry.lineitem.attributes
3802                     ) %]<br />
3803                     Author: [% helpers.get_li_attr(
3804                         "author", "", entry.lineitem.attributes
3805                     ) %]
3806                 [% END %]
3807                 [% IF entry.purchase_order %]
3808                     (PO: [% entry.purchase_order.name %])
3809                 [% END %]<br />
3810                 Invoice item count: [% entry.inv_item_count %]
3811                 [% IF entry.phys_item_count %]
3812                     / Physical item count: [% entry.phys_item_count %]
3813                 [% END %]
3814                 <br />
3815                 [% IF entry.cost_billed %]
3816                     Cost billed: [% entry.cost_billed %]
3817                     [% IF entry.billed_per_item %](per item)[% END %]
3818                     <br />
3819                 [% END %]
3820                 [% IF entry.actual_cost %]
3821                     Actual cost: [% entry.actual_cost %]<br />
3822                 [% END %]
3823                 [% IF entry.amount_paid %]
3824                     Amount paid: [% entry.amount_paid %]<br />
3825                 [% END %]
3826                 [% IF entry.note %]Note: [% entry.note %][% END %]
3827             </li>
3828         [% END %]
3829         [% FOR item IN invoice.items %]
3830             <li>
3831                 [% IF item.inv_item_type %]
3832                     Item Type: [% item.inv_item_type %]<br />
3833                 [% END %]
3834                 [% IF item.title %]Title/Description:
3835                     [% item.title %]<br />
3836                 [% END %]
3837                 [% IF item.author %]Author: [% item.author %]<br />[% END %]
3838                 [% IF item.purchase_order %]PO: [% item.purchase_order %]<br />[% END %]
3839                 [% IF item.note %]Note: [% item.note %]<br />[% END %]
3840                 [% IF item.cost_billed %]
3841                     Cost billed: [% item.cost_billed %]<br />
3842                 [% END %]
3843                 [% IF item.actual_cost %]
3844                     Actual cost: [% item.actual_cost %]<br />
3845                 [% END %]
3846                 [% IF item.amount_paid %]
3847                     Amount paid: [% item.amount_paid %]<br />
3848                 [% END %]
3849             </li>
3850         [% END %]
3851     </ul>
3852 </div>
3853 [% END %]
3854 $$
3855 );
3856
3857 UPDATE action_trigger.event_definition SET template = $$[% FILTER collapse %]
3858 [%- SET invoice = target -%]
3859 <!-- This lacks general refinement -->
3860 <div class="acq-invoice-voucher">
3861     <h1>Invoice</h1>
3862     <div>
3863         <strong>No.</strong> [% invoice.inv_ident %]
3864         [% IF invoice.inv_type %]
3865             / <strong>Type:</strong>[% invoice.inv_type %]
3866         [% END %]
3867     </div>
3868     <div>
3869         <dl>
3870             [% BLOCK ent_with_address %]
3871             <dt>[% ent_label %]: [% ent.name %] ([% ent.code %])</dt>
3872             <dd>
3873                 [% IF ent.addresses.0 %]
3874                     [% SET addr = ent.addresses.0 %]
3875                     [% addr.street1 %]<br />
3876                     [% IF addr.street2 %][% addr.street2 %]<br />[% END %]
3877                     [% addr.city %],
3878                     [% IF addr.county %] [% addr.county %], [% END %]
3879                     [% IF addr.state %] [% addr.state %] [% END %]
3880                     [% IF addr.post_code %][% addr.post_code %][% END %]<br />
3881                     [% IF addr.country %] [% addr.country %] [% END %]
3882                 [% END %]
3883                 <p>
3884                     [% IF ent.phone %] Phone: [% ent.phone %]<br />[% END %]
3885                     [% IF ent.fax_phone %] Fax: [% ent.fax_phone %]<br />[% END %]
3886                     [% IF ent.url %] URL: [% ent.url %]<br />[% END %]
3887                     [% IF ent.email %] E-mail: [% ent.email %] [% END %]
3888                 </p>
3889             </dd>
3890             [% END %]
3891             [% INCLUDE ent_with_address
3892                 ent = invoice.provider
3893                 ent_label = "Provider" %]
3894             [% INCLUDE ent_with_address
3895                 ent = invoice.shipper
3896                 ent_label = "Shipper" %]
3897             <dt>Receiver</dt>
3898             <dd>
3899                 [% invoice.receiver.name %] ([% invoice.receiver.shortname %])
3900             </dd>
3901             <dt>Received</dt>
3902             <dd>
3903                 [% helpers.format_date(invoice.recv_date) %] by
3904                 [% invoice.recv_method %]
3905             </dd>
3906             [% IF invoice.note %]
3907                 <dt>Note</dt>
3908                 <dd>
3909                     [% invoice.note %]
3910                 </dd>
3911             [% END %]
3912         </dl>
3913     </div>
3914     <ul>
3915         [% FOR entry IN invoice.entries %]
3916             <li>
3917                 [% IF entry.lineitem %]
3918                     Title: [% helpers.get_li_attr(
3919                         "title", "", entry.lineitem.attributes
3920                     ) %]<br />
3921                     Author: [% helpers.get_li_attr(
3922                         "author", "", entry.lineitem.attributes
3923                     ) %]
3924                 [% END %]
3925                 [% IF entry.purchase_order %]
3926                     (PO: [% entry.purchase_order.name %])
3927                 [% END %]<br />
3928                 Invoice item count: [% entry.inv_item_count %]
3929                 [% IF entry.phys_item_count %]
3930                     / Physical item count: [% entry.phys_item_count %]
3931                 [% END %]
3932                 <br />
3933                 [% IF entry.cost_billed %]
3934                     Cost billed: [% entry.cost_billed %]
3935                     [% IF entry.billed_per_item %](per item)[% END %]
3936                     <br />
3937                 [% END %]
3938                 [% IF entry.actual_cost %]
3939                     Actual cost: [% entry.actual_cost %]<br />
3940                 [% END %]
3941                 [% IF entry.amount_paid %]
3942                     Amount paid: [% entry.amount_paid %]<br />
3943                 [% END %]
3944                 [% IF entry.note %]Note: [% entry.note %][% END %]
3945             </li>
3946         [% END %]
3947         [% FOR item IN invoice.items %]
3948             <li>
3949                 [% IF item.inv_item_type %]
3950                     Item Type: [% item.inv_item_type %]<br />
3951                 [% END %]
3952                 [% IF item.title %]Title/Description:
3953                     [% item.title %]<br />
3954                 [% END %]
3955                 [% IF item.author %]Author: [% item.author %]<br />[% END %]
3956                 [% IF item.purchase_order %]PO: [% item.purchase_order %]<br />[% END %]
3957                 [% IF item.note %]Note: [% item.note %]<br />[% END %]
3958                 [% IF item.cost_billed %]
3959                     Cost billed: [% item.cost_billed %]<br />
3960                 [% END %]
3961                 [% IF item.actual_cost %]
3962                     Actual cost: [% item.actual_cost %]<br />
3963                 [% END %]
3964                 [% IF item.amount_paid %]
3965                     Amount paid: [% item.amount_paid %]<br />
3966                 [% END %]
3967             </li>
3968         [% END %]
3969     </ul>
3970     <div>
3971         Amounts spent per fund:
3972         <table>
3973         [% FOR blob IN user_data %]
3974             <tr>
3975                 <th style="text-align: left;">[% blob.fund.code %] ([% blob.fund.year %]):</th>
3976                 <td>$[% blob.total %]</td>
3977             </tr>
3978         [% END %]
3979         </table>
3980     </div>
3981 </div>
3982 [% END %]$$ WHERE id = 22;
3983 INSERT INTO action_trigger.environment (event_def, path) VALUES
3984     (22, 'provider'),
3985     (22, 'provider.addresses'),
3986     (22, 'shipper'),
3987     (22, 'shipper.addresses'),
3988     (22, 'receiver'),
3989     (22, 'entries'),
3990     (22, 'entries.purchase_order'),
3991     (22, 'entries.lineitem'),
3992     (22, 'entries.lineitem.attributes'),
3993     (22, 'items')
3994 ;
3995
3996 INSERT INTO action_trigger.environment (event_def, path) VALUES 
3997   (23, 'provider.edi_default');
3998
3999 INSERT INTO action_trigger.validator (module, description) 
4000     VALUES (
4001         'Acq::PurchaseOrderEDIRequired',
4002         oils_i18n_gettext(
4003             'Acq::PurchaseOrderEDIRequired',
4004             'Purchase order is delivered via EDI',
4005             'atval',
4006             'description'
4007         )
4008     );
4009
4010 INSERT INTO action_trigger.reactor (module, description)
4011     VALUES (
4012         'GeneratePurchaseOrderJEDI',
4013         oils_i18n_gettext(
4014             'GeneratePurchaseOrderJEDI',
4015             'Creates purchase order JEDI (JSON EDI) for subsequent EDI processing',
4016             'atreact',
4017             'description'
4018         )
4019     );
4020
4021 UPDATE action_trigger.hook 
4022     SET 
4023         key = 'acqpo.activated', 
4024         passive = FALSE,
4025         description = oils_i18n_gettext(
4026             'acqpo.activated',
4027             'Purchase order was activated',
4028             'ath',
4029             'description'
4030         )
4031     WHERE key = 'format.po.jedi';
4032
4033 -- We just changed a key in action_trigger.hook.  Now redirect any
4034 -- child rows to point to the new key.  (There probably aren't any;
4035 -- this is just a precaution against possible local modifications.)
4036
4037 UPDATE action_trigger.event_definition
4038 SET hook = 'acqpo.activated'
4039 WHERE hook = 'format.po.jedi';
4040
4041 INSERT INTO action_trigger.reactor (module, description) VALUES (
4042     'AstCall', 'Possibly place a phone call with Asterisk'
4043 );
4044
4045 INSERT INTO
4046     action_trigger.event_definition (
4047         id, active, owner, name, hook, validator, reactor,
4048         cleanup_success, cleanup_failure, delay, delay_field, group_field,
4049         max_delay, granularity, usr_field, opt_in_setting, template
4050     ) VALUES (
4051         24,
4052         FALSE,
4053         1,
4054         'Telephone Overdue Notice',
4055         'checkout.due', 'NOOP_True', 'AstCall',
4056         DEFAULT, DEFAULT, '5 seconds', 'due_date', 'usr',
4057         DEFAULT, DEFAULT, DEFAULT, DEFAULT,
4058         $$
4059 [% phone = target.0.usr.day_phone | replace('[\s\-\(\)]', '') -%]
4060 [% IF phone.match('^[2-9]') %][% country = 1 %][% ELSE %][% country = '' %][% END -%]
4061 Channel: [% channel_prefix %]/[% country %][% phone %]
4062 Context: overdue-test
4063 MaxRetries: 1
4064 RetryTime: 60
4065 WaitTime: 30
4066 Extension: 10
4067 Archive: 1
4068 Set: eg_user_id=[% target.0.usr.id %]
4069 Set: items=[% target.size %]
4070 Set: titlestring=[% titles = [] %][% FOR circ IN target %][% titles.push(circ.target_copy.call_number.record.simple_record.title) %][% END %][% titles.join(". ") %]
4071 $$
4072     );
4073
4074 INSERT INTO
4075     action_trigger.environment (id, event_def, path)
4076     VALUES
4077         (DEFAULT, 24, 'target_copy.call_number.record.simple_record'),
4078         (DEFAULT, 24, 'usr')
4079     ;
4080
4081 INSERT INTO action_trigger.hook (key,core_type,description,passive) VALUES (
4082         'circ.format.history.email',
4083         'circ', 
4084         oils_i18n_gettext(
4085             'circ.format.history.email',
4086             'An email has been requested for a circ history.',
4087             'ath',
4088             'description'
4089         ), 
4090         FALSE
4091     )
4092     ,(
4093         'circ.format.history.print',
4094         'circ', 
4095         oils_i18n_gettext(
4096             'circ.format.history.print',
4097             'A circ history needs to be formatted for printing.',
4098             'ath',
4099             'description'
4100         ), 
4101         FALSE
4102     )
4103     ,(
4104         'ahr.format.history.email',
4105         'ahr', 
4106         oils_i18n_gettext(
4107             'ahr.format.history.email',
4108             'An email has been requested for a hold request history.',
4109             'ath',
4110             'description'
4111         ), 
4112         FALSE
4113     )
4114     ,(
4115         'ahr.format.history.print',
4116         'ahr', 
4117         oils_i18n_gettext(
4118             'ahr.format.history.print',
4119             'A hold request history needs to be formatted for printing.',
4120             'ath',
4121             'description'
4122         ), 
4123         FALSE
4124     )
4125
4126 ;
4127
4128 INSERT INTO action_trigger.event_definition (
4129         id,
4130         active,
4131         owner,
4132         name,
4133         hook,
4134         validator,
4135         reactor,
4136         group_field,
4137         granularity,
4138         template
4139     ) VALUES (
4140         25,
4141         TRUE,
4142         1,
4143         'circ.history.email',
4144         'circ.format.history.email',
4145         'NOOP_True',
4146         'SendEmail',
4147         'usr',
4148         NULL,
4149 $$
4150 [%- USE date -%]
4151 [%- SET user = target.0.usr -%]
4152 To: [%- params.recipient_email || user.email %]
4153 From: [%- params.sender_email || default_sender %]
4154 Subject: Circulation History
4155
4156     [% FOR circ IN target %]
4157             [% helpers.get_copy_bib_basics(circ.target_copy.id).title %]
4158             Barcode: [% circ.target_copy.barcode %]
4159             Checked Out: [% date.format(helpers.format_date(circ.xact_start), '%Y-%m-%d') %]
4160             Due Date: [% date.format(helpers.format_date(circ.due_date), '%Y-%m-%d') %]
4161             Returned: [% date.format(helpers.format_date(circ.checkin_time), '%Y-%m-%d') %]
4162     [% END %]
4163 $$
4164     )
4165     ,(
4166         26,
4167         TRUE,
4168         1,
4169         'circ.history.print',
4170         'circ.format.history.print',
4171         'NOOP_True',
4172         'ProcessTemplate',
4173         'usr',
4174         'print-on-demand',
4175 $$
4176 [%- USE date -%]
4177 <div>
4178     <style> li { padding: 8px; margin 5px; }</style>
4179     <div>[% date.format %]</div>
4180     <br/>
4181
4182     [% user.family_name %], [% user.first_given_name %]
4183     <ol>
4184     [% FOR circ IN target %]
4185         <li>
4186             <div>[% helpers.get_copy_bib_basics(circ.target_copy.id).title %]</div>
4187             <div>Barcode: [% circ.target_copy.barcode %]</div>
4188             <div>Checked Out: [% date.format(helpers.format_date(circ.xact_start), '%Y-%m-%d') %]</div>
4189             <div>Due Date: [% date.format(helpers.format_date(circ.due_date), '%Y-%m-%d') %]</div>
4190             <div>Returned: [% date.format(helpers.format_date(circ.checkin_time), '%Y-%m-%d') %]</div>
4191         </li>
4192     [% END %]
4193     </ol>
4194 </div>
4195 $$
4196     )
4197     ,(
4198         27,
4199         TRUE,
4200         1,
4201         'ahr.history.email',
4202         'ahr.format.history.email',
4203         'NOOP_True',
4204         'SendEmail',
4205         'usr',
4206         NULL,
4207 $$
4208 [%- USE date -%]
4209 [%- SET user = target.0.usr -%]
4210 To: [%- params.recipient_email || user.email %]
4211 From: [%- params.sender_email || default_sender %]
4212 Subject: Hold Request History
4213
4214     [% FOR hold IN target %]
4215             [% helpers.get_copy_bib_basics(hold.current_copy.id).title %]
4216             Requested: [% date.format(helpers.format_date(hold.request_time), '%Y-%m-%d') %]
4217             [% IF hold.fulfillment_time %]Fulfilled: [% date.format(helpers.format_date(hold.fulfillment_time), '%Y-%m-%d') %][% END %]
4218     [% END %]
4219 $$
4220     )
4221     ,(
4222         28,
4223         TRUE,
4224         1,
4225         'ahr.history.print',
4226         'ahr.format.history.print',
4227         'NOOP_True',
4228         'ProcessTemplate',
4229         'usr',
4230         'print-on-demand',
4231 $$
4232 [%- USE date -%]
4233 <div>
4234     <style> li { padding: 8px; margin 5px; }</style>
4235     <div>[% date.format %]</div>
4236     <br/>
4237
4238     [% user.family_name %], [% user.first_given_name %]
4239     <ol>
4240     [% FOR hold IN target %]
4241         <li>
4242             <div>[% helpers.get_copy_bib_basics(hold.current_copy.id).title %]</div>
4243             <div>Requested: [% date.format(helpers.format_date(hold.request_time), '%Y-%m-%d') %]</div>
4244             [% IF hold.fulfillment_time %]<div>Fulfilled: [% date.format(helpers.format_date(hold.fulfillment_time), '%Y-%m-%d') %]</div>[% END %]
4245         </li>
4246     [% END %]
4247     </ol>
4248 </div>
4249 $$
4250     )
4251
4252 ;
4253
4254 INSERT INTO action_trigger.environment (
4255         event_def,
4256         path
4257     ) VALUES 
4258          ( 25, 'target_copy')
4259         ,( 25, 'usr' )
4260         ,( 26, 'target_copy' )
4261         ,( 26, 'usr' )
4262         ,( 27, 'current_copy' )
4263         ,( 27, 'usr' )
4264         ,( 28, 'current_copy' )
4265         ,( 28, 'usr' )
4266 ;
4267
4268 INSERT INTO action_trigger.hook (key,core_type,description,passive) VALUES (
4269         'money.format.payment_receipt.email',
4270         'mp', 
4271         oils_i18n_gettext(
4272             'money.format.payment_receipt.email',
4273             'An email has been requested for a payment receipt.',
4274             'ath',
4275             'description'
4276         ), 
4277         FALSE
4278     )
4279     ,(
4280         'money.format.payment_receipt.print',
4281         'mp', 
4282         oils_i18n_gettext(
4283             'money.format.payment_receipt.print',
4284             'A payment receipt needs to be formatted for printing.',
4285             'ath',
4286             'description'
4287         ), 
4288         FALSE
4289     )
4290 ;
4291
4292 INSERT INTO action_trigger.event_definition (
4293         id,
4294         active,
4295         owner,
4296         name,
4297         hook,
4298         validator,
4299         reactor,
4300         group_field,
4301         granularity,
4302         template
4303     ) VALUES (
4304         29,
4305         TRUE,
4306         1,
4307         'money.payment_receipt.email',
4308         'money.format.payment_receipt.email',
4309         'NOOP_True',
4310         'SendEmail',
4311         'xact.usr',
4312         NULL,
4313 $$
4314 [%- USE date -%]
4315 [%- SET user = target.0.xact.usr -%]
4316 To: [%- params.recipient_email || user.email %]
4317 From: [%- params.sender_email || default_sender %]
4318 Subject: Payment Receipt
4319
4320 [% date.format -%]
4321 [%- SET xact_mp_hash = {} -%]
4322 [%- FOR mp IN target %][%# Template is hooked around payments, but let us make the receipt focused on transactions -%]
4323     [%- SET xact_id = mp.xact.id -%]
4324     [%- IF ! xact_mp_hash.defined( xact_id ) -%][%- xact_mp_hash.$xact_id = { 'xact' => mp.xact, 'payments' => [] } -%][%- END -%]
4325     [%- xact_mp_hash.$xact_id.payments.push(mp) -%]
4326 [%- END -%]
4327 [%- FOR xact_id IN xact_mp_hash.keys.sort -%]
4328     [%- SET xact = xact_mp_hash.$xact_id.xact %]
4329 Transaction ID: [% xact_id %]
4330     [% IF xact.circulation %][% helpers.get_copy_bib_basics(xact.circulation.target_copy).title %]
4331     [% ELSE %]Miscellaneous
4332     [% END %]
4333     Line item billings:
4334         [%- SET mb_type_hash = {} -%]
4335         [%- FOR mb IN xact.billings %][%# Group billings by their btype -%]
4336             [%- IF mb.voided == 'f' -%]
4337                 [%- SET mb_type = mb.btype.id -%]
4338                 [%- IF ! mb_type_hash.defined( mb_type ) -%][%- mb_type_hash.$mb_type = { 'sum' => 0.00, 'billings' => [] } -%][%- END -%]
4339                 [%- IF ! mb_type_hash.$mb_type.defined( 'first_ts' ) -%][%- mb_type_hash.$mb_type.first_ts = mb.billing_ts -%][%- END -%]
4340                 [%- mb_type_hash.$mb_type.last_ts = mb.billing_ts -%]
4341                 [%- mb_type_hash.$mb_type.sum = mb_type_hash.$mb_type.sum + mb.amount -%]
4342                 [%- mb_type_hash.$mb_type.billings.push( mb ) -%]
4343             [%- END -%]
4344         [%- END -%]
4345         [%- FOR mb_type IN mb_type_hash.keys.sort -%]
4346             [%- IF mb_type == 1 %][%-# Consolidated view of overdue billings -%]
4347                 $[% mb_type_hash.$mb_type.sum %] for [% mb_type_hash.$mb_type.billings.0.btype.name %] 
4348                     on [% mb_type_hash.$mb_type.first_ts %] through [% mb_type_hash.$mb_type.last_ts %]
4349             [%- ELSE -%][%# all other billings show individually %]
4350                 [% FOR mb IN mb_type_hash.$mb_type.billings %]
4351                     $[% mb.amount %] for [% mb.btype.name %] on [% mb.billing_ts %] [% mb.note %]
4352                 [% END %]
4353             [% END %]
4354         [% END %]
4355     Line item payments:
4356         [% FOR mp IN xact_mp_hash.$xact_id.payments %]
4357             Payment ID: [% mp.id %]
4358                 Paid [% mp.amount %] via [% SWITCH mp.payment_type -%]
4359                     [% CASE "cash_payment" %]cash
4360                     [% CASE "check_payment" %]check
4361                     [% CASE "credit_card_payment" %]credit card (
4362                         [%- SET cc_chunks = mp.credit_card_payment.cc_number.replace(' ','').chunk(4); -%]
4363                         [%- cc_chunks.slice(0, -1+cc_chunks.max).join.replace('\S','X') -%] 
4364                         [% cc_chunks.last -%]
4365                         exp [% mp.credit_card_payment.expire_month %]/[% mp.credit_card_payment.expire_year -%]
4366                     )
4367                     [% CASE "credit_payment" %]credit
4368                     [% CASE "forgive_payment" %]forgiveness
4369                     [% CASE "goods_payment" %]goods
4370                     [% CASE "work_payment" %]work
4371                 [%- END %] on [% mp.payment_ts %] [% mp.note %]
4372         [% END %]
4373 [% END %]
4374 $$
4375     )
4376     ,(
4377         30,
4378         TRUE,
4379         1,
4380         'money.payment_receipt.print',
4381         'money.format.payment_receipt.print',
4382         'NOOP_True',
4383         'ProcessTemplate',
4384         'xact.usr',
4385         'print-on-demand',
4386 $$
4387 [%- USE date -%][%- SET user = target.0.xact.usr -%]
4388 <div style="li { padding: 8px; margin 5px; }">
4389     <div>[% date.format %]</div><br/>
4390     <ol>
4391     [% SET xact_mp_hash = {} %]
4392     [% FOR mp IN target %][%# Template is hooked around payments, but let us make the receipt focused on transactions %]
4393         [% SET xact_id = mp.xact.id %]
4394         [% IF ! xact_mp_hash.defined( xact_id ) %][% xact_mp_hash.$xact_id = { 'xact' => mp.xact, 'payments' => [] } %][% END %]
4395         [% xact_mp_hash.$xact_id.payments.push(mp) %]
4396     [% END %]
4397     [% FOR xact_id IN xact_mp_hash.keys.sort %]
4398         [% SET xact = xact_mp_hash.$xact_id.xact %]
4399         <li>Transaction ID: [% xact_id %]
4400             [% IF xact.circulation %][% helpers.get_copy_bib_basics(xact.circulation.target_copy).title %]
4401             [% ELSE %]Miscellaneous
4402             [% END %]
4403             Line item billings:<ol>
4404                 [% SET mb_type_hash = {} %]
4405                 [% FOR mb IN xact.billings %][%# Group billings by their btype %]
4406                     [% IF mb.voided == 'f' %]
4407                         [% SET mb_type = mb.btype.id %]
4408                         [% IF ! mb_type_hash.defined( mb_type ) %][% mb_type_hash.$mb_type = { 'sum' => 0.00, 'billings' => [] } %][% END %]
4409                         [% IF ! mb_type_hash.$mb_type.defined( 'first_ts' ) %][% mb_type_hash.$mb_type.first_ts = mb.billing_ts %][% END %]
4410                         [% mb_type_hash.$mb_type.last_ts = mb.billing_ts %]
4411                         [% mb_type_hash.$mb_type.sum = mb_type_hash.$mb_type.sum + mb.amount %]
4412                         [% mb_type_hash.$mb_type.billings.push( mb ) %]
4413                     [% END %]
4414                 [% END %]
4415                 [% FOR mb_type IN mb_type_hash.keys.sort %]
4416                     <li>[% IF mb_type == 1 %][%# Consolidated view of overdue billings %]
4417                         $[% mb_type_hash.$mb_type.sum %] for [% mb_type_hash.$mb_type.billings.0.btype.name %] 
4418                             on [% mb_type_hash.$mb_type.first_ts %] through [% mb_type_hash.$mb_type.last_ts %]
4419                     [% ELSE %][%# all other billings show individually %]
4420                         [% FOR mb IN mb_type_hash.$mb_type.billings %]
4421                             $[% mb.amount %] for [% mb.btype.name %] on [% mb.billing_ts %] [% mb.note %]
4422                         [% END %]
4423                     [% END %]</li>
4424                 [% END %]
4425             </ol>
4426             Line item payments:<ol>
4427                 [% FOR mp IN xact_mp_hash.$xact_id.payments %]
4428                     <li>Payment ID: [% mp.id %]
4429                         Paid [% mp.amount %] via [% SWITCH mp.payment_type -%]
4430                             [% CASE "cash_payment" %]cash
4431                             [% CASE "check_payment" %]check
4432                             [% CASE "credit_card_payment" %]credit card (
4433                                 [%- SET cc_chunks = mp.credit_card_payment.cc_number.replace(' ','').chunk(4); -%]
4434                                 [%- cc_chunks.slice(0, -1+cc_chunks.max).join.replace('\S','X') -%] 
4435                                 [% cc_chunks.last -%]
4436                                 exp [% mp.credit_card_payment.expire_month %]/[% mp.credit_card_payment.expire_year -%]
4437                             )
4438                             [% CASE "credit_payment" %]credit
4439                             [% CASE "forgive_payment" %]forgiveness
4440                             [% CASE "goods_payment" %]goods
4441                             [% CASE "work_payment" %]work
4442                         [%- END %] on [% mp.payment_ts %] [% mp.note %]
4443                     </li>
4444                 [% END %]
4445             </ol>
4446         </li>
4447     [% END %]
4448     </ol>
4449 </div>
4450 $$
4451     )
4452 ;
4453
4454 INSERT INTO action_trigger.environment (
4455         event_def,
4456         path
4457     ) VALUES -- for fleshing mp objects
4458          ( 29, 'xact')
4459         ,( 29, 'xact.usr')
4460         ,( 29, 'xact.grocery' )
4461         ,( 29, 'xact.circulation' )
4462         ,( 29, 'xact.summary' )
4463         ,( 30, 'xact')
4464         ,( 30, 'xact.usr')
4465         ,( 30, 'xact.grocery' )
4466         ,( 30, 'xact.circulation' )
4467         ,( 30, 'xact.summary' )
4468 ;
4469
4470 INSERT INTO action_trigger.cleanup ( module, description ) VALUES (
4471     'DeleteTempBiblioBucket',
4472     oils_i18n_gettext(
4473         'DeleteTempBiblioBucket',
4474         'Deletes a cbreb object used as a target if it has a btype of "temp"',
4475         'atclean',
4476         'description'
4477     )
4478 );
4479
4480 INSERT INTO action_trigger.hook (key,core_type,description,passive) VALUES (
4481         'biblio.format.record_entry.email',
4482         'cbreb', 
4483         oils_i18n_gettext(
4484             'biblio.format.record_entry.email',
4485             'An email has been requested for one or more biblio record entries.',
4486             'ath',
4487             'description'
4488         ), 
4489         FALSE
4490     )
4491     ,(
4492         'biblio.format.record_entry.print',
4493         'cbreb', 
4494         oils_i18n_gettext(
4495             'biblio.format.record_entry.print',
4496             'One or more biblio record entries need to be formatted for printing.',
4497             'ath',
4498             'description'
4499         ), 
4500         FALSE
4501     )
4502 ;
4503
4504 INSERT INTO action_trigger.event_definition (
4505         id,
4506         active,
4507         owner,
4508         name,
4509         hook,
4510         validator,
4511         reactor,
4512         cleanup_success,
4513         cleanup_failure,
4514         group_field,
4515         granularity,
4516         template
4517     ) VALUES (
4518         31,
4519         TRUE,
4520         1,
4521         'biblio.record_entry.email',
4522         'biblio.format.record_entry.email',
4523         'NOOP_True',
4524         'SendEmail',
4525         'DeleteTempBiblioBucket',
4526         'DeleteTempBiblioBucket',
4527         'owner',
4528         NULL,
4529 $$
4530 [%- USE date -%]
4531 [%- SET user = target.0.owner -%]
4532 To: [%- params.recipient_email || user.email %]
4533 From: [%- params.sender_email || default_sender %]
4534 Subject: Bibliographic Records
4535
4536     [% FOR cbreb IN target %]
4537     [% FOR cbrebi IN cbreb.items %]
4538         Bib ID# [% cbrebi.target_biblio_record_entry.id %] ISBN: [% crebi.target_biblio_record_entry.simple_record.isbn %]
4539         Title: [% cbrebi.target_biblio_record_entry.simple_record.title %]
4540         Author: [% cbrebi.target_biblio_record_entry.simple_record.author %]
4541         Publication Year: [% cbrebi.target_biblio_record_entry.simple_record.pubdate %]
4542
4543     [% END %]
4544     [% END %]
4545 $$
4546     )
4547     ,(
4548         32,
4549         TRUE,
4550         1,
4551         'biblio.record_entry.print',
4552         'biblio.format.record_entry.print',
4553         'NOOP_True',
4554         'ProcessTemplate',
4555         'DeleteTempBiblioBucket',
4556         'DeleteTempBiblioBucket',
4557         'owner',
4558         'print-on-demand',
4559 $$
4560 [%- USE date -%]
4561 <div>
4562     <style> li { padding: 8px; margin 5px; }</style>
4563     <ol>
4564     [% FOR cbreb IN target %]
4565     [% FOR cbrebi IN cbreb.items %]
4566         <li>Bib ID# [% cbrebi.target_biblio_record_entry.id %] ISBN: [% crebi.target_biblio_record_entry.simple_record.isbn %]<br />
4567             Title: [% cbrebi.target_biblio_record_entry.simple_record.title %]<br />
4568             Author: [% cbrebi.target_biblio_record_entry.simple_record.author %]<br />
4569             Publication Year: [% cbrebi.target_biblio_record_entry.simple_record.pubdate %]
4570         </li>
4571     [% END %]
4572     [% END %]
4573     </ol>
4574 </div>
4575 $$
4576     )
4577 ;
4578
4579 INSERT INTO action_trigger.environment (
4580         event_def,
4581         path
4582     ) VALUES -- for fleshing cbreb objects
4583          ( 31, 'owner' )
4584         ,( 31, 'items' )
4585         ,( 31, 'items.target_biblio_record_entry' )
4586         ,( 31, 'items.target_biblio_record_entry.simple_record' )
4587         ,( 31, 'items.target_biblio_record_entry.call_numbers' )
4588         ,( 31, 'items.target_biblio_record_entry.fixed_fields' )
4589         ,( 31, 'items.target_biblio_record_entry.notes' )
4590         ,( 31, 'items.target_biblio_record_entry.full_record_entries' )
4591         ,( 32, 'owner' )
4592         ,( 32, 'items' )
4593         ,( 32, 'items.target_biblio_record_entry' )
4594         ,( 32, 'items.target_biblio_record_entry.simple_record' )
4595         ,( 32, 'items.target_biblio_record_entry.call_numbers' )
4596         ,( 32, 'items.target_biblio_record_entry.fixed_fields' )
4597         ,( 32, 'items.target_biblio_record_entry.notes' )
4598         ,( 32, 'items.target_biblio_record_entry.full_record_entries' )
4599 ;
4600
4601 INSERT INTO action_trigger.environment (
4602         event_def,
4603         path
4604     ) VALUES -- for fleshing mp objects
4605          ( 29, 'credit_card_payment')
4606         ,( 29, 'xact.billings')
4607         ,( 29, 'xact.billings.btype')
4608         ,( 30, 'credit_card_payment')
4609         ,( 30, 'xact.billings')
4610         ,( 30, 'xact.billings.btype')
4611 ;
4612
4613 INSERT INTO action_trigger.hook (key,core_type,description,passive) VALUES 
4614     (   'circ.format.missing_pieces.slip.print',
4615         'circ', 
4616         oils_i18n_gettext(
4617             'circ.format.missing_pieces.slip.print',
4618             'A missing pieces slip needs to be formatted for printing.',
4619             'ath',
4620             'description'
4621         ), 
4622         FALSE
4623     )
4624     ,(  'circ.format.missing_pieces.letter.print',
4625         'circ', 
4626         oils_i18n_gettext(
4627             'circ.format.missing_pieces.letter.print',
4628             'A missing pieces patron letter needs to be formatted for printing.',
4629             'ath',
4630             'description'
4631         ), 
4632         FALSE
4633     )
4634 ;
4635
4636 INSERT INTO action_trigger.event_definition (
4637         id,
4638         active,
4639         owner,
4640         name,
4641         hook,
4642         validator,
4643         reactor,
4644         group_field,
4645         granularity,
4646         template
4647     ) VALUES (
4648         33,
4649         TRUE,
4650         1,
4651         'circ.missing_pieces.slip.print',
4652         'circ.format.missing_pieces.slip.print',
4653         'NOOP_True',
4654         'ProcessTemplate',
4655         'usr',
4656         'print-on-demand',
4657 $$
4658 [%- USE date -%]
4659 [%- SET user = target.0.usr -%]
4660 <div style="li { padding: 8px; margin 5px; }">
4661     <div>[% date.format %]</div><br/>
4662     Missing pieces for:
4663     <ol>
4664     [% FOR circ IN target %]
4665         <li>Barcode: [% circ.target_copy.barcode %] Transaction ID: [% circ.id %] Due: [% circ.due_date.format %]<br />
4666             [% helpers.get_copy_bib_basics(circ.target_copy.id).title %]
4667         </li>
4668     [% END %]
4669     </ol>
4670 </div>
4671 $$
4672     )
4673     ,(
4674         34,
4675         TRUE,
4676         1,
4677         'circ.missing_pieces.letter.print',
4678         'circ.format.missing_pieces.letter.print',
4679         'NOOP_True',
4680         'ProcessTemplate',
4681         'usr',
4682         'print-on-demand',
4683 $$
4684 [%- USE date -%]
4685 [%- SET user = target.0.usr -%]
4686 [% date.format %]
4687 Dear [% user.prefix %] [% user.first_given_name %] [% user.family_name %],
4688
4689 We are missing pieces for the following returned items:
4690 [% FOR circ IN target %]
4691 Barcode: [% circ.target_copy.barcode %] Transaction ID: [% circ.id %] Due: [% circ.due_date.format %]
4692 [% helpers.get_copy_bib_basics(circ.target_copy.id).title %]
4693 [% END %]
4694
4695 Please return these pieces as soon as possible.
4696
4697 Thanks!
4698
4699 Library Staff
4700 $$
4701     )
4702 ;
4703
4704 INSERT INTO action_trigger.environment (
4705         event_def,
4706         path
4707     ) VALUES -- for fleshing circ objects
4708          ( 33, 'usr')
4709         ,( 33, 'target_copy')
4710         ,( 33, 'target_copy.circ_lib')
4711         ,( 33, 'target_copy.circ_lib.mailing_address')
4712         ,( 33, 'target_copy.circ_lib.billing_address')
4713         ,( 33, 'target_copy.call_number')
4714         ,( 33, 'target_copy.call_number.owning_lib')
4715         ,( 33, 'target_copy.call_number.owning_lib.mailing_address')
4716         ,( 33, 'target_copy.call_number.owning_lib.billing_address')
4717         ,( 33, 'circ_lib')
4718         ,( 33, 'circ_lib.mailing_address')
4719         ,( 33, 'circ_lib.billing_address')
4720         ,( 34, 'usr')
4721         ,( 34, 'target_copy')
4722         ,( 34, 'target_copy.circ_lib')
4723         ,( 34, 'target_copy.circ_lib.mailing_address')
4724         ,( 34, 'target_copy.circ_lib.billing_address')
4725         ,( 34, 'target_copy.call_number')
4726         ,( 34, 'target_copy.call_number.owning_lib')
4727         ,( 34, 'target_copy.call_number.owning_lib.mailing_address')
4728         ,( 34, 'target_copy.call_number.owning_lib.billing_address')
4729         ,( 34, 'circ_lib')
4730         ,( 34, 'circ_lib.mailing_address')
4731         ,( 34, 'circ_lib.billing_address')
4732 ;
4733
4734 INSERT INTO action_trigger.hook (key,core_type,description,passive) 
4735     VALUES (   
4736         'ahr.format.pull_list',
4737         'ahr', 
4738         oils_i18n_gettext(
4739             'ahr.format.pull_list',
4740             'Format holds pull list for printing',
4741             'ath',
4742             'description'
4743         ), 
4744         FALSE
4745     );
4746
4747 INSERT INTO action_trigger.event_definition (
4748         id,
4749         active,
4750         owner,
4751         name,
4752         hook,
4753         validator,
4754         reactor,
4755         group_field,
4756         granularity,
4757         template
4758     ) VALUES (
4759         35,
4760         TRUE,
4761         1,
4762         'Holds Pull List',
4763         'ahr.format.pull_list',
4764         'NOOP_True',
4765         'ProcessTemplate',
4766         'pickup_lib',
4767         'print-on-demand',
4768 $$
4769 [%- USE date -%]
4770 <style>
4771     table { border-collapse: collapse; } 
4772     td { padding: 5px; border-bottom: 1px solid #888; } 
4773     th { font-weight: bold; }
4774 </style>
4775 [% 
4776     # Sort the holds into copy-location buckets
4777     # In the main print loop, sort each bucket by callnumber before printing
4778     SET holds_list = [];
4779     SET loc_data = [];
4780     SET current_location = target.0.current_copy.location.id;
4781     FOR hold IN target;
4782         IF current_location != hold.current_copy.location.id;
4783             SET current_location = hold.current_copy.location.id;
4784             holds_list.push(loc_data);
4785             SET loc_data = [];
4786         END;
4787         SET hold_data = {
4788             'hold' => hold,
4789             'callnumber' => hold.current_copy.call_number.label
4790         };
4791         loc_data.push(hold_data);
4792     END;
4793     holds_list.push(loc_data)
4794 %]
4795 <table>
4796     <thead>
4797         <tr>
4798             <th>Title</th>
4799             <th>Author</th>
4800             <th>Shelving Location</th>
4801             <th>Call Number</th>
4802             <th>Barcode</th>
4803             <th>Patron</th>
4804         </tr>
4805     </thead>
4806     <tbody>
4807     [% FOR loc_data IN holds_list  %]
4808         [% FOR hold_data IN loc_data.sort('callnumber') %]
4809             [% 
4810                 SET hold = hold_data.hold;
4811                 SET copy_data = helpers.get_copy_bib_basics(hold.current_copy.id);
4812             %]
4813             <tr>
4814                 <td>[% copy_data.title | truncate %]</td>
4815                 <td>[% copy_data.author | truncate %]</td>
4816                 <td>[% hold.current_copy.location.name %]</td>
4817                 <td>[% hold.current_copy.call_number.label %]</td>
4818                 <td>[% hold.current_copy.barcode %]</td>
4819                 <td>[% hold.usr.card.barcode %]</td>
4820             </tr>
4821         [% END %]
4822     [% END %]
4823     <tbody>
4824 </table>
4825 $$
4826 );
4827
4828 INSERT INTO action_trigger.environment (
4829         event_def,
4830         path
4831     ) VALUES
4832         (35, 'current_copy.location'),
4833         (35, 'current_copy.call_number'),
4834         (35, 'usr.card'),
4835         (35, 'pickup_lib')
4836 ;
4837
4838 INSERT INTO action_trigger.validator (module, description) VALUES ( 
4839     'HoldIsCancelled', 
4840     oils_i18n_gettext( 
4841         'HoldIsCancelled', 
4842         'Check whether a hold request is cancelled.', 
4843         'atval', 
4844         'description' 
4845     ) 
4846 );
4847
4848 -- Create the query schema, and the tables and views therein
4849
4850 DROP SCHEMA IF EXISTS sql CASCADE;
4851 DROP SCHEMA IF EXISTS query CASCADE;
4852
4853 CREATE SCHEMA query;
4854
4855 CREATE TABLE query.datatype (
4856         id              SERIAL            PRIMARY KEY,
4857         datatype_name   TEXT              NOT NULL UNIQUE,
4858         is_numeric      BOOL              NOT NULL DEFAULT FALSE,
4859         is_composite    BOOL              NOT NULL DEFAULT FALSE,
4860         CONSTRAINT qdt_comp_not_num CHECK
4861         ( is_numeric IS FALSE OR is_composite IS FALSE )
4862 );
4863
4864 -- Define the most common datatypes in query.datatype.  Note that none of
4865 -- these stock datatypes specifies a width or precision.
4866
4867 -- Also: set the sequence for query.datatype to 1000, leaving plenty of
4868 -- room for more stock datatypes if we ever want to add them.
4869
4870 SELECT setval( 'query.datatype_id_seq', 1000 );
4871
4872 INSERT INTO query.datatype (id, datatype_name, is_numeric )
4873   VALUES (1, 'SMALLINT', true);
4874  
4875 INSERT INTO query.datatype (id, datatype_name, is_numeric )
4876   VALUES (2, 'INTEGER', true);
4877  
4878 INSERT INTO query.datatype (id, datatype_name, is_numeric )
4879   VALUES (3, 'BIGINT', true);
4880  
4881 INSERT INTO query.datatype (id, datatype_name, is_numeric )
4882   VALUES (4, 'DECIMAL', true);
4883  
4884 INSERT INTO query.datatype (id, datatype_name, is_numeric )
4885   VALUES (5, 'NUMERIC', true);
4886  
4887 INSERT INTO query.datatype (id, datatype_name, is_numeric )
4888   VALUES (6, 'REAL', true);
4889  
4890 INSERT INTO query.datatype (id, datatype_name, is_numeric )
4891   VALUES (7, 'DOUBLE PRECISION', true);
4892  
4893 INSERT INTO query.datatype (id, datatype_name, is_numeric )
4894   VALUES (8, 'SERIAL', true);
4895  
4896 INSERT INTO query.datatype (id, datatype_name, is_numeric )
4897   VALUES (9, 'BIGSERIAL', true);
4898  
4899 INSERT INTO query.datatype (id, datatype_name, is_numeric )
4900   VALUES (10, 'MONEY', false);
4901  
4902 INSERT INTO query.datatype (id, datatype_name, is_numeric )
4903   VALUES (11, 'VARCHAR', false);
4904  
4905 INSERT INTO query.datatype (id, datatype_name, is_numeric )
4906   VALUES (12, 'CHAR', false);
4907  
4908 INSERT INTO query.datatype (id, datatype_name, is_numeric )
4909   VALUES (13, 'TEXT', false);
4910  
4911 INSERT INTO query.datatype (id, datatype_name, is_numeric )
4912   VALUES (14, '"char"', false);
4913  
4914 INSERT INTO query.datatype (id, datatype_name, is_numeric )
4915   VALUES (15, 'NAME', false);
4916  
4917 INSERT INTO query.datatype (id, datatype_name, is_numeric )
4918   VALUES (16, 'BYTEA', false);
4919  
4920 INSERT INTO query.datatype (id, datatype_name, is_numeric )
4921   VALUES (17, 'TIMESTAMP WITHOUT TIME ZONE', false);
4922  
4923 INSERT INTO query.datatype (id, datatype_name, is_numeric )
4924   VALUES (18, 'TIMESTAMP WITH TIME ZONE', false);
4925  
4926 INSERT INTO query.datatype (id, datatype_name, is_numeric )
4927   VALUES (19, 'DATE', false);
4928  
4929 INSERT INTO query.datatype (id, datatype_name, is_numeric )
4930   VALUES (20, 'TIME WITHOUT TIME ZONE', false);
4931  
4932 INSERT INTO query.datatype (id, datatype_name, is_numeric )
4933   VALUES (21, 'TIME WITH TIME ZONE', false);
4934  
4935 INSERT INTO query.datatype (id, datatype_name, is_numeric )
4936   VALUES (22, 'INTERVAL', false);
4937  
4938 INSERT INTO query.datatype (id, datatype_name, is_numeric )
4939   VALUES (23, 'BOOLEAN', false);
4940  
4941 CREATE TABLE query.subfield (
4942         id              SERIAL            PRIMARY KEY,
4943         composite_type  INT               NOT NULL
4944                                           REFERENCES query.datatype(id)
4945                                           ON DELETE CASCADE
4946                                           DEFERRABLE INITIALLY DEFERRED,
4947         seq_no          INT               NOT NULL
4948                                           CONSTRAINT qsf_pos_seq_no
4949                                           CHECK( seq_no > 0 ),
4950         subfield_type   INT               NOT NULL
4951                                           REFERENCES query.datatype(id)
4952                                           DEFERRABLE INITIALLY DEFERRED,
4953         CONSTRAINT qsf_datatype_seq_no UNIQUE (composite_type, seq_no)
4954 );
4955
4956 CREATE TABLE query.function_sig (
4957         id              SERIAL            PRIMARY KEY,
4958         function_name   TEXT              NOT NULL,
4959         return_type     INT               REFERENCES query.datatype(id)
4960                                           DEFERRABLE INITIALLY DEFERRED,
4961         is_aggregate    BOOL              NOT NULL DEFAULT FALSE,
4962         CONSTRAINT qfd_rtn_or_aggr CHECK
4963         ( return_type IS NULL OR is_aggregate = FALSE )
4964 );
4965
4966 CREATE INDEX query_function_sig_name_idx 
4967         ON query.function_sig (function_name);
4968
4969 CREATE TABLE query.function_param_def (
4970         id              SERIAL            PRIMARY KEY,
4971         function_id     INT               NOT NULL
4972                                           REFERENCES query.function_sig( id )
4973                                           ON DELETE CASCADE
4974                                           DEFERRABLE INITIALLY DEFERRED,
4975         seq_no          INT               NOT NULL
4976                                           CONSTRAINT qfpd_pos_seq_no CHECK
4977                                           ( seq_no > 0 ),
4978         datatype        INT               NOT NULL
4979                                           REFERENCES query.datatype( id )
4980                                           DEFERRABLE INITIALLY DEFERRED,
4981         CONSTRAINT qfpd_function_param_seq UNIQUE (function_id, seq_no)
4982 );
4983
4984 CREATE TABLE  query.stored_query (
4985         id            SERIAL         PRIMARY KEY,
4986         type          TEXT           NOT NULL CONSTRAINT query_type CHECK
4987                                      ( type IN ( 'SELECT', 'UNION', 'INTERSECT', 'EXCEPT' ) ),
4988         use_all       BOOLEAN        NOT NULL DEFAULT FALSE,
4989         use_distinct  BOOLEAN        NOT NULL DEFAULT FALSE,
4990         from_clause   INT            , --REFERENCES query.from_clause
4991                                      --DEFERRABLE INITIALLY DEFERRED,
4992         where_clause  INT            , --REFERENCES query.expression
4993                                      --DEFERRABLE INITIALLY DEFERRED,
4994         having_clause INT            , --REFERENCES query.expression
4995                                      --DEFERRABLE INITIALLY DEFERRED
4996         limit_count   INT            , --REFERENCES query.expression( id )
4997                                      --DEFERRABLE INITIALLY DEFERRED,
4998         offset_count  INT            --REFERENCES query.expression( id )
4999                                      --DEFERRABLE INITIALLY DEFERRED
5000 );
5001
5002 -- (Foreign keys to be defined later after other tables are created)
5003
5004 CREATE TABLE query.query_sequence (
5005         id              SERIAL            PRIMARY KEY,
5006         parent_query    INT               NOT NULL
5007                                           REFERENCES query.stored_query
5008                                                                           ON DELETE CASCADE
5009                                                                           DEFERRABLE INITIALLY DEFERRED,
5010         seq_no          INT               NOT NULL,
5011         child_query     INT               NOT NULL
5012                                           REFERENCES query.stored_query
5013                                                                           ON DELETE CASCADE
5014                                                                           DEFERRABLE INITIALLY DEFERRED,
5015         CONSTRAINT query_query_seq UNIQUE( parent_query, seq_no )
5016 );
5017
5018 CREATE TABLE query.bind_variable (
5019         name          TEXT             PRIMARY KEY,
5020         type          TEXT             NOT NULL
5021                                            CONSTRAINT bind_variable_type CHECK
5022                                            ( type in ( 'string', 'number', 'string_list', 'number_list' )),
5023         description   TEXT             NOT NULL,
5024         default_value TEXT,            -- to be encoded in JSON
5025         label         TEXT             NOT NULL
5026 );
5027
5028 CREATE TABLE query.expression (
5029         id            SERIAL        PRIMARY KEY,
5030         type          TEXT          NOT NULL CONSTRAINT expression_type CHECK
5031                                     ( type IN (
5032                                     'xbet',    -- between
5033                                     'xbind',   -- bind variable
5034                                     'xbool',   -- boolean
5035                                     'xcase',   -- case
5036                                     'xcast',   -- cast
5037                                     'xcol',    -- column
5038                                     'xex',     -- exists
5039                                     'xfunc',   -- function
5040                                     'xin',     -- in
5041                                     'xisnull', -- is null
5042                                     'xnull',   -- null
5043                                     'xnum',    -- number
5044                                     'xop',     -- operator
5045                                     'xser',    -- series
5046                                     'xstr',    -- string
5047                                     'xsubq'    -- subquery
5048                                                                 ) ),
5049         parenthesize  BOOL          NOT NULL DEFAULT FALSE,
5050         parent_expr   INT           REFERENCES query.expression
5051                                     ON DELETE CASCADE
5052                                     DEFERRABLE INITIALLY DEFERRED,
5053         seq_no        INT           NOT NULL DEFAULT 1,
5054         literal       TEXT,
5055         table_alias   TEXT,
5056         column_name   TEXT,
5057         left_operand  INT           REFERENCES query.expression
5058                                     DEFERRABLE INITIALLY DEFERRED,
5059         operator      TEXT,
5060         right_operand INT           REFERENCES query.expression
5061                                     DEFERRABLE INITIALLY DEFERRED,
5062         function_id   INT           REFERENCES query.function_sig
5063                                     DEFERRABLE INITIALLY DEFERRED,
5064         subquery      INT           REFERENCES query.stored_query
5065                                     DEFERRABLE INITIALLY DEFERRED,
5066         cast_type     INT           REFERENCES query.datatype
5067                                     DEFERRABLE INITIALLY DEFERRED,
5068         negate        BOOL          NOT NULL DEFAULT FALSE,
5069         bind_variable TEXT          REFERENCES query.bind_variable
5070                                         DEFERRABLE INITIALLY DEFERRED
5071 );
5072
5073 CREATE UNIQUE INDEX query_expr_parent_seq
5074         ON query.expression( parent_expr, seq_no )
5075         WHERE parent_expr IS NOT NULL;
5076
5077 -- Due to some circular references, the following foreign key definitions
5078 -- had to be deferred until query.expression existed:
5079
5080 ALTER TABLE query.stored_query
5081         ADD FOREIGN KEY ( where_clause )
5082         REFERENCES query.expression( id )
5083         DEFERRABLE INITIALLY DEFERRED;
5084
5085 ALTER TABLE query.stored_query
5086         ADD FOREIGN KEY ( having_clause )
5087         REFERENCES query.expression( id )
5088         DEFERRABLE INITIALLY DEFERRED;
5089
5090 ALTER TABLE query.stored_query
5091     ADD FOREIGN KEY ( limit_count )
5092     REFERENCES query.expression( id )
5093     DEFERRABLE INITIALLY DEFERRED;
5094
5095 ALTER TABLE query.stored_query
5096     ADD FOREIGN KEY ( offset_count )
5097     REFERENCES query.expression( id )
5098     DEFERRABLE INITIALLY DEFERRED;
5099
5100 CREATE TABLE query.case_branch (
5101         id            SERIAL        PRIMARY KEY,
5102         parent_expr   INT           NOT NULL REFERENCES query.expression
5103                                     ON DELETE CASCADE
5104                                     DEFERRABLE INITIALLY DEFERRED,
5105         seq_no        INT           NOT NULL,
5106         condition     INT           REFERENCES query.expression
5107                                     DEFERRABLE INITIALLY DEFERRED,
5108         result        INT           NOT NULL REFERENCES query.expression
5109                                     DEFERRABLE INITIALLY DEFERRED,
5110         CONSTRAINT case_branch_parent_seq UNIQUE (parent_expr, seq_no)
5111 );
5112
5113 CREATE TABLE query.from_relation (
5114         id               SERIAL        PRIMARY KEY,
5115         type             TEXT          NOT NULL CONSTRAINT relation_type CHECK (
5116                                            type IN ( 'RELATION', 'SUBQUERY', 'FUNCTION' ) ),
5117         table_name       TEXT,
5118         class_name       TEXT,
5119         subquery         INT           REFERENCES query.stored_query,
5120         function_call    INT           REFERENCES query.expression,
5121         table_alias      TEXT,
5122         parent_relation  INT           REFERENCES query.from_relation
5123                                        ON DELETE CASCADE
5124                                        DEFERRABLE INITIALLY DEFERRED,
5125         seq_no           INT           NOT NULL DEFAULT 1,
5126         join_type        TEXT          CONSTRAINT good_join_type CHECK (
5127                                            join_type IS NULL OR join_type IN
5128                                            ( 'INNER', 'LEFT', 'RIGHT', 'FULL' )
5129                                        ),
5130         on_clause        INT           REFERENCES query.expression
5131                                        DEFERRABLE INITIALLY DEFERRED,
5132         CONSTRAINT join_or_core CHECK (
5133         ( parent_relation IS NULL AND join_type IS NULL
5134           AND on_clause IS NULL )
5135         OR
5136         ( parent_relation IS NOT NULL AND join_type IS NOT NULL
5137           AND on_clause IS NOT NULL )
5138         )
5139 );
5140
5141 CREATE UNIQUE INDEX from_parent_seq
5142         ON query.from_relation( parent_relation, seq_no )
5143         WHERE parent_relation IS NOT NULL;
5144
5145 -- The following foreign key had to be deferred until
5146 -- query.from_relation existed
5147
5148 ALTER TABLE query.stored_query
5149         ADD FOREIGN KEY (from_clause)
5150         REFERENCES query.from_relation
5151         DEFERRABLE INITIALLY DEFERRED;
5152
5153 CREATE TABLE query.record_column (
5154         id            SERIAL            PRIMARY KEY,
5155         from_relation INT               NOT NULL REFERENCES query.from_relation
5156                                         ON DELETE CASCADE
5157                                         DEFERRABLE INITIALLY DEFERRED,
5158         seq_no        INT               NOT NULL,
5159         column_name   TEXT              NOT NULL,
5160         column_type   INT               NOT NULL REFERENCES query.datatype
5161                                         ON DELETE CASCADE
5162                                                                         DEFERRABLE INITIALLY DEFERRED,
5163         CONSTRAINT column_sequence UNIQUE (from_relation, seq_no)
5164 );
5165
5166 CREATE TABLE query.select_item (
5167         id               SERIAL         PRIMARY KEY,
5168         stored_query     INT            NOT NULL REFERENCES query.stored_query
5169                                         ON DELETE CASCADE
5170                                         DEFERRABLE INITIALLY DEFERRED,
5171         seq_no           INT            NOT NULL,
5172         expression       INT            NOT NULL REFERENCES query.expression
5173                                         DEFERRABLE INITIALLY DEFERRED,
5174         column_alias     TEXT,
5175         grouped_by       BOOL           NOT NULL DEFAULT FALSE,
5176         CONSTRAINT select_sequence UNIQUE( stored_query, seq_no )
5177 );
5178
5179 CREATE TABLE query.order_by_item (
5180         id               SERIAL         PRIMARY KEY,
5181         stored_query     INT            NOT NULL REFERENCES query.stored_query
5182                                         ON DELETE CASCADE
5183                                         DEFERRABLE INITIALLY DEFERRED,
5184         seq_no           INT            NOT NULL,
5185         expression       INT            NOT NULL REFERENCES query.expression
5186                                         ON DELETE CASCADE
5187                                         DEFERRABLE INITIALLY DEFERRED,
5188         CONSTRAINT order_by_sequence UNIQUE( stored_query, seq_no )
5189 );
5190
5191 ------------------------------------------------------------
5192 -- Create updatable views for different kinds of expressions
5193 ------------------------------------------------------------
5194
5195 -- Create updatable view for BETWEEN expressions
5196
5197 CREATE OR REPLACE VIEW query.expr_xbet AS
5198     SELECT
5199                 id,
5200                 parenthesize,
5201                 parent_expr,
5202                 seq_no,
5203                 left_operand,
5204                 negate
5205     FROM
5206         query.expression
5207     WHERE
5208         type = 'xbet';
5209
5210 CREATE OR REPLACE RULE query_expr_xbet_insert_rule AS
5211     ON INSERT TO query.expr_xbet
5212     DO INSTEAD
5213     INSERT INTO query.expression (
5214                 id,
5215                 type,
5216                 parenthesize,
5217                 parent_expr,
5218                 seq_no,
5219                 left_operand,
5220                 negate
5221     ) VALUES (
5222         COALESCE(NEW.id, NEXTVAL('query.expression_id_seq'::REGCLASS)),
5223         'xbet',
5224         COALESCE(NEW.parenthesize, FALSE),
5225         NEW.parent_expr,
5226         COALESCE(NEW.seq_no, 1),
5227                 NEW.left_operand,
5228                 COALESCE(NEW.negate, false)
5229     );
5230
5231 CREATE OR REPLACE RULE query_expr_xbet_update_rule AS
5232     ON UPDATE TO query.expr_xbet
5233     DO INSTEAD
5234     UPDATE query.expression SET
5235         id = NEW.id,
5236         parenthesize = NEW.parenthesize,
5237         parent_expr = NEW.parent_expr,
5238         seq_no = NEW.seq_no,
5239                 left_operand = NEW.left_operand,
5240                 negate = NEW.negate
5241     WHERE
5242         id = OLD.id;
5243
5244 CREATE OR REPLACE RULE query_expr_xbet_delete_rule AS
5245     ON DELETE TO query.expr_xbet
5246     DO INSTEAD
5247     DELETE FROM query.expression WHERE id = OLD.id;
5248
5249 -- Create updatable view for bind variable expressions
5250
5251 CREATE OR REPLACE VIEW query.expr_xbind AS
5252     SELECT
5253                 id,
5254                 parenthesize,
5255                 parent_expr,
5256                 seq_no,
5257                 bind_variable
5258     FROM
5259         query.expression
5260     WHERE
5261         type = 'xbind';
5262
5263 CREATE OR REPLACE RULE query_expr_xbind_insert_rule AS
5264     ON INSERT TO query.expr_xbind
5265     DO INSTEAD
5266     INSERT INTO query.expression (
5267                 id,
5268                 type,
5269                 parenthesize,
5270                 parent_expr,
5271                 seq_no,
5272                 bind_variable
5273     ) VALUES (
5274         COALESCE(NEW.id, NEXTVAL('query.expression_id_seq'::REGCLASS)),
5275         'xbind',
5276         COALESCE(NEW.parenthesize, FALSE),
5277         NEW.parent_expr,
5278         COALESCE(NEW.seq_no, 1),
5279                 NEW.bind_variable
5280     );
5281
5282 CREATE OR REPLACE RULE query_expr_xbind_update_rule AS
5283     ON UPDATE TO query.expr_xbind
5284     DO INSTEAD
5285     UPDATE query.expression SET
5286         id = NEW.id,
5287         parenthesize = NEW.parenthesize,
5288         parent_expr = NEW.parent_expr,
5289         seq_no = NEW.seq_no,
5290                 bind_variable = NEW.bind_variable
5291     WHERE
5292         id = OLD.id;
5293
5294 CREATE OR REPLACE RULE query_expr_xbind_delete_rule AS
5295     ON DELETE TO query.expr_xbind
5296     DO INSTEAD
5297     DELETE FROM query.expression WHERE id = OLD.id;
5298
5299 -- Create updatable view for boolean expressions
5300
5301 CREATE OR REPLACE VIEW query.expr_xbool AS
5302     SELECT
5303                 id,
5304                 parenthesize,
5305                 parent_expr,
5306                 seq_no,
5307                 literal,
5308                 negate
5309     FROM
5310         query.expression
5311     WHERE
5312         type = 'xbool';
5313
5314 CREATE OR REPLACE RULE query_expr_xbool_insert_rule AS
5315     ON INSERT TO query.expr_xbool
5316     DO INSTEAD
5317     INSERT INTO query.expression (
5318                 id,
5319                 type,
5320                 parenthesize,
5321                 parent_expr,
5322                 seq_no,
5323                 literal,
5324                 negate
5325     ) VALUES (
5326         COALESCE(NEW.id, NEXTVAL('query.expression_id_seq'::REGCLASS)),
5327         'xbool',
5328         COALESCE(NEW.parenthesize, FALSE),
5329         NEW.parent_expr,
5330         COALESCE(NEW.seq_no, 1),
5331         NEW.literal,
5332                 COALESCE(NEW.negate, false)
5333     );
5334
5335 CREATE OR REPLACE RULE query_expr_xbool_update_rule AS
5336     ON UPDATE TO query.expr_xbool
5337     DO INSTEAD
5338     UPDATE query.expression SET
5339         id = NEW.id,
5340         parenthesize = NEW.parenthesize,
5341         parent_expr = NEW.parent_expr,
5342         seq_no = NEW.seq_no,
5343         literal = NEW.literal,
5344                 negate = NEW.negate
5345     WHERE
5346         id = OLD.id;
5347
5348 CREATE OR REPLACE RULE query_expr_xbool_delete_rule AS
5349     ON DELETE TO query.expr_xbool
5350     DO INSTEAD
5351     DELETE FROM query.expression WHERE id = OLD.id;
5352
5353 -- Create updatable view for CASE expressions
5354
5355 CREATE OR REPLACE VIEW query.expr_xcase AS
5356     SELECT
5357                 id,
5358                 parenthesize,
5359                 parent_expr,
5360                 seq_no,
5361                 left_operand,
5362                 negate
5363     FROM
5364         query.expression
5365     WHERE
5366         type = 'xcase';
5367
5368 CREATE OR REPLACE RULE query_expr_xcase_insert_rule AS
5369     ON INSERT TO query.expr_xcase
5370     DO INSTEAD
5371     INSERT INTO query.expression (
5372                 id,
5373                 type,
5374                 parenthesize,
5375                 parent_expr,
5376                 seq_no,
5377                 left_operand,
5378                 negate
5379     ) VALUES (
5380         COALESCE(NEW.id, NEXTVAL('query.expression_id_seq'::REGCLASS)),
5381         'xcase',
5382         COALESCE(NEW.parenthesize, FALSE),
5383         NEW.parent_expr,
5384         COALESCE(NEW.seq_no, 1),
5385                 NEW.left_operand,
5386                 COALESCE(NEW.negate, false)
5387     );
5388
5389 CREATE OR REPLACE RULE query_expr_xcase_update_rule AS
5390     ON UPDATE TO query.expr_xcase
5391     DO INSTEAD
5392     UPDATE query.expression SET
5393         id = NEW.id,
5394         parenthesize = NEW.parenthesize,
5395         parent_expr = NEW.parent_expr,
5396         seq_no = NEW.seq_no,
5397                 left_operand = NEW.left_operand,
5398                 negate = NEW.negate
5399     WHERE
5400         id = OLD.id;
5401
5402 CREATE OR REPLACE RULE query_expr_xcase_delete_rule AS
5403     ON DELETE TO query.expr_xcase
5404     DO INSTEAD
5405     DELETE FROM query.expression WHERE id = OLD.id;
5406
5407 -- Create updatable view for cast expressions
5408
5409 CREATE OR REPLACE VIEW query.expr_xcast AS
5410     SELECT
5411                 id,
5412                 parenthesize,
5413                 parent_expr,
5414                 seq_no,
5415                 left_operand,
5416                 cast_type,
5417                 negate
5418     FROM
5419         query.expression
5420     WHERE
5421         type = 'xcast';
5422
5423 CREATE OR REPLACE RULE query_expr_xcast_insert_rule AS
5424     ON INSERT TO query.expr_xcast
5425     DO INSTEAD
5426     INSERT INTO query.expression (
5427         id,
5428         type,
5429         parenthesize,
5430         parent_expr,
5431         seq_no,
5432         left_operand,
5433         cast_type,
5434         negate
5435     ) VALUES (
5436         COALESCE(NEW.id, NEXTVAL('query.expression_id_seq'::REGCLASS)),
5437         'xcast',
5438         COALESCE(NEW.parenthesize, FALSE),
5439         NEW.parent_expr,
5440         COALESCE(NEW.seq_no, 1),
5441         NEW.left_operand,
5442         NEW.cast_type,
5443         COALESCE(NEW.negate, false)
5444     );
5445
5446 CREATE OR REPLACE RULE query_expr_xcast_update_rule AS
5447     ON UPDATE TO query.expr_xcast
5448     DO INSTEAD
5449     UPDATE query.expression SET
5450         id = NEW.id,
5451         parenthesize = NEW.parenthesize,
5452         parent_expr = NEW.parent_expr,
5453         seq_no = NEW.seq_no,
5454                 left_operand = NEW.left_operand,
5455                 cast_type = NEW.cast_type,
5456                 negate = NEW.negate
5457     WHERE
5458         id = OLD.id;
5459
5460 CREATE OR REPLACE RULE query_expr_xcast_delete_rule AS
5461     ON DELETE TO query.expr_xcast
5462     DO INSTEAD
5463     DELETE FROM query.expression WHERE id = OLD.id;
5464
5465 -- Create updatable view for column expressions
5466
5467 CREATE OR REPLACE VIEW query.expr_xcol AS
5468     SELECT
5469                 id,
5470                 parenthesize,
5471                 parent_expr,
5472                 seq_no,
5473                 table_alias,
5474                 column_name,
5475                 negate
5476     FROM
5477         query.expression
5478     WHERE
5479         type = 'xcol';
5480
5481 CREATE OR REPLACE RULE query_expr_xcol_insert_rule AS
5482     ON INSERT TO query.expr_xcol
5483     DO INSTEAD
5484     INSERT INTO query.expression (
5485                 id,
5486                 type,
5487                 parenthesize,
5488                 parent_expr,
5489                 seq_no,
5490                 table_alias,
5491                 column_name,
5492                 negate
5493     ) VALUES (
5494         COALESCE(NEW.id, NEXTVAL('query.expression_id_seq'::REGCLASS)),
5495         'xcol',
5496         COALESCE(NEW.parenthesize, FALSE),
5497         NEW.parent_expr,
5498         COALESCE(NEW.seq_no, 1),
5499                 NEW.table_alias,
5500                 NEW.column_name,
5501                 COALESCE(NEW.negate, false)
5502     );
5503
5504 CREATE OR REPLACE RULE query_expr_xcol_update_rule AS
5505     ON UPDATE TO query.expr_xcol
5506     DO INSTEAD
5507     UPDATE query.expression SET
5508         id = NEW.id,
5509         parenthesize = NEW.parenthesize,
5510         parent_expr = NEW.parent_expr,
5511         seq_no = NEW.seq_no,
5512                 table_alias = NEW.table_alias,
5513                 column_name = NEW.column_name,
5514                 negate = NEW.negate
5515     WHERE
5516         id = OLD.id;
5517
5518 CREATE OR REPLACE RULE query_expr_xcol_delete_rule AS
5519     ON DELETE TO query.expr_xcol
5520     DO INSTEAD
5521     DELETE FROM query.expression WHERE id = OLD.id;
5522
5523 -- Create updatable view for EXISTS expressions
5524
5525 CREATE OR REPLACE VIEW query.expr_xex AS
5526     SELECT
5527                 id,
5528                 parenthesize,
5529                 parent_expr,
5530                 seq_no,
5531                 subquery,
5532                 negate
5533     FROM
5534         query.expression
5535     WHERE
5536         type = 'xex';
5537
5538 CREATE OR REPLACE RULE query_expr_xex_insert_rule AS
5539     ON INSERT TO query.expr_xex
5540     DO INSTEAD
5541     INSERT INTO query.expression (
5542                 id,
5543                 type,
5544                 parenthesize,
5545                 parent_expr,
5546                 seq_no,
5547                 subquery,
5548                 negate
5549     ) VALUES (
5550         COALESCE(NEW.id, NEXTVAL('query.expression_id_seq'::REGCLASS)),
5551         'xex',
5552         COALESCE(NEW.parenthesize, FALSE),
5553         NEW.parent_expr,
5554         COALESCE(NEW.seq_no, 1),
5555                 NEW.subquery,
5556                 COALESCE(NEW.negate, false)
5557     );
5558
5559 CREATE OR REPLACE RULE query_expr_xex_update_rule AS
5560     ON UPDATE TO query.expr_xex
5561     DO INSTEAD
5562     UPDATE query.expression SET
5563         id = NEW.id,
5564         parenthesize = NEW.parenthesize,
5565         parent_expr = NEW.parent_expr,
5566         seq_no = NEW.seq_no,
5567                 subquery = NEW.subquery,
5568                 negate = NEW.negate
5569     WHERE
5570         id = OLD.id;
5571
5572 CREATE OR REPLACE RULE query_expr_xex_delete_rule AS
5573     ON DELETE TO query.expr_xex
5574     DO INSTEAD
5575     DELETE FROM query.expression WHERE id = OLD.id;
5576
5577 -- Create updatable view for function call expressions
5578
5579 CREATE OR REPLACE VIEW query.expr_xfunc AS
5580     SELECT
5581         id,
5582         parenthesize,
5583         parent_expr,
5584         seq_no,
5585         column_name,
5586         function_id,
5587         negate
5588     FROM
5589         query.expression
5590     WHERE
5591         type = 'xfunc';
5592
5593 CREATE OR REPLACE RULE query_expr_xfunc_insert_rule AS
5594     ON INSERT TO query.expr_xfunc
5595     DO INSTEAD
5596     INSERT INTO query.expression (
5597         id,
5598         type,
5599         parenthesize,
5600         parent_expr,
5601         seq_no,
5602         column_name,
5603         function_id,
5604         negate
5605     ) VALUES (
5606         COALESCE(NEW.id, NEXTVAL('query.expression_id_seq'::REGCLASS)),
5607         'xfunc',
5608         COALESCE(NEW.parenthesize, FALSE),
5609         NEW.parent_expr,
5610         COALESCE(NEW.seq_no, 1),
5611         NEW.column_name,
5612         NEW.function_id,
5613         COALESCE(NEW.negate, false)
5614     );
5615
5616 CREATE OR REPLACE RULE query_expr_xfunc_update_rule AS
5617     ON UPDATE TO query.expr_xfunc
5618     DO INSTEAD
5619     UPDATE query.expression SET
5620         id = NEW.id,
5621         parenthesize = NEW.parenthesize,
5622         parent_expr = NEW.parent_expr,
5623         seq_no = NEW.seq_no,
5624         column_name = NEW.column_name,
5625         function_id = NEW.function_id,
5626         negate = NEW.negate
5627     WHERE
5628         id = OLD.id;
5629
5630 CREATE OR REPLACE RULE query_expr_xfunc_delete_rule AS
5631     ON DELETE TO query.expr_xfunc
5632     DO INSTEAD
5633     DELETE FROM query.expression WHERE id = OLD.id;
5634
5635 -- Create updatable view for IN expressions
5636
5637 CREATE OR REPLACE VIEW query.expr_xin AS
5638     SELECT
5639                 id,
5640                 parenthesize,
5641                 parent_expr,
5642                 seq_no,
5643                 left_operand,
5644                 subquery,
5645                 negate
5646     FROM
5647         query.expression
5648     WHERE
5649         type = 'xin';
5650
5651 CREATE OR REPLACE RULE query_expr_xin_insert_rule AS
5652     ON INSERT TO query.expr_xin
5653     DO INSTEAD
5654     INSERT INTO query.expression (
5655                 id,
5656                 type,
5657                 parenthesize,
5658                 parent_expr,
5659                 seq_no,
5660                 left_operand,
5661                 subquery,
5662                 negate
5663     ) VALUES (
5664         COALESCE(NEW.id, NEXTVAL('query.expression_id_seq'::REGCLASS)),
5665         'xin',
5666         COALESCE(NEW.parenthesize, FALSE),
5667         NEW.parent_expr,
5668         COALESCE(NEW.seq_no, 1),
5669                 NEW.left_operand,
5670                 NEW.subquery,
5671                 COALESCE(NEW.negate, false)
5672     );
5673
5674 CREATE OR REPLACE RULE query_expr_xin_update_rule AS
5675     ON UPDATE TO query.expr_xin
5676     DO INSTEAD
5677     UPDATE query.expression SET
5678         id = NEW.id,
5679         parenthesize = NEW.parenthesize,
5680         parent_expr = NEW.parent_expr,
5681         seq_no = NEW.seq_no,
5682                 left_operand = NEW.left_operand,
5683                 subquery = NEW.subquery,
5684                 negate = NEW.negate
5685     WHERE
5686         id = OLD.id;
5687
5688 CREATE OR REPLACE RULE query_expr_xin_delete_rule AS
5689     ON DELETE TO query.expr_xin
5690     DO INSTEAD
5691     DELETE FROM query.expression WHERE id = OLD.id;
5692
5693 -- Create updatable view for IS NULL expressions
5694
5695 CREATE OR REPLACE VIEW query.expr_xisnull AS
5696     SELECT
5697                 id,
5698                 parenthesize,
5699                 parent_expr,
5700                 seq_no,
5701                 left_operand,
5702                 negate
5703     FROM
5704         query.expression
5705     WHERE
5706         type = 'xisnull';
5707
5708 CREATE OR REPLACE RULE query_expr_xisnull_insert_rule AS
5709     ON INSERT TO query.expr_xisnull
5710     DO INSTEAD
5711     INSERT INTO query.expression (
5712                 id,
5713                 type,
5714                 parenthesize,
5715                 parent_expr,
5716                 seq_no,
5717                 left_operand,
5718                 negate
5719     ) VALUES (
5720         COALESCE(NEW.id, NEXTVAL('query.expression_id_seq'::REGCLASS)),
5721         'xisnull',
5722         COALESCE(NEW.parenthesize, FALSE),
5723         NEW.parent_expr,
5724         COALESCE(NEW.seq_no, 1),
5725                 NEW.left_operand,
5726                 COALESCE(NEW.negate, false)
5727     );
5728
5729 CREATE OR REPLACE RULE query_expr_xisnull_update_rule AS
5730     ON UPDATE TO query.expr_xisnull
5731     DO INSTEAD
5732     UPDATE query.expression SET
5733         id = NEW.id,
5734         parenthesize = NEW.parenthesize,
5735         parent_expr = NEW.parent_expr,
5736         seq_no = NEW.seq_no,
5737                 left_operand = NEW.left_operand,
5738                 negate = NEW.negate
5739     WHERE
5740         id = OLD.id;
5741
5742 CREATE OR REPLACE RULE query_expr_xisnull_delete_rule AS
5743     ON DELETE TO query.expr_xisnull
5744     DO INSTEAD
5745     DELETE FROM query.expression WHERE id = OLD.id;
5746
5747 -- Create updatable view for NULL expressions
5748
5749 CREATE OR REPLACE VIEW query.expr_xnull AS
5750     SELECT
5751                 id,
5752                 parenthesize,
5753                 parent_expr,
5754                 seq_no,
5755                 negate
5756     FROM
5757         query.expression
5758     WHERE
5759         type = 'xnull';
5760
5761 CREATE OR REPLACE RULE query_expr_xnull_insert_rule AS
5762     ON INSERT TO query.expr_xnull
5763     DO INSTEAD
5764     INSERT INTO query.expression (
5765                 id,
5766                 type,
5767                 parenthesize,
5768                 parent_expr,
5769                 seq_no,
5770                 negate
5771     ) VALUES (
5772         COALESCE(NEW.id, NEXTVAL('query.expression_id_seq'::REGCLASS)),
5773         'xnull',
5774         COALESCE(NEW.parenthesize, FALSE),
5775         NEW.parent_expr,
5776         COALESCE(NEW.seq_no, 1),
5777                 COALESCE(NEW.negate, false)
5778     );
5779
5780 CREATE OR REPLACE RULE query_expr_xnull_update_rule AS
5781     ON UPDATE TO query.expr_xnull
5782     DO INSTEAD
5783     UPDATE query.expression SET
5784         id = NEW.id,
5785         parenthesize = NEW.parenthesize,
5786         parent_expr = NEW.parent_expr,
5787         seq_no = NEW.seq_no,
5788                 negate = NEW.negate
5789     WHERE
5790         id = OLD.id;
5791
5792 CREATE OR REPLACE RULE query_expr_xnull_delete_rule AS
5793     ON DELETE TO query.expr_xnull
5794     DO INSTEAD
5795     DELETE FROM query.expression WHERE id = OLD.id;
5796
5797 -- Create updatable view for numeric literal expressions
5798
5799 CREATE OR REPLACE VIEW query.expr_xnum AS
5800     SELECT
5801                 id,
5802                 parenthesize,
5803                 parent_expr,
5804                 seq_no,
5805                 literal
5806     FROM
5807         query.expression
5808     WHERE
5809         type = 'xnum';
5810
5811 CREATE OR REPLACE RULE query_expr_xnum_insert_rule AS
5812     ON INSERT TO query.expr_xnum
5813     DO INSTEAD
5814     INSERT INTO query.expression (
5815                 id,
5816                 type,
5817                 parenthesize,
5818                 parent_expr,
5819                 seq_no,
5820                 literal
5821     ) VALUES (
5822         COALESCE(NEW.id, NEXTVAL('query.expression_id_seq'::REGCLASS)),
5823         'xnum',
5824         COALESCE(NEW.parenthesize, FALSE),
5825         NEW.parent_expr,
5826         COALESCE(NEW.seq_no, 1),
5827         NEW.literal
5828     );
5829
5830 CREATE OR REPLACE RULE query_expr_xnum_update_rule AS
5831     ON UPDATE TO query.expr_xnum
5832     DO INSTEAD
5833     UPDATE query.expression SET
5834         id = NEW.id,
5835         parenthesize = NEW.parenthesize,
5836         parent_expr = NEW.parent_expr,
5837         seq_no = NEW.seq_no,
5838         literal = NEW.literal
5839     WHERE
5840         id = OLD.id;
5841
5842 CREATE OR REPLACE RULE query_expr_xnum_delete_rule AS
5843     ON DELETE TO query.expr_xnum
5844     DO INSTEAD
5845     DELETE FROM query.expression WHERE id = OLD.id;
5846
5847 -- Create updatable view for operator expressions
5848
5849 CREATE OR REPLACE VIEW query.expr_xop AS
5850     SELECT
5851                 id,
5852                 parenthesize,
5853                 parent_expr,
5854                 seq_no,
5855                 left_operand,
5856                 operator,
5857                 right_operand,
5858                 negate
5859     FROM
5860         query.expression
5861     WHERE
5862         type = 'xop';
5863
5864 CREATE OR REPLACE RULE query_expr_xop_insert_rule AS
5865     ON INSERT TO query.expr_xop
5866     DO INSTEAD
5867     INSERT INTO query.expression (
5868                 id,
5869                 type,
5870                 parenthesize,
5871                 parent_expr,
5872                 seq_no,
5873                 left_operand,
5874                 operator,
5875                 right_operand,
5876                 negate
5877     ) VALUES (
5878         COALESCE(NEW.id, NEXTVAL('query.expression_id_seq'::REGCLASS)),
5879         'xop',
5880         COALESCE(NEW.parenthesize, FALSE),
5881         NEW.parent_expr,
5882         COALESCE(NEW.seq_no, 1),
5883                 NEW.left_operand,
5884                 NEW.operator,
5885                 NEW.right_operand,
5886                 COALESCE(NEW.negate, false)
5887     );
5888
5889 CREATE OR REPLACE RULE query_expr_xop_update_rule AS
5890     ON UPDATE TO query.expr_xop
5891     DO INSTEAD
5892     UPDATE query.expression SET
5893         id = NEW.id,
5894         parenthesize = NEW.parenthesize,
5895         parent_expr = NEW.parent_expr,
5896         seq_no = NEW.seq_no,
5897                 left_operand = NEW.left_operand,
5898                 operator = NEW.operator,
5899                 right_operand = NEW.right_operand,
5900                 negate = NEW.negate
5901     WHERE
5902         id = OLD.id;
5903
5904 CREATE OR REPLACE RULE query_expr_xop_delete_rule AS
5905     ON DELETE TO query.expr_xop
5906     DO INSTEAD
5907     DELETE FROM query.expression WHERE id = OLD.id;
5908
5909 -- Create updatable view for series expressions
5910 -- i.e. series of expressions separated by operators
5911
5912 CREATE OR REPLACE VIEW query.expr_xser AS
5913     SELECT
5914                 id,
5915                 parenthesize,
5916                 parent_expr,
5917                 seq_no,
5918                 operator,
5919                 negate
5920     FROM
5921         query.expression
5922     WHERE
5923         type = 'xser';
5924
5925 CREATE OR REPLACE RULE query_expr_xser_insert_rule AS
5926     ON INSERT TO query.expr_xser
5927     DO INSTEAD
5928     INSERT INTO query.expression (
5929                 id,
5930                 type,
5931                 parenthesize,
5932                 parent_expr,
5933                 seq_no,
5934                 operator,
5935                 negate
5936     ) VALUES (
5937         COALESCE(NEW.id, NEXTVAL('query.expression_id_seq'::REGCLASS)),
5938         'xser',
5939         COALESCE(NEW.parenthesize, FALSE),
5940         NEW.parent_expr,
5941         COALESCE(NEW.seq_no, 1),
5942                 NEW.operator,
5943                 COALESCE(NEW.negate, false)
5944     );
5945
5946 CREATE OR REPLACE RULE query_expr_xser_update_rule AS
5947     ON UPDATE TO query.expr_xser
5948     DO INSTEAD
5949     UPDATE query.expression SET
5950         id = NEW.id,
5951         parenthesize = NEW.parenthesize,
5952         parent_expr = NEW.parent_expr,
5953         seq_no = NEW.seq_no,
5954                 operator = NEW.operator,
5955                 negate = NEW.negate
5956     WHERE
5957         id = OLD.id;
5958
5959 CREATE OR REPLACE RULE query_expr_xser_delete_rule AS
5960     ON DELETE TO query.expr_xser
5961     DO INSTEAD
5962     DELETE FROM query.expression WHERE id = OLD.id;
5963
5964 -- Create updatable view for string literal expressions
5965
5966 CREATE OR REPLACE VIEW query.expr_xstr AS
5967     SELECT
5968         id,
5969         parenthesize,
5970         parent_expr,
5971         seq_no,
5972         literal
5973     FROM
5974         query.expression
5975     WHERE
5976         type = 'xstr';
5977
5978 CREATE OR REPLACE RULE query_expr_string_insert_rule AS
5979     ON INSERT TO query.expr_xstr
5980     DO INSTEAD
5981     INSERT INTO query.expression (
5982         id,
5983         type,
5984         parenthesize,
5985         parent_expr,
5986         seq_no,
5987         literal
5988     ) VALUES (
5989         COALESCE(NEW.id, NEXTVAL('query.expression_id_seq'::REGCLASS)),
5990         'xstr',
5991         COALESCE(NEW.parenthesize, FALSE),
5992         NEW.parent_expr,
5993         COALESCE(NEW.seq_no, 1),
5994         NEW.literal
5995     );
5996
5997 CREATE OR REPLACE RULE query_expr_string_update_rule AS
5998     ON UPDATE TO query.expr_xstr
5999     DO INSTEAD
6000     UPDATE query.expression SET
6001         id = NEW.id,
6002         parenthesize = NEW.parenthesize,
6003         parent_expr = NEW.parent_expr,
6004         seq_no = NEW.seq_no,
6005         literal = NEW.literal
6006     WHERE
6007         id = OLD.id;
6008
6009 CREATE OR REPLACE RULE query_expr_string_delete_rule AS
6010     ON DELETE TO query.expr_xstr
6011     DO INSTEAD
6012     DELETE FROM query.expression WHERE id = OLD.id;
6013
6014 -- Create updatable view for subquery expressions
6015
6016 CREATE OR REPLACE VIEW query.expr_xsubq AS
6017     SELECT
6018                 id,
6019                 parenthesize,
6020                 parent_expr,
6021                 seq_no,
6022                 subquery,
6023                 negate
6024     FROM
6025         query.expression
6026     WHERE
6027         type = 'xsubq';
6028
6029 CREATE OR REPLACE RULE query_expr_xsubq_insert_rule AS
6030     ON INSERT TO query.expr_xsubq
6031     DO INSTEAD
6032     INSERT INTO query.expression (
6033                 id,
6034                 type,
6035                 parenthesize,
6036                 parent_expr,
6037                 seq_no,
6038                 subquery,
6039                 negate
6040     ) VALUES (
6041         COALESCE(NEW.id, NEXTVAL('query.expression_id_seq'::REGCLASS)),
6042         'xsubq',
6043         COALESCE(NEW.parenthesize, FALSE),
6044         NEW.parent_expr,
6045         COALESCE(NEW.seq_no, 1),
6046                 NEW.subquery,
6047                 COALESCE(NEW.negate, false)
6048     );
6049
6050 CREATE OR REPLACE RULE query_expr_xsubq_update_rule AS
6051     ON UPDATE TO query.expr_xsubq
6052     DO INSTEAD
6053     UPDATE query.expression SET
6054         id = NEW.id,
6055         parenthesize = NEW.parenthesize,
6056         parent_expr = NEW.parent_expr,
6057         seq_no = NEW.seq_no,
6058                 subquery = NEW.subquery,
6059                 negate = NEW.negate
6060     WHERE
6061         id = OLD.id;
6062
6063 CREATE OR REPLACE RULE query_expr_xsubq_delete_rule AS
6064     ON DELETE TO query.expr_xsubq
6065     DO INSTEAD
6066     DELETE FROM query.expression WHERE id = OLD.id;
6067
6068 CREATE TABLE action.fieldset (
6069     id              SERIAL          PRIMARY KEY,
6070     owner           INT             NOT NULL REFERENCES actor.usr (id)
6071                                     DEFERRABLE INITIALLY DEFERRED,
6072     owning_lib      INT             NOT NULL REFERENCES actor.org_unit (id)
6073                                     DEFERRABLE INITIALLY DEFERRED,
6074     status          TEXT            NOT NULL
6075                                     CONSTRAINT valid_status CHECK ( status in
6076                                     ( 'PENDING', 'APPLIED', 'ERROR' )),
6077     creation_time   TIMESTAMPTZ     NOT NULL DEFAULT NOW(),
6078     scheduled_time  TIMESTAMPTZ,
6079     applied_time    TIMESTAMPTZ,
6080     classname       TEXT            NOT NULL, -- an IDL class name
6081     name            TEXT            NOT NULL,
6082     stored_query    INT             REFERENCES query.stored_query (id)
6083                                     DEFERRABLE INITIALLY DEFERRED,
6084     pkey_value      TEXT,
6085     CONSTRAINT lib_name_unique UNIQUE (owning_lib, name),
6086     CONSTRAINT fieldset_one_or_the_other CHECK (
6087         (stored_query IS NOT NULL AND pkey_value IS NULL) OR
6088         (pkey_value IS NOT NULL AND stored_query IS NULL)
6089     )
6090     -- the CHECK constraint means we can update the fields for a single
6091     -- row without all the extra overhead involved in a query
6092 );
6093
6094 CREATE INDEX action_fieldset_sched_time_idx ON action.fieldset( scheduled_time );
6095 CREATE INDEX action_owner_idx               ON action.fieldset( owner );
6096
6097 CREATE TABLE action.fieldset_col_val (
6098     id              SERIAL  PRIMARY KEY,
6099     fieldset        INT     NOT NULL REFERENCES action.fieldset
6100                                          ON DELETE CASCADE
6101                                          DEFERRABLE INITIALLY DEFERRED,
6102     col             TEXT    NOT NULL,  -- "field" from the idl ... the column on the table
6103     val             TEXT,              -- value for the column ... NULL means, well, NULL
6104     CONSTRAINT fieldset_col_once_per_set UNIQUE (fieldset, col)
6105 );
6106
6107 CREATE OR REPLACE FUNCTION action.apply_fieldset(
6108         fieldset_id IN INT,        -- id from action.fieldset
6109         table_name  IN TEXT,       -- table to be updated
6110         pkey_name   IN TEXT,       -- name of primary key column in that table
6111         query       IN TEXT        -- query constructed by qstore (for query-based
6112                                    --    fieldsets only; otherwise null
6113 )
6114 RETURNS TEXT AS $$
6115 DECLARE
6116         statement TEXT;
6117         fs_status TEXT;
6118         fs_pkey_value TEXT;
6119         fs_query TEXT;
6120         sep CHAR;
6121         status_code TEXT;
6122         msg TEXT;
6123         update_count INT;
6124         cv RECORD;
6125 BEGIN
6126         -- Sanity checks
6127         IF fieldset_id IS NULL THEN
6128                 RETURN 'Fieldset ID parameter is NULL';
6129         END IF;
6130         IF table_name IS NULL THEN
6131                 RETURN 'Table name parameter is NULL';
6132         END IF;
6133         IF pkey_name IS NULL THEN
6134                 RETURN 'Primary key name parameter is NULL';
6135         END IF;
6136         --
6137         statement := 'UPDATE ' || table_name || ' SET';
6138         --
6139         SELECT
6140                 status,
6141                 quote_literal( pkey_value )
6142         INTO
6143                 fs_status,
6144                 fs_pkey_value
6145         FROM
6146                 action.fieldset
6147         WHERE
6148                 id = fieldset_id;
6149         --
6150         IF fs_status IS NULL THEN
6151                 RETURN 'No fieldset found for id = ' || fieldset_id;
6152         ELSIF fs_status = 'APPLIED' THEN
6153                 RETURN 'Fieldset ' || fieldset_id || ' has already been applied';
6154         END IF;
6155         --
6156         sep := '';
6157         FOR cv IN
6158                 SELECT  col,
6159                                 val
6160                 FROM    action.fieldset_col_val
6161                 WHERE   fieldset = fieldset_id
6162         LOOP
6163                 statement := statement || sep || ' ' || cv.col
6164                                          || ' = ' || coalesce( quote_literal( cv.val ), 'NULL' );
6165                 sep := ',';
6166         END LOOP;
6167         --
6168         IF sep = '' THEN
6169                 RETURN 'Fieldset ' || fieldset_id || ' has no column values defined';
6170         END IF;
6171         --
6172         -- Add the WHERE clause.  This differs according to whether it's a
6173         -- single-row fieldset or a query-based fieldset.
6174         --
6175         IF query IS NULL        AND fs_pkey_value IS NULL THEN
6176                 RETURN 'Incomplete fieldset: neither a primary key nor a query available';
6177         ELSIF query IS NOT NULL AND fs_pkey_value IS NULL THEN
6178             fs_query := rtrim( query, ';' );
6179             statement := statement || ' WHERE ' || pkey_name || ' IN ( '
6180                          || fs_query || ' );';
6181         ELSIF query IS NULL     AND fs_pkey_value IS NOT NULL THEN
6182                 statement := statement || ' WHERE ' || pkey_name || ' = '
6183                                      || fs_pkey_value || ';';
6184         ELSE  -- both are not null
6185                 RETURN 'Ambiguous fieldset: both a primary key and a query provided';
6186         END IF;
6187         --
6188         -- Execute the update
6189         --
6190         BEGIN
6191                 EXECUTE statement;
6192                 GET DIAGNOSTICS update_count = ROW_COUNT;
6193                 --
6194                 IF UPDATE_COUNT > 0 THEN
6195                         status_code := 'APPLIED';
6196                         msg := NULL;
6197                 ELSE
6198                         status_code := 'ERROR';
6199                         msg := 'No eligible rows found for fieldset ' || fieldset_id;
6200         END IF;
6201         EXCEPTION WHEN OTHERS THEN
6202                 status_code := 'ERROR';
6203                 msg := 'Unable to apply fieldset ' || fieldset_id
6204                            || ': ' || sqlerrm;
6205         END;
6206         --
6207         -- Update fieldset status
6208         --
6209         UPDATE action.fieldset
6210         SET status       = status_code,
6211             applied_time = now()
6212         WHERE id = fieldset_id;
6213         --
6214         RETURN msg;
6215 END;
6216 $$ LANGUAGE plpgsql;
6217
6218 COMMENT ON FUNCTION action.apply_fieldset( INT, TEXT, TEXT, TEXT ) IS $$
6219 /**
6220  * Applies a specified fieldset, using a supplied table name and primary
6221  * key name.  The query parameter should be non-null only for
6222  * query-based fieldsets.
6223  *
6224  * Returns NULL if successful, or an error message if not.
6225  */
6226 $$;
6227
6228 CREATE INDEX uhr_hold_idx ON action.unfulfilled_hold_list (hold);
6229
6230 CREATE OR REPLACE VIEW action.unfulfilled_hold_loops AS
6231     SELECT  u.hold,
6232             c.circ_lib,
6233             count(*)
6234       FROM  action.unfulfilled_hold_list u
6235             JOIN asset.copy c ON (c.id = u.current_copy)
6236       GROUP BY 1,2;
6237
6238 CREATE OR REPLACE VIEW action.unfulfilled_hold_min_loop AS
6239     SELECT  hold,
6240             min(count)
6241       FROM  action.unfulfilled_hold_loops
6242       GROUP BY 1;
6243
6244 CREATE OR REPLACE VIEW action.unfulfilled_hold_innermost_loop AS
6245     SELECT  DISTINCT l.*
6246       FROM  action.unfulfilled_hold_loops l
6247             JOIN action.unfulfilled_hold_min_loop m USING (hold)
6248       WHERE l.count = m.min;
6249
6250 ALTER TABLE asset.copy
6251 ADD COLUMN dummy_isbn TEXT;
6252
6253 ALTER TABLE auditor.asset_copy_history
6254 ADD COLUMN dummy_isbn TEXT;
6255
6256 -- Add new column status_changed_date to asset.copy, with trigger to maintain it
6257 -- Add corresponding new column to auditor.asset_copy_history
6258
6259 ALTER TABLE asset.copy
6260         ADD COLUMN status_changed_time TIMESTAMPTZ;
6261
6262 ALTER TABLE auditor.asset_copy_history
6263         ADD COLUMN status_changed_time TIMESTAMPTZ;
6264
6265 CREATE OR REPLACE FUNCTION asset.acp_status_changed()
6266 RETURNS TRIGGER AS $$
6267 BEGIN
6268     IF NEW.status <> OLD.status THEN
6269         NEW.status_changed_time := now();
6270     END IF;
6271     RETURN NEW;
6272 END;
6273 $$ LANGUAGE plpgsql;
6274
6275 CREATE TRIGGER acp_status_changed_trig
6276         BEFORE UPDATE ON asset.copy
6277         FOR EACH ROW EXECUTE PROCEDURE asset.acp_status_changed();
6278
6279 ALTER TABLE asset.copy
6280 ADD COLUMN mint_condition boolean NOT NULL DEFAULT TRUE;
6281
6282 ALTER TABLE auditor.asset_copy_history
6283 ADD COLUMN mint_condition boolean NOT NULL DEFAULT TRUE;
6284
6285 ALTER TABLE asset.copy ADD COLUMN floating BOOL NOT NULL DEFAULT FALSE;
6286 ALTER TABLE auditor.asset_copy_history ADD COLUMN floating BOOL;
6287
6288 DROP INDEX IF EXISTS asset.copy_barcode_key;
6289 CREATE UNIQUE INDEX copy_barcode_key ON asset.copy (barcode) WHERE deleted = FALSE OR deleted IS FALSE;
6290
6291 -- Note: later we create a trigger a_opac_vis_mat_view_tgr
6292 -- AFTER INSERT OR UPDATE ON asset.copy
6293
6294 ALTER TABLE asset.copy ADD COLUMN cost NUMERIC(8,2);
6295 ALTER TABLE auditor.asset_copy_history ADD COLUMN cost NUMERIC(8,2);
6296
6297 -- Moke mostly parallel changes to action.circulation
6298 -- and action.aged_circulation
6299
6300 ALTER TABLE action.circulation
6301 ADD COLUMN workstation INT
6302     REFERENCES actor.workstation
6303         ON DELETE SET NULL
6304         DEFERRABLE INITIALLY DEFERRED;
6305
6306 ALTER TABLE action.aged_circulation
6307 ADD COLUMN workstation INT;
6308
6309 ALTER TABLE action.circulation
6310 ADD COLUMN parent_circ BIGINT
6311         REFERENCES action.circulation(id)
6312         DEFERRABLE INITIALLY DEFERRED;
6313
6314 CREATE UNIQUE INDEX circ_parent_idx
6315 ON action.circulation( parent_circ )
6316 WHERE parent_circ IS NOT NULL;
6317
6318 ALTER TABLE action.aged_circulation
6319 ADD COLUMN parent_circ BIGINT;
6320
6321 ALTER TABLE action.circulation
6322 ADD COLUMN checkin_workstation INT
6323         REFERENCES actor.workstation(id)
6324         ON DELETE SET NULL
6325         DEFERRABLE INITIALLY DEFERRED;
6326
6327 ALTER TABLE action.aged_circulation
6328 ADD COLUMN checkin_workstation INT;
6329
6330 ALTER TABLE action.circulation
6331 ADD COLUMN checkin_scan_time TIMESTAMPTZ;
6332
6333 ALTER TABLE action.aged_circulation
6334 ADD COLUMN checkin_scan_time TIMESTAMPTZ;
6335
6336 CREATE INDEX action_circulation_target_copy_idx
6337 ON action.circulation (target_copy);
6338
6339 CREATE INDEX action_aged_circulation_target_copy_idx
6340 ON action.aged_circulation (target_copy);
6341
6342 ALTER TABLE action.circulation
6343 DROP CONSTRAINT circulation_stop_fines_check;
6344
6345 ALTER TABLE action.circulation
6346         ADD CONSTRAINT circulation_stop_fines_check
6347         CHECK (stop_fines IN (
6348         'CHECKIN','CLAIMSRETURNED','LOST','MAXFINES','RENEW','LONGOVERDUE','CLAIMSNEVERCHECKEDOUT'));
6349
6350 -- Hard due-date functionality
6351 CREATE TABLE config.hard_due_date (
6352         id          SERIAL      PRIMARY KEY,
6353         name        TEXT        NOT NULL UNIQUE CHECK ( name ~ E'^\\w+$' ),
6354         ceiling_date    TIMESTAMPTZ NOT NULL,
6355         forceto     BOOL        NOT NULL,
6356         owner       INT         NOT NULL
6357 );
6358
6359 CREATE TABLE config.hard_due_date_values (
6360     id                  SERIAL      PRIMARY KEY,
6361     hard_due_date       INT         NOT NULL REFERENCES config.hard_due_date (id)
6362                                     DEFERRABLE INITIALLY DEFERRED,
6363     ceiling_date        TIMESTAMPTZ NOT NULL,
6364     active_date         TIMESTAMPTZ NOT NULL
6365 );
6366
6367 ALTER TABLE config.circ_matrix_matchpoint ADD COLUMN hard_due_date INT REFERENCES config.hard_due_date (id);
6368
6369 CREATE OR REPLACE FUNCTION config.update_hard_due_dates () RETURNS INT AS $func$
6370 DECLARE
6371     temp_value  config.hard_due_date_values%ROWTYPE;
6372     updated     INT := 0;
6373 BEGIN
6374     FOR temp_value IN
6375       SELECT  DISTINCT ON (hard_due_date) *
6376         FROM  config.hard_due_date_values
6377         WHERE active_date <= NOW() -- We've passed (or are at) the rollover time
6378         ORDER BY active_date DESC -- Latest (nearest to us) active time
6379    LOOP
6380         UPDATE  config.hard_due_date
6381           SET   ceiling_date = temp_value.ceiling_date
6382           WHERE id = temp_value.hard_due_date
6383                 AND ceiling_date <> temp_value.ceiling_date; -- Time is equal if we've already updated the chdd
6384
6385         IF FOUND THEN
6386             updated := updated + 1;
6387         END IF;
6388     END LOOP;
6389
6390     RETURN updated;
6391 END;
6392 $func$ LANGUAGE plpgsql;
6393
6394 -- Correct some long-standing misspellings involving variations of "recur"
6395
6396 ALTER TABLE action.circulation RENAME COLUMN recuring_fine TO recurring_fine;
6397 ALTER TABLE action.circulation RENAME COLUMN recuring_fine_rule TO recurring_fine_rule;
6398
6399 ALTER TABLE action.aged_circulation RENAME COLUMN recuring_fine TO recurring_fine;
6400 ALTER TABLE action.aged_circulation RENAME COLUMN recuring_fine_rule TO recurring_fine_rule;
6401
6402 ALTER TABLE config.rule_recuring_fine RENAME TO rule_recurring_fine;
6403 ALTER TABLE config.rule_recuring_fine_id_seq RENAME TO rule_recurring_fine_id_seq;
6404
6405 ALTER TABLE config.rule_recurring_fine RENAME COLUMN recurance_interval TO recurrence_interval;
6406
6407 -- Might as well keep the comment in sync as well
6408 COMMENT ON TABLE config.rule_recurring_fine IS $$
6409 /*
6410  * Copyright (C) 2005  Georgia Public Library Service 
6411  * Mike Rylander <mrylander@gmail.com>
6412  *
6413  * Circulation Recurring Fine rules
6414  *
6415  * Each circulation is given a recurring fine amount based on one of
6416  * these rules.  The recurrence_interval should not be any shorter
6417  * than the interval between runs of the fine_processor.pl script
6418  * (which is run from CRON), or you could miss fines.
6419  * 
6420  *
6421  * ****
6422  *
6423  * This program is free software; you can redistribute it and/or
6424  * modify it under the terms of the GNU General Public License
6425  * as published by the Free Software Foundation; either version 2
6426  * of the License, or (at your option) any later version.
6427  *
6428  * This program is distributed in the hope that it will be useful,
6429  * but WITHOUT ANY WARRANTY; without even the implied warranty of
6430  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
6431  * GNU General Public License for more details.
6432  */
6433 $$;
6434
6435 -- Extend the name change to some related views:
6436
6437 DROP VIEW IF EXISTS reporter.overdue_circs;
6438
6439 CREATE OR REPLACE VIEW reporter.overdue_circs AS
6440 SELECT  *
6441   FROM  action.circulation
6442     WHERE checkin_time is null
6443                 AND (stop_fines NOT IN ('LOST','CLAIMSRETURNED') OR stop_fines IS NULL)
6444                                 AND due_date < now();
6445
6446 DROP VIEW IF EXISTS stats.fleshed_circulation;
6447
6448 DROP VIEW IF EXISTS stats.fleshed_copy;
6449
6450 CREATE VIEW stats.fleshed_copy AS
6451         SELECT  cp.*,
6452         CAST(cp.create_date AS DATE) AS create_date_day,
6453         CAST(cp.edit_date AS DATE) AS edit_date_day,
6454         DATE_TRUNC('hour', cp.create_date) AS create_date_hour,
6455         DATE_TRUNC('hour', cp.edit_date) AS edit_date_hour,
6456                 cn.label AS call_number_label,
6457                 cn.owning_lib,
6458                 rd.item_lang,
6459                 rd.item_type,
6460                 rd.item_form
6461         FROM    asset.copy cp
6462                 JOIN asset.call_number cn ON (cp.call_number = cn.id)
6463                 JOIN metabib.rec_descriptor rd ON (rd.record = cn.record);
6464
6465 CREATE VIEW stats.fleshed_circulation AS
6466         SELECT  c.*,
6467                 CAST(c.xact_start AS DATE) AS start_date_day,
6468                 CAST(c.xact_finish AS DATE) AS finish_date_day,
6469                 DATE_TRUNC('hour', c.xact_start) AS start_date_hour,
6470                 DATE_TRUNC('hour', c.xact_finish) AS finish_date_hour,
6471                 cp.call_number_label,
6472                 cp.owning_lib,
6473                 cp.item_lang,
6474                 cp.item_type,
6475                 cp.item_form
6476         FROM    action.circulation c
6477                 JOIN stats.fleshed_copy cp ON (cp.id = c.target_copy);
6478
6479 -- Drop a view temporarily in order to alter action.all_circulation, upon
6480 -- which it is dependent.  We will recreate the view later.
6481
6482 DROP VIEW IF EXISTS extend_reporter.full_circ_count;
6483
6484 -- You would think that CREATE OR REPLACE would be enough, but in testing
6485 -- PostgreSQL complained about renaming the columns in the view. So we
6486 -- drop the view first.
6487 DROP VIEW IF EXISTS action.all_circulation;
6488
6489 CREATE OR REPLACE VIEW action.all_circulation AS
6490     SELECT  id,usr_post_code, usr_home_ou, usr_profile, usr_birth_year, copy_call_number, copy_location,
6491         copy_owning_lib, copy_circ_lib, copy_bib_record, xact_start, xact_finish, target_copy,
6492         circ_lib, circ_staff, checkin_staff, checkin_lib, renewal_remaining, due_date,
6493         stop_fines_time, checkin_time, create_time, duration, fine_interval, recurring_fine,
6494         max_fine, phone_renewal, desk_renewal, opac_renewal, duration_rule, recurring_fine_rule,
6495         max_fine_rule, stop_fines, workstation, checkin_workstation, checkin_scan_time, parent_circ
6496       FROM  action.aged_circulation
6497             UNION ALL
6498     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,
6499         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,
6500         cn.record AS copy_bib_record, circ.xact_start, circ.xact_finish, circ.target_copy, circ.circ_lib, circ.circ_staff, circ.checkin_staff,
6501         circ.checkin_lib, circ.renewal_remaining, circ.due_date, circ.stop_fines_time, circ.checkin_time, circ.create_time, circ.duration,
6502         circ.fine_interval, circ.recurring_fine, circ.max_fine, circ.phone_renewal, circ.desk_renewal, circ.opac_renewal, circ.duration_rule,
6503         circ.recurring_fine_rule, circ.max_fine_rule, circ.stop_fines, circ.workstation, circ.checkin_workstation, circ.checkin_scan_time,
6504         circ.parent_circ
6505       FROM  action.circulation circ
6506         JOIN asset.copy cp ON (circ.target_copy = cp.id)
6507         JOIN asset.call_number cn ON (cp.call_number = cn.id)
6508         JOIN actor.usr p ON (circ.usr = p.id)
6509         LEFT JOIN actor.usr_address a ON (p.mailing_address = a.id)
6510         LEFT JOIN actor.usr_address b ON (p.billing_address = a.id);
6511
6512 -- Recreate the temporarily dropped view, having altered the action.all_circulation view:
6513
6514 CREATE OR REPLACE VIEW extend_reporter.full_circ_count AS
6515  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
6516    FROM asset."copy" cp
6517    LEFT JOIN extend_reporter.legacy_circ_count c USING (id)
6518    LEFT JOIN "action".circulation circ ON circ.target_copy = cp.id
6519    LEFT JOIN "action".aged_circulation acirc ON acirc.target_copy = cp.id
6520   GROUP BY cp.id;
6521
6522 CREATE UNIQUE INDEX only_one_concurrent_checkout_per_copy ON action.circulation(target_copy) WHERE checkin_time IS NULL;
6523
6524 ALTER TABLE action.circulation DROP CONSTRAINT action_circulation_target_copy_fkey;
6525
6526 -- Rebuild dependent views
6527
6528 DROP VIEW IF EXISTS action.billable_circulations;
6529
6530 CREATE OR REPLACE VIEW action.billable_circulations AS
6531     SELECT  *
6532       FROM  action.circulation
6533       WHERE xact_finish IS NULL;
6534
6535 DROP VIEW IF EXISTS action.open_circulation;
6536
6537 CREATE OR REPLACE VIEW action.open_circulation AS
6538     SELECT  *
6539       FROM  action.circulation
6540       WHERE checkin_time IS NULL
6541       ORDER BY due_date;
6542
6543 CREATE OR REPLACE FUNCTION action.age_circ_on_delete () RETURNS TRIGGER AS $$
6544 DECLARE
6545 found char := 'N';
6546 BEGIN
6547
6548     -- If there are any renewals for this circulation, don't archive or delete
6549     -- it yet.   We'll do so later, when we archive and delete the renewals.
6550
6551     SELECT 'Y' INTO found
6552     FROM action.circulation
6553     WHERE parent_circ = OLD.id
6554     LIMIT 1;
6555
6556     IF found = 'Y' THEN
6557         RETURN NULL;  -- don't delete
6558         END IF;
6559
6560     -- Archive a copy of the old row to action.aged_circulation
6561
6562     INSERT INTO action.aged_circulation
6563         (id,usr_post_code, usr_home_ou, usr_profile, usr_birth_year, copy_call_number, copy_location,
6564         copy_owning_lib, copy_circ_lib, copy_bib_record, xact_start, xact_finish, target_copy,
6565         circ_lib, circ_staff, checkin_staff, checkin_lib, renewal_remaining, due_date,
6566         stop_fines_time, checkin_time, create_time, duration, fine_interval, recurring_fine,
6567         max_fine, phone_renewal, desk_renewal, opac_renewal, duration_rule, recurring_fine_rule,
6568         max_fine_rule, stop_fines, workstation, checkin_workstation, checkin_scan_time, parent_circ)
6569       SELECT
6570         id,usr_post_code, usr_home_ou, usr_profile, usr_birth_year, copy_call_number, copy_location,
6571         copy_owning_lib, copy_circ_lib, copy_bib_record, xact_start, xact_finish, target_copy,
6572         circ_lib, circ_staff, checkin_staff, checkin_lib, renewal_remaining, due_date,
6573         stop_fines_time, checkin_time, create_time, duration, fine_interval, recurring_fine,
6574         max_fine, phone_renewal, desk_renewal, opac_renewal, duration_rule, recurring_fine_rule,
6575         max_fine_rule, stop_fines, workstation, checkin_workstation, checkin_scan_time, parent_circ
6576         FROM action.all_circulation WHERE id = OLD.id;
6577
6578     RETURN OLD;
6579 END;
6580 $$ LANGUAGE 'plpgsql';
6581
6582 UPDATE config.z3950_attr SET truncation = 1 WHERE source = 'biblios' AND name = 'title';
6583
6584 UPDATE config.z3950_attr SET truncation = 1 WHERE source = 'biblios' AND truncation = 0;
6585
6586 -- Adding circ.holds.target_skip_me OU setting logic to the pre-matchpoint tests
6587
6588 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$
6589 DECLARE
6590     current_requestor_group    permission.grp_tree%ROWTYPE;
6591     requestor_object    actor.usr%ROWTYPE;
6592     user_object        actor.usr%ROWTYPE;
6593     item_object        asset.copy%ROWTYPE;
6594     item_cn_object        asset.call_number%ROWTYPE;
6595     rec_descriptor        metabib.rec_descriptor%ROWTYPE;
6596     current_mp_weight    FLOAT;
6597     matchpoint_weight    FLOAT;
6598     tmp_weight        FLOAT;
6599     current_mp        config.hold_matrix_matchpoint%ROWTYPE;
6600     matchpoint        config.hold_matrix_matchpoint%ROWTYPE;
6601 BEGIN
6602     SELECT INTO user_object * FROM actor.usr WHERE id = match_user;
6603     SELECT INTO requestor_object * FROM actor.usr WHERE id = match_requestor;
6604     SELECT INTO item_object * FROM asset.copy WHERE id = match_item;
6605     SELECT INTO item_cn_object * FROM asset.call_number WHERE id = item_object.call_number;
6606     SELECT INTO rec_descriptor r.* FROM metabib.rec_descriptor r WHERE r.record = item_cn_object.record;
6607
6608     PERFORM * FROM config.internal_flag WHERE name = 'circ.holds.usr_not_requestor' AND enabled;
6609
6610     IF NOT FOUND THEN
6611         SELECT INTO current_requestor_group * FROM permission.grp_tree WHERE id = requestor_object.profile;
6612     ELSE
6613         SELECT INTO current_requestor_group * FROM permission.grp_tree WHERE id = user_object.profile;
6614     END IF;
6615
6616     LOOP 
6617         -- for each potential matchpoint for this ou and group ...
6618         FOR current_mp IN
6619             SELECT    m.*
6620               FROM    config.hold_matrix_matchpoint m
6621               WHERE    m.requestor_grp = current_requestor_group.id AND m.active
6622               ORDER BY    CASE WHEN m.circ_modifier    IS NOT NULL THEN 16 ELSE 0 END +
6623                     CASE WHEN m.juvenile_flag    IS NOT NULL THEN 16 ELSE 0 END +
6624                     CASE WHEN m.marc_type        IS NOT NULL THEN 8 ELSE 0 END +
6625                     CASE WHEN m.marc_form        IS NOT NULL THEN 4 ELSE 0 END +
6626                     CASE WHEN m.marc_vr_format    IS NOT NULL THEN 2 ELSE 0 END +
6627                     CASE WHEN m.ref_flag        IS NOT NULL THEN 1 ELSE 0 END DESC LOOP
6628
6629             current_mp_weight := 5.0;
6630
6631             IF current_mp.circ_modifier IS NOT NULL THEN
6632                 CONTINUE WHEN current_mp.circ_modifier <> item_object.circ_modifier OR item_object.circ_modifier IS NULL;
6633             END IF;
6634
6635             IF current_mp.marc_type IS NOT NULL THEN
6636                 IF item_object.circ_as_type IS NOT NULL THEN
6637                     CONTINUE WHEN current_mp.marc_type <> item_object.circ_as_type;
6638                 ELSE
6639                     CONTINUE WHEN current_mp.marc_type <> rec_descriptor.item_type;
6640                 END IF;
6641             END IF;
6642
6643             IF current_mp.marc_form IS NOT NULL THEN
6644                 CONTINUE WHEN current_mp.marc_form <> rec_descriptor.item_form;
6645             END IF;
6646
6647             IF current_mp.marc_vr_format IS NOT NULL THEN
6648                 CONTINUE WHEN current_mp.marc_vr_format <> rec_descriptor.vr_format;
6649             END IF;
6650
6651             IF current_mp.juvenile_flag IS NOT NULL THEN
6652                 CONTINUE WHEN current_mp.juvenile_flag <> user_object.juvenile;
6653             END IF;
6654
6655             IF current_mp.ref_flag IS NOT NULL THEN
6656                 CONTINUE WHEN current_mp.ref_flag <> item_object.ref;
6657             END IF;
6658
6659
6660             -- caclulate the rule match weight
6661             IF current_mp.item_owning_ou IS NOT NULL THEN
6662                 CONTINUE WHEN current_mp.item_owning_ou NOT IN (SELECT (actor.org_unit_ancestors(item_cn_object.owning_lib)).id);
6663                 SELECT INTO tmp_weight 1.0 / (actor.org_unit_proximity(current_mp.item_owning_ou, item_cn_object.owning_lib)::FLOAT + 1.0)::FLOAT;
6664                 current_mp_weight := current_mp_weight - tmp_weight;
6665             END IF; 
6666
6667             IF current_mp.item_circ_ou IS NOT NULL THEN
6668                 CONTINUE WHEN current_mp.item_circ_ou NOT IN (SELECT (actor.org_unit_ancestors(item_object.circ_lib)).id);
6669                 SELECT INTO tmp_weight 1.0 / (actor.org_unit_proximity(current_mp.item_circ_ou, item_object.circ_lib)::FLOAT + 1.0)::FLOAT;
6670                 current_mp_weight := current_mp_weight - tmp_weight;
6671             END IF; 
6672
6673             IF current_mp.pickup_ou IS NOT NULL THEN
6674                 CONTINUE WHEN current_mp.pickup_ou NOT IN (SELECT (actor.org_unit_ancestors(pickup_ou)).id);
6675                 SELECT INTO tmp_weight 1.0 / (actor.org_unit_proximity(current_mp.pickup_ou, pickup_ou)::FLOAT + 1.0)::FLOAT;
6676                 current_mp_weight := current_mp_weight - tmp_weight;
6677             END IF; 
6678
6679             IF current_mp.request_ou IS NOT NULL THEN
6680                 CONTINUE WHEN current_mp.request_ou NOT IN (SELECT (actor.org_unit_ancestors(request_ou)).id);
6681                 SELECT INTO tmp_weight 1.0 / (actor.org_unit_proximity(current_mp.request_ou, request_ou)::FLOAT + 1.0)::FLOAT;
6682                 current_mp_weight := current_mp_weight - tmp_weight;
6683             END IF; 
6684
6685             IF current_mp.user_home_ou IS NOT NULL THEN
6686                 CONTINUE WHEN current_mp.user_home_ou NOT IN (SELECT (actor.org_unit_ancestors(user_object.home_ou)).id);
6687                 SELECT INTO tmp_weight 1.0 / (actor.org_unit_proximity(current_mp.user_home_ou, user_object.home_ou)::FLOAT + 1.0)::FLOAT;
6688                 current_mp_weight := current_mp_weight - tmp_weight;
6689             END IF; 
6690
6691             -- set the matchpoint if we found the best one
6692             IF matchpoint_weight IS NULL OR matchpoint_weight > current_mp_weight THEN
6693                 matchpoint = current_mp;
6694                 matchpoint_weight = current_mp_weight;
6695             END IF;
6696
6697         END LOOP;
6698
6699         EXIT WHEN current_requestor_group.parent IS NULL OR matchpoint.id IS NOT NULL;
6700
6701         SELECT INTO current_requestor_group * FROM permission.grp_tree WHERE id = current_requestor_group.parent;
6702     END LOOP;
6703
6704     RETURN matchpoint.id;
6705 END;
6706 $func$ LANGUAGE plpgsql;
6707
6708 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$
6709 DECLARE
6710     matchpoint_id        INT;
6711     user_object        actor.usr%ROWTYPE;
6712     age_protect_object    config.rule_age_hold_protect%ROWTYPE;
6713     standing_penalty    config.standing_penalty%ROWTYPE;
6714     transit_range_ou_type    actor.org_unit_type%ROWTYPE;
6715     transit_source        actor.org_unit%ROWTYPE;
6716     item_object        asset.copy%ROWTYPE;
6717     ou_skip              actor.org_unit_setting%ROWTYPE;
6718     result            action.matrix_test_result;
6719     hold_test        config.hold_matrix_matchpoint%ROWTYPE;
6720     hold_count        INT;
6721     hold_transit_prox    INT;
6722     frozen_hold_count    INT;
6723     context_org_list    INT[];
6724     done            BOOL := FALSE;
6725 BEGIN
6726     SELECT INTO user_object * FROM actor.usr WHERE id = match_user;
6727     SELECT INTO context_org_list ARRAY_ACCUM(id) FROM actor.org_unit_full_path( pickup_ou );
6728
6729     result.success := TRUE;
6730
6731     -- Fail if we couldn't find a user
6732     IF user_object.id IS NULL THEN
6733         result.fail_part := 'no_user';
6734         result.success := FALSE;
6735         done := TRUE;
6736         RETURN NEXT result;
6737         RETURN;
6738     END IF;
6739
6740     SELECT INTO item_object * FROM asset.copy WHERE id = match_item;
6741
6742     -- Fail if we couldn't find a copy
6743     IF item_object.id IS NULL THEN
6744         result.fail_part := 'no_item';
6745         result.success := FALSE;
6746         done := TRUE;
6747         RETURN NEXT result;
6748         RETURN;
6749     END IF;
6750
6751     SELECT INTO matchpoint_id action.find_hold_matrix_matchpoint(pickup_ou, request_ou, match_item, match_user, match_requestor);
6752     result.matchpoint := matchpoint_id;
6753
6754     SELECT INTO ou_skip * FROM actor.org_unit_setting WHERE name = 'circ.holds.target_skip_me' AND org_unit = item_object.circ_lib;
6755
6756     -- Fail if the circ_lib for the item has circ.holds.target_skip_me set to true
6757     IF ou_skip.id IS NOT NULL AND ou_skip.value = 'true' THEN
6758         result.fail_part := 'circ.holds.target_skip_me';
6759         result.success := FALSE;
6760         done := TRUE;
6761         RETURN NEXT result;
6762         RETURN;
6763     END IF;
6764
6765     -- Fail if user is barred
6766     IF user_object.barred IS TRUE THEN
6767         result.fail_part := 'actor.usr.barred';
6768         result.success := FALSE;
6769         done := TRUE;
6770         RETURN NEXT result;
6771         RETURN;
6772     END IF;
6773
6774     -- Fail if we couldn't find any matchpoint (requires a default)
6775     IF matchpoint_id IS NULL THEN
6776         result.fail_part := 'no_matchpoint';
6777         result.success := FALSE;
6778         done := TRUE;
6779         RETURN NEXT result;
6780         RETURN;
6781     END IF;
6782
6783     SELECT INTO hold_test * FROM config.hold_matrix_matchpoint WHERE id = matchpoint_id;
6784
6785     IF hold_test.holdable IS FALSE THEN
6786         result.fail_part := 'config.hold_matrix_test.holdable';
6787         result.success := FALSE;
6788         done := TRUE;
6789         RETURN NEXT result;
6790     END IF;
6791
6792     IF hold_test.transit_range IS NOT NULL THEN
6793         SELECT INTO transit_range_ou_type * FROM actor.org_unit_type WHERE id = hold_test.transit_range;
6794         IF hold_test.distance_is_from_owner THEN
6795             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;
6796         ELSE
6797             SELECT INTO transit_source * FROM actor.org_unit WHERE id = item_object.circ_lib;
6798         END IF;
6799
6800         PERFORM * FROM actor.org_unit_descendants( transit_source.id, transit_range_ou_type.depth ) WHERE id = pickup_ou;
6801
6802         IF NOT FOUND THEN
6803             result.fail_part := 'transit_range';
6804             result.success := FALSE;
6805             done := TRUE;
6806             RETURN NEXT result;
6807         END IF;
6808     END IF;
6809  
6810     IF NOT retargetting THEN
6811         FOR standing_penalty IN
6812             SELECT  DISTINCT csp.*
6813               FROM  actor.usr_standing_penalty usp
6814                     JOIN config.standing_penalty csp ON (csp.id = usp.standing_penalty)
6815               WHERE usr = match_user
6816                     AND usp.org_unit IN ( SELECT * FROM explode_array(context_org_list) )
6817                     AND (usp.stop_date IS NULL or usp.stop_date > NOW())
6818                     AND csp.block_list LIKE '%HOLD%' LOOP
6819     
6820             result.fail_part := standing_penalty.name;
6821             result.success := FALSE;
6822             done := TRUE;
6823             RETURN NEXT result;
6824         END LOOP;
6825     
6826         IF hold_test.stop_blocked_user IS TRUE THEN
6827             FOR standing_penalty IN
6828                 SELECT  DISTINCT csp.*
6829                   FROM  actor.usr_standing_penalty usp
6830                         JOIN config.standing_penalty csp ON (csp.id = usp.standing_penalty)
6831                   WHERE usr = match_user
6832                         AND usp.org_unit IN ( SELECT * FROM explode_array(context_org_list) )
6833                         AND (usp.stop_date IS NULL or usp.stop_date > NOW())
6834                         AND csp.block_list LIKE '%CIRC%' LOOP
6835         
6836                 result.fail_part := standing_penalty.name;
6837                 result.success := FALSE;
6838                 done := TRUE;
6839                 RETURN NEXT result;
6840             END LOOP;
6841         END IF;
6842     
6843         IF hold_test.max_holds IS NOT NULL THEN
6844             SELECT    INTO hold_count COUNT(*)
6845               FROM    action.hold_request
6846               WHERE    usr = match_user
6847                 AND fulfillment_time IS NULL
6848                 AND cancel_time IS NULL
6849                 AND CASE WHEN hold_test.include_frozen_holds THEN TRUE ELSE frozen IS FALSE END;
6850     
6851             IF hold_count >= hold_test.max_holds THEN
6852                 result.fail_part := 'config.hold_matrix_test.max_holds';
6853                 result.success := FALSE;
6854                 done := TRUE;
6855                 RETURN NEXT result;
6856             END IF;
6857         END IF;
6858     END IF;
6859
6860     IF item_object.age_protect IS NOT NULL THEN
6861         SELECT INTO age_protect_object * FROM config.rule_age_hold_protect WHERE id = item_object.age_protect;
6862
6863         IF item_object.create_date + age_protect_object.age > NOW() THEN
6864             IF hold_test.distance_is_from_owner THEN
6865                 SELECT INTO hold_transit_prox prox FROM actor.org_unit_proximity WHERE from_org = item_cn_object.owning_lib AND to_org = pickup_ou;
6866             ELSE
6867                 SELECT INTO hold_transit_prox prox FROM actor.org_unit_proximity WHERE from_org = item_object.circ_lib AND to_org = pickup_ou;
6868             END IF;
6869
6870             IF hold_transit_prox > age_protect_object.prox THEN
6871                 result.fail_part := 'config.rule_age_hold_protect.prox';
6872                 result.success := FALSE;
6873                 done := TRUE;
6874                 RETURN NEXT result;
6875             END IF;
6876         END IF;
6877     END IF;
6878
6879     IF NOT done THEN
6880         RETURN NEXT result;
6881     END IF;
6882
6883     RETURN;
6884 END;
6885 $func$ LANGUAGE plpgsql;
6886
6887 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$
6888     SELECT * FROM action.hold_request_permit_test( $1, $2, $3, $4, $5, FALSE);
6889 $func$ LANGUAGE SQL;
6890
6891 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$
6892     SELECT * FROM action.hold_request_permit_test( $1, $2, $3, $4, $5, TRUE );
6893 $func$ LANGUAGE SQL;
6894
6895 -- New post-delete trigger to propagate deletions to parent(s)
6896
6897 CREATE OR REPLACE FUNCTION action.age_parent_circ_on_delete () RETURNS TRIGGER AS $$
6898 BEGIN
6899
6900     -- Having deleted a renewal, we can delete the original circulation (or a previous
6901     -- renewal, if that's what parent_circ is pointing to).  That deletion will trigger
6902     -- deletion of any prior parents, etc. recursively.
6903
6904     IF OLD.parent_circ IS NOT NULL THEN
6905         DELETE FROM action.circulation
6906         WHERE id = OLD.parent_circ;
6907     END IF;
6908
6909     RETURN OLD;
6910 END;
6911 $$ LANGUAGE 'plpgsql';
6912
6913 CREATE TRIGGER age_parent_circ AFTER DELETE ON action.circulation
6914 FOR EACH ROW EXECUTE PROCEDURE action.age_parent_circ_on_delete ();
6915
6916 -- This only gets inserted if there are no other id > 100 billing types
6917 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;
6918 SELECT SETVAL('config.billing_type_id_seq'::TEXT, 101) FROM config.billing_type_id_seq WHERE last_value < 101;
6919
6920 -- Populate xact_type column in the materialized version of billable_xact_summary
6921
6922 CREATE OR REPLACE FUNCTION money.mat_summary_create () RETURNS TRIGGER AS $$
6923 BEGIN
6924         INSERT INTO money.materialized_billable_xact_summary (id, usr, xact_start, xact_finish, total_paid, total_owed, balance_owed, xact_type)
6925                 VALUES ( NEW.id, NEW.usr, NEW.xact_start, NEW.xact_finish, 0.0, 0.0, 0.0, TG_ARGV[0]);
6926         RETURN NEW;
6927 END;
6928 $$ LANGUAGE PLPGSQL;
6929  
6930 DROP TRIGGER IF EXISTS mat_summary_create_tgr ON action.circulation;
6931 CREATE TRIGGER mat_summary_create_tgr AFTER INSERT ON action.circulation FOR EACH ROW EXECUTE PROCEDURE money.mat_summary_create ('circulation');
6932  
6933 DROP TRIGGER IF EXISTS mat_summary_create_tgr ON money.grocery;
6934 CREATE TRIGGER mat_summary_create_tgr AFTER INSERT ON money.grocery FOR EACH ROW EXECUTE PROCEDURE money.mat_summary_create ('grocery');
6935
6936 CREATE RULE money_payment_view_update AS ON UPDATE TO money.payment_view DO INSTEAD 
6937     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;
6938
6939 -- Generate the equivalent of compound subject entries from the existing rows
6940 -- so that we don't have to laboriously reindex them
6941
6942 --INSERT INTO config.metabib_field (field_class, name, format, xpath ) VALUES
6943 --    ( 'subject', 'complete', 'mods32', $$//mods32:mods/mods32:subject//text()$$ );
6944 --
6945 --CREATE INDEX metabib_subject_field_entry_source_idx ON metabib.subject_field_entry (source);
6946 --
6947 --INSERT INTO metabib.subject_field_entry (source, field, value)
6948 --    SELECT source, (
6949 --            SELECT id 
6950 --            FROM config.metabib_field
6951 --            WHERE field_class = 'subject' AND name = 'complete'
6952 --        ), 
6953 --        ARRAY_TO_STRING ( 
6954 --            ARRAY (
6955 --                SELECT value 
6956 --                FROM metabib.subject_field_entry msfe
6957 --                WHERE msfe.source = groupee.source
6958 --                ORDER BY source 
6959 --            ), ' ' 
6960 --        ) AS grouped
6961 --    FROM ( 
6962 --        SELECT source
6963 --        FROM metabib.subject_field_entry
6964 --        GROUP BY source
6965 --    ) AS groupee;
6966
6967 CREATE OR REPLACE FUNCTION money.materialized_summary_billing_del () RETURNS TRIGGER AS $$
6968 DECLARE
6969         prev_billing    money.billing%ROWTYPE;
6970         old_billing     money.billing%ROWTYPE;
6971 BEGIN
6972         SELECT * INTO prev_billing FROM money.billing WHERE xact = OLD.xact AND NOT voided ORDER BY billing_ts DESC LIMIT 1 OFFSET 1;
6973         SELECT * INTO old_billing FROM money.billing WHERE xact = OLD.xact AND NOT voided ORDER BY billing_ts DESC LIMIT 1;
6974
6975         IF OLD.id = old_billing.id THEN
6976                 UPDATE  money.materialized_billable_xact_summary
6977                   SET   last_billing_ts = prev_billing.billing_ts,
6978                         last_billing_note = prev_billing.note,
6979                         last_billing_type = prev_billing.billing_type
6980                   WHERE id = OLD.xact;
6981         END IF;
6982
6983         IF NOT OLD.voided THEN
6984                 UPDATE  money.materialized_billable_xact_summary
6985                   SET   total_owed = total_owed - OLD.amount,
6986                         balance_owed = balance_owed + OLD.amount
6987                   WHERE id = OLD.xact;
6988         END IF;
6989
6990         RETURN OLD;
6991 END;
6992 $$ LANGUAGE PLPGSQL;
6993
6994 -- ARG! need to rid ourselves of the broken table definition ... this mechanism is not ideal, sorry.
6995 DROP TABLE IF EXISTS config.index_normalizer CASCADE;
6996
6997 CREATE OR REPLACE FUNCTION public.naco_normalize( TEXT, TEXT ) RETURNS TEXT AS $func$
6998     use Unicode::Normalize;
6999     use Encode;
7000
7001     # When working with Unicode data, the first step is to decode it to
7002     # a byte string; after that, lowercasing is safe
7003     my $txt = lc(decode_utf8(shift));
7004     my $sf = shift;
7005
7006     $txt = NFD($txt);
7007     $txt =~ s/\pM+//go; # Remove diacritics
7008
7009     # remove non-combining diacritics
7010     # this list of characters follows the NACO normalization spec,
7011     # but a looser but more comprehensive version might be
7012     # $txt =~ s/\pLm+//go;
7013     $txt =~ tr/\x{02B9}\x{02BA}\x{02BB}\x{02BC}//d;
7014
7015     $txt =~ s/\xE6/AE/go;   # Convert ae digraph
7016     $txt =~ s/\x{153}/OE/go;# Convert oe digraph
7017     $txt =~ s/\xFE/TH/go;   # Convert Icelandic thorn
7018
7019     $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
7020     $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
7021
7022     $txt =~ tr/\x{0251}\x{03B1}\x{03B2}\x{0262}\x{03B3}/AABGG/;     # Convert Latin and Greek
7023     $txt =~ tr/\x{2113}\xF0\x{111}\!\"\(\)\-\{\}\<\>\;\:\.\?\xA1\xBF\/\\\@\*\%\=\xB1\+\xAE\xA9\x{2117}\$\xA3\x{FFE1}\xB0\^\_\~\`/LDD /; # Convert Misc
7024     $txt =~ tr/\'\[\]\|//d;                         # Remove Misc
7025
7026     if ($sf && $sf =~ /^a/o) {
7027         my $commapos = index($txt,',');
7028         if ($commapos > -1) {
7029             if ($commapos != length($txt) - 1) {
7030                 my @list = split /,/, $txt;
7031                 my $first = shift @list;
7032                 $txt = $first . ',' . join(' ', @list);
7033             } else {
7034                 $txt =~ s/,/ /go;
7035             }
7036         }
7037     } else {
7038         $txt =~ s/,/ /go;
7039     }
7040
7041     $txt =~ s/\s+/ /go; # Compress multiple spaces
7042     $txt =~ s/^\s+//o;  # Remove leading space
7043     $txt =~ s/\s+$//o;  # Remove trailing space
7044
7045     # Encoding the outgoing string is good practice, but not strictly
7046     # necessary in this case because we've stripped everything from it
7047     return encode_utf8($txt);
7048 $func$ LANGUAGE 'plperlu' STRICT IMMUTABLE;
7049
7050 -- Some handy functions, based on existing ones, to provide optional ingest normalization
7051
7052 CREATE OR REPLACE FUNCTION public.left_trunc( TEXT, INT ) RETURNS TEXT AS $func$
7053         SELECT SUBSTRING($1,$2);
7054 $func$ LANGUAGE SQL STRICT IMMUTABLE;
7055
7056 CREATE OR REPLACE FUNCTION public.right_trunc( TEXT, INT ) RETURNS TEXT AS $func$
7057         SELECT SUBSTRING($1,1,$2);
7058 $func$ LANGUAGE SQL STRICT IMMUTABLE;
7059
7060 CREATE OR REPLACE FUNCTION public.naco_normalize_keep_comma( TEXT ) RETURNS TEXT AS $func$
7061         SELECT public.naco_normalize($1,'a');
7062 $func$ LANGUAGE SQL STRICT IMMUTABLE;
7063
7064 CREATE OR REPLACE FUNCTION public.split_date_range( TEXT ) RETURNS TEXT AS $func$
7065         SELECT REGEXP_REPLACE( $1, E'(\\d{4})-(\\d{4})', E'\\1 \\2', 'g' );
7066 $func$ LANGUAGE SQL STRICT IMMUTABLE;
7067
7068 -- And ... a table in which to register them
7069
7070 CREATE TABLE config.index_normalizer (
7071         id              SERIAL  PRIMARY KEY,
7072         name            TEXT    UNIQUE NOT NULL,
7073         description     TEXT,
7074         func            TEXT    NOT NULL,
7075         param_count     INT     NOT NULL DEFAULT 0
7076 );
7077
7078 CREATE TABLE config.metabib_field_index_norm_map (
7079         id      SERIAL  PRIMARY KEY,
7080         field   INT     NOT NULL REFERENCES config.metabib_field (id) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED,
7081         norm    INT     NOT NULL REFERENCES config.index_normalizer (id) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED,
7082         params  TEXT,
7083         pos     INT     NOT NULL DEFAULT 0
7084 );
7085
7086 INSERT INTO config.index_normalizer (name, description, func, param_count) VALUES (
7087         'NACO Normalize',
7088         'Apply NACO normalization rules to the extracted text.  See http://www.loc.gov/catdir/pcc/naco/normrule-2.html for details.',
7089         'naco_normalize',
7090         0
7091 );
7092
7093 INSERT INTO config.index_normalizer (name, description, func, param_count) VALUES (
7094         'Normalize date range',
7095         'Split date ranges in the form of "XXXX-YYYY" into "XXXX YYYY" for proper index.',
7096         'split_date_range',
7097         1
7098 );
7099
7100 INSERT INTO config.index_normalizer (name, description, func, param_count) VALUES (
7101         'NACO Normalize -- retain first comma',
7102         '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.',
7103         'naco_normalize_keep_comma',
7104         0
7105 );
7106
7107 INSERT INTO config.index_normalizer (name, description, func, param_count) VALUES (
7108         'Strip Diacritics',
7109         'Convert text to NFD form and remove non-spacing combining marks.',
7110         'remove_diacritics',
7111         0
7112 );
7113
7114 INSERT INTO config.index_normalizer (name, description, func, param_count) VALUES (
7115         'Up-case',
7116         'Convert text upper case.',
7117         'uppercase',
7118         0
7119 );
7120
7121 INSERT INTO config.index_normalizer (name, description, func, param_count) VALUES (
7122         'Down-case',
7123         'Convert text lower case.',
7124         'lowercase',
7125         0
7126 );
7127
7128 INSERT INTO config.index_normalizer (name, description, func, param_count) VALUES (
7129         'Extract Dewey-like number',
7130         'Extract a string of numeric characters ther resembles a DDC number.',
7131         'call_number_dewey',
7132         0
7133 );
7134
7135 INSERT INTO config.index_normalizer (name, description, func, param_count) VALUES (
7136         'Left truncation',
7137         'Discard the specified number of characters from the left side of the string.',
7138         'left_trunc',
7139         1
7140 );
7141
7142 INSERT INTO config.index_normalizer (name, description, func, param_count) VALUES (
7143         'Right truncation',
7144         'Include only the specified number of characters from the left side of the string.',
7145         'right_trunc',
7146         1
7147 );
7148
7149 INSERT INTO config.index_normalizer (name, description, func, param_count) VALUES (
7150         'First word',
7151         'Include only the first space-separated word of a string.',
7152         'first_word',
7153         0
7154 );
7155
7156 INSERT INTO config.metabib_field_index_norm_map (field,norm)
7157         SELECT  m.id,
7158                 i.id
7159           FROM  config.metabib_field m,
7160                 config.index_normalizer i
7161           WHERE i.func IN ('naco_normalize','split_date_range');
7162
7163 CREATE OR REPLACE FUNCTION oils_tsearch2 () RETURNS TRIGGER AS $$
7164 DECLARE
7165     normalizer      RECORD;
7166     value           TEXT := '';
7167 BEGIN
7168
7169     value := NEW.value;
7170
7171     IF TG_TABLE_NAME::TEXT ~ 'field_entry$' THEN
7172         FOR normalizer IN
7173             SELECT  n.func AS func,
7174                     n.param_count AS param_count,
7175                     m.params AS params
7176               FROM  config.index_normalizer n
7177                     JOIN config.metabib_field_index_norm_map m ON (m.norm = n.id)
7178               WHERE field = NEW.field AND m.pos < 0
7179               ORDER BY m.pos LOOP
7180                 EXECUTE 'SELECT ' || normalizer.func || '(' ||
7181                     quote_literal( value ) ||
7182                     CASE
7183                         WHEN normalizer.param_count > 0
7184                             THEN ',' || REPLACE(REPLACE(BTRIM(normalizer.params,'[]'),E'\'',E'\\\''),E'"',E'\'')
7185                             ELSE ''
7186                         END ||
7187                     ')' INTO value;
7188
7189         END LOOP;
7190
7191         NEW.value := value;
7192     END IF;
7193
7194     IF NEW.index_vector = ''::tsvector THEN
7195         RETURN NEW;
7196     END IF;
7197
7198     IF TG_TABLE_NAME::TEXT ~ 'field_entry$' THEN
7199         FOR normalizer IN
7200             SELECT  n.func AS func,
7201                     n.param_count AS param_count,
7202                     m.params AS params
7203               FROM  config.index_normalizer n
7204                     JOIN config.metabib_field_index_norm_map m ON (m.norm = n.id)
7205               WHERE field = NEW.field AND m.pos >= 0
7206               ORDER BY m.pos LOOP
7207                 EXECUTE 'SELECT ' || normalizer.func || '(' ||
7208                     quote_literal( value ) ||
7209                     CASE
7210                         WHEN normalizer.param_count > 0
7211                             THEN ',' || REPLACE(REPLACE(BTRIM(normalizer.params,'[]'),E'\'',E'\\\''),E'"',E'\'')
7212                             ELSE ''
7213                         END ||
7214                     ')' INTO value;
7215
7216         END LOOP;
7217     END IF;
7218
7219     IF REGEXP_REPLACE(VERSION(),E'^.+?(\\d+\\.\\d+).*?$',E'\\1')::FLOAT > 8.2 THEN
7220         NEW.index_vector = to_tsvector((TG_ARGV[0])::regconfig, value);
7221     ELSE
7222         NEW.index_vector = to_tsvector(TG_ARGV[0], value);
7223     END IF;
7224
7225     RETURN NEW;
7226 END;
7227 $$ LANGUAGE PLPGSQL;
7228
7229 CREATE OR REPLACE FUNCTION oils_xpath ( TEXT, TEXT, ANYARRAY ) RETURNS TEXT[] AS 'SELECT XPATH( $1, $2::XML, $3 )::TEXT[];' LANGUAGE SQL IMMUTABLE;
7230
7231 CREATE OR REPLACE FUNCTION oils_xpath ( TEXT, TEXT ) RETURNS TEXT[] AS 'SELECT XPATH( $1, $2::XML )::TEXT[];' LANGUAGE SQL IMMUTABLE;
7232
7233 CREATE OR REPLACE FUNCTION oils_xpath_string ( TEXT, TEXT, TEXT, ANYARRAY ) RETURNS TEXT AS $func$
7234     SELECT  ARRAY_TO_STRING(
7235                 oils_xpath(
7236                     $1 ||
7237                         CASE WHEN $1 ~ $re$/[^/[]*@[^]]+$$re$ OR $1 ~ $re$text\(\)$$re$ THEN '' ELSE '//text()' END,
7238                     $2,
7239                     $4
7240                 ),
7241                 $3
7242             );
7243 $func$ LANGUAGE SQL IMMUTABLE;
7244
7245 CREATE OR REPLACE FUNCTION oils_xpath_string ( TEXT, TEXT, TEXT ) RETURNS TEXT AS $func$
7246     SELECT oils_xpath_string( $1, $2, $3, '{}'::TEXT[] );
7247 $func$ LANGUAGE SQL IMMUTABLE;
7248
7249 CREATE OR REPLACE FUNCTION oils_xpath_string ( TEXT, TEXT, ANYARRAY ) RETURNS TEXT AS $func$
7250     SELECT oils_xpath_string( $1, $2, '', $3 );
7251 $func$ LANGUAGE SQL IMMUTABLE;
7252
7253 CREATE OR REPLACE FUNCTION oils_xpath_string ( TEXT, TEXT ) RETURNS TEXT AS $func$
7254     SELECT oils_xpath_string( $1, $2, '{}'::TEXT[] );
7255 $func$ LANGUAGE SQL IMMUTABLE;
7256
7257 CREATE TYPE metabib.field_entry_template AS (
7258         field_class     TEXT,
7259         field           INT,
7260         source          BIGINT,
7261         value           TEXT
7262 );
7263
7264 CREATE OR REPLACE FUNCTION oils_xslt_process(TEXT, TEXT) RETURNS TEXT AS $func$
7265   use strict;
7266
7267   use XML::LibXSLT;
7268   use XML::LibXML;
7269
7270   my $doc = shift;
7271   my $xslt = shift;
7272
7273   # The following approach uses the older XML::LibXML 1.69 / XML::LibXSLT 1.68
7274   # methods of parsing XML documents and stylesheets, in the hopes of broader
7275   # compatibility with distributions
7276   my $parser = $_SHARED{'_xslt_process'}{parsers}{xml} || XML::LibXML->new();
7277
7278   # Cache the XML parser, if we do not already have one
7279   $_SHARED{'_xslt_process'}{parsers}{xml} = $parser
7280     unless ($_SHARED{'_xslt_process'}{parsers}{xml});
7281
7282   my $xslt_parser = $_SHARED{'_xslt_process'}{parsers}{xslt} || XML::LibXSLT->new();
7283
7284   # Cache the XSLT processor, if we do not already have one
7285   $_SHARED{'_xslt_process'}{parsers}{xslt} = $xslt_parser
7286     unless ($_SHARED{'_xslt_process'}{parsers}{xslt});
7287
7288   my $stylesheet = $_SHARED{'_xslt_process'}{stylesheets}{$xslt} ||
7289     $xslt_parser->parse_stylesheet( $parser->parse_string($xslt) );
7290
7291   $_SHARED{'_xslt_process'}{stylesheets}{$xslt} = $stylesheet
7292     unless ($_SHARED{'_xslt_process'}{stylesheets}{$xslt});
7293
7294   return $stylesheet->output_string(
7295     $stylesheet->transform(
7296       $parser->parse_string($doc)
7297     )
7298   );
7299
7300 $func$ LANGUAGE 'plperlu' STRICT IMMUTABLE;
7301
7302 -- Add two columns so that the following function will compile.
7303 -- Eventually the label column will be NOT NULL, but not yet.
7304 ALTER TABLE config.metabib_field ADD COLUMN label TEXT;
7305 ALTER TABLE config.metabib_field ADD COLUMN facet_xpath TEXT;
7306
7307 CREATE OR REPLACE FUNCTION biblio.extract_metabib_field_entry ( rid BIGINT, default_joiner TEXT ) RETURNS SETOF metabib.field_entry_template AS $func$
7308 DECLARE
7309     bib     biblio.record_entry%ROWTYPE;
7310     idx     config.metabib_field%ROWTYPE;
7311     xfrm        config.xml_transform%ROWTYPE;
7312     prev_xfrm   TEXT;
7313     transformed_xml TEXT;
7314     xml_node    TEXT;
7315     xml_node_list   TEXT[];
7316     facet_text  TEXT;
7317     raw_text    TEXT;
7318     curr_text   TEXT;
7319     joiner      TEXT := default_joiner; -- XXX will index defs supply a joiner?
7320     output_row  metabib.field_entry_template%ROWTYPE;
7321 BEGIN
7322
7323     -- Get the record
7324     SELECT INTO bib * FROM biblio.record_entry WHERE id = rid;
7325
7326     -- Loop over the indexing entries
7327     FOR idx IN SELECT * FROM config.metabib_field ORDER BY format LOOP
7328
7329         SELECT INTO xfrm * from config.xml_transform WHERE name = idx.format;
7330
7331         -- See if we can skip the XSLT ... it's expensive
7332         IF prev_xfrm IS NULL OR prev_xfrm <> xfrm.name THEN
7333             -- Can't skip the transform
7334             IF xfrm.xslt <> '---' THEN
7335                 transformed_xml := oils_xslt_process(bib.marc,xfrm.xslt);
7336             ELSE
7337                 transformed_xml := bib.marc;
7338             END IF;
7339
7340             prev_xfrm := xfrm.name;
7341         END IF;
7342
7343         xml_node_list := oils_xpath( idx.xpath, transformed_xml, ARRAY[ARRAY[xfrm.prefix, xfrm.namespace_uri]] );
7344
7345         raw_text := NULL;
7346         FOR xml_node IN SELECT x FROM explode_array(xml_node_list) AS x LOOP
7347             CONTINUE WHEN xml_node !~ E'^\\s*<';
7348
7349             curr_text := ARRAY_TO_STRING(
7350                 oils_xpath( '//text()',
7351                     REGEXP_REPLACE( -- This escapes all &s not followed by "amp;".  Data ise returned from oils_xpath (above) in UTF-8, not entity encoded
7352                         REGEXP_REPLACE( -- This escapes embeded <s
7353                             xml_node,
7354                             $re$(>[^<]+)(<)([^>]+<)$re$,
7355                             E'\\1&lt;\\3',
7356                             'g'
7357                         ),
7358                         '&(?!amp;)',
7359                         '&amp;',
7360                         'g'
7361                     )
7362                 ),
7363                 ' '
7364             );
7365
7366             CONTINUE WHEN curr_text IS NULL OR curr_text = '';
7367
7368             IF raw_text IS NOT NULL THEN
7369                 raw_text := raw_text || joiner;
7370             END IF;
7371
7372             raw_text := COALESCE(raw_text,'') || curr_text;
7373
7374             -- insert raw node text for faceting
7375             IF idx.facet_field THEN
7376
7377                 IF idx.facet_xpath IS NOT NULL AND idx.facet_xpath <> '' THEN
7378                     facet_text := oils_xpath_string( idx.facet_xpath, xml_node, joiner, ARRAY[ARRAY[xfrm.prefix, xfrm.namespace_uri]] );
7379                 ELSE
7380                     facet_text := curr_text;
7381                 END IF;
7382
7383                 output_row.field_class = idx.field_class;
7384                 output_row.field = -1 * idx.id;
7385                 output_row.source = rid;
7386                 output_row.value = BTRIM(REGEXP_REPLACE(facet_text, E'\\s+', ' ', 'g'));
7387
7388                 RETURN NEXT output_row;
7389             END IF;
7390
7391         END LOOP;
7392
7393         CONTINUE WHEN raw_text IS NULL OR raw_text = '';
7394
7395         -- insert combined node text for searching
7396         IF idx.search_field THEN
7397             output_row.field_class = idx.field_class;
7398             output_row.field = idx.id;
7399             output_row.source = rid;
7400             output_row.value = BTRIM(REGEXP_REPLACE(raw_text, E'\\s+', ' ', 'g'));
7401
7402             RETURN NEXT output_row;
7403         END IF;
7404
7405     END LOOP;
7406
7407 END;
7408 $func$ LANGUAGE PLPGSQL;
7409
7410 -- default to a space joiner
7411 CREATE OR REPLACE FUNCTION biblio.extract_metabib_field_entry ( BIGINT ) RETURNS SETOF metabib.field_entry_template AS $func$
7412         SELECT * FROM biblio.extract_metabib_field_entry($1, ' ');
7413 $func$ LANGUAGE SQL;
7414
7415 CREATE OR REPLACE FUNCTION biblio.flatten_marc ( TEXT ) RETURNS SETOF metabib.full_rec AS $func$
7416
7417 use MARC::Record;
7418 use MARC::File::XML (BinaryEncoding => 'UTF-8');
7419
7420 my $xml = shift;
7421 my $r = MARC::Record->new_from_xml( $xml );
7422
7423 return_next( { tag => 'LDR', value => $r->leader } );
7424
7425 for my $f ( $r->fields ) {
7426     if ($f->is_control_field) {
7427         return_next({ tag => $f->tag, value => $f->data });
7428     } else {
7429         for my $s ($f->subfields) {
7430             return_next({
7431                 tag      => $f->tag,
7432                 ind1     => $f->indicator(1),
7433                 ind2     => $f->indicator(2),
7434                 subfield => $s->[0],
7435                 value    => $s->[1]
7436             });
7437
7438             if ( $f->tag eq '245' and $s->[0] eq 'a' ) {
7439                 my $trim = $f->indicator(2) || 0;
7440                 return_next({
7441                     tag      => 'tnf',
7442                     ind1     => $f->indicator(1),
7443                     ind2     => $f->indicator(2),
7444                     subfield => 'a',
7445                     value    => substr( $s->[1], $trim )
7446                 });
7447             }
7448         }
7449     }
7450 }
7451
7452 return undef;
7453
7454 $func$ LANGUAGE PLPERLU;
7455
7456 CREATE OR REPLACE FUNCTION biblio.flatten_marc ( rid BIGINT ) RETURNS SETOF metabib.full_rec AS $func$
7457 DECLARE
7458     bib biblio.record_entry%ROWTYPE;
7459     output  metabib.full_rec%ROWTYPE;
7460     field   RECORD;
7461 BEGIN
7462     SELECT INTO bib * FROM biblio.record_entry WHERE id = rid;
7463
7464     FOR field IN SELECT * FROM biblio.flatten_marc( bib.marc ) LOOP
7465         output.record := rid;
7466         output.ind1 := field.ind1;
7467         output.ind2 := field.ind2;
7468         output.tag := field.tag;
7469         output.subfield := field.subfield;
7470         IF field.subfield IS NOT NULL AND field.tag NOT IN ('020','022','024') THEN -- exclude standard numbers and control fields
7471             output.value := naco_normalize(field.value, field.subfield);
7472         ELSE
7473             output.value := field.value;
7474         END IF;
7475
7476         CONTINUE WHEN output.value IS NULL;
7477
7478         RETURN NEXT output;
7479     END LOOP;
7480 END;
7481 $func$ LANGUAGE PLPGSQL;
7482
7483 -- functions to create auditor objects
7484
7485 CREATE FUNCTION auditor.create_auditor_seq     ( sch TEXT, tbl TEXT ) RETURNS BOOL AS $creator$
7486 BEGIN
7487     EXECUTE $$
7488         CREATE SEQUENCE auditor.$$ || sch || $$_$$ || tbl || $$_pkey_seq;
7489     $$;
7490         RETURN TRUE;
7491 END;
7492 $creator$ LANGUAGE 'plpgsql';
7493
7494 CREATE FUNCTION auditor.create_auditor_history ( sch TEXT, tbl TEXT ) RETURNS BOOL AS $creator$
7495 BEGIN
7496     EXECUTE $$
7497         CREATE TABLE auditor.$$ || sch || $$_$$ || tbl || $$_history (
7498             audit_id    BIGINT                          PRIMARY KEY,
7499             audit_time  TIMESTAMP WITH TIME ZONE        NOT NULL,
7500             audit_action        TEXT                            NOT NULL,
7501             LIKE $$ || sch || $$.$$ || tbl || $$
7502         );
7503     $$;
7504         RETURN TRUE;
7505 END;
7506 $creator$ LANGUAGE 'plpgsql';
7507
7508 CREATE FUNCTION auditor.create_auditor_func    ( sch TEXT, tbl TEXT ) RETURNS BOOL AS $creator$
7509 BEGIN
7510     EXECUTE $$
7511         CREATE FUNCTION auditor.audit_$$ || sch || $$_$$ || tbl || $$_func ()
7512         RETURNS TRIGGER AS $func$
7513         BEGIN
7514             INSERT INTO auditor.$$ || sch || $$_$$ || tbl || $$_history
7515                 SELECT  nextval('auditor.$$ || sch || $$_$$ || tbl || $$_pkey_seq'),
7516                     now(),
7517                     SUBSTR(TG_OP,1,1),
7518                     OLD.*;
7519             RETURN NULL;
7520         END;
7521         $func$ LANGUAGE 'plpgsql';
7522     $$;
7523         RETURN TRUE;
7524 END;
7525 $creator$ LANGUAGE 'plpgsql';
7526
7527 CREATE FUNCTION auditor.create_auditor_update_trigger ( sch TEXT, tbl TEXT ) RETURNS BOOL AS $creator$
7528 BEGIN
7529     EXECUTE $$
7530         CREATE TRIGGER audit_$$ || sch || $$_$$ || tbl || $$_update_trigger
7531             AFTER UPDATE OR DELETE ON $$ || sch || $$.$$ || tbl || $$ FOR EACH ROW
7532             EXECUTE PROCEDURE auditor.audit_$$ || sch || $$_$$ || tbl || $$_func ();
7533     $$;
7534         RETURN TRUE;
7535 END;
7536 $creator$ LANGUAGE 'plpgsql';
7537
7538 CREATE FUNCTION auditor.create_auditor_lifecycle     ( sch TEXT, tbl TEXT ) RETURNS BOOL AS $creator$
7539 BEGIN
7540     EXECUTE $$
7541         CREATE VIEW auditor.$$ || sch || $$_$$ || tbl || $$_lifecycle AS
7542             SELECT      -1, now() as audit_time, '-' as audit_action, *
7543               FROM      $$ || sch || $$.$$ || tbl || $$
7544                 UNION ALL
7545             SELECT      *
7546               FROM      auditor.$$ || sch || $$_$$ || tbl || $$_history;
7547     $$;
7548         RETURN TRUE;
7549 END;
7550 $creator$ LANGUAGE 'plpgsql';
7551
7552 DROP FUNCTION IF EXISTS auditor.create_auditor (TEXT, TEXT);
7553
7554 -- The main event
7555
7556 CREATE FUNCTION auditor.create_auditor ( sch TEXT, tbl TEXT ) RETURNS BOOL AS $creator$
7557 BEGIN
7558     PERFORM auditor.create_auditor_seq(sch, tbl);
7559     PERFORM auditor.create_auditor_history(sch, tbl);
7560     PERFORM auditor.create_auditor_func(sch, tbl);
7561     PERFORM auditor.create_auditor_update_trigger(sch, tbl);
7562     PERFORM auditor.create_auditor_lifecycle(sch, tbl);
7563     RETURN TRUE;
7564 END;
7565 $creator$ LANGUAGE 'plpgsql';
7566
7567 ALTER TABLE action.hold_request ADD COLUMN cut_in_line BOOL;
7568
7569 ALTER TABLE action.hold_request
7570 ADD COLUMN mint_condition boolean NOT NULL DEFAULT TRUE;
7571
7572 ALTER TABLE action.hold_request
7573 ADD COLUMN shelf_expire_time TIMESTAMPTZ;
7574
7575 ALTER TABLE action.hold_request DROP CONSTRAINT hold_request_current_copy_fkey;
7576
7577 ALTER TABLE action.hold_request DROP CONSTRAINT hold_request_hold_type_check;
7578
7579 UPDATE config.index_normalizer SET param_count = 0 WHERE func = 'split_date_range';
7580
7581 CREATE INDEX actor_usr_usrgroup_idx ON actor.usr (usrgroup);
7582
7583 -- Add claims_never_checked_out_count to actor.usr, related history
7584
7585 ALTER TABLE actor.usr ADD COLUMN
7586         claims_never_checked_out_count  INT         NOT NULL DEFAULT 0;
7587
7588 ALTER TABLE AUDITOR.actor_usr_history ADD COLUMN 
7589         claims_never_checked_out_count INT;
7590
7591 DROP VIEW IF EXISTS auditor.actor_usr_lifecycle;
7592
7593 SELECT auditor.create_auditor_lifecycle( 'actor', 'usr' );
7594
7595 -----------
7596
7597 CREATE OR REPLACE FUNCTION action.circulation_claims_returned () RETURNS TRIGGER AS $$
7598 BEGIN
7599         IF OLD.stop_fines IS NULL OR OLD.stop_fines <> NEW.stop_fines THEN
7600                 IF NEW.stop_fines = 'CLAIMSRETURNED' THEN
7601                         UPDATE actor.usr SET claims_returned_count = claims_returned_count + 1 WHERE id = NEW.usr;
7602                 END IF;
7603                 IF NEW.stop_fines = 'CLAIMSNEVERCHECKEDOUT' THEN
7604                         UPDATE actor.usr SET claims_never_checked_out_count = claims_never_checked_out_count + 1 WHERE id = NEW.usr;
7605                 END IF;
7606                 IF NEW.stop_fines = 'LOST' THEN
7607                         UPDATE asset.copy SET status = 3 WHERE id = NEW.target_copy;
7608                 END IF;
7609         END IF;
7610         RETURN NEW;
7611 END;
7612 $$ LANGUAGE 'plpgsql';
7613
7614 -- Create new table acq.fund_allocation_percent
7615 -- Populate it from acq.fund_allocation
7616 -- Convert all percentages to amounts in acq.fund_allocation
7617
7618 CREATE TABLE acq.fund_allocation_percent
7619 (
7620     id                   SERIAL            PRIMARY KEY,
7621     funding_source       INT               NOT NULL REFERENCES acq.funding_source
7622                                                DEFERRABLE INITIALLY DEFERRED,
7623     org                  INT               NOT NULL REFERENCES actor.org_unit
7624                                                DEFERRABLE INITIALLY DEFERRED,
7625     fund_code            TEXT,
7626     percent              NUMERIC           NOT NULL,
7627     allocator            INTEGER           NOT NULL REFERENCES actor.usr
7628                                                DEFERRABLE INITIALLY DEFERRED,
7629     note                 TEXT,
7630     create_time          TIMESTAMPTZ       NOT NULL DEFAULT now(),
7631     CONSTRAINT logical_key UNIQUE( funding_source, org, fund_code ),
7632     CONSTRAINT percentage_range CHECK( percent >= 0 AND percent <= 100 )
7633 );
7634
7635 -- Trigger function to validate combination of org_unit and fund_code
7636
7637 CREATE OR REPLACE FUNCTION acq.fund_alloc_percent_val()
7638 RETURNS TRIGGER AS $$
7639 --
7640 DECLARE
7641 --
7642 dummy int := 0;
7643 --
7644 BEGIN
7645     SELECT
7646         1
7647     INTO
7648         dummy
7649     FROM
7650         acq.fund
7651     WHERE
7652         org = NEW.org
7653         AND code = NEW.fund_code
7654         LIMIT 1;
7655     --
7656     IF dummy = 1 then
7657         RETURN NEW;
7658     ELSE
7659         RAISE EXCEPTION 'No fund exists for org % and code %', NEW.org, NEW.fund_code;
7660     END IF;
7661 END;
7662 $$ LANGUAGE plpgsql;
7663
7664 CREATE TRIGGER acq_fund_alloc_percent_val_trig
7665     BEFORE INSERT OR UPDATE ON acq.fund_allocation_percent
7666     FOR EACH ROW EXECUTE PROCEDURE acq.fund_alloc_percent_val();
7667
7668 CREATE OR REPLACE FUNCTION acq.fap_limit_100()
7669 RETURNS TRIGGER AS $$
7670 DECLARE
7671 --
7672 total_percent numeric;
7673 --
7674 BEGIN
7675     SELECT
7676         sum( percent )
7677     INTO
7678         total_percent
7679     FROM
7680         acq.fund_allocation_percent AS fap
7681     WHERE
7682         fap.funding_source = NEW.funding_source;
7683     --
7684     IF total_percent > 100 THEN
7685         RAISE EXCEPTION 'Total percentages exceed 100 for funding_source %',
7686             NEW.funding_source;
7687     ELSE
7688         RETURN NEW;
7689     END IF;
7690 END;
7691 $$ LANGUAGE plpgsql;
7692
7693 CREATE TRIGGER acqfap_limit_100_trig
7694     AFTER INSERT OR UPDATE ON acq.fund_allocation_percent
7695     FOR EACH ROW EXECUTE PROCEDURE acq.fap_limit_100();
7696
7697 -- Populate new table from acq.fund_allocation
7698
7699 INSERT INTO acq.fund_allocation_percent
7700 (
7701     funding_source,
7702     org,
7703     fund_code,
7704     percent,
7705     allocator,
7706     note,
7707     create_time
7708 )
7709     SELECT
7710         fa.funding_source,
7711         fund.org,
7712         fund.code,
7713         fa.percent,
7714         fa.allocator,
7715         fa.note,
7716         fa.create_time
7717     FROM
7718         acq.fund_allocation AS fa
7719             INNER JOIN acq.fund AS fund
7720                 ON ( fa.fund = fund.id )
7721     WHERE
7722         fa.percent is not null
7723     ORDER BY
7724         fund.org;
7725
7726 -- Temporary function to convert percentages to amounts in acq.fund_allocation
7727
7728 -- Algorithm to apply to each funding source:
7729
7730 -- 1. Add up the credits.
7731 -- 2. Add up the percentages.
7732 -- 3. Multiply the sum of the percentages times the sum of the credits.  Drop any
7733 --    fractional cents from the result.  This is the total amount to be allocated.
7734 -- 4. For each allocation: multiply the percentage by the total allocation.  Drop any
7735 --    fractional cents to get a preliminary amount.
7736 -- 5. Add up the preliminary amounts for all the allocations.
7737 -- 6. Subtract the results of step 5 from the result of step 3.  The difference is the
7738 --    number of residual cents (resulting from having dropped fractional cents) that
7739 --    must be distributed across the funds in order to make the total of the amounts
7740 --    match the total allocation.
7741 -- 7. Make a second pass through the allocations, in decreasing order of the fractional
7742 --    cents that were dropped from their amounts in step 4.  Add one cent to the amount
7743 --    for each successive fund, until all the residual cents have been exhausted.
7744
7745 -- Result: the sum of the individual allocations now equals the total to be allocated,
7746 -- to the penny.  The individual amounts match the percentages as closely as possible,
7747 -- given the constraint that the total must match.
7748
7749 CREATE OR REPLACE FUNCTION acq.apply_percents()
7750 RETURNS VOID AS $$
7751 declare
7752 --
7753 tot              RECORD;
7754 fund             RECORD;
7755 tot_cents        INTEGER;
7756 src              INTEGER;
7757 id               INTEGER[];
7758 curr_id          INTEGER;
7759 pennies          NUMERIC[];
7760 curr_amount      NUMERIC;
7761 i                INTEGER;
7762 total_of_floors  INTEGER;
7763 total_percent    NUMERIC;
7764 total_allocation INTEGER;
7765 residue          INTEGER;
7766 --
7767 begin
7768         RAISE NOTICE 'Applying percents';
7769         FOR tot IN
7770                 SELECT
7771                         fsrc.funding_source,
7772                         sum( fsrc.amount ) AS total
7773                 FROM
7774                         acq.funding_source_credit AS fsrc
7775                 WHERE fsrc.funding_source IN
7776                         ( SELECT DISTINCT fa.funding_source
7777                           FROM acq.fund_allocation AS fa
7778                           WHERE fa.percent IS NOT NULL )
7779                 GROUP BY
7780                         fsrc.funding_source
7781         LOOP
7782                 tot_cents = floor( tot.total * 100 );
7783                 src = tot.funding_source;
7784                 RAISE NOTICE 'Funding source % total %',
7785                         src, tot_cents;
7786                 i := 0;
7787                 total_of_floors := 0;
7788                 total_percent := 0;
7789                 --
7790                 FOR fund in
7791                         SELECT
7792                                 fa.id,
7793                                 fa.percent,
7794                                 floor( fa.percent * tot_cents / 100 ) as floor_pennies
7795                         FROM
7796                                 acq.fund_allocation AS fa
7797                         WHERE
7798                                 fa.funding_source = src
7799                                 AND fa.percent IS NOT NULL
7800                         ORDER BY
7801                                 mod( fa.percent * tot_cents / 100, 1 ),
7802                                 fa.fund,
7803                                 fa.id
7804                 LOOP
7805                         RAISE NOTICE '   %: %',
7806                                 fund.id,
7807                                 fund.floor_pennies;
7808                         i := i + 1;
7809                         id[i] = fund.id;
7810                         pennies[i] = fund.floor_pennies;
7811                         total_percent := total_percent + fund.percent;
7812                         total_of_floors := total_of_floors + pennies[i];
7813                 END LOOP;
7814                 total_allocation := floor( total_percent * tot_cents /100 );
7815                 RAISE NOTICE 'Total before distributing residue: %', total_of_floors;
7816                 residue := total_allocation - total_of_floors;
7817                 RAISE NOTICE 'Residue: %', residue;
7818                 --
7819                 -- Post the calculated amounts, revising as needed to
7820                 -- distribute the rounding error
7821                 --
7822                 WHILE i > 0 LOOP
7823                         IF residue > 0 THEN
7824                                 pennies[i] = pennies[i] + 1;
7825                                 residue := residue - 1;
7826                         END IF;
7827                         --
7828                         -- Post amount
7829                         --
7830                         curr_id     := id[i];
7831                         curr_amount := trunc( pennies[i] / 100, 2 );
7832                         --
7833                         UPDATE
7834                                 acq.fund_allocation AS fa
7835                         SET
7836                                 amount = curr_amount,
7837                                 percent = NULL
7838                         WHERE
7839                                 fa.id = curr_id;
7840                         --
7841                         RAISE NOTICE '   ID % and amount %',
7842                                 curr_id,
7843                                 curr_amount;
7844                         i = i - 1;
7845                 END LOOP;
7846         END LOOP;
7847 end;
7848 $$ LANGUAGE 'plpgsql';
7849
7850 -- Run the temporary function
7851
7852 select * from acq.apply_percents();
7853
7854 -- Drop the temporary function now that we're done with it
7855
7856 DROP FUNCTION IF EXISTS acq.apply_percents();
7857
7858 -- Eliminate acq.fund_allocation.percent, which has been moved to the acq.fund_allocation_percent table.
7859
7860 -- If the following step fails, it's probably because there are still some non-null percent values in
7861 -- acq.fund_allocation.  They should have all been converted to amounts, and then set to null, by a
7862 -- previous upgrade step based on 0049.schema.acq_funding_allocation_percent.sql.  If there are any
7863 -- non-null values, then either that step didn't run, or it didn't work, or some non-null values
7864 -- slipped in afterwards.
7865
7866 -- To convert any remaining percents to amounts: create, run, and then drop the temporary stored
7867 -- procedure acq.apply_percents() as defined above.
7868
7869 ALTER TABLE acq.fund_allocation
7870 ALTER COLUMN amount SET NOT NULL;
7871
7872 CREATE OR REPLACE VIEW acq.fund_allocation_total AS
7873     SELECT  fund,
7874             SUM(a.amount * acq.exchange_ratio(s.currency_type, f.currency_type))::NUMERIC(100,2) AS amount
7875     FROM acq.fund_allocation a
7876          JOIN acq.fund f ON (a.fund = f.id)
7877          JOIN acq.funding_source s ON (a.funding_source = s.id)
7878     GROUP BY 1;
7879
7880 CREATE OR REPLACE VIEW acq.funding_source_allocation_total AS
7881     SELECT  funding_source,
7882             SUM(a.amount)::NUMERIC(100,2) AS amount
7883     FROM  acq.fund_allocation a
7884     GROUP BY 1;
7885
7886 ALTER TABLE acq.fund_allocation
7887 DROP COLUMN percent;
7888
7889 CREATE TABLE asset.copy_location_order
7890 (
7891         id              SERIAL           PRIMARY KEY,
7892         location        INT              NOT NULL
7893                                              REFERENCES asset.copy_location
7894                                              ON DELETE CASCADE
7895                                              DEFERRABLE INITIALLY DEFERRED,
7896         org             INT              NOT NULL
7897                                              REFERENCES actor.org_unit
7898                                              ON DELETE CASCADE
7899                                              DEFERRABLE INITIALLY DEFERRED,
7900         position        INT              NOT NULL DEFAULT 0,
7901         CONSTRAINT acplo_once_per_org UNIQUE ( location, org )
7902 );
7903
7904 ALTER TABLE money.credit_card_payment ADD COLUMN cc_processor TEXT;
7905
7906 -- If you ran this before its most recent incarnation:
7907 -- delete from config.upgrade_log where version = '0328';
7908 -- alter table money.credit_card_payment drop column cc_name;
7909
7910 ALTER TABLE money.credit_card_payment ADD COLUMN cc_first_name TEXT;
7911 ALTER TABLE money.credit_card_payment ADD COLUMN cc_last_name TEXT;
7912
7913 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$
7914 DECLARE
7915     current_group    permission.grp_tree%ROWTYPE;
7916     user_object    actor.usr%ROWTYPE;
7917     item_object    asset.copy%ROWTYPE;
7918     cn_object    asset.call_number%ROWTYPE;
7919     rec_descriptor    metabib.rec_descriptor%ROWTYPE;
7920     current_mp    config.circ_matrix_matchpoint%ROWTYPE;
7921     matchpoint    config.circ_matrix_matchpoint%ROWTYPE;
7922 BEGIN
7923     SELECT INTO user_object * FROM actor.usr WHERE id = match_user;
7924     SELECT INTO item_object * FROM asset.copy WHERE id = match_item;
7925     SELECT INTO cn_object * FROM asset.call_number WHERE id = item_object.call_number;
7926     SELECT INTO rec_descriptor r.* FROM metabib.rec_descriptor r JOIN asset.call_number c USING (record) WHERE c.id = item_object.call_number;
7927     SELECT INTO current_group * FROM permission.grp_tree WHERE id = user_object.profile;
7928
7929     LOOP 
7930         -- for each potential matchpoint for this ou and group ...
7931         FOR current_mp IN
7932             SELECT  m.*
7933               FROM  config.circ_matrix_matchpoint m
7934                     JOIN actor.org_unit_ancestors( context_ou ) d ON (m.org_unit = d.id)
7935                     LEFT JOIN actor.org_unit_proximity p ON (p.from_org = context_ou AND p.to_org = d.id)
7936               WHERE m.grp = current_group.id
7937                     AND m.active
7938                     AND (m.copy_owning_lib IS NULL OR cn_object.owning_lib IN ( SELECT id FROM actor.org_unit_descendants(m.copy_owning_lib) ))
7939                     AND (m.copy_circ_lib   IS NULL OR item_object.circ_lib IN ( SELECT id FROM actor.org_unit_descendants(m.copy_circ_lib)   ))
7940               ORDER BY    CASE WHEN p.prox        IS NULL THEN 999 ELSE p.prox END,
7941                     CASE WHEN m.copy_owning_lib IS NOT NULL
7942                         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 )
7943                         ELSE 0
7944                     END +
7945                     CASE WHEN m.copy_circ_lib IS NOT NULL
7946                         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 )
7947                         ELSE 0
7948                     END +
7949                     CASE WHEN m.is_renewal = renewal        THEN 128 ELSE 0 END +
7950                     CASE WHEN m.juvenile_flag    IS NOT NULL THEN 64 ELSE 0 END +
7951                     CASE WHEN m.circ_modifier    IS NOT NULL THEN 32 ELSE 0 END +
7952                     CASE WHEN m.marc_type        IS NOT NULL THEN 16 ELSE 0 END +
7953                     CASE WHEN m.marc_form        IS NOT NULL THEN 8 ELSE 0 END +
7954                     CASE WHEN m.marc_vr_format    IS NOT NULL THEN 4 ELSE 0 END +
7955                     CASE WHEN m.ref_flag        IS NOT NULL THEN 2 ELSE 0 END +
7956                     CASE WHEN m.usr_age_lower_bound    IS NOT NULL THEN 0.5 ELSE 0 END +
7957                     CASE WHEN m.usr_age_upper_bound    IS NOT NULL THEN 0.5 ELSE 0 END DESC LOOP
7958
7959             IF current_mp.is_renewal IS NOT NULL THEN
7960                 CONTINUE WHEN current_mp.is_renewal <> renewal;
7961             END IF;
7962
7963             IF current_mp.circ_modifier IS NOT NULL THEN
7964                 CONTINUE WHEN current_mp.circ_modifier <> item_object.circ_modifier OR item_object.circ_modifier IS NULL;
7965             END IF;
7966
7967             IF current_mp.marc_type IS NOT NULL THEN
7968                 IF item_object.circ_as_type IS NOT NULL THEN
7969                     CONTINUE WHEN current_mp.marc_type <> item_object.circ_as_type;
7970                 ELSE
7971                     CONTINUE WHEN current_mp.marc_type <> rec_descriptor.item_type;
7972                 END IF;
7973             END IF;
7974
7975             IF current_mp.marc_form IS NOT NULL THEN
7976                 CONTINUE WHEN current_mp.marc_form <> rec_descriptor.item_form;
7977             END IF;
7978
7979             IF current_mp.marc_vr_format IS NOT NULL THEN
7980                 CONTINUE WHEN current_mp.marc_vr_format <> rec_descriptor.vr_format;
7981             END IF;
7982
7983             IF current_mp.ref_flag IS NOT NULL THEN
7984                 CONTINUE WHEN current_mp.ref_flag <> item_object.ref;
7985             END IF;
7986
7987             IF current_mp.juvenile_flag IS NOT NULL THEN
7988                 CONTINUE WHEN current_mp.juvenile_flag <> user_object.juvenile;
7989             END IF;
7990
7991             IF current_mp.usr_age_lower_bound IS NOT NULL THEN
7992                 CONTINUE WHEN user_object.dob IS NULL OR current_mp.usr_age_lower_bound < age(user_object.dob);
7993             END IF;
7994
7995             IF current_mp.usr_age_upper_bound IS NOT NULL THEN
7996                 CONTINUE WHEN user_object.dob IS NULL OR current_mp.usr_age_upper_bound > age(user_object.dob);
7997             END IF;
7998
7999
8000             -- everything was undefined or matched
8001             matchpoint = current_mp;
8002
8003             EXIT WHEN matchpoint.id IS NOT NULL;
8004         END LOOP;
8005
8006         EXIT WHEN current_group.parent IS NULL OR matchpoint.id IS NOT NULL;
8007
8008         SELECT INTO current_group * FROM permission.grp_tree WHERE id = current_group.parent;
8009     END LOOP;
8010
8011     RETURN matchpoint;
8012 END;
8013 $func$ LANGUAGE plpgsql;
8014
8015 CREATE TYPE action.hold_stats AS (
8016     hold_count              INT,
8017     copy_count              INT,
8018     available_count         INT,
8019     total_copy_ratio        FLOAT,
8020     available_copy_ratio    FLOAT
8021 );
8022
8023 CREATE OR REPLACE FUNCTION action.copy_related_hold_stats (copy_id INT) RETURNS action.hold_stats AS $func$
8024 DECLARE
8025     output          action.hold_stats%ROWTYPE;
8026     hold_count      INT := 0;
8027     copy_count      INT := 0;
8028     available_count INT := 0;
8029     hold_map_data   RECORD;
8030 BEGIN
8031
8032     output.hold_count := 0;
8033     output.copy_count := 0;
8034     output.available_count := 0;
8035
8036     SELECT  COUNT( DISTINCT m.hold ) INTO hold_count
8037       FROM  action.hold_copy_map m
8038             JOIN action.hold_request h ON (m.hold = h.id)
8039       WHERE m.target_copy = copy_id
8040             AND NOT h.frozen;
8041
8042     output.hold_count := hold_count;
8043
8044     IF output.hold_count > 0 THEN
8045         FOR hold_map_data IN
8046             SELECT  DISTINCT m.target_copy,
8047                     acp.status
8048               FROM  action.hold_copy_map m
8049                     JOIN asset.copy acp ON (m.target_copy = acp.id)
8050                     JOIN action.hold_request h ON (m.hold = h.id)
8051               WHERE m.hold IN ( SELECT DISTINCT hold FROM action.hold_copy_map WHERE target_copy = copy_id ) AND NOT h.frozen
8052         LOOP
8053             output.copy_count := output.copy_count + 1;
8054             IF hold_map_data.status IN (0,7,12) THEN
8055                 output.available_count := output.available_count + 1;
8056             END IF;
8057         END LOOP;
8058         output.total_copy_ratio = output.copy_count::FLOAT / output.hold_count::FLOAT;
8059         output.available_copy_ratio = output.available_count::FLOAT / output.hold_count::FLOAT;
8060
8061     END IF;
8062
8063     RETURN output;
8064
8065 END;
8066 $func$ LANGUAGE PLPGSQL;
8067
8068 ALTER TABLE config.circ_matrix_matchpoint ADD COLUMN total_copy_hold_ratio FLOAT;
8069 ALTER TABLE config.circ_matrix_matchpoint ADD COLUMN available_copy_hold_ratio FLOAT;
8070
8071 ALTER TABLE config.circ_matrix_matchpoint DROP CONSTRAINT ep_once_per_grp_loc_mod_marc;
8072
8073 ALTER TABLE config.circ_matrix_matchpoint ADD COLUMN copy_circ_lib   INT REFERENCES actor.org_unit (id) DEFERRABLE INITIALLY DEFERRED;
8074 ALTER TABLE config.circ_matrix_matchpoint ADD COLUMN copy_owning_lib INT REFERENCES actor.org_unit (id) DEFERRABLE INITIALLY DEFERRED;
8075
8076 ALTER TABLE config.circ_matrix_matchpoint ADD CONSTRAINT ep_once_per_grp_loc_mod_marc UNIQUE (
8077     grp, org_unit, circ_modifier, marc_type, marc_form, marc_vr_format, ref_flag,
8078     juvenile_flag, usr_age_lower_bound, usr_age_upper_bound, is_renewal, copy_circ_lib,
8079     copy_owning_lib
8080 );
8081
8082 -- Return the correct fail_part when the item can't be found
8083 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$
8084 DECLARE
8085     user_object        actor.usr%ROWTYPE;
8086     standing_penalty    config.standing_penalty%ROWTYPE;
8087     item_object        asset.copy%ROWTYPE;
8088     item_status_object    config.copy_status%ROWTYPE;
8089     item_location_object    asset.copy_location%ROWTYPE;
8090     result            action.matrix_test_result;
8091     circ_test        config.circ_matrix_matchpoint%ROWTYPE;
8092     out_by_circ_mod        config.circ_matrix_circ_mod_test%ROWTYPE;
8093     circ_mod_map        config.circ_matrix_circ_mod_test_map%ROWTYPE;
8094     hold_ratio          action.hold_stats%ROWTYPE;
8095     penalty_type         TEXT;
8096     tmp_grp         INT;
8097     items_out        INT;
8098     context_org_list        INT[];
8099     done            BOOL := FALSE;
8100 BEGIN
8101     result.success := TRUE;
8102
8103     -- Fail if the user is BARRED
8104     SELECT INTO user_object * FROM actor.usr WHERE id = match_user;
8105
8106     -- Fail if we couldn't find the user 
8107     IF user_object.id IS NULL THEN
8108         result.fail_part := 'no_user';
8109         result.success := FALSE;
8110         done := TRUE;
8111         RETURN NEXT result;
8112         RETURN;
8113     END IF;
8114
8115     SELECT INTO item_object * FROM asset.copy WHERE id = match_item;
8116
8117     -- Fail if we couldn't find the item 
8118     IF item_object.id IS NULL THEN
8119         result.fail_part := 'no_item';
8120         result.success := FALSE;
8121         done := TRUE;
8122         RETURN NEXT result;
8123         RETURN;
8124     END IF;
8125
8126     SELECT INTO circ_test * FROM action.find_circ_matrix_matchpoint(circ_ou, match_item, match_user, renewal);
8127     result.matchpoint := circ_test.id;
8128
8129     -- Fail if we couldn't find a matchpoint
8130     IF result.matchpoint IS NULL THEN
8131         result.fail_part := 'no_matchpoint';
8132         result.success := FALSE;
8133         done := TRUE;
8134         RETURN NEXT result;
8135     END IF;
8136
8137     IF user_object.barred IS TRUE THEN
8138         result.fail_part := 'actor.usr.barred';
8139         result.success := FALSE;
8140         done := TRUE;
8141         RETURN NEXT result;
8142     END IF;
8143
8144     -- Fail if the item can't circulate
8145     IF item_object.circulate IS FALSE THEN
8146         result.fail_part := 'asset.copy.circulate';
8147         result.success := FALSE;
8148         done := TRUE;
8149         RETURN NEXT result;
8150     END IF;
8151
8152     -- Fail if the item isn't in a circulateable status on a non-renewal
8153     IF NOT renewal AND item_object.status NOT IN ( 0, 7, 8 ) THEN 
8154         result.fail_part := 'asset.copy.status';
8155         result.success := FALSE;
8156         done := TRUE;
8157         RETURN NEXT result;
8158     ELSIF renewal AND item_object.status <> 1 THEN
8159         result.fail_part := 'asset.copy.status';
8160         result.success := FALSE;
8161         done := TRUE;
8162         RETURN NEXT result;
8163     END IF;
8164
8165     -- Fail if the item can't circulate because of the shelving location
8166     SELECT INTO item_location_object * FROM asset.copy_location WHERE id = item_object.location;
8167     IF item_location_object.circulate IS FALSE THEN
8168         result.fail_part := 'asset.copy_location.circulate';
8169         result.success := FALSE;
8170         done := TRUE;
8171         RETURN NEXT result;
8172     END IF;
8173
8174     SELECT INTO context_org_list ARRAY_ACCUM(id) FROM actor.org_unit_full_path( circ_test.org_unit );
8175
8176     -- Fail if the test is set to hard non-circulating
8177     IF circ_test.circulate IS FALSE THEN
8178         result.fail_part := 'config.circ_matrix_test.circulate';
8179         result.success := FALSE;
8180         done := TRUE;
8181         RETURN NEXT result;
8182     END IF;
8183
8184     -- Fail if the total copy-hold ratio is too low
8185     IF circ_test.total_copy_hold_ratio IS NOT NULL THEN
8186         SELECT INTO hold_ratio * FROM action.copy_related_hold_stats(match_item);
8187         IF hold_ratio.total_copy_ratio IS NOT NULL AND hold_ratio.total_copy_ratio < circ_test.total_copy_hold_ratio THEN
8188             result.fail_part := 'config.circ_matrix_test.total_copy_hold_ratio';
8189             result.success := FALSE;
8190             done := TRUE;
8191             RETURN NEXT result;
8192         END IF;
8193     END IF;
8194
8195     -- Fail if the available copy-hold ratio is too low
8196     IF circ_test.available_copy_hold_ratio IS NOT NULL THEN
8197         SELECT INTO hold_ratio * FROM action.copy_related_hold_stats(match_item);
8198         IF hold_ratio.available_copy_ratio IS NOT NULL AND hold_ratio.available_copy_ratio < circ_test.available_copy_hold_ratio THEN
8199             result.fail_part := 'config.circ_matrix_test.available_copy_hold_ratio';
8200             result.success := FALSE;
8201             done := TRUE;
8202             RETURN NEXT result;
8203         END IF;
8204     END IF;
8205
8206     IF renewal THEN
8207         penalty_type = '%RENEW%';
8208     ELSE
8209         penalty_type = '%CIRC%';
8210     END IF;
8211
8212     FOR standing_penalty IN
8213         SELECT  DISTINCT csp.*
8214           FROM  actor.usr_standing_penalty usp
8215                 JOIN config.standing_penalty csp ON (csp.id = usp.standing_penalty)
8216           WHERE usr = match_user
8217                 AND usp.org_unit IN ( SELECT * FROM unnest(context_org_list) )
8218                 AND (usp.stop_date IS NULL or usp.stop_date > NOW())
8219                 AND csp.block_list LIKE penalty_type LOOP
8220
8221         result.fail_part := standing_penalty.name;
8222         result.success := FALSE;
8223         done := TRUE;
8224         RETURN NEXT result;
8225     END LOOP;
8226
8227     -- Fail if the user has too many items with specific circ_modifiers checked out
8228     FOR out_by_circ_mod IN SELECT * FROM config.circ_matrix_circ_mod_test WHERE matchpoint = circ_test.id LOOP
8229         SELECT  INTO items_out COUNT(*)
8230           FROM  action.circulation circ
8231             JOIN asset.copy cp ON (cp.id = circ.target_copy)
8232           WHERE circ.usr = match_user
8233                AND circ.circ_lib IN ( SELECT * FROM unnest(context_org_list) )
8234             AND circ.checkin_time IS NULL
8235             AND (circ.stop_fines IN ('MAXFINES','LONGOVERDUE') OR circ.stop_fines IS NULL)
8236             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);
8237         IF items_out >= out_by_circ_mod.items_out THEN
8238             result.fail_part := 'config.circ_matrix_circ_mod_test';
8239             result.success := FALSE;
8240             done := TRUE;
8241             RETURN NEXT result;
8242         END IF;
8243     END LOOP;
8244
8245     -- If we passed everything, return the successful matchpoint id
8246     IF NOT done THEN
8247         RETURN NEXT result;
8248     END IF;
8249
8250     RETURN;
8251 END;
8252 $func$ LANGUAGE plpgsql;
8253
8254 CREATE TABLE config.remote_account (
8255     id          SERIAL  PRIMARY KEY,
8256     label       TEXT    NOT NULL,
8257     host        TEXT    NOT NULL,   -- name or IP, :port optional
8258     username    TEXT,               -- optional, since we could default to $USER
8259     password    TEXT,               -- optional, since we could use SSH keys, or anonymous login.
8260     account     TEXT,               -- aka profile or FTP "account" command
8261     path        TEXT,               -- aka directory
8262     owner       INT     NOT NULL REFERENCES actor.org_unit (id) DEFERRABLE INITIALLY DEFERRED,
8263     last_activity TIMESTAMP WITH TIME ZONE
8264 );
8265
8266 CREATE TABLE acq.edi_account (      -- similar tables can extend remote_account for other parts of EG
8267     provider    INT     NOT NULL REFERENCES acq.provider          (id) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED,
8268     in_dir      TEXT,   -- incoming messages dir (probably different than config.remote_account.path, the outgoing dir)
8269         vendcode    TEXT,
8270         vendacct    TEXT
8271
8272 ) INHERITS (config.remote_account);
8273
8274 ALTER TABLE acq.edi_account ADD PRIMARY KEY (id);
8275
8276 CREATE TABLE acq.claim_type (
8277         id             SERIAL           PRIMARY KEY,
8278         org_unit       INT              NOT NULL REFERENCES actor.org_unit(id)
8279                                                  DEFERRABLE INITIALLY DEFERRED,
8280         code           TEXT             NOT NULL,
8281         description    TEXT             NOT NULL,
8282         CONSTRAINT claim_type_once_per_org UNIQUE ( org_unit, code )
8283 );
8284
8285 CREATE TABLE acq.claim (
8286         id             SERIAL           PRIMARY KEY,
8287         type           INT              NOT NULL REFERENCES acq.claim_type
8288                                                  DEFERRABLE INITIALLY DEFERRED,
8289         lineitem_detail BIGINT          NOT NULL REFERENCES acq.lineitem_detail
8290                                                  DEFERRABLE INITIALLY DEFERRED
8291 );
8292
8293 CREATE TABLE acq.claim_policy (
8294         id              SERIAL       PRIMARY KEY,
8295         org_unit        INT          NOT NULL REFERENCES actor.org_unit
8296                                      DEFERRABLE INITIALLY DEFERRED,
8297         name            TEXT         NOT NULL,
8298         description     TEXT         NOT NULL,
8299         CONSTRAINT name_once_per_org UNIQUE (org_unit, name)
8300 );
8301
8302 -- Add a san column for EDI. 
8303 -- See: http://isbn.org/standards/home/isbn/us/san/san-qa.asp
8304
8305 ALTER TABLE acq.provider ADD COLUMN san INT;
8306
8307 ALTER TABLE acq.provider ALTER COLUMN san TYPE TEXT USING lpad(text(san), 7, '0');
8308
8309 -- null edi_default is OK... it has to be, since we have no values in acq.edi_account yet
8310 ALTER TABLE acq.provider ADD COLUMN edi_default INT REFERENCES acq.edi_account (id) DEFERRABLE INITIALLY DEFERRED;
8311
8312 ALTER TABLE acq.provider
8313         ADD COLUMN active BOOL NOT NULL DEFAULT TRUE;
8314
8315 ALTER TABLE acq.provider
8316         ADD COLUMN prepayment_required BOOLEAN NOT NULL DEFAULT FALSE;
8317
8318 ALTER TABLE acq.provider
8319         ADD COLUMN url TEXT;
8320
8321 ALTER TABLE acq.provider
8322         ADD COLUMN email TEXT;
8323
8324 ALTER TABLE acq.provider
8325         ADD COLUMN phone TEXT;
8326
8327 ALTER TABLE acq.provider
8328         ADD COLUMN fax_phone TEXT;
8329
8330 ALTER TABLE acq.provider
8331         ADD COLUMN default_claim_policy INT
8332                 REFERENCES acq.claim_policy
8333                 DEFERRABLE INITIALLY DEFERRED;
8334
8335 ALTER TABLE action.transit_copy
8336 ADD COLUMN prev_dest INTEGER REFERENCES actor.org_unit( id )
8337                                                          DEFERRABLE INITIALLY DEFERRED;
8338
8339 DROP SCHEMA IF EXISTS booking CASCADE;
8340
8341 CREATE SCHEMA booking;
8342
8343 CREATE TABLE booking.resource_type (
8344         id             SERIAL          PRIMARY KEY,
8345         name           TEXT            NOT NULL,
8346         fine_interval  INTERVAL,
8347         fine_amount    DECIMAL(8,2)    NOT NULL DEFAULT 0,
8348         owner          INT             NOT NULL
8349                                        REFERENCES actor.org_unit( id )
8350                                        DEFERRABLE INITIALLY DEFERRED,
8351         catalog_item   BOOLEAN         NOT NULL DEFAULT FALSE,
8352         transferable   BOOLEAN         NOT NULL DEFAULT FALSE,
8353     record         BIGINT          REFERENCES biblio.record_entry (id)
8354                                        DEFERRABLE INITIALLY DEFERRED,
8355     max_fine       NUMERIC(8,2),
8356     elbow_room     INTERVAL,
8357     CONSTRAINT brt_name_and_record_once_per_owner UNIQUE(owner, name, record)
8358 );
8359
8360 CREATE TABLE booking.resource (
8361         id             SERIAL           PRIMARY KEY,
8362         owner          INT              NOT NULL
8363                                         REFERENCES actor.org_unit(id)
8364                                         DEFERRABLE INITIALLY DEFERRED,
8365         type           INT              NOT NULL
8366                                         REFERENCES booking.resource_type(id)
8367                                         DEFERRABLE INITIALLY DEFERRED,
8368         overbook       BOOLEAN          NOT NULL DEFAULT FALSE,
8369         barcode        TEXT             NOT NULL,
8370         deposit        BOOLEAN          NOT NULL DEFAULT FALSE,
8371         deposit_amount DECIMAL(8,2)     NOT NULL DEFAULT 0.00,
8372         user_fee       DECIMAL(8,2)     NOT NULL DEFAULT 0.00,
8373         CONSTRAINT br_unique UNIQUE (owner, barcode)
8374 );
8375
8376 -- For non-catalog items: hijack barcode for name/description
8377
8378 CREATE TABLE booking.resource_attr (
8379         id              SERIAL          PRIMARY KEY,
8380         owner           INT             NOT NULL
8381                                         REFERENCES actor.org_unit(id)
8382                                         DEFERRABLE INITIALLY DEFERRED,
8383         name            TEXT            NOT NULL,
8384         resource_type   INT             NOT NULL
8385                                         REFERENCES booking.resource_type(id)
8386                                         ON DELETE CASCADE
8387                                         DEFERRABLE INITIALLY DEFERRED,
8388         required        BOOLEAN         NOT NULL DEFAULT FALSE,
8389         CONSTRAINT bra_name_once_per_type UNIQUE(resource_type, name)
8390 );
8391
8392 CREATE TABLE booking.resource_attr_value (
8393         id               SERIAL         PRIMARY KEY,
8394         owner            INT            NOT NULL
8395                                         REFERENCES actor.org_unit(id)
8396                                         DEFERRABLE INITIALLY DEFERRED,
8397         attr             INT            NOT NULL
8398                                         REFERENCES booking.resource_attr(id)
8399                                         DEFERRABLE INITIALLY DEFERRED,
8400         valid_value      TEXT           NOT NULL,
8401         CONSTRAINT brav_logical_key UNIQUE(owner, attr, valid_value)
8402 );
8403
8404 CREATE TABLE booking.resource_attr_map (
8405         id               SERIAL         PRIMARY KEY,
8406         resource         INT            NOT NULL
8407                                         REFERENCES booking.resource(id)
8408                                         ON DELETE CASCADE
8409                                         DEFERRABLE INITIALLY DEFERRED,
8410         resource_attr    INT            NOT NULL
8411                                         REFERENCES booking.resource_attr(id)
8412                                         ON DELETE CASCADE
8413                                         DEFERRABLE INITIALLY DEFERRED,
8414         value            INT            NOT NULL
8415                                         REFERENCES booking.resource_attr_value(id)
8416                                         DEFERRABLE INITIALLY DEFERRED,
8417         CONSTRAINT bram_one_value_per_attr UNIQUE(resource, resource_attr)
8418 );
8419
8420 CREATE TABLE booking.reservation (
8421         request_time     TIMESTAMPTZ   NOT NULL DEFAULT now(),
8422         start_time       TIMESTAMPTZ,
8423         end_time         TIMESTAMPTZ,
8424         capture_time     TIMESTAMPTZ,
8425         cancel_time      TIMESTAMPTZ,
8426         pickup_time      TIMESTAMPTZ,
8427         return_time      TIMESTAMPTZ,
8428         booking_interval INTERVAL,
8429         fine_interval    INTERVAL,
8430         fine_amount      DECIMAL(8,2),
8431         target_resource_type  INT       NOT NULL
8432                                         REFERENCES booking.resource_type(id)
8433                                         ON DELETE CASCADE
8434                                         DEFERRABLE INITIALLY DEFERRED,
8435         target_resource  INT            REFERENCES booking.resource(id)
8436                                         ON DELETE CASCADE
8437                                         DEFERRABLE INITIALLY DEFERRED,
8438         current_resource INT            REFERENCES booking.resource(id)
8439                                         ON DELETE CASCADE
8440                                         DEFERRABLE INITIALLY DEFERRED,
8441         request_lib      INT            NOT NULL
8442                                         REFERENCES actor.org_unit(id)
8443                                         DEFERRABLE INITIALLY DEFERRED,
8444         pickup_lib       INT            REFERENCES actor.org_unit(id)
8445                                         DEFERRABLE INITIALLY DEFERRED,
8446         capture_staff    INT            REFERENCES actor.usr(id)
8447                                         DEFERRABLE INITIALLY DEFERRED,
8448     max_fine         NUMERIC(8,2)
8449 ) INHERITS (money.billable_xact);
8450
8451 ALTER TABLE booking.reservation ADD PRIMARY KEY (id);
8452
8453 ALTER TABLE booking.reservation
8454         ADD CONSTRAINT booking_reservation_usr_fkey
8455         FOREIGN KEY (usr) REFERENCES actor.usr (id)
8456         DEFERRABLE INITIALLY DEFERRED;
8457
8458 CREATE TABLE booking.reservation_attr_value_map (
8459         id               SERIAL         PRIMARY KEY,
8460         reservation      INT            NOT NULL
8461                                         REFERENCES booking.reservation(id)
8462                                         ON DELETE CASCADE
8463                                         DEFERRABLE INITIALLY DEFERRED,
8464         attr_value       INT            NOT NULL
8465                                         REFERENCES booking.resource_attr_value(id)
8466                                         ON DELETE CASCADE
8467                                         DEFERRABLE INITIALLY DEFERRED,
8468         CONSTRAINT bravm_logical_key UNIQUE(reservation, attr_value)
8469 );
8470
8471 -- represents a circ chain summary
8472 CREATE TYPE action.circ_chain_summary AS (
8473     num_circs INTEGER,
8474     start_time TIMESTAMP WITH TIME ZONE,
8475     checkout_workstation TEXT,
8476     last_renewal_time TIMESTAMP WITH TIME ZONE, -- NULL if no renewals
8477     last_stop_fines TEXT,
8478     last_stop_fines_time TIMESTAMP WITH TIME ZONE,
8479     last_renewal_workstation TEXT, -- NULL if no renewals
8480     last_checkin_workstation TEXT,
8481     last_checkin_time TIMESTAMP WITH TIME ZONE,
8482     last_checkin_scan_time TIMESTAMP WITH TIME ZONE
8483 );
8484
8485 CREATE OR REPLACE FUNCTION action.circ_chain ( ctx_circ_id INTEGER ) RETURNS SETOF action.circulation AS $$
8486 DECLARE
8487     tmp_circ action.circulation%ROWTYPE;
8488     circ_0 action.circulation%ROWTYPE;
8489 BEGIN
8490
8491     SELECT INTO tmp_circ * FROM action.circulation WHERE id = ctx_circ_id;
8492
8493     IF tmp_circ IS NULL THEN
8494         RETURN NEXT tmp_circ;
8495     END IF;
8496     circ_0 := tmp_circ;
8497
8498     -- find the front of the chain
8499     WHILE TRUE LOOP
8500         SELECT INTO tmp_circ * FROM action.circulation WHERE id = tmp_circ.parent_circ;
8501         IF tmp_circ IS NULL THEN
8502             EXIT;
8503         END IF;
8504         circ_0 := tmp_circ;
8505     END LOOP;
8506
8507     -- now send the circs to the caller, oldest to newest
8508     tmp_circ := circ_0;
8509     WHILE TRUE LOOP
8510         IF tmp_circ IS NULL THEN
8511             EXIT;
8512         END IF;
8513         RETURN NEXT tmp_circ;
8514         SELECT INTO tmp_circ * FROM action.circulation WHERE parent_circ = tmp_circ.id;
8515     END LOOP;
8516
8517 END;
8518 $$ LANGUAGE 'plpgsql';
8519
8520 CREATE OR REPLACE FUNCTION action.summarize_circ_chain ( ctx_circ_id INTEGER ) RETURNS action.circ_chain_summary AS $$
8521
8522 DECLARE
8523
8524     -- first circ in the chain
8525     circ_0 action.circulation%ROWTYPE;
8526
8527     -- last circ in the chain
8528     circ_n action.circulation%ROWTYPE;
8529
8530     -- circ chain under construction
8531     chain action.circ_chain_summary;
8532     tmp_circ action.circulation%ROWTYPE;
8533
8534 BEGIN
8535     
8536     chain.num_circs := 0;
8537     FOR tmp_circ IN SELECT * FROM action.circ_chain(ctx_circ_id) LOOP
8538
8539         IF chain.num_circs = 0 THEN
8540             circ_0 := tmp_circ;
8541         END IF;
8542
8543         chain.num_circs := chain.num_circs + 1;
8544         circ_n := tmp_circ;
8545     END LOOP;
8546
8547     chain.start_time := circ_0.xact_start;
8548     chain.last_stop_fines := circ_n.stop_fines;
8549     chain.last_stop_fines_time := circ_n.stop_fines_time;
8550     chain.last_checkin_time := circ_n.checkin_time;
8551     chain.last_checkin_scan_time := circ_n.checkin_scan_time;
8552     SELECT INTO chain.checkout_workstation name FROM actor.workstation WHERE id = circ_0.workstation;
8553     SELECT INTO chain.last_checkin_workstation name FROM actor.workstation WHERE id = circ_n.checkin_workstation;
8554
8555     IF chain.num_circs > 1 THEN
8556         chain.last_renewal_time := circ_n.xact_start;
8557         SELECT INTO chain.last_renewal_workstation name FROM actor.workstation WHERE id = circ_n.workstation;
8558     END IF;
8559
8560     RETURN chain;
8561
8562 END;
8563 $$ LANGUAGE 'plpgsql';
8564
8565 CREATE TRIGGER mat_summary_create_tgr AFTER INSERT ON booking.reservation FOR EACH ROW EXECUTE PROCEDURE money.mat_summary_create ('reservation');
8566 CREATE TRIGGER mat_summary_change_tgr AFTER UPDATE ON booking.reservation FOR EACH ROW EXECUTE PROCEDURE money.mat_summary_update ();
8567 CREATE TRIGGER mat_summary_remove_tgr AFTER DELETE ON booking.reservation FOR EACH ROW EXECUTE PROCEDURE money.mat_summary_delete ();
8568
8569 ALTER TABLE config.standing_penalty
8570         ADD COLUMN org_depth   INTEGER;
8571
8572 CREATE OR REPLACE FUNCTION actor.calculate_system_penalties( match_user INT, context_org INT ) RETURNS SETOF actor.usr_standing_penalty AS $func$
8573 DECLARE
8574     user_object         actor.usr%ROWTYPE;
8575     new_sp_row          actor.usr_standing_penalty%ROWTYPE;
8576     existing_sp_row     actor.usr_standing_penalty%ROWTYPE;
8577     collections_fines   permission.grp_penalty_threshold%ROWTYPE;
8578     max_fines           permission.grp_penalty_threshold%ROWTYPE;
8579     max_overdue         permission.grp_penalty_threshold%ROWTYPE;
8580     max_items_out       permission.grp_penalty_threshold%ROWTYPE;
8581     tmp_grp             INT;
8582     items_overdue       INT;
8583     items_out           INT;
8584     context_org_list    INT[];
8585     current_fines        NUMERIC(8,2) := 0.0;
8586     tmp_fines            NUMERIC(8,2);
8587     tmp_groc            RECORD;
8588     tmp_circ            RECORD;
8589     tmp_org             actor.org_unit%ROWTYPE;
8590     tmp_penalty         config.standing_penalty%ROWTYPE;
8591     tmp_depth           INTEGER;
8592 BEGIN
8593     SELECT INTO user_object * FROM actor.usr WHERE id = match_user;
8594
8595     -- Max fines
8596     SELECT INTO tmp_org * FROM actor.org_unit WHERE id = context_org;
8597
8598     -- Fail if the user has a high fine balance
8599     LOOP
8600         tmp_grp := user_object.profile;
8601         LOOP
8602             SELECT * INTO max_fines FROM permission.grp_penalty_threshold WHERE grp = tmp_grp AND penalty = 1 AND org_unit = tmp_org.id;
8603
8604             IF max_fines.threshold IS NULL THEN
8605                 SELECT parent INTO tmp_grp FROM permission.grp_tree WHERE id = tmp_grp;
8606             ELSE
8607                 EXIT;
8608             END IF;
8609
8610             IF tmp_grp IS NULL THEN
8611                 EXIT;
8612             END IF;
8613         END LOOP;
8614
8615         IF max_fines.threshold IS NOT NULL OR tmp_org.parent_ou IS NULL THEN
8616             EXIT;
8617         END IF;
8618
8619         SELECT * INTO tmp_org FROM actor.org_unit WHERE id = tmp_org.parent_ou;
8620
8621     END LOOP;
8622
8623     IF max_fines.threshold IS NOT NULL THEN
8624
8625         RETURN QUERY
8626             SELECT  *
8627               FROM  actor.usr_standing_penalty
8628               WHERE usr = match_user
8629                     AND org_unit = max_fines.org_unit
8630                     AND (stop_date IS NULL or stop_date > NOW())
8631                     AND standing_penalty = 1;
8632
8633         SELECT INTO context_org_list ARRAY_ACCUM(id) FROM actor.org_unit_full_path( max_fines.org_unit );
8634
8635         SELECT  SUM(f.balance_owed) INTO current_fines
8636           FROM  money.materialized_billable_xact_summary f
8637                 JOIN (
8638                     SELECT  r.id
8639                       FROM  booking.reservation r
8640                       WHERE r.usr = match_user
8641                             AND r.pickup_lib IN (SELECT * FROM unnest(context_org_list))
8642                             AND xact_finish IS NULL
8643                                 UNION ALL
8644                     SELECT  g.id
8645                       FROM  money.grocery g
8646                       WHERE g.usr = match_user
8647                             AND g.billing_location IN (SELECT * FROM unnest(context_org_list))
8648                             AND xact_finish IS NULL
8649                                 UNION ALL
8650                     SELECT  circ.id
8651                       FROM  action.circulation circ
8652                       WHERE circ.usr = match_user
8653                             AND circ.circ_lib IN (SELECT * FROM unnest(context_org_list))
8654                             AND xact_finish IS NULL ) l USING (id);
8655
8656         IF current_fines >= max_fines.threshold THEN
8657             new_sp_row.usr := match_user;
8658             new_sp_row.org_unit := max_fines.org_unit;
8659             new_sp_row.standing_penalty := 1;
8660             RETURN NEXT new_sp_row;
8661         END IF;
8662     END IF;
8663
8664     -- Start over for max overdue
8665     SELECT INTO tmp_org * FROM actor.org_unit WHERE id = context_org;
8666
8667     -- Fail if the user has too many overdue items
8668     LOOP
8669         tmp_grp := user_object.profile;
8670         LOOP
8671
8672             SELECT * INTO max_overdue FROM permission.grp_penalty_threshold WHERE grp = tmp_grp AND penalty = 2 AND org_unit = tmp_org.id;
8673
8674             IF max_overdue.threshold IS NULL THEN
8675                 SELECT parent INTO tmp_grp FROM permission.grp_tree WHERE id = tmp_grp;
8676             ELSE
8677                 EXIT;
8678             END IF;
8679
8680             IF tmp_grp IS NULL THEN
8681                 EXIT;
8682             END IF;
8683         END LOOP;
8684
8685         IF max_overdue.threshold IS NOT NULL OR tmp_org.parent_ou IS NULL THEN
8686             EXIT;
8687         END IF;
8688
8689         SELECT INTO tmp_org * FROM actor.org_unit WHERE id = tmp_org.parent_ou;
8690
8691     END LOOP;
8692
8693     IF max_overdue.threshold IS NOT NULL THEN
8694
8695         RETURN QUERY
8696             SELECT  *
8697               FROM  actor.usr_standing_penalty
8698               WHERE usr = match_user
8699                     AND org_unit = max_overdue.org_unit
8700                     AND (stop_date IS NULL or stop_date > NOW())
8701                     AND standing_penalty = 2;
8702
8703         SELECT  INTO items_overdue COUNT(*)
8704           FROM  action.circulation circ
8705                 JOIN  actor.org_unit_full_path( max_overdue.org_unit ) fp ON (circ.circ_lib = fp.id)
8706           WHERE circ.usr = match_user
8707             AND circ.checkin_time IS NULL
8708             AND circ.due_date < NOW()
8709             AND (circ.stop_fines = 'MAXFINES' OR circ.stop_fines IS NULL);
8710
8711         IF items_overdue >= max_overdue.threshold::INT THEN
8712             new_sp_row.usr := match_user;
8713             new_sp_row.org_unit := max_overdue.org_unit;
8714             new_sp_row.standing_penalty := 2;
8715             RETURN NEXT new_sp_row;
8716         END IF;
8717     END IF;
8718
8719     -- Start over for max out
8720     SELECT INTO tmp_org * FROM actor.org_unit WHERE id = context_org;
8721
8722     -- Fail if the user has too many checked out items
8723     LOOP
8724         tmp_grp := user_object.profile;
8725         LOOP
8726             SELECT * INTO max_items_out FROM permission.grp_penalty_threshold WHERE grp = tmp_grp AND penalty = 3 AND org_unit = tmp_org.id;
8727
8728             IF max_items_out.threshold IS NULL THEN
8729                 SELECT parent INTO tmp_grp FROM permission.grp_tree WHERE id = tmp_grp;
8730             ELSE
8731                 EXIT;
8732             END IF;
8733
8734             IF tmp_grp IS NULL THEN
8735                 EXIT;
8736             END IF;
8737         END LOOP;
8738
8739         IF max_items_out.threshold IS NOT NULL OR tmp_org.parent_ou IS NULL THEN
8740             EXIT;
8741         END IF;
8742
8743         SELECT INTO tmp_org * FROM actor.org_unit WHERE id = tmp_org.parent_ou;
8744
8745     END LOOP;
8746
8747
8748     -- Fail if the user has too many items checked out
8749     IF max_items_out.threshold IS NOT NULL THEN
8750
8751         RETURN QUERY
8752             SELECT  *
8753               FROM  actor.usr_standing_penalty
8754               WHERE usr = match_user
8755                     AND org_unit = max_items_out.org_unit
8756                     AND (stop_date IS NULL or stop_date > NOW())
8757                     AND standing_penalty = 3;
8758
8759         SELECT  INTO items_out COUNT(*)
8760           FROM  action.circulation circ
8761                 JOIN  actor.org_unit_full_path( max_items_out.org_unit ) fp ON (circ.circ_lib = fp.id)
8762           WHERE circ.usr = match_user
8763                 AND circ.checkin_time IS NULL
8764                 AND (circ.stop_fines IN ('MAXFINES','LONGOVERDUE') OR circ.stop_fines IS NULL);
8765
8766            IF items_out >= max_items_out.threshold::INT THEN
8767             new_sp_row.usr := match_user;
8768             new_sp_row.org_unit := max_items_out.org_unit;
8769             new_sp_row.standing_penalty := 3;
8770             RETURN NEXT new_sp_row;
8771            END IF;
8772     END IF;
8773
8774     -- Start over for collections warning
8775     SELECT INTO tmp_org * FROM actor.org_unit WHERE id = context_org;
8776
8777     -- Fail if the user has a collections-level fine balance
8778     LOOP
8779         tmp_grp := user_object.profile;
8780         LOOP
8781             SELECT * INTO max_fines FROM permission.grp_penalty_threshold WHERE grp = tmp_grp AND penalty = 4 AND org_unit = tmp_org.id;
8782
8783             IF max_fines.threshold IS NULL THEN
8784                 SELECT parent INTO tmp_grp FROM permission.grp_tree WHERE id = tmp_grp;
8785             ELSE
8786                 EXIT;
8787             END IF;
8788
8789             IF tmp_grp IS NULL THEN
8790                 EXIT;
8791             END IF;
8792         END LOOP;
8793
8794         IF max_fines.threshold IS NOT NULL OR tmp_org.parent_ou IS NULL THEN
8795             EXIT;
8796         END IF;
8797
8798         SELECT * INTO tmp_org FROM actor.org_unit WHERE id = tmp_org.parent_ou;
8799
8800     END LOOP;
8801
8802     IF max_fines.threshold IS NOT NULL THEN
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 = 4;
8811
8812         SELECT INTO context_org_list ARRAY_ACCUM(id) FROM actor.org_unit_full_path( max_fines.org_unit );
8813
8814         SELECT  SUM(f.balance_owed) INTO current_fines
8815           FROM  money.materialized_billable_xact_summary f
8816                 JOIN (
8817                     SELECT  r.id
8818                       FROM  booking.reservation r
8819                       WHERE r.usr = match_user
8820                             AND r.pickup_lib IN (SELECT * FROM unnest(context_org_list))
8821                             AND r.xact_finish IS NULL
8822                                 UNION ALL
8823                     SELECT  g.id
8824                       FROM  money.grocery g
8825                       WHERE g.usr = match_user
8826                             AND g.billing_location IN (SELECT * FROM unnest(context_org_list))
8827                             AND g.xact_finish IS NULL
8828                                 UNION ALL
8829                     SELECT  circ.id
8830                       FROM  action.circulation circ
8831                       WHERE circ.usr = match_user
8832                             AND circ.circ_lib IN (SELECT * FROM unnest(context_org_list))
8833                             AND circ.xact_finish IS NULL ) l USING (id);
8834
8835         IF current_fines >= max_fines.threshold THEN
8836             new_sp_row.usr := match_user;
8837             new_sp_row.org_unit := max_fines.org_unit;
8838             new_sp_row.standing_penalty := 4;
8839             RETURN NEXT new_sp_row;
8840         END IF;
8841     END IF;
8842
8843     -- Start over for in collections
8844     SELECT INTO tmp_org * FROM actor.org_unit WHERE id = context_org;
8845
8846     -- Remove the in-collections penalty if the user has paid down enough
8847     -- This penalty is different, because this code is not responsible for creating 
8848     -- new in-collections penalties, only for removing them
8849     LOOP
8850         tmp_grp := user_object.profile;
8851         LOOP
8852             SELECT * INTO max_fines FROM permission.grp_penalty_threshold WHERE grp = tmp_grp AND penalty = 30 AND org_unit = tmp_org.id;
8853
8854             IF max_fines.threshold IS NULL THEN
8855                 SELECT parent INTO tmp_grp FROM permission.grp_tree WHERE id = tmp_grp;
8856             ELSE
8857                 EXIT;
8858             END IF;
8859
8860             IF tmp_grp IS NULL THEN
8861                 EXIT;
8862             END IF;
8863         END LOOP;
8864
8865         IF max_fines.threshold IS NOT NULL OR tmp_org.parent_ou IS NULL THEN
8866             EXIT;
8867         END IF;
8868
8869         SELECT * INTO tmp_org FROM actor.org_unit WHERE id = tmp_org.parent_ou;
8870
8871     END LOOP;
8872
8873     IF max_fines.threshold IS NOT NULL THEN
8874
8875         SELECT INTO context_org_list ARRAY_ACCUM(id) FROM actor.org_unit_full_path( max_fines.org_unit );
8876
8877         -- first, see if the user had paid down to the threshold
8878         SELECT  SUM(f.balance_owed) INTO current_fines
8879           FROM  money.materialized_billable_xact_summary f
8880                 JOIN (
8881                     SELECT  r.id
8882                       FROM  booking.reservation r
8883                       WHERE r.usr = match_user
8884                             AND r.pickup_lib IN (SELECT * FROM unnest(context_org_list))
8885                             AND r.xact_finish IS NULL
8886                                 UNION ALL
8887                     SELECT  g.id
8888                       FROM  money.grocery g
8889                       WHERE g.usr = match_user
8890                             AND g.billing_location IN (SELECT * FROM unnest(context_org_list))
8891                             AND g.xact_finish IS NULL
8892                                 UNION ALL
8893                     SELECT  circ.id
8894                       FROM  action.circulation circ
8895                       WHERE circ.usr = match_user
8896                             AND circ.circ_lib IN (SELECT * FROM unnest(context_org_list))
8897                             AND circ.xact_finish IS NULL ) l USING (id);
8898
8899         IF current_fines IS NULL OR current_fines <= max_fines.threshold THEN
8900             -- patron has paid down enough
8901
8902             SELECT INTO tmp_penalty * FROM config.standing_penalty WHERE id = 30;
8903
8904             IF tmp_penalty.org_depth IS NOT NULL THEN
8905
8906                 -- since this code is not responsible for applying the penalty, it can't 
8907                 -- guarantee the current context org will match the org at which the penalty 
8908                 --- was applied.  search up the org tree until we hit the configured penalty depth
8909                 SELECT INTO tmp_org * FROM actor.org_unit WHERE id = context_org;
8910                 SELECT INTO tmp_depth depth FROM actor.org_unit_type WHERE id = tmp_org.ou_type;
8911
8912                 WHILE tmp_depth >= tmp_penalty.org_depth LOOP
8913
8914                     RETURN QUERY
8915                         SELECT  *
8916                           FROM  actor.usr_standing_penalty
8917                           WHERE usr = match_user
8918                                 AND org_unit = tmp_org.id
8919                                 AND (stop_date IS NULL or stop_date > NOW())
8920                                 AND standing_penalty = 30;
8921
8922                     IF tmp_org.parent_ou IS NULL THEN
8923                         EXIT;
8924                     END IF;
8925
8926                     SELECT * INTO tmp_org FROM actor.org_unit WHERE id = tmp_org.parent_ou;
8927                     SELECT INTO tmp_depth depth FROM actor.org_unit_type WHERE id = tmp_org.ou_type;
8928                 END LOOP;
8929
8930             ELSE
8931
8932                 -- no penalty depth is defined, look for exact matches
8933
8934                 RETURN QUERY
8935                     SELECT  *
8936                       FROM  actor.usr_standing_penalty
8937                       WHERE usr = match_user
8938                             AND org_unit = max_fines.org_unit
8939                             AND (stop_date IS NULL or stop_date > NOW())
8940                             AND standing_penalty = 30;
8941             END IF;
8942     
8943         END IF;
8944
8945     END IF;
8946
8947     RETURN;
8948 END;
8949 $func$ LANGUAGE plpgsql;
8950
8951 -- Create a default row in acq.fiscal_calendar
8952 -- Add a column in actor.org_unit to point to it
8953
8954 INSERT INTO acq.fiscal_calendar ( id, name ) VALUES ( 1, 'Default' );
8955
8956 ALTER TABLE actor.org_unit
8957 ADD COLUMN fiscal_calendar INT NOT NULL
8958         REFERENCES acq.fiscal_calendar( id )
8959         DEFERRABLE INITIALLY DEFERRED
8960         DEFAULT 1;
8961
8962 ALTER TABLE auditor.actor_org_unit_history
8963         ADD COLUMN fiscal_calendar INT;
8964
8965 DROP VIEW IF EXISTS auditor.actor_org_unit_lifecycle;
8966
8967 SELECT auditor.create_auditor_lifecycle( 'actor', 'org_unit' );
8968
8969 ALTER TABLE acq.funding_source_credit
8970 ADD COLUMN deadline_date TIMESTAMPTZ;
8971
8972 ALTER TABLE acq.funding_source_credit
8973 ADD COLUMN effective_date TIMESTAMPTZ NOT NULL DEFAULT now();
8974
8975 INSERT INTO config.standing_penalty (id,name,label) VALUES (30,'PATRON_IN_COLLECTIONS','Patron has been referred to a collections agency');
8976
8977 CREATE TABLE acq.fund_transfer (
8978         id               SERIAL         PRIMARY KEY,
8979         src_fund         INT            NOT NULL REFERENCES acq.fund( id )
8980                                         DEFERRABLE INITIALLY DEFERRED,
8981         src_amount       NUMERIC        NOT NULL,
8982         dest_fund        INT            REFERENCES acq.fund( id )
8983                                         DEFERRABLE INITIALLY DEFERRED,
8984         dest_amount      NUMERIC,
8985         transfer_time    TIMESTAMPTZ    NOT NULL DEFAULT now(),
8986         transfer_user    INT            NOT NULL REFERENCES actor.usr( id )
8987                                         DEFERRABLE INITIALLY DEFERRED,
8988         note             TEXT,
8989     funding_source_credit INTEGER   NOT NULL
8990                                         REFERENCES acq.funding_source_credit(id)
8991                                         DEFERRABLE INITIALLY DEFERRED
8992 );
8993
8994 CREATE INDEX acqftr_usr_idx
8995 ON acq.fund_transfer( transfer_user );
8996
8997 COMMENT ON TABLE acq.fund_transfer IS $$
8998 /*
8999  * Copyright (C) 2009  Georgia Public Library Service
9000  * Scott McKellar <scott@esilibrary.com>
9001  *
9002  * Fund Transfer
9003  *
9004  * Each row represents the transfer of money from a source fund
9005  * to a destination fund.  There should be corresponding entries
9006  * in acq.fund_allocation.  The purpose of acq.fund_transfer is
9007  * to record how much money moved from which fund to which other
9008  * fund.
9009  * 
9010  * The presence of two amount fields, rather than one, reflects
9011  * the possibility that the two funds are denominated in different
9012  * currencies.  If they use the same currency type, the two
9013  * amounts should be the same.
9014  *
9015  * ****
9016  *
9017  * This program is free software; you can redistribute it and/or
9018  * modify it under the terms of the GNU General Public License
9019  * as published by the Free Software Foundation; either version 2
9020  * of the License, or (at your option) any later version.
9021  *
9022  * This program is distributed in the hope that it will be useful,
9023  * but WITHOUT ANY WARRANTY; without even the implied warranty of
9024  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
9025  * GNU General Public License for more details.
9026  */
9027 $$;
9028
9029 CREATE TABLE acq.claim_event_type (
9030         id             SERIAL           PRIMARY KEY,
9031         org_unit       INT              NOT NULL REFERENCES actor.org_unit(id)
9032                                                  DEFERRABLE INITIALLY DEFERRED,
9033         code           TEXT             NOT NULL,
9034         description    TEXT             NOT NULL,
9035         library_initiated BOOL          NOT NULL DEFAULT FALSE,
9036         CONSTRAINT event_type_once_per_org UNIQUE ( org_unit, code )
9037 );
9038
9039 CREATE TABLE acq.claim_event (
9040         id             BIGSERIAL        PRIMARY KEY,
9041         type           INT              NOT NULL REFERENCES acq.claim_event_type
9042                                                  DEFERRABLE INITIALLY DEFERRED,
9043         claim          SERIAL           NOT NULL REFERENCES acq.claim
9044                                                  DEFERRABLE INITIALLY DEFERRED,
9045         event_date     TIMESTAMPTZ      NOT NULL DEFAULT now(),
9046         creator        INT              NOT NULL REFERENCES actor.usr
9047                                                  DEFERRABLE INITIALLY DEFERRED,
9048         note           TEXT
9049 );
9050
9051 CREATE INDEX claim_event_claim_date_idx ON acq.claim_event( claim, event_date );
9052
9053 CREATE OR REPLACE FUNCTION actor.usr_purge_data(
9054         src_usr  IN INTEGER,
9055         dest_usr IN INTEGER
9056 ) RETURNS VOID AS $$
9057 DECLARE
9058         suffix TEXT;
9059         renamable_row RECORD;
9060 BEGIN
9061
9062         UPDATE actor.usr SET
9063                 active = FALSE,
9064                 card = NULL,
9065                 mailing_address = NULL,
9066                 billing_address = NULL
9067         WHERE id = src_usr;
9068
9069         -- acq.*
9070         UPDATE acq.fund_allocation SET allocator = dest_usr WHERE allocator = src_usr;
9071         UPDATE acq.lineitem SET creator = dest_usr WHERE creator = src_usr;
9072         UPDATE acq.lineitem SET editor = dest_usr WHERE editor = src_usr;
9073         UPDATE acq.lineitem SET selector = dest_usr WHERE selector = src_usr;
9074         UPDATE acq.lineitem_note SET creator = dest_usr WHERE creator = src_usr;
9075         UPDATE acq.lineitem_note SET editor = dest_usr WHERE editor = src_usr;
9076         DELETE FROM acq.lineitem_usr_attr_definition WHERE usr = src_usr;
9077
9078         -- Update with a rename to avoid collisions
9079         FOR renamable_row in
9080                 SELECT id, name
9081                 FROM   acq.picklist
9082                 WHERE  owner = src_usr
9083         LOOP
9084                 suffix := ' (' || src_usr || ')';
9085                 LOOP
9086                         BEGIN
9087                                 UPDATE  acq.picklist
9088                                 SET     owner = dest_usr, name = name || suffix
9089                                 WHERE   id = renamable_row.id;
9090                         EXCEPTION WHEN unique_violation THEN
9091                                 suffix := suffix || ' ';
9092                                 CONTINUE;
9093                         END;
9094                         EXIT;
9095                 END LOOP;
9096         END LOOP;
9097
9098         UPDATE acq.picklist SET creator = dest_usr WHERE creator = src_usr;
9099         UPDATE acq.picklist SET editor = dest_usr WHERE editor = src_usr;
9100         UPDATE acq.po_note SET creator = dest_usr WHERE creator = src_usr;
9101         UPDATE acq.po_note SET editor = dest_usr WHERE editor = src_usr;
9102         UPDATE acq.purchase_order SET owner = dest_usr WHERE owner = src_usr;
9103         UPDATE acq.purchase_order SET creator = dest_usr WHERE creator = src_usr;
9104         UPDATE acq.purchase_order SET editor = dest_usr WHERE editor = src_usr;
9105         UPDATE acq.claim_event SET creator = dest_usr WHERE creator = src_usr;
9106
9107         -- action.*
9108         DELETE FROM action.circulation WHERE usr = src_usr;
9109         UPDATE action.circulation SET circ_staff = dest_usr WHERE circ_staff = src_usr;
9110         UPDATE action.circulation SET checkin_staff = dest_usr WHERE checkin_staff = src_usr;
9111         UPDATE action.hold_notification SET notify_staff = dest_usr WHERE notify_staff = src_usr;
9112         UPDATE action.hold_request SET fulfillment_staff = dest_usr WHERE fulfillment_staff = src_usr;
9113         UPDATE action.hold_request SET requestor = dest_usr WHERE requestor = src_usr;
9114         DELETE FROM action.hold_request WHERE usr = src_usr;
9115         UPDATE action.in_house_use SET staff = dest_usr WHERE staff = src_usr;
9116         UPDATE action.non_cat_in_house_use SET staff = dest_usr WHERE staff = src_usr;
9117         DELETE FROM action.non_cataloged_circulation WHERE patron = src_usr;
9118         UPDATE action.non_cataloged_circulation SET staff = dest_usr WHERE staff = src_usr;
9119         DELETE FROM action.survey_response WHERE usr = src_usr;
9120         UPDATE action.fieldset SET owner = dest_usr WHERE owner = src_usr;
9121
9122         -- actor.*
9123         DELETE FROM actor.card WHERE usr = src_usr;
9124         DELETE FROM actor.stat_cat_entry_usr_map WHERE target_usr = src_usr;
9125
9126         -- The following update is intended to avoid transient violations of a foreign
9127         -- key constraint, whereby actor.usr_address references itself.  It may not be
9128         -- necessary, but it does no harm.
9129         UPDATE actor.usr_address SET replaces = NULL
9130                 WHERE usr = src_usr AND replaces IS NOT NULL;
9131         DELETE FROM actor.usr_address WHERE usr = src_usr;
9132         DELETE FROM actor.usr_note WHERE usr = src_usr;
9133         UPDATE actor.usr_note SET creator = dest_usr WHERE creator = src_usr;
9134         DELETE FROM actor.usr_org_unit_opt_in WHERE usr = src_usr;
9135         UPDATE actor.usr_org_unit_opt_in SET staff = dest_usr WHERE staff = src_usr;
9136         DELETE FROM actor.usr_setting WHERE usr = src_usr;
9137         DELETE FROM actor.usr_standing_penalty WHERE usr = src_usr;
9138         UPDATE actor.usr_standing_penalty SET staff = dest_usr WHERE staff = src_usr;
9139
9140         -- asset.*
9141         UPDATE asset.call_number SET creator = dest_usr WHERE creator = src_usr;
9142         UPDATE asset.call_number SET editor = dest_usr WHERE editor = src_usr;
9143         UPDATE asset.call_number_note SET creator = dest_usr WHERE creator = src_usr;
9144         UPDATE asset.copy SET creator = dest_usr WHERE creator = src_usr;
9145         UPDATE asset.copy SET editor = dest_usr WHERE editor = src_usr;
9146         UPDATE asset.copy_note SET creator = dest_usr WHERE creator = src_usr;
9147
9148         -- auditor.*
9149         DELETE FROM auditor.actor_usr_address_history WHERE id = src_usr;
9150         DELETE FROM auditor.actor_usr_history WHERE id = src_usr;
9151         UPDATE auditor.asset_call_number_history SET creator = dest_usr WHERE creator = src_usr;
9152         UPDATE auditor.asset_call_number_history SET editor  = dest_usr WHERE editor  = src_usr;
9153         UPDATE auditor.asset_copy_history SET creator = dest_usr WHERE creator = src_usr;
9154         UPDATE auditor.asset_copy_history SET editor  = dest_usr WHERE editor  = src_usr;
9155         UPDATE auditor.biblio_record_entry_history SET creator = dest_usr WHERE creator = src_usr;
9156         UPDATE auditor.biblio_record_entry_history SET editor  = dest_usr WHERE editor  = src_usr;
9157
9158         -- biblio.*
9159         UPDATE biblio.record_entry SET creator = dest_usr WHERE creator = src_usr;
9160         UPDATE biblio.record_entry SET editor = dest_usr WHERE editor = src_usr;
9161         UPDATE biblio.record_note SET creator = dest_usr WHERE creator = src_usr;
9162         UPDATE biblio.record_note SET editor = dest_usr WHERE editor = src_usr;
9163
9164         -- container.*
9165         -- Update buckets with a rename to avoid collisions
9166         FOR renamable_row in
9167                 SELECT id, name
9168                 FROM   container.biblio_record_entry_bucket
9169                 WHERE  owner = src_usr
9170         LOOP
9171                 suffix := ' (' || src_usr || ')';
9172                 LOOP
9173                         BEGIN
9174                                 UPDATE  container.biblio_record_entry_bucket
9175                                 SET     owner = dest_usr, name = name || suffix
9176                                 WHERE   id = renamable_row.id;
9177                         EXCEPTION WHEN unique_violation THEN
9178                                 suffix := suffix || ' ';
9179                                 CONTINUE;
9180                         END;
9181                         EXIT;
9182                 END LOOP;
9183         END LOOP;
9184
9185         FOR renamable_row in
9186                 SELECT id, name
9187                 FROM   container.call_number_bucket
9188                 WHERE  owner = src_usr
9189         LOOP
9190                 suffix := ' (' || src_usr || ')';
9191                 LOOP
9192                         BEGIN
9193                                 UPDATE  container.call_number_bucket
9194                                 SET     owner = dest_usr, name = name || suffix
9195                                 WHERE   id = renamable_row.id;
9196                         EXCEPTION WHEN unique_violation THEN
9197                                 suffix := suffix || ' ';
9198                                 CONTINUE;
9199                         END;
9200                         EXIT;
9201                 END LOOP;
9202         END LOOP;
9203
9204         FOR renamable_row in
9205                 SELECT id, name
9206                 FROM   container.copy_bucket
9207                 WHERE  owner = src_usr
9208         LOOP
9209                 suffix := ' (' || src_usr || ')';
9210                 LOOP
9211                         BEGIN
9212                                 UPDATE  container.copy_bucket
9213                                 SET     owner = dest_usr, name = name || suffix
9214                                 WHERE   id = renamable_row.id;
9215                         EXCEPTION WHEN unique_violation THEN
9216                                 suffix := suffix || ' ';
9217                                 CONTINUE;
9218                         END;
9219                         EXIT;
9220                 END LOOP;
9221         END LOOP;
9222
9223         FOR renamable_row in
9224                 SELECT id, name
9225                 FROM   container.user_bucket
9226                 WHERE  owner = src_usr
9227         LOOP
9228                 suffix := ' (' || src_usr || ')';
9229                 LOOP
9230                         BEGIN
9231                                 UPDATE  container.user_bucket
9232                                 SET     owner = dest_usr, name = name || suffix
9233                                 WHERE   id = renamable_row.id;
9234                         EXCEPTION WHEN unique_violation THEN
9235                                 suffix := suffix || ' ';
9236                                 CONTINUE;
9237                         END;
9238                         EXIT;
9239                 END LOOP;
9240         END LOOP;
9241
9242         DELETE FROM container.user_bucket_item WHERE target_user = src_usr;
9243
9244         -- money.*
9245         DELETE FROM money.billable_xact WHERE usr = src_usr;
9246         DELETE FROM money.collections_tracker WHERE usr = src_usr;
9247         UPDATE money.collections_tracker SET collector = dest_usr WHERE collector = src_usr;
9248
9249         -- permission.*
9250         DELETE FROM permission.usr_grp_map WHERE usr = src_usr;
9251         DELETE FROM permission.usr_object_perm_map WHERE usr = src_usr;
9252         DELETE FROM permission.usr_perm_map WHERE usr = src_usr;
9253         DELETE FROM permission.usr_work_ou_map WHERE usr = src_usr;
9254
9255         -- reporter.*
9256         -- Update with a rename to avoid collisions
9257         BEGIN
9258                 FOR renamable_row in
9259                         SELECT id, name
9260                         FROM   reporter.output_folder
9261                         WHERE  owner = src_usr
9262                 LOOP
9263                         suffix := ' (' || src_usr || ')';
9264                         LOOP
9265                                 BEGIN
9266                                         UPDATE  reporter.output_folder
9267                                         SET     owner = dest_usr, name = name || suffix
9268                                         WHERE   id = renamable_row.id;
9269                                 EXCEPTION WHEN unique_violation THEN
9270                                         suffix := suffix || ' ';
9271                                         CONTINUE;
9272                                 END;
9273                                 EXIT;
9274                         END LOOP;
9275                 END LOOP;
9276         EXCEPTION WHEN undefined_table THEN
9277                 -- do nothing
9278         END;
9279
9280         BEGIN
9281                 UPDATE reporter.report SET owner = dest_usr WHERE owner = src_usr;
9282         EXCEPTION WHEN undefined_table THEN
9283                 -- do nothing
9284         END;
9285
9286         -- Update with a rename to avoid collisions
9287         BEGIN
9288                 FOR renamable_row in
9289                         SELECT id, name
9290                         FROM   reporter.report_folder
9291                         WHERE  owner = src_usr
9292                 LOOP
9293                         suffix := ' (' || src_usr || ')';
9294                         LOOP
9295                                 BEGIN
9296                                         UPDATE  reporter.report_folder
9297                                         SET     owner = dest_usr, name = name || suffix
9298                                         WHERE   id = renamable_row.id;
9299                                 EXCEPTION WHEN unique_violation THEN
9300                                         suffix := suffix || ' ';
9301                                         CONTINUE;
9302                                 END;
9303                                 EXIT;
9304                         END LOOP;
9305                 END LOOP;
9306         EXCEPTION WHEN undefined_table THEN
9307                 -- do nothing
9308         END;
9309
9310         BEGIN
9311                 UPDATE reporter.schedule SET runner = dest_usr WHERE runner = src_usr;
9312         EXCEPTION WHEN undefined_table THEN
9313                 -- do nothing
9314         END;
9315
9316         BEGIN
9317                 UPDATE reporter.template SET owner = dest_usr WHERE owner = src_usr;
9318         EXCEPTION WHEN undefined_table THEN
9319                 -- do nothing
9320         END;
9321
9322         -- Update with a rename to avoid collisions
9323         BEGIN
9324                 FOR renamable_row in
9325                         SELECT id, name
9326                         FROM   reporter.template_folder
9327                         WHERE  owner = src_usr
9328                 LOOP
9329                         suffix := ' (' || src_usr || ')';
9330                         LOOP
9331                                 BEGIN
9332                                         UPDATE  reporter.template_folder
9333                                         SET     owner = dest_usr, name = name || suffix
9334                                         WHERE   id = renamable_row.id;
9335                                 EXCEPTION WHEN unique_violation THEN
9336                                         suffix := suffix || ' ';
9337                                         CONTINUE;
9338                                 END;
9339                                 EXIT;
9340                         END LOOP;
9341                 END LOOP;
9342         EXCEPTION WHEN undefined_table THEN
9343         -- do nothing
9344         END;
9345
9346         -- vandelay.*
9347         -- Update with a rename to avoid collisions
9348         FOR renamable_row in
9349                 SELECT id, name
9350                 FROM   vandelay.queue
9351                 WHERE  owner = src_usr
9352         LOOP
9353                 suffix := ' (' || src_usr || ')';
9354                 LOOP
9355                         BEGIN
9356                                 UPDATE  vandelay.queue
9357                                 SET     owner = dest_usr, name = name || suffix
9358                                 WHERE   id = renamable_row.id;
9359                         EXCEPTION WHEN unique_violation THEN
9360                                 suffix := suffix || ' ';
9361                                 CONTINUE;
9362                         END;
9363                         EXIT;
9364                 END LOOP;
9365         END LOOP;
9366
9367 END;
9368 $$ LANGUAGE plpgsql;
9369
9370 COMMENT ON FUNCTION actor.usr_purge_data(INT, INT) IS $$
9371 /**
9372  * Finds rows dependent on a given row in actor.usr and either deletes them
9373  * or reassigns them to a different user.
9374  */
9375 $$;
9376
9377 CREATE OR REPLACE FUNCTION actor.usr_delete(
9378         src_usr  IN INTEGER,
9379         dest_usr IN INTEGER
9380 ) RETURNS VOID AS $$
9381 DECLARE
9382         old_profile actor.usr.profile%type;
9383         old_home_ou actor.usr.home_ou%type;
9384         new_profile actor.usr.profile%type;
9385         new_home_ou actor.usr.home_ou%type;
9386         new_name    text;
9387         new_dob     actor.usr.dob%type;
9388 BEGIN
9389         SELECT
9390                 id || '-PURGED-' || now(),
9391                 profile,
9392                 home_ou,
9393                 dob
9394         INTO
9395                 new_name,
9396                 old_profile,
9397                 old_home_ou,
9398                 new_dob
9399         FROM
9400                 actor.usr
9401         WHERE
9402                 id = src_usr;
9403         --
9404         -- Quit if no such user
9405         --
9406         IF old_profile IS NULL THEN
9407                 RETURN;
9408         END IF;
9409         --
9410         perform actor.usr_purge_data( src_usr, dest_usr );
9411         --
9412         -- Find the root grp_tree and the root org_unit.  This would be simpler if we 
9413         -- could assume that there is only one root.  Theoretically, someday, maybe,
9414         -- there could be multiple roots, so we take extra trouble to get the right ones.
9415         --
9416         SELECT
9417                 id
9418         INTO
9419                 new_profile
9420         FROM
9421                 permission.grp_ancestors( old_profile )
9422         WHERE
9423                 parent is null;
9424         --
9425         SELECT
9426                 id
9427         INTO
9428                 new_home_ou
9429         FROM
9430                 actor.org_unit_ancestors( old_home_ou )
9431         WHERE
9432                 parent_ou is null;
9433         --
9434         -- Truncate date of birth
9435         --
9436         IF new_dob IS NOT NULL THEN
9437                 new_dob := date_trunc( 'year', new_dob );
9438         END IF;
9439         --
9440         UPDATE
9441                 actor.usr
9442                 SET
9443                         card = NULL,
9444                         profile = new_profile,
9445                         usrname = new_name,
9446                         email = NULL,
9447                         passwd = random()::text,
9448                         standing = DEFAULT,
9449                         ident_type = 
9450                         (
9451                                 SELECT MIN( id )
9452                                 FROM config.identification_type
9453                         ),
9454                         ident_value = NULL,
9455                         ident_type2 = NULL,
9456                         ident_value2 = NULL,
9457                         net_access_level = DEFAULT,
9458                         photo_url = NULL,
9459                         prefix = NULL,
9460                         first_given_name = new_name,
9461                         second_given_name = NULL,
9462                         family_name = new_name,
9463                         suffix = NULL,
9464                         alias = NULL,
9465                         day_phone = NULL,
9466                         evening_phone = NULL,
9467                         other_phone = NULL,
9468                         mailing_address = NULL,
9469                         billing_address = NULL,
9470                         home_ou = new_home_ou,
9471                         dob = new_dob,
9472                         active = FALSE,
9473                         master_account = DEFAULT, 
9474                         super_user = DEFAULT,
9475                         barred = FALSE,
9476                         deleted = TRUE,
9477                         juvenile = DEFAULT,
9478                         usrgroup = 0,
9479                         claims_returned_count = DEFAULT,
9480                         credit_forward_balance = DEFAULT,
9481                         last_xact_id = DEFAULT,
9482                         alert_message = NULL,
9483                         create_date = now(),
9484                         expire_date = now()
9485         WHERE
9486                 id = src_usr;
9487 END;
9488 $$ LANGUAGE plpgsql;
9489
9490 COMMENT ON FUNCTION actor.usr_delete(INT, INT) IS $$
9491 /**
9492  * Logically deletes a user.  Removes personally identifiable information,
9493  * and purges associated data in other tables.
9494  */
9495 $$;
9496
9497 -- INSERT INTO config.copy_status (id,name) VALUES (15,oils_i18n_gettext(15, 'On reservation shelf', 'ccs', 'name'));
9498
9499 ALTER TABLE acq.fund
9500 ADD COLUMN rollover BOOL NOT NULL DEFAULT FALSE;
9501
9502 ALTER TABLE acq.fund
9503         ADD COLUMN propagate BOOLEAN NOT NULL DEFAULT TRUE;
9504
9505 -- A fund can't roll over if it doesn't propagate from one year to the next
9506
9507 ALTER TABLE acq.fund
9508         ADD CONSTRAINT acq_fund_rollover_implies_propagate CHECK
9509         ( propagate OR NOT rollover );
9510
9511 ALTER TABLE acq.fund
9512         ADD COLUMN active BOOL NOT NULL DEFAULT TRUE;
9513
9514 ALTER TABLE acq.fund
9515     ADD COLUMN balance_warning_percent INT;
9516
9517 ALTER TABLE acq.fund
9518     ADD COLUMN balance_stop_percent INT;
9519
9520 CREATE VIEW acq.ordered_funding_source_credit AS
9521         SELECT
9522                 CASE WHEN deadline_date IS NULL THEN
9523                         2
9524                 ELSE
9525                         1
9526                 END AS sort_priority,
9527                 CASE WHEN deadline_date IS NULL THEN
9528                         effective_date
9529                 ELSE
9530                         deadline_date
9531                 END AS sort_date,
9532                 id,
9533                 funding_source,
9534                 amount,
9535                 note
9536         FROM
9537                 acq.funding_source_credit;
9538
9539 COMMENT ON VIEW acq.ordered_funding_source_credit IS $$
9540 /*
9541  * Copyright (C) 2009  Georgia Public Library Service
9542  * Scott McKellar <scott@gmail.com>
9543  *
9544  * The acq.ordered_funding_source_credit view is a prioritized
9545  * ordering of funding source credits.  When ordered by the first
9546  * three columns, this view defines the order in which the various
9547  * credits are to be tapped for spending, subject to the allocations
9548  * in the acq.fund_allocation table.
9549  *
9550  * The first column reflects the principle that we should spend
9551  * money with deadlines before spending money without deadlines.
9552  *
9553  * The second column reflects the principle that we should spend the
9554  * oldest money first.  For money with deadlines, that means that we
9555  * spend first from the credit with the earliest deadline.  For
9556  * money without deadlines, we spend first from the credit with the
9557  * earliest effective date.  
9558  *
9559  * The third column is a tie breaker to ensure a consistent
9560  * ordering.
9561  *
9562  * ****
9563  *
9564  * This program is free software; you can redistribute it and/or
9565  * modify it under the terms of the GNU General Public License
9566  * as published by the Free Software Foundation; either version 2
9567  * of the License, or (at your option) any later version.
9568  *
9569  * This program is distributed in the hope that it will be useful,
9570  * but WITHOUT ANY WARRANTY; without even the implied warranty of
9571  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
9572  * GNU General Public License for more details.
9573  */
9574 $$;
9575
9576 CREATE OR REPLACE VIEW money.billable_xact_summary_location_view AS
9577     SELECT  m.*, COALESCE(c.circ_lib, g.billing_location, r.pickup_lib) AS billing_location
9578       FROM  money.materialized_billable_xact_summary m
9579             LEFT JOIN action.circulation c ON (c.id = m.id)
9580             LEFT JOIN money.grocery g ON (g.id = m.id)
9581             LEFT JOIN booking.reservation r ON (r.id = m.id);
9582
9583 CREATE TABLE config.marc21_rec_type_map (
9584     code        TEXT    PRIMARY KEY,
9585     type_val    TEXT    NOT NULL,
9586     blvl_val    TEXT    NOT NULL
9587 );
9588
9589 CREATE TABLE config.marc21_ff_pos_map (
9590     id          SERIAL  PRIMARY KEY,
9591     fixed_field TEXT    NOT NULL,
9592     tag         TEXT    NOT NULL,
9593     rec_type    TEXT    NOT NULL,
9594     start_pos   INT     NOT NULL,
9595     length      INT     NOT NULL,
9596     default_val TEXT    NOT NULL DEFAULT ' '
9597 );
9598
9599 CREATE TABLE config.marc21_physical_characteristic_type_map (
9600     ptype_key   TEXT    PRIMARY KEY,
9601     label       TEXT    NOT NULL -- I18N
9602 );
9603
9604 CREATE TABLE config.marc21_physical_characteristic_subfield_map (
9605     id          SERIAL  PRIMARY KEY,
9606     ptype_key   TEXT    NOT NULL REFERENCES config.marc21_physical_characteristic_type_map (ptype_key) ON DELETE CASCADE ON UPDATE CASCADE,
9607     subfield    TEXT    NOT NULL,
9608     start_pos   INT     NOT NULL,
9609     length      INT     NOT NULL,
9610     label       TEXT    NOT NULL -- I18N
9611 );
9612
9613 CREATE TABLE config.marc21_physical_characteristic_value_map (
9614     id              SERIAL  PRIMARY KEY,
9615     value           TEXT    NOT NULL,
9616     ptype_subfield  INT     NOT NULL REFERENCES config.marc21_physical_characteristic_subfield_map (id),
9617     label           TEXT    NOT NULL -- I18N
9618 );
9619
9620 ----------------------------------
9621 -- MARC21 record structure data --
9622 ----------------------------------
9623
9624 -- Record type map
9625 INSERT INTO config.marc21_rec_type_map (code, type_val, blvl_val) VALUES ('BKS','at','acdm');
9626 INSERT INTO config.marc21_rec_type_map (code, type_val, blvl_val) VALUES ('SER','a','bsi');
9627 INSERT INTO config.marc21_rec_type_map (code, type_val, blvl_val) VALUES ('VIS','gkro','abcdmsi');
9628 INSERT INTO config.marc21_rec_type_map (code, type_val, blvl_val) VALUES ('MIX','p','cdi');
9629 INSERT INTO config.marc21_rec_type_map (code, type_val, blvl_val) VALUES ('MAP','ef','abcdmsi');
9630 INSERT INTO config.marc21_rec_type_map (code, type_val, blvl_val) VALUES ('SCO','cd','abcdmsi');
9631 INSERT INTO config.marc21_rec_type_map (code, type_val, blvl_val) VALUES ('REC','ij','abcdmsi');
9632 INSERT INTO config.marc21_rec_type_map (code, type_val, blvl_val) VALUES ('COM','m','abcdmsi');
9633
9634 ------ Physical Characteristics
9635
9636 -- Map
9637 INSERT INTO config.marc21_physical_characteristic_type_map (ptype_key, label) VALUES ('a','Map');
9638 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('a','b','1','1','SMD');
9639 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('d',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Atlas');
9640 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('g',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Diagram');
9641 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('j',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Map');
9642 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('k',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Profile');
9643 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('q',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Model');
9644 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');
9645 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('s',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Section');
9646 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unspecified');
9647 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('y',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'View');
9648 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
9649 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('a','d','3','1','Color');
9650 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');
9651 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('c',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Multicolored');
9652 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('a','e','4','1','Physical medium');
9653 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('a',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Paper');
9654 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('b',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Wood');
9655 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('c',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Stone');
9656 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('d',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Metal');
9657 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('e',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Synthetics');
9658 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('f',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Skins');
9659 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('g',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Textile');
9660 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('p',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Plaster');
9661 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');
9662 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');
9663 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');
9664 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');
9665 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
9666 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('y',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other photographic medium');
9667 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
9668 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('a','f','5','1','Type of reproduction');
9669 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('f',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Facsimile');
9670 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');
9671 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
9672 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
9673 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('a','g','6','1','Production/reproduction details');
9674 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');
9675 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('b',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Photocopy');
9676 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');
9677 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('d',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Film');
9678 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
9679 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
9680 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('a','h','7','1','Positive/negative');
9681 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('a',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Positive');
9682 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('b',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Negative');
9683 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('m',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Mixed');
9684 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('n',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Not applicable');
9685
9686 -- Electronic Resource
9687 INSERT INTO config.marc21_physical_characteristic_type_map (ptype_key, label) VALUES ('c','Electronic Resource');
9688 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('c','b','1','1','SMD');
9689 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');
9690 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');
9691 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');
9692 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');
9693 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');
9694 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');
9695 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');
9696 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');
9697 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('r',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Remote');
9698 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unspecified');
9699 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
9700 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('c','d','3','1','Color');
9701 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');
9702 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');
9703 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('c',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Multicolored');
9704 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('g',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Gray scale');
9705 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('m',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Mixed');
9706 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');
9707 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
9708 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
9709 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('c','e','4','1','Dimensions');
9710 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.');
9711 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.');
9712 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.');
9713 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.');
9714 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.');
9715 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');
9716 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.');
9717 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
9718 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.');
9719 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
9720 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('c','f','5','1','Sound');
9721 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)');
9722 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('a',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Sound');
9723 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
9724 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('c','g','6','3','Image bit depth');
9725 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('---',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
9726 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('mmm',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Multiple');
9727 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');
9728 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('c','h','9','1','File formats');
9729 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');
9730 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');
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_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('c','i','10','1','Quality assurance target(s)');
9733 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('a',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Absent');
9734 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');
9735 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('p',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Present');
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 ('c','j','11','1','Antecedent/Source');
9738 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');
9739 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');
9740 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');
9741 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)');
9742 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('m',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Mixed');
9743 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');
9744 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
9745 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('c','k','12','1','Level of compression');
9746 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('a',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Uncompressed');
9747 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('b',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Lossless');
9748 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('d',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Lossy');
9749 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('m',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Mixed');
9750 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
9751 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('c','l','13','1','Reformatting quality');
9752 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('a',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Access');
9753 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');
9754 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('p',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Preservation');
9755 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('r',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Replacement');
9756 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
9757
9758 -- Globe
9759 INSERT INTO config.marc21_physical_characteristic_type_map (ptype_key, label) VALUES ('d','Globe');
9760 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('d','b','1','1','SMD');
9761 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');
9762 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');
9763 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');
9764 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');
9765 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unspecified');
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 ('d','d','3','1','Color');
9768 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');
9769 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('c',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Multicolored');
9770 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('d','e','4','1','Physical medium');
9771 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('a',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Paper');
9772 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('b',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Wood');
9773 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('c',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Stone');
9774 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('d',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Metal');
9775 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('e',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Synthetics');
9776 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('f',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Skins');
9777 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('g',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Textile');
9778 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('p',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Plaster');
9779 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
9780 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
9781 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('d','f','5','1','Type of reproduction');
9782 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('f',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Facsimile');
9783 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');
9784 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
9785 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
9786
9787 -- Tactile Material
9788 INSERT INTO config.marc21_physical_characteristic_type_map (ptype_key, label) VALUES ('f','Tactile Material');
9789 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('f','b','1','1','SMD');
9790 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('a',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Moon');
9791 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('b',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Braille');
9792 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('c',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Combination');
9793 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');
9794 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unspecified');
9795 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
9796 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('f','d','3','2','Class of braille writing');
9797 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');
9798 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');
9799 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');
9800 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');
9801 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');
9802 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');
9803 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');
9804 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
9805 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
9806 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('f','e','4','1','Level of contraction');
9807 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('a',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Uncontracted');
9808 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('b',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Contracted');
9809 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('m',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Combination');
9810 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');
9811 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
9812 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
9813 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('f','f','6','3','Braille music format');
9814 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');
9815 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');
9816 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');
9817 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('d',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Paragraph');
9818 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');
9819 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');
9820 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');
9821 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');
9822 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');
9823 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');
9824 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('k',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Outline');
9825 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');
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 ('f','g','9','1','Special physical characteristics');
9830 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');
9831 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');
9832 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');
9833 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
9834 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
9835
9836 -- Projected Graphic
9837 INSERT INTO config.marc21_physical_characteristic_type_map (ptype_key, label) VALUES ('g','Projected Graphic');
9838 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('g','b','1','1','SMD');
9839 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');
9840 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('d',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Filmstrip');
9841 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');
9842 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');
9843 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('s',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Slide');
9844 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('t',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Transparency');
9845 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
9846 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('g','d','3','1','Color');
9847 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');
9848 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('c',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Multicolored');
9849 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');
9850 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('m',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Mixed');
9851 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');
9852 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
9853 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
9854 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('g','e','4','1','Base of emulsion');
9855 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('d',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Glass');
9856 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('e',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Synthetics');
9857 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('j',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Safety film');
9858 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');
9859 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');
9860 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('o',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Paper');
9861 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
9862 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
9863 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');
9864 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');
9865 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');
9866 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
9867 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('g','g','6','1','Medium for sound');
9868 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');
9869 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');
9870 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');
9871 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');
9872 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');
9873 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');
9874 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');
9875 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('h',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Videotape');
9876 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('i',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Videodisc');
9877 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
9878 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
9879 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('g','h','7','1','Dimensions');
9880 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.');
9881 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.');
9882 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.');
9883 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.');
9884 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.');
9885 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.');
9886 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.');
9887 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.)');
9888 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.)');
9889 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.)');
9890 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.)');
9891 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
9892 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('v',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'8 x 10 in. (21 x 26 cm.)');
9893 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.)');
9894 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.)');
9895 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.)');
9896 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
9897 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('g','i','8','1','Secondary support material');
9898 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('c',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Cardboard');
9899 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('d',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Glass');
9900 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('e',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Synthetics');
9901 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('h',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'metal');
9902 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');
9903 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');
9904 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('m',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Mixed collection');
9905 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
9906 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
9907
9908 -- Microform
9909 INSERT INTO config.marc21_physical_characteristic_type_map (ptype_key, label) VALUES ('h','Microform');
9910 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('h','b','1','1','SMD');
9911 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');
9912 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');
9913 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');
9914 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');
9915 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('e',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Microfiche');
9916 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');
9917 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('g',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Microopaque');
9918 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unspecified');
9919 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
9920 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('h','d','3','1','Positive/negative');
9921 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('a',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Positive');
9922 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('b',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Negative');
9923 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('m',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Mixed');
9924 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
9925 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('h','e','4','1','Dimensions');
9926 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.');
9927 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.');
9928 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.');
9929 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('g',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'70mm.');
9930 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.');
9931 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.)');
9932 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.)');
9933 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.)');
9934 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.)');
9935 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
9936 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
9937 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');
9938 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)');
9939 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)');
9940 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)');
9941 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)');
9942 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-)');
9943 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
9944 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');
9945 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('h','g','9','1','Color');
9946 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');
9947 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('c',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Multicolored');
9948 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('m',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Mixed');
9949 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
9950 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
9951 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('h','h','10','1','Emulsion on film');
9952 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');
9953 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('b',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Diazo');
9954 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('c',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Vesicular');
9955 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('m',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Mixed');
9956 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');
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 ('h','i','11','1','Quality assurance target(s)');
9960 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');
9961 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');
9962 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');
9963 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('m',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Mixed generation');
9964 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
9965 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('h','j','12','1','Base of film');
9966 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');
9967 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');
9968 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');
9969 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');
9970 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');
9971 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');
9972 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');
9973 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');
9974 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');
9975 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
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 -- Non-projected Graphic
9979 INSERT INTO config.marc21_physical_characteristic_type_map (ptype_key, label) VALUES ('k','Non-projected Graphic');
9980 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('k','b','1','1','SMD');
9981 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('c',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Collage');
9982 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('d',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Drawing');
9983 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('e',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Painting');
9984 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');
9985 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('g',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Photonegative');
9986 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('h',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Photoprint');
9987 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('i',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Picture');
9988 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('j',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Print');
9989 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');
9990 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('n',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Chart');
9991 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');
9992 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unspecified');
9993 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
9994 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('k','d','3','1','Color');
9995 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');
9996 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');
9997 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('c',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Multicolored');
9998 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');
9999 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('m',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Mixed');
10000 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
10001 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
10002 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('k','e','4','1','Primary support material');
10003 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('a',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Canvas');
10004 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');
10005 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');
10006 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('d',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Glass');
10007 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('e',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Synthetics');
10008 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('f',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Skins');
10009 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('g',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Textile');
10010 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('h',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Metal');
10011 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');
10012 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('o',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Paper');
10013 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('p',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Plaster');
10014 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('q',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Hardboard');
10015 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('r',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Porcelain');
10016 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('s',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Stone');
10017 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('t',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Wood');
10018 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
10019 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
10020 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('k','f','5','1','Secondary support material');
10021 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('a',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Canvas');
10022 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');
10023 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');
10024 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('d',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Glass');
10025 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('e',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Synthetics');
10026 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('f',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Skins');
10027 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('g',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Textile');
10028 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('h',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Metal');
10029 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');
10030 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('o',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Paper');
10031 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('p',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Plaster');
10032 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('q',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Hardboard');
10033 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('r',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Porcelain');
10034 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('s',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Stone');
10035 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('t',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Wood');
10036 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
10037 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
10038
10039 -- Motion Picture
10040 INSERT INTO config.marc21_physical_characteristic_type_map (ptype_key, label) VALUES ('m','Motion Picture');
10041 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('m','b','1','1','SMD');
10042 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');
10043 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');
10044 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');
10045 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unspecified');
10046 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
10047 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('m','d','3','1','Color');
10048 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');
10049 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('c',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Multicolored');
10050 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');
10051 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('m',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Mixed');
10052 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
10053 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
10054 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('m','e','4','1','Motion picture presentation format');
10055 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');
10056 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)');
10057 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('c',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'3D');
10058 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)');
10059 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');
10060 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');
10061 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
10062 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
10063 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');
10064 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');
10065 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');
10066 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
10067 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('m','g','6','1','Medium for sound');
10068 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');
10069 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');
10070 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');
10071 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');
10072 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');
10073 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');
10074 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');
10075 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('h',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Videotape');
10076 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('i',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Videodisc');
10077 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
10078 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
10079 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('m','h','7','1','Dimensions');
10080 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.');
10081 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.');
10082 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.');
10083 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.');
10084 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.');
10085 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.');
10086 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.');
10087 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
10088 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
10089 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('m','i','8','1','Configuration of playback channels');
10090 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('k',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Mixed');
10091 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('m',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Monaural');
10092 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');
10093 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');
10094 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('s',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Stereophonic');
10095 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
10096 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
10097 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('m','j','9','1','Production elements');
10098 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');
10099 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('b',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Trims');
10100 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('c',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Outtakes');
10101 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('d',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Rushes');
10102 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');
10103 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');
10104 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');
10105 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');
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
10108 -- Remote-sensing Image
10109 INSERT INTO config.marc21_physical_characteristic_type_map (ptype_key, label) VALUES ('r','Remote-sensing Image');
10110 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('r','b','1','1','SMD');
10111 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unspecified');
10112 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('r','d','3','1','Altitude of sensor');
10113 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('a',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Surface');
10114 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('b',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Airborne');
10115 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('c',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Spaceborne');
10116 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');
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 ('r','e','4','1','Attitude of sensor');
10120 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');
10121 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');
10122 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('c',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Vertical');
10123 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');
10124 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
10125 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('r','f','5','1','Cloud cover');
10126 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%');
10127 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%');
10128 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%');
10129 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%');
10130 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%');
10131 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%');
10132 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%');
10133 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%');
10134 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%');
10135 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%');
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 ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
10138 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('r','g','6','1','Platform construction type');
10139 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('a',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Balloon');
10140 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');
10141 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');
10142 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');
10143 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');
10144 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');
10145 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');
10146 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');
10147 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');
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 ('r','h','7','1','Platform use category');
10152 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('a',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Meteorological');
10153 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');
10154 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');
10155 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');
10156 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');
10157 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
10158 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
10159 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('r','i','8','1','Sensor type');
10160 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('a',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Active');
10161 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('b',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Passive');
10162 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
10163 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
10164 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('r','j','9','2','Data type');
10165 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');
10166 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');
10167 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');
10168 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');
10169 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');
10170 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)');
10171 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');
10172 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('dv',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Combinations');
10173 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');
10174 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)');
10175 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)');
10176 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)');
10177 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');
10178 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');
10179 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');
10180 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');
10181 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');
10182 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');
10183 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');
10184 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');
10185 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');
10186 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');
10187 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');
10188 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');
10189 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');
10190 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');
10191 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');
10192 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');
10193 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');
10194 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');
10195 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');
10196 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');
10197 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');
10198 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)');
10199 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');
10200 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('rc',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Bouger');
10201 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('rd',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Isostatic');
10202 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');
10203 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');
10204 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('uu',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
10205 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('zz',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
10206
10207 -- Sound Recording
10208 INSERT INTO config.marc21_physical_characteristic_type_map (ptype_key, label) VALUES ('s','Sound Recording');
10209 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('s','b','1','1','SMD');
10210 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');
10211 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('e',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Cylinder');
10212 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');
10213 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');
10214 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('q',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Roll');
10215 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');
10216 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');
10217 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unspecified');
10218 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');
10219 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
10220 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('s','d','3','1','Speed');
10221 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');
10222 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');
10223 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');
10224 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');
10225 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');
10226 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');
10227 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');
10228 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');
10229 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');
10230 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');
10231 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');
10232 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');
10233 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');
10234 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');
10235 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
10236 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
10237 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('s','e','4','1','Configuration of playback channels');
10238 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('m',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Monaural');
10239 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('q',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Quadraphonic');
10240 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('s',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Stereophonic');
10241 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
10242 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
10243 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('s','f','5','1','Groove width or pitch');
10244 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');
10245 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');
10246 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');
10247 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
10248 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
10249 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('s','g','6','1','Dimensions');
10250 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('a',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'3 in.');
10251 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.');
10252 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.');
10253 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.');
10254 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.');
10255 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.');
10256 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.)');
10257 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.');
10258 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');
10259 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.');
10260 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.');
10261 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
10262 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
10263 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('s','h','7','1','Tape width');
10264 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.');
10265 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.');
10266 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');
10267 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.');
10268 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.');
10269 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
10270 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
10271 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('s','i','8','1','Tape configuration ');
10272 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');
10273 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');
10274 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');
10275 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');
10276 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');
10277 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');
10278 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');
10279 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
10280 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
10281 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('s','m','12','1','Special playback');
10282 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');
10283 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');
10284 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');
10285 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');
10286 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');
10287 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');
10288 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');
10289 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');
10290 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');
10291 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
10292 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
10293 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('s','n','13','1','Capture and storage');
10294 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');
10295 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');
10296 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');
10297 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');
10298 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
10299 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
10300
10301 -- Videorecording
10302 INSERT INTO config.marc21_physical_characteristic_type_map (ptype_key, label) VALUES ('v','Videorecording');
10303 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('v','b','1','1','SMD');
10304 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('c',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Videocartridge');
10305 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('d',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Videodisc');
10306 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('f',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Videocassette');
10307 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('r',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Videoreel');
10308 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unspecified');
10309 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
10310 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('v','d','3','1','Color');
10311 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');
10312 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('c',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Multicolored');
10313 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('m',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Mixed');
10314 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');
10315 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
10316 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
10317 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('v','e','4','1','Videorecording format');
10318 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('a',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Beta');
10319 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('b',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'VHS');
10320 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');
10321 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('d',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'EIAJ');
10322 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');
10323 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('f',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Quadruplex');
10324 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('g',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Laserdisc');
10325 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('h',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'CED');
10326 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('i',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Betacam');
10327 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');
10328 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');
10329 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');
10330 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');
10331 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.');
10332 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.');
10333 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
10334 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('v',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'DVD');
10335 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
10336 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');
10337 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');
10338 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');
10339 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
10340 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('v','g','6','1','Medium for sound');
10341 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');
10342 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');
10343 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');
10344 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');
10345 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');
10346 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');
10347 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');
10348 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('h',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Videotape');
10349 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('i',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Videodisc');
10350 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
10351 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
10352 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('v','h','7','1','Dimensions');
10353 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.');
10354 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.');
10355 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.');
10356 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.');
10357 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.');
10358 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.');
10359 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
10360 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
10361 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('v','i','8','1','Configuration of playback channel');
10362 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('k',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Mixed');
10363 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('m',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Monaural');
10364 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');
10365 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');
10366 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('s',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Stereophonic');
10367 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
10368 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
10369
10370 -- Fixed Field position data -- 0-based!
10371 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Alph', '006', 'SER', 16, 1, ' ');
10372 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Alph', '008', 'SER', 33, 1, ' ');
10373 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Audn', '006', 'BKS', 5, 1, ' ');
10374 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Audn', '006', 'COM', 5, 1, ' ');
10375 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Audn', '006', 'REC', 5, 1, ' ');
10376 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Audn', '006', 'SCO', 5, 1, ' ');
10377 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Audn', '006', 'SER', 5, 1, ' ');
10378 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Audn', '006', 'VIS', 5, 1, ' ');
10379 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Audn', '008', 'BKS', 22, 1, ' ');
10380 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Audn', '008', 'COM', 22, 1, ' ');
10381 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Audn', '008', 'REC', 22, 1, ' ');
10382 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Audn', '008', 'SCO', 22, 1, ' ');
10383 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Audn', '008', 'SER', 22, 1, ' ');
10384 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Audn', '008', 'VIS', 22, 1, ' ');
10385 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('BLvl', 'ldr', 'BKS', 7, 1, 'm');
10386 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('BLvl', 'ldr', 'COM', 7, 1, 'm');
10387 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('BLvl', 'ldr', 'MAP', 7, 1, 'm');
10388 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('BLvl', 'ldr', 'MIX', 7, 1, 'c');
10389 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('BLvl', 'ldr', 'REC', 7, 1, 'm');
10390 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('BLvl', 'ldr', 'SCO', 7, 1, 'm');
10391 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('BLvl', 'ldr', 'SER', 7, 1, 's');
10392 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('BLvl', 'ldr', 'VIS', 7, 1, 'm');
10393 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Biog', '006', 'BKS', 17, 1, ' ');
10394 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Biog', '008', 'BKS', 34, 1, ' ');
10395 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Conf', '006', 'BKS', 7, 4, ' ');
10396 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Conf', '006', 'SER', 8, 3, ' ');
10397 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Conf', '008', 'BKS', 24, 4, ' ');
10398 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Conf', '008', 'SER', 25, 3, ' ');
10399 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Ctrl', 'ldr', 'BKS', 8, 1, ' ');
10400 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Ctrl', 'ldr', 'COM', 8, 1, ' ');
10401 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Ctrl', 'ldr', 'MAP', 8, 1, ' ');
10402 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Ctrl', 'ldr', 'MIX', 8, 1, ' ');
10403 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Ctrl', 'ldr', 'REC', 8, 1, ' ');
10404 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Ctrl', 'ldr', 'SCO', 8, 1, ' ');
10405 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Ctrl', 'ldr', 'SER', 8, 1, ' ');
10406 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Ctrl', 'ldr', 'VIS', 8, 1, ' ');
10407 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Ctry', '008', 'BKS', 15, 3, ' ');
10408 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Ctry', '008', 'COM', 15, 3, ' ');
10409 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Ctry', '008', 'MAP', 15, 3, ' ');
10410 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Ctry', '008', 'MIX', 15, 3, ' ');
10411 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Ctry', '008', 'REC', 15, 3, ' ');
10412 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Ctry', '008', 'SCO', 15, 3, ' ');
10413 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Ctry', '008', 'SER', 15, 3, ' ');
10414 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Ctry', '008', 'VIS', 15, 3, ' ');
10415 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Date1', '008', 'BKS', 7, 4, ' ');
10416 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Date1', '008', 'COM', 7, 4, ' ');
10417 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Date1', '008', 'MAP', 7, 4, ' ');
10418 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Date1', '008', 'MIX', 7, 4, ' ');
10419 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Date1', '008', 'REC', 7, 4, ' ');
10420 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Date1', '008', 'SCO', 7, 4, ' ');
10421 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Date1', '008', 'SER', 7, 4, ' ');
10422 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Date1', '008', 'VIS', 7, 4, ' ');
10423 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Date2', '008', 'BKS', 11, 4, ' ');
10424 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Date2', '008', 'COM', 11, 4, ' ');
10425 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Date2', '008', 'MAP', 11, 4, ' ');
10426 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Date2', '008', 'MIX', 11, 4, ' ');
10427 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Date2', '008', 'REC', 11, 4, ' ');
10428 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Date2', '008', 'SCO', 11, 4, ' ');
10429 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Date2', '008', 'SER', 11, 4, '9');
10430 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Date2', '008', 'VIS', 11, 4, ' ');
10431 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Desc', 'ldr', 'BKS', 18, 1, ' ');
10432 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Desc', 'ldr', 'COM', 18, 1, ' ');
10433 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Desc', 'ldr', 'MAP', 18, 1, ' ');
10434 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Desc', 'ldr', 'MIX', 18, 1, ' ');
10435 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Desc', 'ldr', 'REC', 18, 1, ' ');
10436 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Desc', 'ldr', 'SCO', 18, 1, ' ');
10437 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Desc', 'ldr', 'SER', 18, 1, ' ');
10438 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Desc', 'ldr', 'VIS', 18, 1, ' ');
10439 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('DtSt', '008', 'BKS', 6, 1, ' ');
10440 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('DtSt', '008', 'COM', 6, 1, ' ');
10441 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('DtSt', '008', 'MAP', 6, 1, ' ');
10442 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('DtSt', '008', 'MIX', 6, 1, ' ');
10443 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('DtSt', '008', 'REC', 6, 1, ' ');
10444 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('DtSt', '008', 'SCO', 6, 1, ' ');
10445 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('DtSt', '008', 'SER', 6, 1, 'c');
10446 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('DtSt', '008', 'VIS', 6, 1, ' ');
10447 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('ELvl', 'ldr', 'BKS', 17, 1, ' ');
10448 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('ELvl', 'ldr', 'COM', 17, 1, ' ');
10449 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('ELvl', 'ldr', 'MAP', 17, 1, ' ');
10450 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('ELvl', 'ldr', 'MIX', 17, 1, ' ');
10451 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('ELvl', 'ldr', 'REC', 17, 1, ' ');
10452 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('ELvl', 'ldr', 'SCO', 17, 1, ' ');
10453 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('ELvl', 'ldr', 'SER', 17, 1, ' ');
10454 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('ELvl', 'ldr', 'VIS', 17, 1, ' ');
10455 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Fest', '006', 'BKS', 13, 1, '0');
10456 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Fest', '008', 'BKS', 30, 1, '0');
10457 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Form', '006', 'BKS', 6, 1, ' ');
10458 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Form', '006', 'MAP', 12, 1, ' ');
10459 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Form', '006', 'MIX', 6, 1, ' ');
10460 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Form', '006', 'REC', 6, 1, ' ');
10461 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Form', '006', 'SCO', 6, 1, ' ');
10462 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Form', '006', 'SER', 6, 1, ' ');
10463 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Form', '006', 'VIS', 12, 1, ' ');
10464 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Form', '008', 'BKS', 23, 1, ' ');
10465 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Form', '008', 'MAP', 29, 1, ' ');
10466 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Form', '008', 'MIX', 23, 1, ' ');
10467 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Form', '008', 'REC', 23, 1, ' ');
10468 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Form', '008', 'SCO', 23, 1, ' ');
10469 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Form', '008', 'SER', 23, 1, ' ');
10470 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Form', '008', 'VIS', 29, 1, ' ');
10471 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('GPub', '006', 'BKS', 11, 1, ' ');
10472 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('GPub', '006', 'COM', 11, 1, ' ');
10473 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('GPub', '006', 'MAP', 11, 1, ' ');
10474 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('GPub', '006', 'SER', 11, 1, ' ');
10475 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('GPub', '006', 'VIS', 11, 1, ' ');
10476 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('GPub', '008', 'BKS', 28, 1, ' ');
10477 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('GPub', '008', 'COM', 28, 1, ' ');
10478 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('GPub', '008', 'MAP', 28, 1, ' ');
10479 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('GPub', '008', 'SER', 28, 1, ' ');
10480 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('GPub', '008', 'VIS', 28, 1, ' ');
10481 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Ills', '006', 'BKS', 1, 4, ' ');
10482 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Ills', '008', 'BKS', 18, 4, ' ');
10483 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Indx', '006', 'BKS', 14, 1, '0');
10484 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Indx', '006', 'MAP', 14, 1, '0');
10485 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Indx', '008', 'BKS', 31, 1, '0');
10486 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Indx', '008', 'MAP', 31, 1, '0');
10487 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Lang', '008', 'BKS', 35, 3, ' ');
10488 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Lang', '008', 'COM', 35, 3, ' ');
10489 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Lang', '008', 'MAP', 35, 3, ' ');
10490 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Lang', '008', 'MIX', 35, 3, ' ');
10491 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Lang', '008', 'REC', 35, 3, ' ');
10492 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Lang', '008', 'SCO', 35, 3, ' ');
10493 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Lang', '008', 'SER', 35, 3, ' ');
10494 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Lang', '008', 'VIS', 35, 3, ' ');
10495 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('LitF', '006', 'BKS', 16, 1, '0');
10496 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('LitF', '008', 'BKS', 33, 1, '0');
10497 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('MRec', '008', 'BKS', 38, 1, ' ');
10498 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('MRec', '008', 'COM', 38, 1, ' ');
10499 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('MRec', '008', 'MAP', 38, 1, ' ');
10500 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('MRec', '008', 'MIX', 38, 1, ' ');
10501 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('MRec', '008', 'REC', 38, 1, ' ');
10502 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('MRec', '008', 'SCO', 38, 1, ' ');
10503 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('MRec', '008', 'SER', 38, 1, ' ');
10504 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('MRec', '008', 'VIS', 38, 1, ' ');
10505 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');
10506 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');
10507 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('TMat', '006', 'VIS', 16, 1, ' ');
10508 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('TMat', '008', 'VIS', 33, 1, ' ');
10509 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Type', 'ldr', 'BKS', 6, 1, 'a');
10510 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Type', 'ldr', 'COM', 6, 1, 'm');
10511 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Type', 'ldr', 'MAP', 6, 1, 'e');
10512 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Type', 'ldr', 'MIX', 6, 1, 'p');
10513 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Type', 'ldr', 'REC', 6, 1, 'i');
10514 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Type', 'ldr', 'SCO', 6, 1, 'c');
10515 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Type', 'ldr', 'SER', 6, 1, 'a');
10516 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Type', 'ldr', 'VIS', 6, 1, 'g');
10517
10518 CREATE OR REPLACE FUNCTION biblio.marc21_record_type( rid BIGINT ) RETURNS config.marc21_rec_type_map AS $func$
10519 DECLARE
10520         ldr         RECORD;
10521         tval        TEXT;
10522         tval_rec    RECORD;
10523         bval        TEXT;
10524         bval_rec    RECORD;
10525     retval      config.marc21_rec_type_map%ROWTYPE;
10526 BEGIN
10527     SELECT * INTO ldr FROM metabib.full_rec WHERE record = rid AND tag = 'LDR' LIMIT 1;
10528
10529     IF ldr.id IS NULL THEN
10530         SELECT * INTO retval FROM config.marc21_rec_type_map WHERE code = 'BKS';
10531         RETURN retval;
10532     END IF;
10533
10534     SELECT * INTO tval_rec FROM config.marc21_ff_pos_map WHERE fixed_field = 'Type' LIMIT 1; -- They're all the same
10535     SELECT * INTO bval_rec FROM config.marc21_ff_pos_map WHERE fixed_field = 'BLvl' LIMIT 1; -- They're all the same
10536
10537
10538     tval := SUBSTRING( ldr.value, tval_rec.start_pos + 1, tval_rec.length );
10539     bval := SUBSTRING( ldr.value, bval_rec.start_pos + 1, bval_rec.length );
10540
10541     -- RAISE NOTICE 'type %, blvl %, ldr %', tval, bval, ldr.value;
10542
10543     SELECT * INTO retval FROM config.marc21_rec_type_map WHERE type_val LIKE '%' || tval || '%' AND blvl_val LIKE '%' || bval || '%';
10544
10545
10546     IF retval.code IS NULL THEN
10547         SELECT * INTO retval FROM config.marc21_rec_type_map WHERE code = 'BKS';
10548     END IF;
10549
10550     RETURN retval;
10551 END;
10552 $func$ LANGUAGE PLPGSQL;
10553
10554 CREATE OR REPLACE FUNCTION biblio.marc21_extract_fixed_field( rid BIGINT, ff TEXT ) RETURNS TEXT AS $func$
10555 DECLARE
10556     rtype       TEXT;
10557     ff_pos      RECORD;
10558     tag_data    RECORD;
10559     val         TEXT;
10560 BEGIN
10561     rtype := (biblio.marc21_record_type( rid )).code;
10562     FOR ff_pos IN SELECT * FROM config.marc21_ff_pos_map WHERE fixed_field = ff AND rec_type = rtype ORDER BY tag DESC LOOP
10563         FOR tag_data IN SELECT * FROM metabib.full_rec WHERE tag = UPPER(ff_pos.tag) AND record = rid LOOP
10564             val := SUBSTRING( tag_data.value, ff_pos.start_pos + 1, ff_pos.length );
10565             RETURN val;
10566         END LOOP;
10567         val := REPEAT( ff_pos.default_val, ff_pos.length );
10568         RETURN val;
10569     END LOOP;
10570
10571     RETURN NULL;
10572 END;
10573 $func$ LANGUAGE PLPGSQL;
10574
10575 CREATE TYPE biblio.marc21_physical_characteristics AS ( id INT, record BIGINT, ptype TEXT, subfield INT, value INT );
10576 CREATE OR REPLACE FUNCTION biblio.marc21_physical_characteristics( rid BIGINT ) RETURNS SETOF biblio.marc21_physical_characteristics AS $func$
10577 DECLARE
10578     rowid   INT := 0;
10579     _007    RECORD;
10580     ptype   config.marc21_physical_characteristic_type_map%ROWTYPE;
10581     psf     config.marc21_physical_characteristic_subfield_map%ROWTYPE;
10582     pval    config.marc21_physical_characteristic_value_map%ROWTYPE;
10583     retval  biblio.marc21_physical_characteristics%ROWTYPE;
10584 BEGIN
10585
10586     SELECT * INTO _007 FROM metabib.full_rec WHERE record = rid AND tag = '007' LIMIT 1;
10587
10588     IF _007.id IS NOT NULL THEN
10589         SELECT * INTO ptype FROM config.marc21_physical_characteristic_type_map WHERE ptype_key = SUBSTRING( _007.value, 1, 1 );
10590
10591         IF ptype.ptype_key IS NOT NULL THEN
10592             FOR psf IN SELECT * FROM config.marc21_physical_characteristic_subfield_map WHERE ptype_key = ptype.ptype_key LOOP
10593                 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 );
10594
10595                 IF pval.id IS NOT NULL THEN
10596                     rowid := rowid + 1;
10597                     retval.id := rowid;
10598                     retval.record := rid;
10599                     retval.ptype := ptype.ptype_key;
10600                     retval.subfield := psf.id;
10601                     retval.value := pval.id;
10602                     RETURN NEXT retval;
10603                 END IF;
10604
10605             END LOOP;
10606         END IF;
10607     END IF;
10608
10609     RETURN;
10610 END;
10611 $func$ LANGUAGE PLPGSQL;
10612
10613 DROP VIEW IF EXISTS money.open_usr_circulation_summary;
10614 DROP VIEW IF EXISTS money.open_usr_summary;
10615 DROP VIEW IF EXISTS money.open_billable_xact_summary;
10616
10617 -- The view should supply defaults for numeric (amount) columns
10618 CREATE OR REPLACE VIEW money.billable_xact_summary AS
10619     SELECT  xact.id,
10620         xact.usr,
10621         xact.xact_start,
10622         xact.xact_finish,
10623         COALESCE(credit.amount, 0.0::numeric) AS total_paid,
10624         credit.payment_ts AS last_payment_ts,
10625         credit.note AS last_payment_note,
10626         credit.payment_type AS last_payment_type,
10627         COALESCE(debit.amount, 0.0::numeric) AS total_owed,
10628         debit.billing_ts AS last_billing_ts,
10629         debit.note AS last_billing_note,
10630         debit.billing_type AS last_billing_type,
10631         COALESCE(debit.amount, 0.0::numeric) - COALESCE(credit.amount, 0.0::numeric) AS balance_owed,
10632         p.relname AS xact_type
10633       FROM  money.billable_xact xact
10634         JOIN pg_class p ON xact.tableoid = p.oid
10635         LEFT JOIN (
10636             SELECT  billing.xact,
10637                 sum(billing.amount) AS amount,
10638                 max(billing.billing_ts) AS billing_ts,
10639                 last(billing.note) AS note,
10640                 last(billing.billing_type) AS billing_type
10641               FROM  money.billing
10642               WHERE billing.voided IS FALSE
10643               GROUP BY billing.xact
10644             ) debit ON xact.id = debit.xact
10645         LEFT JOIN (
10646             SELECT  payment_view.xact,
10647                 sum(payment_view.amount) AS amount,
10648                 max(payment_view.payment_ts) AS payment_ts,
10649                 last(payment_view.note) AS note,
10650                 last(payment_view.payment_type) AS payment_type
10651               FROM  money.payment_view
10652               WHERE payment_view.voided IS FALSE
10653               GROUP BY payment_view.xact
10654             ) credit ON xact.id = credit.xact
10655       ORDER BY debit.billing_ts, credit.payment_ts;
10656
10657 CREATE OR REPLACE VIEW money.open_billable_xact_summary AS 
10658     SELECT * FROM money.billable_xact_summary_location_view
10659     WHERE xact_finish IS NULL;
10660
10661 CREATE OR REPLACE VIEW money.open_usr_circulation_summary AS
10662     SELECT 
10663         usr,
10664         SUM(total_paid) AS total_paid,
10665         SUM(total_owed) AS total_owed,
10666         SUM(balance_owed) AS balance_owed
10667     FROM  money.materialized_billable_xact_summary
10668     WHERE xact_type = 'circulation' AND xact_finish IS NULL
10669     GROUP BY usr;
10670
10671 CREATE OR REPLACE VIEW money.usr_summary AS
10672     SELECT 
10673         usr, 
10674         sum(total_paid) AS total_paid, 
10675         sum(total_owed) AS total_owed, 
10676         sum(balance_owed) AS balance_owed
10677     FROM money.materialized_billable_xact_summary
10678     GROUP BY usr;
10679
10680 CREATE OR REPLACE VIEW money.open_usr_summary AS
10681     SELECT 
10682         usr, 
10683         sum(total_paid) AS total_paid, 
10684         sum(total_owed) AS total_owed, 
10685         sum(balance_owed) AS balance_owed
10686     FROM money.materialized_billable_xact_summary
10687     WHERE xact_finish IS NULL
10688     GROUP BY usr;
10689
10690 -- 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;
10691
10692 CREATE TABLE config.biblio_fingerprint (
10693         id                      SERIAL  PRIMARY KEY,
10694         name            TEXT    NOT NULL, 
10695         xpath           TEXT    NOT NULL,
10696     first_word  BOOL    NOT NULL DEFAULT FALSE,
10697         format          TEXT    NOT NULL DEFAULT 'marcxml'
10698 );
10699
10700 INSERT INTO config.biblio_fingerprint (name, xpath, format)
10701     VALUES (
10702         'Title',
10703         '//marc:datafield[@tag="700"]/marc:subfield[@code="t"]|' ||
10704             '//marc:datafield[@tag="240"]/marc:subfield[@code="a"]|' ||
10705             '//marc:datafield[@tag="242"]/marc:subfield[@code="a"]|' ||
10706             '//marc:datafield[@tag="246"]/marc:subfield[@code="a"]|' ||
10707             '//marc:datafield[@tag="245"]/marc:subfield[@code="a"]',
10708         'marcxml'
10709     );
10710
10711 INSERT INTO config.biblio_fingerprint (name, xpath, format, first_word)
10712     VALUES (
10713         'Author',
10714         '//marc:datafield[@tag="700" and ./*[@code="t"]]/marc:subfield[@code="a"]|'
10715             '//marc:datafield[@tag="100"]/marc:subfield[@code="a"]|'
10716             '//marc:datafield[@tag="110"]/marc:subfield[@code="a"]|'
10717             '//marc:datafield[@tag="111"]/marc:subfield[@code="a"]|'
10718             '//marc:datafield[@tag="260"]/marc:subfield[@code="b"]',
10719         'marcxml',
10720         TRUE
10721     );
10722
10723 CREATE OR REPLACE FUNCTION biblio.extract_quality ( marc TEXT, best_lang TEXT, best_type TEXT ) RETURNS INT AS $func$
10724 DECLARE
10725     qual        INT;
10726     ldr         TEXT;
10727     tval        TEXT;
10728     tval_rec    RECORD;
10729     bval        TEXT;
10730     bval_rec    RECORD;
10731     type_map    RECORD;
10732     ff_pos      RECORD;
10733     ff_tag_data TEXT;
10734 BEGIN
10735
10736     IF marc IS NULL OR marc = '' THEN
10737         RETURN NULL;
10738     END IF;
10739
10740     -- First, the count of tags
10741     qual := ARRAY_UPPER(oils_xpath('*[local-name()="datafield"]', marc), 1);
10742
10743     -- now go through a bunch of pain to get the record type
10744     IF best_type IS NOT NULL THEN
10745         ldr := (oils_xpath('//*[local-name()="leader"]/text()', marc))[1];
10746
10747         IF ldr IS NOT NULL THEN
10748             SELECT * INTO tval_rec FROM config.marc21_ff_pos_map WHERE fixed_field = 'Type' LIMIT 1; -- They're all the same
10749             SELECT * INTO bval_rec FROM config.marc21_ff_pos_map WHERE fixed_field = 'BLvl' LIMIT 1; -- They're all the same
10750
10751
10752             tval := SUBSTRING( ldr, tval_rec.start_pos + 1, tval_rec.length );
10753             bval := SUBSTRING( ldr, bval_rec.start_pos + 1, bval_rec.length );
10754
10755             -- RAISE NOTICE 'type %, blvl %, ldr %', tval, bval, ldr;
10756
10757             SELECT * INTO type_map FROM config.marc21_rec_type_map WHERE type_val LIKE '%' || tval || '%' AND blvl_val LIKE '%' || bval || '%';
10758
10759             IF type_map.code IS NOT NULL THEN
10760                 IF best_type = type_map.code THEN
10761                     qual := qual + qual / 2;
10762                 END IF;
10763
10764                 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
10765                     ff_tag_data := SUBSTRING((oils_xpath('//*[@tag="' || ff_pos.tag || '"]/text()',marc))[1], ff_pos.start_pos + 1, ff_pos.length);
10766                     IF ff_tag_data = best_lang THEN
10767                             qual := qual + 100;
10768                     END IF;
10769                 END LOOP;
10770             END IF;
10771         END IF;
10772     END IF;
10773
10774     -- Now look for some quality metrics
10775     -- DCL record?
10776     IF ARRAY_UPPER(oils_xpath('//*[@tag="040"]/*[@code="a" and contains(.,"DLC")]', marc), 1) = 1 THEN
10777         qual := qual + 10;
10778     END IF;
10779
10780     -- From OCLC?
10781     IF (oils_xpath('//*[@tag="003"]/text()', marc))[1] ~* E'oclo?c' THEN
10782         qual := qual + 10;
10783     END IF;
10784
10785     RETURN qual;
10786
10787 END;
10788 $func$ LANGUAGE PLPGSQL;
10789
10790 CREATE OR REPLACE FUNCTION biblio.extract_fingerprint ( marc text ) RETURNS TEXT AS $func$
10791 DECLARE
10792     idx     config.biblio_fingerprint%ROWTYPE;
10793     xfrm        config.xml_transform%ROWTYPE;
10794     prev_xfrm   TEXT;
10795     transformed_xml TEXT;
10796     xml_node    TEXT;
10797     xml_node_list   TEXT[];
10798     raw_text    TEXT;
10799     output_text TEXT := '';
10800 BEGIN
10801
10802     IF marc IS NULL OR marc = '' THEN
10803         RETURN NULL;
10804     END IF;
10805
10806     -- Loop over the indexing entries
10807     FOR idx IN SELECT * FROM config.biblio_fingerprint ORDER BY format, id LOOP
10808
10809         SELECT INTO xfrm * from config.xml_transform WHERE name = idx.format;
10810
10811         -- See if we can skip the XSLT ... it's expensive
10812         IF prev_xfrm IS NULL OR prev_xfrm <> xfrm.name THEN
10813             -- Can't skip the transform
10814             IF xfrm.xslt <> '---' THEN
10815                 transformed_xml := oils_xslt_process(marc,xfrm.xslt);
10816             ELSE
10817                 transformed_xml := marc;
10818             END IF;
10819
10820             prev_xfrm := xfrm.name;
10821         END IF;
10822
10823         raw_text := COALESCE(
10824             naco_normalize(
10825                 ARRAY_TO_STRING(
10826                     oils_xpath(
10827                         '//text()',
10828                         (oils_xpath(
10829                             idx.xpath,
10830                             transformed_xml,
10831                             ARRAY[ARRAY[xfrm.prefix, xfrm.namespace_uri]]
10832                         ))[1]
10833                     ),
10834                     ''
10835                 )
10836             ),
10837             ''
10838         );
10839
10840         raw_text := REGEXP_REPLACE(raw_text, E'\\[.+?\\]', E'');
10841         raw_text := REGEXP_REPLACE(raw_text, E'\\mthe\\M|\\man?d?d\\M', E'', 'g'); -- arg! the pain!
10842
10843         IF idx.first_word IS TRUE THEN
10844             raw_text := REGEXP_REPLACE(raw_text, E'^(\\w+).*?$', E'\\1');
10845         END IF;
10846
10847         output_text := output_text || REGEXP_REPLACE(raw_text, E'\\s+', '', 'g');
10848
10849     END LOOP;
10850
10851     RETURN output_text;
10852
10853 END;
10854 $func$ LANGUAGE PLPGSQL;
10855
10856 -- BEFORE UPDATE OR INSERT trigger for biblio.record_entry
10857 CREATE OR REPLACE FUNCTION biblio.fingerprint_trigger () RETURNS TRIGGER AS $func$
10858 BEGIN
10859
10860     -- For TG_ARGV, first param is language (like 'eng'), second is record type (like 'BKS')
10861
10862     IF NEW.deleted IS TRUE THEN -- we don't much care, then, do we?
10863         RETURN NEW;
10864     END IF;
10865
10866     NEW.fingerprint := biblio.extract_fingerprint(NEW.marc);
10867     NEW.quality := biblio.extract_quality(NEW.marc, TG_ARGV[0], TG_ARGV[1]);
10868
10869     RETURN NEW;
10870
10871 END;
10872 $func$ LANGUAGE PLPGSQL;
10873
10874 CREATE TABLE config.internal_flag (
10875     name    TEXT    PRIMARY KEY,
10876     value   TEXT,
10877     enabled BOOL    NOT NULL DEFAULT FALSE
10878 );
10879 INSERT INTO config.internal_flag (name) VALUES ('ingest.metarecord_mapping.skip_on_insert');
10880 INSERT INTO config.internal_flag (name) VALUES ('ingest.reingest.force_on_same_marc');
10881 INSERT INTO config.internal_flag (name) VALUES ('ingest.reingest.skip_located_uri');
10882 INSERT INTO config.internal_flag (name) VALUES ('ingest.disable_located_uri');
10883 INSERT INTO config.internal_flag (name) VALUES ('ingest.disable_metabib_full_rec');
10884 INSERT INTO config.internal_flag (name) VALUES ('ingest.disable_metabib_rec_descriptor');
10885 INSERT INTO config.internal_flag (name) VALUES ('ingest.disable_metabib_field_entry');
10886 INSERT INTO config.internal_flag (name) VALUES ('ingest.disable_authority_linking');
10887 INSERT INTO config.internal_flag (name) VALUES ('ingest.metarecord_mapping.skip_on_update');
10888 INSERT INTO config.internal_flag (name) VALUES ('ingest.assume_inserts_only');
10889
10890 CREATE TABLE authority.bib_linking (
10891     id          BIGSERIAL   PRIMARY KEY,
10892     bib         BIGINT      NOT NULL REFERENCES biblio.record_entry (id),
10893     authority   BIGINT      NOT NULL REFERENCES authority.record_entry (id)
10894 );
10895 CREATE INDEX authority_bl_bib_idx ON authority.bib_linking ( bib );
10896 CREATE UNIQUE INDEX authority_bl_bib_authority_once_idx ON authority.bib_linking ( authority, bib );
10897
10898 CREATE OR REPLACE FUNCTION public.remove_paren_substring( TEXT ) RETURNS TEXT AS $func$
10899     SELECT regexp_replace($1, $$\([^)]+\)$$, '', 'g');
10900 $func$ LANGUAGE SQL STRICT IMMUTABLE;
10901
10902 CREATE OR REPLACE FUNCTION biblio.map_authority_linking (bibid BIGINT, marc TEXT) RETURNS BIGINT AS $func$
10903     DELETE FROM authority.bib_linking WHERE bib = $1;
10904     INSERT INTO authority.bib_linking (bib, authority)
10905         SELECT  y.bib,
10906                 y.authority
10907           FROM (    SELECT  DISTINCT $1 AS bib,
10908                             BTRIM(remove_paren_substring(txt))::BIGINT AS authority
10909                       FROM  explode_array(oils_xpath('//*[@code="0"]/text()',$2)) x(txt)
10910                       WHERE BTRIM(remove_paren_substring(txt)) ~ $re$^\d+$$re$
10911                 ) y JOIN authority.record_entry r ON r.id = y.authority;
10912     SELECT $1;
10913 $func$ LANGUAGE SQL;
10914
10915 CREATE OR REPLACE FUNCTION metabib.reingest_metabib_rec_descriptor( bib_id BIGINT ) RETURNS VOID AS $func$
10916 BEGIN
10917     PERFORM * FROM config.internal_flag WHERE name = 'ingest.assume_inserts_only' AND enabled;
10918     IF NOT FOUND THEN
10919         DELETE FROM metabib.rec_descriptor WHERE record = bib_id;
10920     END IF;
10921     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)
10922         SELECT  bib_id,
10923                 biblio.marc21_extract_fixed_field( bib_id, 'Type' ),
10924                 biblio.marc21_extract_fixed_field( bib_id, 'Form' ),
10925                 biblio.marc21_extract_fixed_field( bib_id, 'BLvl' ),
10926                 biblio.marc21_extract_fixed_field( bib_id, 'Ctrl' ),
10927                 biblio.marc21_extract_fixed_field( bib_id, 'ELvl' ),
10928                 biblio.marc21_extract_fixed_field( bib_id, 'Audn' ),
10929                 biblio.marc21_extract_fixed_field( bib_id, 'LitF' ),
10930                 biblio.marc21_extract_fixed_field( bib_id, 'TMat' ),
10931                 biblio.marc21_extract_fixed_field( bib_id, 'Desc' ),
10932                 biblio.marc21_extract_fixed_field( bib_id, 'DtSt' ),
10933                 biblio.marc21_extract_fixed_field( bib_id, 'Lang' ),
10934                 (   SELECT  v.value
10935                       FROM  biblio.marc21_physical_characteristics( bib_id) p
10936                             JOIN config.marc21_physical_characteristic_subfield_map s ON (s.id = p.subfield)
10937                             JOIN config.marc21_physical_characteristic_value_map v ON (v.id = p.value)
10938                       WHERE p.ptype = 'v' AND s.subfield = 'e'    ),
10939                 LPAD(NULLIF(REGEXP_REPLACE(NULLIF(biblio.marc21_extract_fixed_field( bib_id, 'Date1'), ''), E'\\D', '0', 'g')::INT,0)::TEXT,4,'0'),
10940                 LPAD(NULLIF(REGEXP_REPLACE(NULLIF(biblio.marc21_extract_fixed_field( bib_id, 'Date2'), ''), E'\\D', '9', 'g')::INT,9999)::TEXT,4,'0');
10941
10942     RETURN;
10943 END;
10944 $func$ LANGUAGE PLPGSQL;
10945
10946 CREATE TABLE config.metabib_class (
10947     name    TEXT    PRIMARY KEY,
10948     label   TEXT    NOT NULL UNIQUE
10949 );
10950
10951 INSERT INTO config.metabib_class ( name, label ) VALUES ( 'keyword', oils_i18n_gettext('keyword', 'Keyword', 'cmc', 'label') );
10952 INSERT INTO config.metabib_class ( name, label ) VALUES ( 'title', oils_i18n_gettext('title', 'Title', 'cmc', 'label') );
10953 INSERT INTO config.metabib_class ( name, label ) VALUES ( 'author', oils_i18n_gettext('author', 'Author', 'cmc', 'label') );
10954 INSERT INTO config.metabib_class ( name, label ) VALUES ( 'subject', oils_i18n_gettext('subject', 'Subject', 'cmc', 'label') );
10955 INSERT INTO config.metabib_class ( name, label ) VALUES ( 'series', oils_i18n_gettext('series', 'Series', 'cmc', 'label') );
10956
10957 CREATE TABLE metabib.facet_entry (
10958         id              BIGSERIAL       PRIMARY KEY,
10959         source          BIGINT          NOT NULL,
10960         field           INT             NOT NULL,
10961         value           TEXT            NOT NULL
10962 );
10963
10964 CREATE OR REPLACE FUNCTION metabib.reingest_metabib_field_entries( bib_id BIGINT ) RETURNS VOID AS $func$
10965 DECLARE
10966     fclass          RECORD;
10967     ind_data        metabib.field_entry_template%ROWTYPE;
10968 BEGIN
10969     PERFORM * FROM config.internal_flag WHERE name = 'ingest.assume_inserts_only' AND enabled;
10970     IF NOT FOUND THEN
10971         FOR fclass IN SELECT * FROM config.metabib_class LOOP
10972             -- RAISE NOTICE 'Emptying out %', fclass.name;
10973             EXECUTE $$DELETE FROM metabib.$$ || fclass.name || $$_field_entry WHERE source = $$ || bib_id;
10974         END LOOP;
10975         DELETE FROM metabib.facet_entry WHERE source = bib_id;
10976     END IF;
10977
10978     FOR ind_data IN SELECT * FROM biblio.extract_metabib_field_entry( bib_id ) LOOP
10979         IF ind_data.field < 0 THEN
10980             ind_data.field = -1 * ind_data.field;
10981             INSERT INTO metabib.facet_entry (field, source, value)
10982                 VALUES (ind_data.field, ind_data.source, ind_data.value);
10983         ELSE
10984             EXECUTE $$
10985                 INSERT INTO metabib.$$ || ind_data.field_class || $$_field_entry (field, source, value)
10986                     VALUES ($$ ||
10987                         quote_literal(ind_data.field) || $$, $$ ||
10988                         quote_literal(ind_data.source) || $$, $$ ||
10989                         quote_literal(ind_data.value) ||
10990                     $$);$$;
10991         END IF;
10992
10993     END LOOP;
10994
10995     RETURN;
10996 END;
10997 $func$ LANGUAGE PLPGSQL;
10998
10999 CREATE OR REPLACE FUNCTION biblio.extract_located_uris( bib_id BIGINT, marcxml TEXT, editor_id INT ) RETURNS VOID AS $func$
11000 DECLARE
11001     uris            TEXT[];
11002     uri_xml         TEXT;
11003     uri_label       TEXT;
11004     uri_href        TEXT;
11005     uri_use         TEXT;
11006     uri_owner       TEXT;
11007     uri_owner_id    INT;
11008     uri_id          INT;
11009     uri_cn_id       INT;
11010     uri_map_id      INT;
11011 BEGIN
11012
11013     uris := oils_xpath('//*[@tag="856" and (@ind1="4" or @ind1="1") and (@ind2="0" or @ind2="1")]',marcxml);
11014     IF ARRAY_UPPER(uris,1) > 0 THEN
11015         FOR i IN 1 .. ARRAY_UPPER(uris, 1) LOOP
11016             -- First we pull info out of the 856
11017             uri_xml     := uris[i];
11018
11019             uri_href    := (oils_xpath('//*[@code="u"]/text()',uri_xml))[1];
11020             CONTINUE WHEN uri_href IS NULL;
11021
11022             uri_label   := (oils_xpath('//*[@code="y"]/text()|//*[@code="3"]/text()|//*[@code="u"]/text()',uri_xml))[1];
11023             CONTINUE WHEN uri_label IS NULL;
11024
11025             uri_owner   := (oils_xpath('//*[@code="9"]/text()|//*[@code="w"]/text()|//*[@code="n"]/text()',uri_xml))[1];
11026             CONTINUE WHEN uri_owner IS NULL;
11027
11028             uri_use     := (oils_xpath('//*[@code="z"]/text()|//*[@code="2"]/text()|//*[@code="n"]/text()|//*[@code="u"]/text()',uri_xml))[1];
11029
11030             uri_owner := REGEXP_REPLACE(uri_owner, $re$^.*?\((\w+)\).*$$re$, E'\\1');
11031
11032             SELECT id INTO uri_owner_id FROM actor.org_unit WHERE shortname = uri_owner;
11033             CONTINUE WHEN NOT FOUND;
11034
11035             -- now we look for a matching uri
11036             SELECT id INTO uri_id FROM asset.uri WHERE label = uri_label AND href = uri_href AND use_restriction = uri_use AND active;
11037             IF NOT FOUND THEN -- create one
11038                 INSERT INTO asset.uri (label, href, use_restriction) VALUES (uri_label, uri_href, uri_use);
11039                 SELECT id INTO uri_id FROM asset.uri WHERE label = uri_label AND href = uri_href AND use_restriction = uri_use AND active;
11040             END IF;
11041
11042             -- we need a call number to link through
11043             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;
11044             IF NOT FOUND THEN
11045                 INSERT INTO asset.call_number (owning_lib, record, create_date, edit_date, creator, editor, label)
11046                     VALUES (uri_owner_id, bib_id, 'now', 'now', editor_id, editor_id, '##URI##');
11047                 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;
11048             END IF;
11049
11050             -- now, link them if they're not already
11051             SELECT id INTO uri_map_id FROM asset.uri_call_number_map WHERE call_number = uri_cn_id AND uri = uri_id;
11052             IF NOT FOUND THEN
11053                 INSERT INTO asset.uri_call_number_map (call_number, uri) VALUES (uri_cn_id, uri_id);
11054             END IF;
11055
11056         END LOOP;
11057     END IF;
11058
11059     RETURN;
11060 END;
11061 $func$ LANGUAGE PLPGSQL;
11062
11063 CREATE OR REPLACE FUNCTION metabib.remap_metarecord_for_bib( bib_id BIGINT, fp TEXT ) RETURNS BIGINT AS $func$
11064 DECLARE
11065     source_count    INT;
11066     old_mr          BIGINT;
11067     tmp_mr          metabib.metarecord%ROWTYPE;
11068     deleted_mrs     BIGINT[];
11069 BEGIN
11070
11071     DELETE FROM metabib.metarecord_source_map WHERE source = bib_id; -- Rid ourselves of the search-estimate-killing linkage
11072
11073     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
11074
11075         IF old_mr IS NULL AND fp = tmp_mr.fingerprint THEN -- Find the first fingerprint-matching
11076             old_mr := tmp_mr.id;
11077         ELSE
11078             SELECT COUNT(*) INTO source_count FROM metabib.metarecord_source_map WHERE metarecord = tmp_mr.id;
11079             IF source_count = 0 THEN -- No other records
11080                 deleted_mrs := ARRAY_APPEND(deleted_mrs, tmp_mr.id);
11081                 DELETE FROM metabib.metarecord WHERE id = tmp_mr.id;
11082             END IF;
11083         END IF;
11084
11085     END LOOP;
11086
11087     IF old_mr IS NULL THEN -- we found no suitable, preexisting MR based on old source maps
11088         SELECT id INTO old_mr FROM metabib.metarecord WHERE fingerprint = fp; -- is there one for our current fingerprint?
11089         IF old_mr IS NULL THEN -- nope, create one and grab its id
11090             INSERT INTO metabib.metarecord ( fingerprint, master_record ) VALUES ( fp, bib_id );
11091             SELECT id INTO old_mr FROM metabib.metarecord WHERE fingerprint = fp;
11092         ELSE -- indeed there is. update it with a null cache and recalcualated master record
11093             UPDATE  metabib.metarecord
11094               SET   mods = NULL,
11095                     master_record = ( SELECT id FROM biblio.record_entry WHERE fingerprint = fp ORDER BY quality DESC LIMIT 1)
11096               WHERE id = old_mr;
11097         END IF;
11098     ELSE -- there was one we already attached to, update its mods cache and master_record
11099         UPDATE  metabib.metarecord
11100           SET   mods = NULL,
11101                 master_record = ( SELECT id FROM biblio.record_entry WHERE fingerprint = fp ORDER BY quality DESC LIMIT 1)
11102           WHERE id = old_mr;
11103     END IF;
11104
11105     INSERT INTO metabib.metarecord_source_map (metarecord, source) VALUES (old_mr, bib_id); -- new source mapping
11106
11107     IF ARRAY_UPPER(deleted_mrs,1) > 0 THEN
11108         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
11109     END IF;
11110
11111     RETURN old_mr;
11112
11113 END;
11114 $func$ LANGUAGE PLPGSQL;
11115
11116 CREATE OR REPLACE FUNCTION metabib.reingest_metabib_full_rec( bib_id BIGINT ) RETURNS VOID AS $func$
11117 BEGIN
11118     PERFORM * FROM config.internal_flag WHERE name = 'ingest.assume_inserts_only' AND enabled;
11119     IF NOT FOUND THEN
11120         DELETE FROM metabib.real_full_rec WHERE record = bib_id;
11121     END IF;
11122     INSERT INTO metabib.real_full_rec (record, tag, ind1, ind2, subfield, value)
11123         SELECT record, tag, ind1, ind2, subfield, value FROM biblio.flatten_marc( bib_id );
11124
11125     RETURN;
11126 END;
11127 $func$ LANGUAGE PLPGSQL;
11128
11129 -- AFTER UPDATE OR INSERT trigger for biblio.record_entry
11130 CREATE OR REPLACE FUNCTION biblio.indexing_ingest_or_delete () RETURNS TRIGGER AS $func$
11131 BEGIN
11132
11133     IF NEW.deleted IS TRUE THEN -- If this bib is deleted
11134         DELETE FROM metabib.metarecord_source_map WHERE source = NEW.id; -- Rid ourselves of the search-estimate-killing linkage
11135         DELETE FROM authority.bib_linking WHERE bib = NEW.id; -- Avoid updating fields in bibs that are no longer visible
11136         RETURN NEW; -- and we're done
11137     END IF;
11138
11139     IF TG_OP = 'UPDATE' THEN -- re-ingest?
11140         PERFORM * FROM config.internal_flag WHERE name = 'ingest.reingest.force_on_same_marc' AND enabled;
11141
11142         IF NOT FOUND AND OLD.marc = NEW.marc THEN -- don't do anything if the MARC didn't change
11143             RETURN NEW;
11144         END IF;
11145     END IF;
11146
11147     -- Record authority linking
11148     PERFORM * FROM config.internal_flag WHERE name = 'ingest.disable_authority_linking' AND enabled;
11149     IF NOT FOUND THEN
11150         PERFORM biblio.map_authority_linking( NEW.id, NEW.marc );
11151     END IF;
11152
11153     -- Flatten and insert the mfr data
11154     PERFORM * FROM config.internal_flag WHERE name = 'ingest.disable_metabib_full_rec' AND enabled;
11155     IF NOT FOUND THEN
11156         PERFORM metabib.reingest_metabib_full_rec(NEW.id);
11157         PERFORM * FROM config.internal_flag WHERE name = 'ingest.disable_metabib_rec_descriptor' AND enabled;
11158         IF NOT FOUND THEN
11159             PERFORM metabib.reingest_metabib_rec_descriptor(NEW.id);
11160         END IF;
11161     END IF;
11162
11163     -- Gather and insert the field entry data
11164     PERFORM metabib.reingest_metabib_field_entries(NEW.id);
11165
11166     -- Located URI magic
11167     IF TG_OP = 'INSERT' THEN
11168         PERFORM * FROM config.internal_flag WHERE name = 'ingest.disable_located_uri' AND enabled;
11169         IF NOT FOUND THEN
11170             PERFORM biblio.extract_located_uris( NEW.id, NEW.marc, NEW.editor );
11171         END IF;
11172     ELSE
11173         PERFORM * FROM config.internal_flag WHERE name = 'ingest.disable_located_uri' AND enabled;
11174         IF NOT FOUND THEN
11175             PERFORM biblio.extract_located_uris( NEW.id, NEW.marc, NEW.editor );
11176         END IF;
11177     END IF;
11178
11179     -- (re)map metarecord-bib linking
11180     IF TG_OP = 'INSERT' THEN -- if not deleted and performing an insert, check for the flag
11181         PERFORM * FROM config.internal_flag WHERE name = 'ingest.metarecord_mapping.skip_on_insert' AND enabled;
11182         IF NOT FOUND THEN
11183             PERFORM metabib.remap_metarecord_for_bib( NEW.id, NEW.fingerprint );
11184         END IF;
11185     ELSE -- we're doing an update, and we're not deleted, remap
11186         PERFORM * FROM config.internal_flag WHERE name = 'ingest.metarecord_mapping.skip_on_update' AND enabled;
11187         IF NOT FOUND THEN
11188             PERFORM metabib.remap_metarecord_for_bib( NEW.id, NEW.fingerprint );
11189         END IF;
11190     END IF;
11191
11192     RETURN NEW;
11193 END;
11194 $func$ LANGUAGE PLPGSQL;
11195
11196 CREATE TRIGGER fingerprint_tgr BEFORE INSERT OR UPDATE ON biblio.record_entry FOR EACH ROW EXECUTE PROCEDURE biblio.fingerprint_trigger ('eng','BKS');
11197 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 ();
11198
11199 DROP TRIGGER IF EXISTS zzz_update_materialized_simple_rec_delete_tgr ON biblio.record_entry;
11200
11201 CREATE OR REPLACE FUNCTION oils_xpath_table ( key TEXT, document_field TEXT, relation_name TEXT, xpaths TEXT, criteria TEXT ) RETURNS SETOF RECORD AS $func$
11202 DECLARE
11203     xpath_list  TEXT[];
11204     select_list TEXT[];
11205     where_list  TEXT[];
11206     q           TEXT;
11207     out_record  RECORD;
11208     empty_test  RECORD;
11209 BEGIN
11210     xpath_list := STRING_TO_ARRAY( xpaths, '|' );
11211  
11212     select_list := ARRAY_APPEND( select_list, key || '::INT AS key' );
11213  
11214     FOR i IN 1 .. ARRAY_UPPER(xpath_list,1) LOOP
11215         IF xpath_list[i] = 'null()' THEN
11216             select_list := ARRAY_APPEND( select_list, 'NULL::TEXT AS c_' || i );
11217         ELSE
11218             select_list := ARRAY_APPEND(
11219                 select_list,
11220                 $sel$
11221                 EXPLODE_ARRAY(
11222                     COALESCE(
11223                         NULLIF(
11224                             oils_xpath(
11225                                 $sel$ ||
11226                                     quote_literal(
11227                                         CASE
11228                                             WHEN xpath_list[i] ~ $re$/[^/[]*@[^/]+$$re$ OR xpath_list[i] ~ $re$text\(\)$$re$ THEN xpath_list[i]
11229                                             ELSE xpath_list[i] || '//text()'
11230                                         END
11231                                     ) ||
11232                                 $sel$,
11233                                 $sel$ || document_field || $sel$
11234                             ),
11235                            '{}'::TEXT[]
11236                         ),
11237                         '{NULL}'::TEXT[]
11238                     )
11239                 ) AS c_$sel$ || i
11240             );
11241             where_list := ARRAY_APPEND(
11242                 where_list,
11243                 'c_' || i || ' IS NOT NULL'
11244             );
11245         END IF;
11246     END LOOP;
11247  
11248     q := $q$
11249 SELECT * FROM (
11250     SELECT $q$ || ARRAY_TO_STRING( select_list, ', ' ) || $q$ FROM $q$ || relation_name || $q$ WHERE ($q$ || criteria || $q$)
11251 )x WHERE $q$ || ARRAY_TO_STRING( where_list, ' AND ' );
11252     -- RAISE NOTICE 'query: %', q;
11253  
11254     FOR out_record IN EXECUTE q LOOP
11255         RETURN NEXT out_record;
11256     END LOOP;
11257  
11258     RETURN;
11259 END;
11260 $func$ LANGUAGE PLPGSQL IMMUTABLE;
11261
11262 CREATE OR REPLACE FUNCTION vandelay.ingest_items ( import_id BIGINT, attr_def_id BIGINT ) RETURNS SETOF vandelay.import_item AS $$
11263 DECLARE
11264
11265     owning_lib      TEXT;
11266     circ_lib        TEXT;
11267     call_number     TEXT;
11268     copy_number     TEXT;
11269     status          TEXT;
11270     location        TEXT;
11271     circulate       TEXT;
11272     deposit         TEXT;
11273     deposit_amount  TEXT;
11274     ref             TEXT;
11275     holdable        TEXT;
11276     price           TEXT;
11277     barcode         TEXT;
11278     circ_modifier   TEXT;
11279     circ_as_type    TEXT;
11280     alert_message   TEXT;
11281     opac_visible    TEXT;
11282     pub_note        TEXT;
11283     priv_note       TEXT;
11284
11285     attr_def        RECORD;
11286     tmp_attr_set    RECORD;
11287     attr_set        vandelay.import_item%ROWTYPE;
11288
11289     xpath           TEXT;
11290
11291 BEGIN
11292
11293     SELECT * INTO attr_def FROM vandelay.import_item_attr_definition WHERE id = attr_def_id;
11294
11295     IF FOUND THEN
11296
11297         attr_set.definition := attr_def.id; 
11298     
11299         -- Build the combined XPath
11300     
11301         owning_lib :=
11302             CASE
11303                 WHEN attr_def.owning_lib IS NULL THEN 'null()'
11304                 WHEN LENGTH( attr_def.owning_lib ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.owning_lib || '"]'
11305                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.owning_lib
11306             END;
11307     
11308         circ_lib :=
11309             CASE
11310                 WHEN attr_def.circ_lib IS NULL THEN 'null()'
11311                 WHEN LENGTH( attr_def.circ_lib ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.circ_lib || '"]'
11312                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.circ_lib
11313             END;
11314     
11315         call_number :=
11316             CASE
11317                 WHEN attr_def.call_number IS NULL THEN 'null()'
11318                 WHEN LENGTH( attr_def.call_number ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.call_number || '"]'
11319                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.call_number
11320             END;
11321     
11322         copy_number :=
11323             CASE
11324                 WHEN attr_def.copy_number IS NULL THEN 'null()'
11325                 WHEN LENGTH( attr_def.copy_number ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.copy_number || '"]'
11326                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.copy_number
11327             END;
11328     
11329         status :=
11330             CASE
11331                 WHEN attr_def.status IS NULL THEN 'null()'
11332                 WHEN LENGTH( attr_def.status ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.status || '"]'
11333                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.status
11334             END;
11335     
11336         location :=
11337             CASE
11338                 WHEN attr_def.location IS NULL THEN 'null()'
11339                 WHEN LENGTH( attr_def.location ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.location || '"]'
11340                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.location
11341             END;
11342     
11343         circulate :=
11344             CASE
11345                 WHEN attr_def.circulate IS NULL THEN 'null()'
11346                 WHEN LENGTH( attr_def.circulate ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.circulate || '"]'
11347                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.circulate
11348             END;
11349     
11350         deposit :=
11351             CASE
11352                 WHEN attr_def.deposit IS NULL THEN 'null()'
11353                 WHEN LENGTH( attr_def.deposit ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.deposit || '"]'
11354                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.deposit
11355             END;
11356     
11357         deposit_amount :=
11358             CASE
11359                 WHEN attr_def.deposit_amount IS NULL THEN 'null()'
11360                 WHEN LENGTH( attr_def.deposit_amount ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.deposit_amount || '"]'
11361                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.deposit_amount
11362             END;
11363     
11364         ref :=
11365             CASE
11366                 WHEN attr_def.ref IS NULL THEN 'null()'
11367                 WHEN LENGTH( attr_def.ref ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.ref || '"]'
11368                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.ref
11369             END;
11370     
11371         holdable :=
11372             CASE
11373                 WHEN attr_def.holdable IS NULL THEN 'null()'
11374                 WHEN LENGTH( attr_def.holdable ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.holdable || '"]'
11375                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.holdable
11376             END;
11377     
11378         price :=
11379             CASE
11380                 WHEN attr_def.price IS NULL THEN 'null()'
11381                 WHEN LENGTH( attr_def.price ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.price || '"]'
11382                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.price
11383             END;
11384     
11385         barcode :=
11386             CASE
11387                 WHEN attr_def.barcode IS NULL THEN 'null()'
11388                 WHEN LENGTH( attr_def.barcode ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.barcode || '"]'
11389                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.barcode
11390             END;
11391     
11392         circ_modifier :=
11393             CASE
11394                 WHEN attr_def.circ_modifier IS NULL THEN 'null()'
11395                 WHEN LENGTH( attr_def.circ_modifier ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.circ_modifier || '"]'
11396                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.circ_modifier
11397             END;
11398     
11399         circ_as_type :=
11400             CASE
11401                 WHEN attr_def.circ_as_type IS NULL THEN 'null()'
11402                 WHEN LENGTH( attr_def.circ_as_type ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.circ_as_type || '"]'
11403                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.circ_as_type
11404             END;
11405     
11406         alert_message :=
11407             CASE
11408                 WHEN attr_def.alert_message IS NULL THEN 'null()'
11409                 WHEN LENGTH( attr_def.alert_message ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.alert_message || '"]'
11410                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.alert_message
11411             END;
11412     
11413         opac_visible :=
11414             CASE
11415                 WHEN attr_def.opac_visible IS NULL THEN 'null()'
11416                 WHEN LENGTH( attr_def.opac_visible ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.opac_visible || '"]'
11417                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.opac_visible
11418             END;
11419
11420         pub_note :=
11421             CASE
11422                 WHEN attr_def.pub_note IS NULL THEN 'null()'
11423                 WHEN LENGTH( attr_def.pub_note ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.pub_note || '"]'
11424                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.pub_note
11425             END;
11426         priv_note :=
11427             CASE
11428                 WHEN attr_def.priv_note IS NULL THEN 'null()'
11429                 WHEN LENGTH( attr_def.priv_note ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.priv_note || '"]'
11430                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.priv_note
11431             END;
11432     
11433     
11434         xpath := 
11435             owning_lib      || '|' || 
11436             circ_lib        || '|' || 
11437             call_number     || '|' || 
11438             copy_number     || '|' || 
11439             status          || '|' || 
11440             location        || '|' || 
11441             circulate       || '|' || 
11442             deposit         || '|' || 
11443             deposit_amount  || '|' || 
11444             ref             || '|' || 
11445             holdable        || '|' || 
11446             price           || '|' || 
11447             barcode         || '|' || 
11448             circ_modifier   || '|' || 
11449             circ_as_type    || '|' || 
11450             alert_message   || '|' || 
11451             pub_note        || '|' || 
11452             priv_note       || '|' || 
11453             opac_visible;
11454
11455         -- RAISE NOTICE 'XPath: %', xpath;
11456         
11457         FOR tmp_attr_set IN
11458                 SELECT  *
11459                   FROM  oils_xpath_table( 'id', 'marc', 'vandelay.queued_bib_record', xpath, 'id = ' || import_id )
11460                             AS t( id INT, ol TEXT, clib TEXT, cn TEXT, cnum TEXT, cs TEXT, cl TEXT, circ TEXT,
11461                                   dep TEXT, dep_amount TEXT, r TEXT, hold TEXT, pr TEXT, bc TEXT, circ_mod TEXT,
11462                                   circ_as TEXT, amessage TEXT, note TEXT, pnote TEXT, opac_vis TEXT )
11463         LOOP
11464     
11465             tmp_attr_set.pr = REGEXP_REPLACE(tmp_attr_set.pr, E'[^0-9\\.]', '', 'g');
11466             tmp_attr_set.dep_amount = REGEXP_REPLACE(tmp_attr_set.dep_amount, E'[^0-9\\.]', '', 'g');
11467
11468             tmp_attr_set.pr := NULLIF( tmp_attr_set.pr, '' );
11469             tmp_attr_set.dep_amount := NULLIF( tmp_attr_set.dep_amount, '' );
11470     
11471             SELECT id INTO attr_set.owning_lib FROM actor.org_unit WHERE shortname = UPPER(tmp_attr_set.ol); -- INT
11472             SELECT id INTO attr_set.circ_lib FROM actor.org_unit WHERE shortname = UPPER(tmp_attr_set.clib); -- INT
11473             SELECT id INTO attr_set.status FROM config.copy_status WHERE LOWER(name) = LOWER(tmp_attr_set.cs); -- INT
11474     
11475             SELECT  id INTO attr_set.location
11476               FROM  asset.copy_location
11477               WHERE LOWER(name) = LOWER(tmp_attr_set.cl)
11478                     AND asset.copy_location.owning_lib = COALESCE(attr_set.owning_lib, attr_set.circ_lib); -- INT
11479     
11480             attr_set.circulate      :=
11481                 LOWER( SUBSTRING( tmp_attr_set.circ, 1, 1)) IN ('t','y','1')
11482                 OR LOWER(tmp_attr_set.circ) = 'circulating'; -- BOOL
11483
11484             attr_set.deposit        :=
11485                 LOWER( SUBSTRING( tmp_attr_set.dep, 1, 1 ) ) IN ('t','y','1')
11486                 OR LOWER(tmp_attr_set.dep) = 'deposit'; -- BOOL
11487
11488             attr_set.holdable       :=
11489                 LOWER( SUBSTRING( tmp_attr_set.hold, 1, 1 ) ) IN ('t','y','1')
11490                 OR LOWER(tmp_attr_set.hold) = 'holdable'; -- BOOL
11491
11492             attr_set.opac_visible   :=
11493                 LOWER( SUBSTRING( tmp_attr_set.opac_vis, 1, 1 ) ) IN ('t','y','1')
11494                 OR LOWER(tmp_attr_set.opac_vis) = 'visible'; -- BOOL
11495
11496             attr_set.ref            :=
11497                 LOWER( SUBSTRING( tmp_attr_set.r, 1, 1 ) ) IN ('t','y','1')
11498                 OR LOWER(tmp_attr_set.r) = 'reference'; -- BOOL
11499     
11500             attr_set.copy_number    := tmp_attr_set.cnum::INT; -- INT,
11501             attr_set.deposit_amount := tmp_attr_set.dep_amount::NUMERIC(6,2); -- NUMERIC(6,2),
11502             attr_set.price          := tmp_attr_set.pr::NUMERIC(8,2); -- NUMERIC(8,2),
11503     
11504             attr_set.call_number    := tmp_attr_set.cn; -- TEXT
11505             attr_set.barcode        := tmp_attr_set.bc; -- TEXT,
11506             attr_set.circ_modifier  := tmp_attr_set.circ_mod; -- TEXT,
11507             attr_set.circ_as_type   := tmp_attr_set.circ_as; -- TEXT,
11508             attr_set.alert_message  := tmp_attr_set.amessage; -- TEXT,
11509             attr_set.pub_note       := tmp_attr_set.note; -- TEXT,
11510             attr_set.priv_note      := tmp_attr_set.pnote; -- TEXT,
11511             attr_set.alert_message  := tmp_attr_set.amessage; -- TEXT,
11512     
11513             RETURN NEXT attr_set;
11514     
11515         END LOOP;
11516     
11517     END IF;
11518
11519     RETURN;
11520
11521 END;
11522 $$ LANGUAGE PLPGSQL;
11523
11524 CREATE OR REPLACE FUNCTION vandelay.ingest_bib_items ( ) RETURNS TRIGGER AS $func$
11525 DECLARE
11526     attr_def    BIGINT;
11527     item_data   vandelay.import_item%ROWTYPE;
11528 BEGIN
11529
11530     SELECT item_attr_def INTO attr_def FROM vandelay.bib_queue WHERE id = NEW.queue;
11531
11532     FOR item_data IN SELECT * FROM vandelay.ingest_items( NEW.id::BIGINT, attr_def ) LOOP
11533         INSERT INTO vandelay.import_item (
11534             record,
11535             definition,
11536             owning_lib,
11537             circ_lib,
11538             call_number,
11539             copy_number,
11540             status,
11541             location,
11542             circulate,
11543             deposit,
11544             deposit_amount,
11545             ref,
11546             holdable,
11547             price,
11548             barcode,
11549             circ_modifier,
11550             circ_as_type,
11551             alert_message,
11552             pub_note,
11553             priv_note,
11554             opac_visible
11555         ) VALUES (
11556             NEW.id,
11557             item_data.definition,
11558             item_data.owning_lib,
11559             item_data.circ_lib,
11560             item_data.call_number,
11561             item_data.copy_number,
11562             item_data.status,
11563             item_data.location,
11564             item_data.circulate,
11565             item_data.deposit,
11566             item_data.deposit_amount,
11567             item_data.ref,
11568             item_data.holdable,
11569             item_data.price,
11570             item_data.barcode,
11571             item_data.circ_modifier,
11572             item_data.circ_as_type,
11573             item_data.alert_message,
11574             item_data.pub_note,
11575             item_data.priv_note,
11576             item_data.opac_visible
11577         );
11578     END LOOP;
11579
11580     RETURN NULL;
11581 END;
11582 $func$ LANGUAGE PLPGSQL;
11583
11584 CREATE OR REPLACE FUNCTION acq.create_acq_seq     ( sch TEXT, tbl TEXT ) RETURNS BOOL AS $creator$
11585 BEGIN
11586     EXECUTE $$
11587         CREATE SEQUENCE acq.$$ || sch || $$_$$ || tbl || $$_pkey_seq;
11588     $$;
11589         RETURN TRUE;
11590 END;
11591 $creator$ LANGUAGE 'plpgsql';
11592
11593 CREATE OR REPLACE FUNCTION acq.create_acq_history ( sch TEXT, tbl TEXT ) RETURNS BOOL AS $creator$
11594 BEGIN
11595     EXECUTE $$
11596         CREATE TABLE acq.$$ || sch || $$_$$ || tbl || $$_history (
11597             audit_id    BIGINT                          PRIMARY KEY,
11598             audit_time  TIMESTAMP WITH TIME ZONE        NOT NULL,
11599             audit_action        TEXT                            NOT NULL,
11600             LIKE $$ || sch || $$.$$ || tbl || $$
11601         );
11602     $$;
11603         RETURN TRUE;
11604 END;
11605 $creator$ LANGUAGE 'plpgsql';
11606
11607 CREATE OR REPLACE FUNCTION acq.create_acq_func    ( sch TEXT, tbl TEXT ) RETURNS BOOL AS $creator$
11608 BEGIN
11609     EXECUTE $$
11610         CREATE OR REPLACE FUNCTION acq.audit_$$ || sch || $$_$$ || tbl || $$_func ()
11611         RETURNS TRIGGER AS $func$
11612         BEGIN
11613             INSERT INTO acq.$$ || sch || $$_$$ || tbl || $$_history
11614                 SELECT  nextval('acq.$$ || sch || $$_$$ || tbl || $$_pkey_seq'),
11615                     now(),
11616                     SUBSTR(TG_OP,1,1),
11617                     OLD.*;
11618             RETURN NULL;
11619         END;
11620         $func$ LANGUAGE 'plpgsql';
11621     $$;
11622         RETURN TRUE;
11623 END;
11624 $creator$ LANGUAGE 'plpgsql';
11625
11626 CREATE OR REPLACE FUNCTION acq.create_acq_update_trigger ( sch TEXT, tbl TEXT ) RETURNS BOOL AS $creator$
11627 BEGIN
11628     EXECUTE $$
11629         CREATE TRIGGER audit_$$ || sch || $$_$$ || tbl || $$_update_trigger
11630             AFTER UPDATE OR DELETE ON $$ || sch || $$.$$ || tbl || $$ FOR EACH ROW
11631             EXECUTE PROCEDURE acq.audit_$$ || sch || $$_$$ || tbl || $$_func ();
11632     $$;
11633         RETURN TRUE;
11634 END;
11635 $creator$ LANGUAGE 'plpgsql';
11636
11637 CREATE OR REPLACE FUNCTION acq.create_acq_lifecycle     ( sch TEXT, tbl TEXT ) RETURNS BOOL AS $creator$
11638 BEGIN
11639     EXECUTE $$
11640         CREATE OR REPLACE VIEW acq.$$ || sch || $$_$$ || tbl || $$_lifecycle AS
11641             SELECT      -1, now() as audit_time, '-' as audit_action, *
11642               FROM      $$ || sch || $$.$$ || tbl || $$
11643                 UNION ALL
11644             SELECT      *
11645               FROM      acq.$$ || sch || $$_$$ || tbl || $$_history;
11646     $$;
11647         RETURN TRUE;
11648 END;
11649 $creator$ LANGUAGE 'plpgsql';
11650
11651 -- The main event
11652
11653 CREATE OR REPLACE FUNCTION acq.create_acq_auditor ( sch TEXT, tbl TEXT ) RETURNS BOOL AS $creator$
11654 BEGIN
11655     PERFORM acq.create_acq_seq(sch, tbl);
11656     PERFORM acq.create_acq_history(sch, tbl);
11657     PERFORM acq.create_acq_func(sch, tbl);
11658     PERFORM acq.create_acq_update_trigger(sch, tbl);
11659     PERFORM acq.create_acq_lifecycle(sch, tbl);
11660     RETURN TRUE;
11661 END;
11662 $creator$ LANGUAGE 'plpgsql';
11663
11664 ALTER TABLE acq.lineitem DROP COLUMN item_count;
11665
11666 CREATE OR REPLACE VIEW acq.fund_debit_total AS
11667     SELECT  fund.id AS fund,
11668             fund_debit.encumbrance AS encumbrance,
11669             SUM( COALESCE( fund_debit.amount, 0 ) ) AS amount
11670       FROM acq.fund AS fund
11671                         LEFT JOIN acq.fund_debit AS fund_debit
11672                                 ON ( fund.id = fund_debit.fund )
11673       GROUP BY 1,2;
11674
11675 CREATE TABLE acq.debit_attribution (
11676         id                     INT         NOT NULL PRIMARY KEY,
11677         fund_debit             INT         NOT NULL
11678                                            REFERENCES acq.fund_debit
11679                                            DEFERRABLE INITIALLY DEFERRED,
11680     debit_amount           NUMERIC     NOT NULL,
11681         funding_source_credit  INT         REFERENCES acq.funding_source_credit
11682                                            DEFERRABLE INITIALLY DEFERRED,
11683     credit_amount          NUMERIC
11684 );
11685
11686 CREATE INDEX acq_attribution_debit_idx
11687         ON acq.debit_attribution( fund_debit );
11688
11689 CREATE INDEX acq_attribution_credit_idx
11690         ON acq.debit_attribution( funding_source_credit );
11691
11692 CREATE OR REPLACE FUNCTION acq.attribute_debits() RETURNS VOID AS $$
11693 /*
11694 Function to attribute expenditures and encumbrances to funding source credits,
11695 and thereby to funding sources.
11696
11697 Read the debits in chonological order, attributing each one to one or
11698 more funding source credits.  Constraints:
11699
11700 1. Don't attribute more to a credit than the amount of the credit.
11701
11702 2. For a given fund, don't attribute more to a funding source than the
11703 source has allocated to that fund.
11704
11705 3. Attribute debits to credits with deadlines before attributing them to
11706 credits without deadlines.  Otherwise attribute to the earliest credits
11707 first, based on the deadline date when present, or on the effective date
11708 when there is no deadline.  Use funding_source_credit.id as a tie-breaker.
11709 This ordering is defined by an ORDER BY clause on the view
11710 acq.ordered_funding_source_credit.
11711
11712 Start by truncating the table acq.debit_attribution.  Then insert a row
11713 into that table for each attribution.  If a debit cannot be fully
11714 attributed, insert a row for the unattributable balance, with the 
11715 funding_source_credit and credit_amount columns NULL.
11716 */
11717 DECLARE
11718         curr_fund_source_bal RECORD;
11719         seqno                INT;     -- sequence num for credits applicable to a fund
11720         fund_credit          RECORD;  -- current row in temp t_fund_credit table
11721         fc                   RECORD;  -- used for loading t_fund_credit table
11722         sc                   RECORD;  -- used for loading t_fund_credit table
11723         --
11724         -- Used exclusively in the main loop:
11725         --
11726         deb                 RECORD;   -- current row from acq.fund_debit table
11727         curr_credit_bal     RECORD;   -- current row from temp t_credit table
11728         debit_balance       NUMERIC;  -- amount left to attribute for current debit
11729         conv_debit_balance  NUMERIC;  -- debit balance in currency of the fund
11730         attr_amount         NUMERIC;  -- amount being attributed, in currency of debit
11731         conv_attr_amount    NUMERIC;  -- amount being attributed, in currency of source
11732         conv_cred_balance   NUMERIC;  -- credit_balance in the currency of the fund
11733         conv_alloc_balance  NUMERIC;  -- allocated balance in the currency of the fund
11734         attrib_count        INT;      -- populates id of acq.debit_attribution
11735 BEGIN
11736         --
11737         -- Load a temporary table.  For each combination of fund and funding source,
11738         -- load an entry with the total amount allocated to that fund by that source.
11739         -- This sum may reflect transfers as well as original allocations.  We will
11740         -- reduce this balance whenever we attribute debits to it.
11741         --
11742         CREATE TEMP TABLE t_fund_source_bal
11743         ON COMMIT DROP AS
11744                 SELECT
11745                         fund AS fund,
11746                         funding_source AS source,
11747                         sum( amount ) AS balance
11748                 FROM
11749                         acq.fund_allocation
11750                 GROUP BY
11751                         fund,
11752                         funding_source
11753                 HAVING
11754                         sum( amount ) > 0;
11755         --
11756         CREATE INDEX t_fund_source_bal_idx
11757                 ON t_fund_source_bal( fund, source );
11758         -------------------------------------------------------------------------------
11759         --
11760         -- Load another temporary table.  For each fund, load zero or more
11761         -- funding source credits from which that fund can get money.
11762         --
11763         CREATE TEMP TABLE t_fund_credit (
11764                 fund        INT,
11765                 seq         INT,
11766                 credit      INT
11767         ) ON COMMIT DROP;
11768         --
11769         FOR fc IN
11770                 SELECT DISTINCT fund
11771                 FROM acq.fund_allocation
11772                 ORDER BY fund
11773         LOOP                  -- Loop over the funds
11774                 seqno := 1;
11775                 FOR sc IN
11776                         SELECT
11777                                 ofsc.id
11778                         FROM
11779                                 acq.ordered_funding_source_credit AS ofsc
11780                         WHERE
11781                                 ofsc.funding_source IN
11782                                 (
11783                                         SELECT funding_source
11784                                         FROM acq.fund_allocation
11785                                         WHERE fund = fc.fund
11786                                 )
11787                 ORDER BY
11788                     ofsc.sort_priority,
11789                     ofsc.sort_date,
11790                     ofsc.id
11791                 LOOP                        -- Add each credit to the list
11792                         INSERT INTO t_fund_credit (
11793                                 fund,
11794                                 seq,
11795                                 credit
11796                         ) VALUES (
11797                                 fc.fund,
11798                                 seqno,
11799                                 sc.id
11800                         );
11801                         --RAISE NOTICE 'Fund % credit %', fc.fund, sc.id;
11802                         seqno := seqno + 1;
11803                 END LOOP;     -- Loop over credits for a given fund
11804         END LOOP;         -- Loop over funds
11805         --
11806         CREATE INDEX t_fund_credit_idx
11807                 ON t_fund_credit( fund, seq );
11808         -------------------------------------------------------------------------------
11809         --
11810         -- Load yet another temporary table.  This one is a list of funding source
11811         -- credits, with their balances.  We shall reduce those balances as we
11812         -- attribute debits to them.
11813         --
11814         CREATE TEMP TABLE t_credit
11815         ON COMMIT DROP AS
11816         SELECT
11817             fsc.id AS credit,
11818             fsc.funding_source AS source,
11819             fsc.amount AS balance,
11820             fs.currency_type AS currency_type
11821         FROM
11822             acq.funding_source_credit AS fsc,
11823             acq.funding_source fs
11824         WHERE
11825             fsc.funding_source = fs.id
11826                         AND fsc.amount > 0;
11827         --
11828         CREATE INDEX t_credit_idx
11829                 ON t_credit( credit );
11830         --
11831         -------------------------------------------------------------------------------
11832         --
11833         -- Now that we have loaded the lookup tables: loop through the debits,
11834         -- attributing each one to one or more funding source credits.
11835         -- 
11836         truncate table acq.debit_attribution;
11837         --
11838         attrib_count := 0;
11839         FOR deb in
11840                 SELECT
11841                         fd.id,
11842                         fd.fund,
11843                         fd.amount,
11844                         f.currency_type,
11845                         fd.encumbrance
11846                 FROM
11847                         acq.fund_debit fd,
11848                         acq.fund f
11849                 WHERE
11850                         fd.fund = f.id
11851                 ORDER BY
11852                         fd.id
11853         LOOP
11854                 --RAISE NOTICE 'Debit %, fund %', deb.id, deb.fund;
11855                 --
11856                 debit_balance := deb.amount;
11857                 --
11858                 -- Loop over the funding source credits that are eligible
11859                 -- to pay for this debit
11860                 --
11861                 FOR fund_credit IN
11862                         SELECT
11863                                 credit
11864                         FROM
11865                                 t_fund_credit
11866                         WHERE
11867                                 fund = deb.fund
11868                         ORDER BY
11869                                 seq
11870                 LOOP
11871                         --RAISE NOTICE '   Examining credit %', fund_credit.credit;
11872                         --
11873                         -- Look up the balance for this credit.  If it's zero, then
11874                         -- it's not useful, so treat it as if you didn't find it.
11875                         -- (Actually there shouldn't be any zero balances in the table,
11876                         -- but we check just to make sure.)
11877                         --
11878                         SELECT *
11879                         INTO curr_credit_bal
11880                         FROM t_credit
11881                         WHERE
11882                                 credit = fund_credit.credit
11883                                 AND balance > 0;
11884                         --
11885                         IF curr_credit_bal IS NULL THEN
11886                                 --
11887                                 -- This credit is exhausted; try the next one.
11888                                 --
11889                                 CONTINUE;
11890                         END IF;
11891                         --
11892                         --
11893                         -- At this point we have an applicable credit with some money left.
11894                         -- Now see if the relevant funding_source has any money left.
11895                         --
11896                         -- Look up the balance of the allocation for this combination of
11897                         -- fund and source.  If you find such an entry, but it has a zero
11898                         -- balance, then it's not useful, so treat it as unfound.
11899                         -- (Actually there shouldn't be any zero balances in the table,
11900                         -- but we check just to make sure.)
11901                         --
11902                         SELECT *
11903                         INTO curr_fund_source_bal
11904                         FROM t_fund_source_bal
11905                         WHERE
11906                                 fund = deb.fund
11907                                 AND source = curr_credit_bal.source
11908                                 AND balance > 0;
11909                         --
11910                         IF curr_fund_source_bal IS NULL THEN
11911                                 --
11912                                 -- This fund/source doesn't exist or is already exhausted,
11913                                 -- so we can't use this credit.  Go on to the next one.
11914                                 --
11915                                 CONTINUE;
11916                         END IF;
11917                         --
11918                         -- Convert the available balances to the currency of the fund
11919                         --
11920                         conv_alloc_balance := curr_fund_source_bal.balance * acq.exchange_ratio(
11921                                 curr_credit_bal.currency_type, deb.currency_type );
11922                         conv_cred_balance := curr_credit_bal.balance * acq.exchange_ratio(
11923                                 curr_credit_bal.currency_type, deb.currency_type );
11924                         --
11925                         -- Determine how much we can attribute to this credit: the minimum
11926                         -- of the debit amount, the fund/source balance, and the
11927                         -- credit balance
11928                         --
11929                         --RAISE NOTICE '   deb bal %', debit_balance;
11930                         --RAISE NOTICE '      source % balance %', curr_credit_bal.source, conv_alloc_balance;
11931                         --RAISE NOTICE '      credit % balance %', curr_credit_bal.credit, conv_cred_balance;
11932                         --
11933                         conv_attr_amount := NULL;
11934                         attr_amount := debit_balance;
11935                         --
11936                         IF attr_amount > conv_alloc_balance THEN
11937                                 attr_amount := conv_alloc_balance;
11938                                 conv_attr_amount := curr_fund_source_bal.balance;
11939                         END IF;
11940                         IF attr_amount > conv_cred_balance THEN
11941                                 attr_amount := conv_cred_balance;
11942                                 conv_attr_amount := curr_credit_bal.balance;
11943                         END IF;
11944                         --
11945                         -- If we're attributing all of one of the balances, then that's how
11946                         -- much we will deduct from the balances, and we already captured
11947                         -- that amount above.  Otherwise we must convert the amount of the
11948                         -- attribution from the currency of the fund back to the currency of
11949                         -- the funding source.
11950                         --
11951                         IF conv_attr_amount IS NULL THEN
11952                                 conv_attr_amount := attr_amount * acq.exchange_ratio(
11953                                         deb.currency_type, curr_credit_bal.currency_type );
11954                         END IF;
11955                         --
11956                         -- Insert a row to record the attribution
11957                         --
11958                         attrib_count := attrib_count + 1;
11959                         INSERT INTO acq.debit_attribution (
11960                                 id,
11961                                 fund_debit,
11962                                 debit_amount,
11963                                 funding_source_credit,
11964                                 credit_amount
11965                         ) VALUES (
11966                                 attrib_count,
11967                                 deb.id,
11968                                 attr_amount,
11969                                 curr_credit_bal.credit,
11970                                 conv_attr_amount
11971                         );
11972                         --
11973                         -- Subtract the attributed amount from the various balances
11974                         --
11975                         debit_balance := debit_balance - attr_amount;
11976                         curr_fund_source_bal.balance := curr_fund_source_bal.balance - conv_attr_amount;
11977                         --
11978                         IF curr_fund_source_bal.balance <= 0 THEN
11979                                 --
11980                                 -- This allocation is exhausted.  Delete it so
11981                                 -- that we don't waste time looking at it again.
11982                                 --
11983                                 DELETE FROM t_fund_source_bal
11984                                 WHERE
11985                                         fund = curr_fund_source_bal.fund
11986                                         AND source = curr_fund_source_bal.source;
11987                         ELSE
11988                                 UPDATE t_fund_source_bal
11989                                 SET balance = balance - conv_attr_amount
11990                                 WHERE
11991                                         fund = curr_fund_source_bal.fund
11992                                         AND source = curr_fund_source_bal.source;
11993                         END IF;
11994                         --
11995                         IF curr_credit_bal.balance <= 0 THEN
11996                                 --
11997                                 -- This funding source credit is exhausted.  Delete it
11998                                 -- so that we don't waste time looking at it again.
11999                                 --
12000                                 --DELETE FROM t_credit
12001                                 --WHERE
12002                                 --      credit = curr_credit_bal.credit;
12003                                 --
12004                                 DELETE FROM t_fund_credit
12005                                 WHERE
12006                                         credit = curr_credit_bal.credit;
12007                         ELSE
12008                                 UPDATE t_credit
12009                                 SET balance = curr_credit_bal.balance
12010                                 WHERE
12011                                         credit = curr_credit_bal.credit;
12012                         END IF;
12013                         --
12014                         -- Are we done with this debit yet?
12015                         --
12016                         IF debit_balance <= 0 THEN
12017                                 EXIT;       -- We've fully attributed this debit; stop looking at credits.
12018                         END IF;
12019                 END LOOP;       -- End loop over credits
12020                 --
12021                 IF debit_balance <> 0 THEN
12022                         --
12023                         -- We weren't able to attribute this debit, or at least not
12024                         -- all of it.  Insert a row for the unattributed balance.
12025                         --
12026                         attrib_count := attrib_count + 1;
12027                         INSERT INTO acq.debit_attribution (
12028                                 id,
12029                                 fund_debit,
12030                                 debit_amount,
12031                                 funding_source_credit,
12032                                 credit_amount
12033                         ) VALUES (
12034                                 attrib_count,
12035                                 deb.id,
12036                                 debit_balance,
12037                                 NULL,
12038                                 NULL
12039                         );
12040                 END IF;
12041         END LOOP;   -- End of loop over debits
12042 END;
12043 $$ LANGUAGE 'plpgsql';
12044
12045 CREATE OR REPLACE FUNCTION extract_marc_field ( TEXT, BIGINT, TEXT, TEXT ) RETURNS TEXT AS $$
12046 DECLARE
12047     query TEXT;
12048     output TEXT;
12049 BEGIN
12050     query := $q$
12051         SELECT  regexp_replace(
12052                     oils_xpath_string(
12053                         $q$ || quote_literal($3) || $q$,
12054                         marc,
12055                         ' '
12056                     ),
12057                     $q$ || quote_literal($4) || $q$,
12058                     '',
12059                     'g')
12060           FROM  $q$ || $1 || $q$
12061           WHERE id = $q$ || $2;
12062
12063     EXECUTE query INTO output;
12064
12065     -- RAISE NOTICE 'query: %, output; %', query, output;
12066
12067     RETURN output;
12068 END;
12069 $$ LANGUAGE PLPGSQL IMMUTABLE;
12070
12071 CREATE OR REPLACE FUNCTION extract_marc_field ( TEXT, BIGINT, TEXT ) RETURNS TEXT AS $$
12072     SELECT extract_marc_field($1,$2,$3,'');
12073 $$ LANGUAGE SQL IMMUTABLE;
12074
12075 CREATE OR REPLACE FUNCTION asset.merge_record_assets( target_record BIGINT, source_record BIGINT ) RETURNS INT AS $func$
12076 DECLARE
12077     moved_objects INT := 0;
12078     source_cn     asset.call_number%ROWTYPE;
12079     target_cn     asset.call_number%ROWTYPE;
12080     metarec       metabib.metarecord%ROWTYPE;
12081     hold          action.hold_request%ROWTYPE;
12082     ser_rec       serial.record_entry%ROWTYPE;
12083     uri_count     INT := 0;
12084     counter       INT := 0;
12085     uri_datafield TEXT;
12086     uri_text      TEXT := '';
12087 BEGIN
12088
12089     -- move any 856 entries on records that have at least one MARC-mapped URI entry
12090     SELECT  INTO uri_count COUNT(*)
12091       FROM  asset.uri_call_number_map m
12092             JOIN asset.call_number cn ON (m.call_number = cn.id)
12093       WHERE cn.record = source_record;
12094
12095     IF uri_count > 0 THEN
12096
12097         SELECT  COUNT(*) INTO counter
12098           FROM  oils_xpath_table(
12099                     'id',
12100                     'marc',
12101                     'biblio.record_entry',
12102                     '//*[@tag="856"]',
12103                     'id=' || source_record
12104                 ) as t(i int,c text);
12105
12106         FOR i IN 1 .. counter LOOP
12107             SELECT  '<datafield xmlns="http://www.loc.gov/MARC21/slim"' ||
12108                         ' tag="856"' || 
12109                         ' ind1="' || FIRST(ind1) || '"'  || 
12110                         ' ind2="' || FIRST(ind2) || '">' || 
12111                         array_to_string(
12112                             array_accum(
12113                                 '<subfield code="' || subfield || '">' ||
12114                                 regexp_replace(
12115                                     regexp_replace(
12116                                         regexp_replace(data,'&','&amp;','g'),
12117                                         '>', '&gt;', 'g'
12118                                     ),
12119                                     '<', '&lt;', 'g'
12120                                 ) || '</subfield>'
12121                             ), ''
12122                         ) || '</datafield>' INTO uri_datafield
12123               FROM  oils_xpath_table(
12124                         'id',
12125                         'marc',
12126                         'biblio.record_entry',
12127                         '//*[@tag="856"][position()=' || i || ']/@ind1|' || 
12128                         '//*[@tag="856"][position()=' || i || ']/@ind2|' || 
12129                         '//*[@tag="856"][position()=' || i || ']/*/@code|' ||
12130                         '//*[@tag="856"][position()=' || i || ']/*[@code]',
12131                         'id=' || source_record
12132                     ) as t(id int,ind1 text, ind2 text,subfield text,data text);
12133
12134             uri_text := uri_text || uri_datafield;
12135         END LOOP;
12136
12137         IF uri_text <> '' THEN
12138             UPDATE  biblio.record_entry
12139               SET   marc = regexp_replace(marc,'(</[^>]*record>)', uri_text || E'\\1')
12140               WHERE id = target_record;
12141         END IF;
12142
12143     END IF;
12144
12145     -- Find and move metarecords to the target record
12146     SELECT  INTO metarec *
12147       FROM  metabib.metarecord
12148       WHERE master_record = source_record;
12149
12150     IF FOUND THEN
12151         UPDATE  metabib.metarecord
12152           SET   master_record = target_record,
12153             mods = NULL
12154           WHERE id = metarec.id;
12155
12156         moved_objects := moved_objects + 1;
12157     END IF;
12158
12159     -- Find call numbers attached to the source ...
12160     FOR source_cn IN SELECT * FROM asset.call_number WHERE record = source_record LOOP
12161
12162         SELECT  INTO target_cn *
12163           FROM  asset.call_number
12164           WHERE label = source_cn.label
12165             AND owning_lib = source_cn.owning_lib
12166             AND record = target_record;
12167
12168         -- ... and if there's a conflicting one on the target ...
12169         IF FOUND THEN
12170
12171             -- ... move the copies to that, and ...
12172             UPDATE  asset.copy
12173               SET   call_number = target_cn.id
12174               WHERE call_number = source_cn.id;
12175
12176             -- ... move V holds to the move-target call number
12177             FOR hold IN SELECT * FROM action.hold_request WHERE target = source_cn.id AND hold_type = 'V' LOOP
12178
12179                 UPDATE  action.hold_request
12180                   SET   target = target_cn.id
12181                   WHERE id = hold.id;
12182
12183                 moved_objects := moved_objects + 1;
12184             END LOOP;
12185
12186         -- ... if not ...
12187         ELSE
12188             -- ... just move the call number to the target record
12189             UPDATE  asset.call_number
12190               SET   record = target_record
12191               WHERE id = source_cn.id;
12192         END IF;
12193
12194         moved_objects := moved_objects + 1;
12195     END LOOP;
12196
12197     -- Find T holds targeting the source record ...
12198     FOR hold IN SELECT * FROM action.hold_request WHERE target = source_record AND hold_type = 'T' LOOP
12199
12200         -- ... and move them to the target record
12201         UPDATE  action.hold_request
12202           SET   target = target_record
12203           WHERE id = hold.id;
12204
12205         moved_objects := moved_objects + 1;
12206     END LOOP;
12207
12208     -- Find serial records targeting the source record ...
12209     FOR ser_rec IN SELECT * FROM serial.record_entry WHERE record = source_record LOOP
12210         -- ... and move them to the target record
12211         UPDATE  serial.record_entry
12212           SET   record = target_record
12213           WHERE id = ser_rec.id;
12214
12215         moved_objects := moved_objects + 1;
12216     END LOOP;
12217
12218     -- Finally, "delete" the source record
12219     DELETE FROM biblio.record_entry WHERE id = source_record;
12220
12221     -- That's all, folks!
12222     RETURN moved_objects;
12223 END;
12224 $func$ LANGUAGE plpgsql;
12225
12226 CREATE OR REPLACE FUNCTION acq.transfer_fund(
12227         old_fund   IN INT,
12228         old_amount IN NUMERIC,     -- in currency of old fund
12229         new_fund   IN INT,
12230         new_amount IN NUMERIC,     -- in currency of new fund
12231         user_id    IN INT,
12232         xfer_note  IN TEXT         -- to be recorded in acq.fund_transfer
12233         -- ,funding_source_in IN INT  -- if user wants to specify a funding source (see notes)
12234 ) RETURNS VOID AS $$
12235 /* -------------------------------------------------------------------------------
12236
12237 Function to transfer money from one fund to another.
12238
12239 A transfer is represented as a pair of entries in acq.fund_allocation, with a
12240 negative amount for the old (losing) fund and a positive amount for the new
12241 (gaining) fund.  In some cases there may be more than one such pair of entries
12242 in order to pull the money from different funding sources, or more specifically
12243 from different funding source credits.  For each such pair there is also an
12244 entry in acq.fund_transfer.
12245
12246 Since funding_source is a non-nullable column in acq.fund_allocation, we must
12247 choose a funding source for the transferred money to come from.  This choice
12248 must meet two constraints, so far as possible:
12249
12250 1. The amount transferred from a given funding source must not exceed the
12251 amount allocated to the old fund by the funding source.  To that end we
12252 compare the amount being transferred to the amount allocated.
12253
12254 2. We shouldn't transfer money that has already been spent or encumbered, as
12255 defined by the funding attribution process.  We attribute expenses to the
12256 oldest funding source credits first.  In order to avoid transferring that
12257 attributed money, we reverse the priority, transferring from the newest funding
12258 source credits first.  There can be no guarantee that this approach will
12259 avoid overcommitting a fund, but no other approach can do any better.
12260
12261 In this context the age of a funding source credit is defined by the
12262 deadline_date for credits with deadline_dates, and by the effective_date for
12263 credits without deadline_dates, with the proviso that credits with deadline_dates
12264 are all considered "older" than those without.
12265
12266 ----------
12267
12268 In the signature for this function, there is one last parameter commented out,
12269 named "funding_source_in".  Correspondingly, the WHERE clause for the query
12270 driving the main loop has an OR clause commented out, which references the
12271 funding_source_in parameter.
12272
12273 If these lines are uncommented, this function will allow the user optionally to
12274 restrict a fund transfer to a specified funding source.  If the source
12275 parameter is left NULL, then there will be no such restriction.
12276
12277 ------------------------------------------------------------------------------- */ 
12278 DECLARE
12279         same_currency      BOOLEAN;
12280         currency_ratio     NUMERIC;
12281         old_fund_currency  TEXT;
12282         old_remaining      NUMERIC;  -- in currency of old fund
12283         new_fund_currency  TEXT;
12284         new_fund_active    BOOLEAN;
12285         new_remaining      NUMERIC;  -- in currency of new fund
12286         curr_old_amt       NUMERIC;  -- in currency of old fund
12287         curr_new_amt       NUMERIC;  -- in currency of new fund
12288         source_addition    NUMERIC;  -- in currency of funding source
12289         source_deduction   NUMERIC;  -- in currency of funding source
12290         orig_allocated_amt NUMERIC;  -- in currency of funding source
12291         allocated_amt      NUMERIC;  -- in currency of fund
12292         source             RECORD;
12293 BEGIN
12294         --
12295         -- Sanity checks
12296         --
12297         IF old_fund IS NULL THEN
12298                 RAISE EXCEPTION 'acq.transfer_fund: old fund id is NULL';
12299         END IF;
12300         --
12301         IF old_amount IS NULL THEN
12302                 RAISE EXCEPTION 'acq.transfer_fund: amount to transfer is NULL';
12303         END IF;
12304         --
12305         -- The new fund and its amount must be both NULL or both not NULL.
12306         --
12307         IF new_fund IS NOT NULL AND new_amount IS NULL THEN
12308                 RAISE EXCEPTION 'acq.transfer_fund: amount to transfer to receiving fund is NULL';
12309         END IF;
12310         --
12311         IF new_fund IS NULL AND new_amount IS NOT NULL THEN
12312                 RAISE EXCEPTION 'acq.transfer_fund: receiving fund is NULL, its amount is not NULL';
12313         END IF;
12314         --
12315         IF user_id IS NULL THEN
12316                 RAISE EXCEPTION 'acq.transfer_fund: user id is NULL';
12317         END IF;
12318         --
12319         -- Initialize the amounts to be transferred, each denominated
12320         -- in the currency of its respective fund.  They will be
12321         -- reduced on each iteration of the loop.
12322         --
12323         old_remaining := old_amount;
12324         new_remaining := new_amount;
12325         --
12326         -- RAISE NOTICE 'Transferring % in fund % to % in fund %',
12327         --      old_amount, old_fund, new_amount, new_fund;
12328         --
12329         -- Get the currency types of the old and new funds.
12330         --
12331         SELECT
12332                 currency_type
12333         INTO
12334                 old_fund_currency
12335         FROM
12336                 acq.fund
12337         WHERE
12338                 id = old_fund;
12339         --
12340         IF old_fund_currency IS NULL THEN
12341                 RAISE EXCEPTION 'acq.transfer_fund: old fund id % is not defined', old_fund;
12342         END IF;
12343         --
12344         IF new_fund IS NOT NULL THEN
12345                 SELECT
12346                         currency_type,
12347                         active
12348                 INTO
12349                         new_fund_currency,
12350                         new_fund_active
12351                 FROM
12352                         acq.fund
12353                 WHERE
12354                         id = new_fund;
12355                 --
12356                 IF new_fund_currency IS NULL THEN
12357                         RAISE EXCEPTION 'acq.transfer_fund: new fund id % is not defined', new_fund;
12358                 ELSIF NOT new_fund_active THEN
12359                         --
12360                         -- No point in putting money into a fund from whence you can't spend it
12361                         --
12362                         RAISE EXCEPTION 'acq.transfer_fund: new fund id % is inactive', new_fund;
12363                 END IF;
12364                 --
12365                 IF new_amount = old_amount THEN
12366                         same_currency := true;
12367                         currency_ratio := 1;
12368                 ELSE
12369                         --
12370                         -- We'll have to translate currency between funds.  We presume that
12371                         -- the calling code has already applied an appropriate exchange rate,
12372                         -- so we'll apply the same conversion to each sub-transfer.
12373                         --
12374                         same_currency := false;
12375                         currency_ratio := new_amount / old_amount;
12376                 END IF;
12377         END IF;
12378         --
12379         -- Identify the funding source(s) from which we want to transfer the money.
12380         -- The principle is that we want to transfer the newest money first, because
12381         -- we spend the oldest money first.  The priority for spending is defined
12382         -- by a sort of the view acq.ordered_funding_source_credit.
12383         --
12384         FOR source in
12385                 SELECT
12386                         ofsc.id,
12387                         ofsc.funding_source,
12388                         ofsc.amount,
12389                         ofsc.amount * acq.exchange_ratio( fs.currency_type, old_fund_currency )
12390                                 AS converted_amt,
12391                         fs.currency_type
12392                 FROM
12393                         acq.ordered_funding_source_credit AS ofsc,
12394                         acq.funding_source fs
12395                 WHERE
12396                         ofsc.funding_source = fs.id
12397                         and ofsc.funding_source IN
12398                         (
12399                                 SELECT funding_source
12400                                 FROM acq.fund_allocation
12401                                 WHERE fund = old_fund
12402                         )
12403                         -- and
12404                         -- (
12405                         --      ofsc.funding_source = funding_source_in
12406                         --      OR funding_source_in IS NULL
12407                         -- )
12408                 ORDER BY
12409                         ofsc.sort_priority desc,
12410                         ofsc.sort_date desc,
12411                         ofsc.id desc
12412         LOOP
12413                 --
12414                 -- Determine how much money the old fund got from this funding source,
12415                 -- denominated in the currency types of the source and of the fund.
12416                 -- This result may reflect transfers from previous iterations.
12417                 --
12418                 SELECT
12419                         COALESCE( sum( amount ), 0 ),
12420                         COALESCE( sum( amount )
12421                                 * acq.exchange_ratio( source.currency_type, old_fund_currency ), 0 )
12422                 INTO
12423                         orig_allocated_amt,     -- in currency of the source
12424                         allocated_amt           -- in currency of the old fund
12425                 FROM
12426                         acq.fund_allocation
12427                 WHERE
12428                         fund = old_fund
12429                         and funding_source = source.funding_source;
12430                 --      
12431                 -- Determine how much to transfer from this credit, in the currency
12432                 -- of the fund.   Begin with the amount remaining to be attributed:
12433                 --
12434                 curr_old_amt := old_remaining;
12435                 --
12436                 -- Can't attribute more than was allocated from the fund:
12437                 --
12438                 IF curr_old_amt > allocated_amt THEN
12439                         curr_old_amt := allocated_amt;
12440                 END IF;
12441                 --
12442                 -- Can't attribute more than the amount of the current credit:
12443                 --
12444                 IF curr_old_amt > source.converted_amt THEN
12445                         curr_old_amt := source.converted_amt;
12446                 END IF;
12447                 --
12448                 curr_old_amt := trunc( curr_old_amt, 2 );
12449                 --
12450                 old_remaining := old_remaining - curr_old_amt;
12451                 --
12452                 -- Determine the amount to be deducted, if any,
12453                 -- from the old allocation.
12454                 --
12455                 IF old_remaining > 0 THEN
12456                         --
12457                         -- In this case we're using the whole allocation, so use that
12458                         -- amount directly instead of applying a currency translation
12459                         -- and thereby inviting round-off errors.
12460                         --
12461                         source_deduction := - orig_allocated_amt;
12462                 ELSE 
12463                         source_deduction := trunc(
12464                                 ( - curr_old_amt ) *
12465                                         acq.exchange_ratio( old_fund_currency, source.currency_type ),
12466                                 2 );
12467                 END IF;
12468                 --
12469                 IF source_deduction <> 0 THEN
12470                         --
12471                         -- Insert negative allocation for old fund in fund_allocation,
12472                         -- converted into the currency of the funding source
12473                         --
12474                         INSERT INTO acq.fund_allocation (
12475                                 funding_source,
12476                                 fund,
12477                                 amount,
12478                                 allocator,
12479                                 note
12480                         ) VALUES (
12481                                 source.funding_source,
12482                                 old_fund,
12483                                 source_deduction,
12484                                 user_id,
12485                                 'Transfer to fund ' || new_fund
12486                         );
12487                 END IF;
12488                 --
12489                 IF new_fund IS NOT NULL THEN
12490                         --
12491                         -- Determine how much to add to the new fund, in
12492                         -- its currency, and how much remains to be added:
12493                         --
12494                         IF same_currency THEN
12495                                 curr_new_amt := curr_old_amt;
12496                         ELSE
12497                                 IF old_remaining = 0 THEN
12498                                         --
12499                                         -- This is the last iteration, so nothing should be left
12500                                         --
12501                                         curr_new_amt := new_remaining;
12502                                         new_remaining := 0;
12503                                 ELSE
12504                                         curr_new_amt := trunc( curr_old_amt * currency_ratio, 2 );
12505                                         new_remaining := new_remaining - curr_new_amt;
12506                                 END IF;
12507                         END IF;
12508                         --
12509                         -- Determine how much to add, if any,
12510                         -- to the new fund's allocation.
12511                         --
12512                         IF old_remaining > 0 THEN
12513                                 --
12514                                 -- In this case we're using the whole allocation, so use that amount
12515                                 -- amount directly instead of applying a currency translation and
12516                                 -- thereby inviting round-off errors.
12517                                 --
12518                                 source_addition := orig_allocated_amt;
12519                         ELSIF source.currency_type = old_fund_currency THEN
12520                                 --
12521                                 -- In this case we don't need a round trip currency translation,
12522                                 -- thereby inviting round-off errors:
12523                                 --
12524                                 source_addition := curr_old_amt;
12525                         ELSE 
12526                                 source_addition := trunc(
12527                                         curr_new_amt *
12528                                                 acq.exchange_ratio( new_fund_currency, source.currency_type ),
12529                                         2 );
12530                         END IF;
12531                         --
12532                         IF source_addition <> 0 THEN
12533                                 --
12534                                 -- Insert positive allocation for new fund in fund_allocation,
12535                                 -- converted to the currency of the founding source
12536                                 --
12537                                 INSERT INTO acq.fund_allocation (
12538                                         funding_source,
12539                                         fund,
12540                                         amount,
12541                                         allocator,
12542                                         note
12543                                 ) VALUES (
12544                                         source.funding_source,
12545                                         new_fund,
12546                                         source_addition,
12547                                         user_id,
12548                                         'Transfer from fund ' || old_fund
12549                                 );
12550                         END IF;
12551                 END IF;
12552                 --
12553                 IF trunc( curr_old_amt, 2 ) <> 0
12554                 OR trunc( curr_new_amt, 2 ) <> 0 THEN
12555                         --
12556                         -- Insert row in fund_transfer, using amounts in the currency of the funds
12557                         --
12558                         INSERT INTO acq.fund_transfer (
12559                                 src_fund,
12560                                 src_amount,
12561                                 dest_fund,
12562                                 dest_amount,
12563                                 transfer_user,
12564                                 note,
12565                                 funding_source_credit
12566                         ) VALUES (
12567                                 old_fund,
12568                                 trunc( curr_old_amt, 2 ),
12569                                 new_fund,
12570                                 trunc( curr_new_amt, 2 ),
12571                                 user_id,
12572                                 xfer_note,
12573                                 source.id
12574                         );
12575                 END IF;
12576                 --
12577                 if old_remaining <= 0 THEN
12578                         EXIT;                   -- Nothing more to be transferred
12579                 END IF;
12580         END LOOP;
12581 END;
12582 $$ LANGUAGE plpgsql;
12583
12584 CREATE OR REPLACE FUNCTION acq.propagate_funds_by_org_unit(
12585         old_year INTEGER,
12586         user_id INTEGER,
12587         org_unit_id INTEGER
12588 ) RETURNS VOID AS $$
12589 DECLARE
12590 --
12591 new_id      INT;
12592 old_fund    RECORD;
12593 org_found   BOOLEAN;
12594 --
12595 BEGIN
12596         --
12597         -- Sanity checks
12598         --
12599         IF old_year IS NULL THEN
12600                 RAISE EXCEPTION 'Input year argument is NULL';
12601         ELSIF old_year NOT BETWEEN 2008 and 2200 THEN
12602                 RAISE EXCEPTION 'Input year is out of range';
12603         END IF;
12604         --
12605         IF user_id IS NULL THEN
12606                 RAISE EXCEPTION 'Input user id argument is NULL';
12607         END IF;
12608         --
12609         IF org_unit_id IS NULL THEN
12610                 RAISE EXCEPTION 'Org unit id argument is NULL';
12611         ELSE
12612                 SELECT TRUE INTO org_found
12613                 FROM actor.org_unit
12614                 WHERE id = org_unit_id;
12615                 --
12616                 IF org_found IS NULL THEN
12617                         RAISE EXCEPTION 'Org unit id is invalid';
12618                 END IF;
12619         END IF;
12620         --
12621         -- Loop over the applicable funds
12622         --
12623         FOR old_fund in SELECT * FROM acq.fund
12624         WHERE
12625                 year = old_year
12626                 AND propagate
12627                 AND org = org_unit_id
12628         LOOP
12629                 BEGIN
12630                         INSERT INTO acq.fund (
12631                                 org,
12632                                 name,
12633                                 year,
12634                                 currency_type,
12635                                 code,
12636                                 rollover,
12637                                 propagate,
12638                                 balance_warning_percent,
12639                                 balance_stop_percent
12640                         ) VALUES (
12641                                 old_fund.org,
12642                                 old_fund.name,
12643                                 old_year + 1,
12644                                 old_fund.currency_type,
12645                                 old_fund.code,
12646                                 old_fund.rollover,
12647                                 true,
12648                                 old_fund.balance_warning_percent,
12649                                 old_fund.balance_stop_percent
12650                         )
12651                         RETURNING id INTO new_id;
12652                 EXCEPTION
12653                         WHEN unique_violation THEN
12654                                 --RAISE NOTICE 'Fund % already propagated', old_fund.id;
12655                                 CONTINUE;
12656                 END;
12657                 --RAISE NOTICE 'Propagating fund % to fund %',
12658                 --      old_fund.code, new_id;
12659         END LOOP;
12660 END;
12661 $$ LANGUAGE plpgsql;
12662
12663 CREATE OR REPLACE FUNCTION acq.propagate_funds_by_org_tree(
12664         old_year INTEGER,
12665         user_id INTEGER,
12666         org_unit_id INTEGER
12667 ) RETURNS VOID AS $$
12668 DECLARE
12669 --
12670 new_id      INT;
12671 old_fund    RECORD;
12672 org_found   BOOLEAN;
12673 --
12674 BEGIN
12675         --
12676         -- Sanity checks
12677         --
12678         IF old_year IS NULL THEN
12679                 RAISE EXCEPTION 'Input year argument is NULL';
12680         ELSIF old_year NOT BETWEEN 2008 and 2200 THEN
12681                 RAISE EXCEPTION 'Input year is out of range';
12682         END IF;
12683         --
12684         IF user_id IS NULL THEN
12685                 RAISE EXCEPTION 'Input user id argument is NULL';
12686         END IF;
12687         --
12688         IF org_unit_id IS NULL THEN
12689                 RAISE EXCEPTION 'Org unit id argument is NULL';
12690         ELSE
12691                 SELECT TRUE INTO org_found
12692                 FROM actor.org_unit
12693                 WHERE id = org_unit_id;
12694                 --
12695                 IF org_found IS NULL THEN
12696                         RAISE EXCEPTION 'Org unit id is invalid';
12697                 END IF;
12698         END IF;
12699         --
12700         -- Loop over the applicable funds
12701         --
12702         FOR old_fund in SELECT * FROM acq.fund
12703         WHERE
12704                 year = old_year
12705                 AND propagate
12706                 AND org in (
12707                         SELECT id FROM actor.org_unit_descendants( org_unit_id )
12708                 )
12709         LOOP
12710                 BEGIN
12711                         INSERT INTO acq.fund (
12712                                 org,
12713                                 name,
12714                                 year,
12715                                 currency_type,
12716                                 code,
12717                                 rollover,
12718                                 propagate,
12719                                 balance_warning_percent,
12720                                 balance_stop_percent
12721                         ) VALUES (
12722                                 old_fund.org,
12723                                 old_fund.name,
12724                                 old_year + 1,
12725                                 old_fund.currency_type,
12726                                 old_fund.code,
12727                                 old_fund.rollover,
12728                                 true,
12729                                 old_fund.balance_warning_percent,
12730                                 old_fund.balance_stop_percent
12731                         )
12732                         RETURNING id INTO new_id;
12733                 EXCEPTION
12734                         WHEN unique_violation THEN
12735                                 --RAISE NOTICE 'Fund % already propagated', old_fund.id;
12736                                 CONTINUE;
12737                 END;
12738                 --RAISE NOTICE 'Propagating fund % to fund %',
12739                 --      old_fund.code, new_id;
12740         END LOOP;
12741 END;
12742 $$ LANGUAGE plpgsql;
12743
12744 CREATE OR REPLACE FUNCTION acq.rollover_funds_by_org_unit(
12745         old_year INTEGER,
12746         user_id INTEGER,
12747         org_unit_id INTEGER
12748 ) RETURNS VOID AS $$
12749 DECLARE
12750 --
12751 new_fund    INT;
12752 new_year    INT := old_year + 1;
12753 org_found   BOOL;
12754 xfer_amount NUMERIC;
12755 roll_fund   RECORD;
12756 deb         RECORD;
12757 detail      RECORD;
12758 --
12759 BEGIN
12760         --
12761         -- Sanity checks
12762         --
12763         IF old_year IS NULL THEN
12764                 RAISE EXCEPTION 'Input year argument is NULL';
12765     ELSIF old_year NOT BETWEEN 2008 and 2200 THEN
12766         RAISE EXCEPTION 'Input year is out of range';
12767         END IF;
12768         --
12769         IF user_id IS NULL THEN
12770                 RAISE EXCEPTION 'Input user id argument is NULL';
12771         END IF;
12772         --
12773         IF org_unit_id IS NULL THEN
12774                 RAISE EXCEPTION 'Org unit id argument is NULL';
12775         ELSE
12776                 --
12777                 -- Validate the org unit
12778                 --
12779                 SELECT TRUE
12780                 INTO org_found
12781                 FROM actor.org_unit
12782                 WHERE id = org_unit_id;
12783                 --
12784                 IF org_found IS NULL THEN
12785                         RAISE EXCEPTION 'Org unit id % is invalid', org_unit_id;
12786                 END IF;
12787         END IF;
12788         --
12789         -- Loop over the propagable funds to identify the details
12790         -- from the old fund plus the id of the new one, if it exists.
12791         --
12792         FOR roll_fund in
12793         SELECT
12794             oldf.id AS old_fund,
12795             oldf.org,
12796             oldf.name,
12797             oldf.currency_type,
12798             oldf.code,
12799                 oldf.rollover,
12800             newf.id AS new_fund_id
12801         FROM
12802         acq.fund AS oldf
12803         LEFT JOIN acq.fund AS newf
12804                 ON ( oldf.code = newf.code )
12805         WHERE
12806                     oldf.org = org_unit_id
12807                 and oldf.year = old_year
12808                 and oldf.propagate
12809         and newf.year = new_year
12810         LOOP
12811                 --RAISE NOTICE 'Processing fund %', roll_fund.old_fund;
12812                 --
12813                 IF roll_fund.new_fund_id IS NULL THEN
12814                         --
12815                         -- The old fund hasn't been propagated yet.  Propagate it now.
12816                         --
12817                         INSERT INTO acq.fund (
12818                                 org,
12819                                 name,
12820                                 year,
12821                                 currency_type,
12822                                 code,
12823                                 rollover,
12824                                 propagate,
12825                                 balance_warning_percent,
12826                                 balance_stop_percent
12827                         ) VALUES (
12828                                 roll_fund.org,
12829                                 roll_fund.name,
12830                                 new_year,
12831                                 roll_fund.currency_type,
12832                                 roll_fund.code,
12833                                 true,
12834                                 true,
12835                                 roll_fund.balance_warning_percent,
12836                                 roll_fund.balance_stop_percent
12837                         )
12838                         RETURNING id INTO new_fund;
12839                 ELSE
12840                         new_fund = roll_fund.new_fund_id;
12841                 END IF;
12842                 --
12843                 -- Determine the amount to transfer
12844                 --
12845                 SELECT amount
12846                 INTO xfer_amount
12847                 FROM acq.fund_spent_balance
12848                 WHERE fund = roll_fund.old_fund;
12849                 --
12850                 IF xfer_amount <> 0 THEN
12851                         IF roll_fund.rollover THEN
12852                                 --
12853                                 -- Transfer balance from old fund to new
12854                                 --
12855                                 --RAISE NOTICE 'Transferring % from fund % to %', xfer_amount, roll_fund.old_fund, new_fund;
12856                                 --
12857                                 PERFORM acq.transfer_fund(
12858                                         roll_fund.old_fund,
12859                                         xfer_amount,
12860                                         new_fund,
12861                                         xfer_amount,
12862                                         user_id,
12863                                         'Rollover'
12864                                 );
12865                         ELSE
12866                                 --
12867                                 -- Transfer balance from old fund to the void
12868                                 --
12869                                 -- RAISE NOTICE 'Transferring % from fund % to the void', xfer_amount, roll_fund.old_fund;
12870                                 --
12871                                 PERFORM acq.transfer_fund(
12872                                         roll_fund.old_fund,
12873                                         xfer_amount,
12874                                         NULL,
12875                                         NULL,
12876                                         user_id,
12877                                         'Rollover'
12878                                 );
12879                         END IF;
12880                 END IF;
12881                 --
12882                 IF roll_fund.rollover THEN
12883                         --
12884                         -- Move any lineitems from the old fund to the new one
12885                         -- where the associated debit is an encumbrance.
12886                         --
12887                         -- Any other tables tying expenditure details to funds should
12888                         -- receive similar treatment.  At this writing there are none.
12889                         --
12890                         UPDATE acq.lineitem_detail
12891                         SET fund = new_fund
12892                         WHERE
12893                         fund = roll_fund.old_fund -- this condition may be redundant
12894                         AND fund_debit in
12895                         (
12896                                 SELECT id
12897                                 FROM acq.fund_debit
12898                                 WHERE
12899                                 fund = roll_fund.old_fund
12900                                 AND encumbrance
12901                         );
12902                         --
12903                         -- Move encumbrance debits from the old fund to the new fund
12904                         --
12905                         UPDATE acq.fund_debit
12906                         SET fund = new_fund
12907                         wHERE
12908                                 fund = roll_fund.old_fund
12909                                 AND encumbrance;
12910                 END IF;
12911                 --
12912                 -- Mark old fund as inactive, now that we've closed it
12913                 --
12914                 UPDATE acq.fund
12915                 SET active = FALSE
12916                 WHERE id = roll_fund.old_fund;
12917         END LOOP;
12918 END;
12919 $$ LANGUAGE plpgsql;
12920
12921 CREATE OR REPLACE FUNCTION acq.rollover_funds_by_org_tree(
12922         old_year INTEGER,
12923         user_id INTEGER,
12924         org_unit_id INTEGER
12925 ) RETURNS VOID AS $$
12926 DECLARE
12927 --
12928 new_fund    INT;
12929 new_year    INT := old_year + 1;
12930 org_found   BOOL;
12931 xfer_amount NUMERIC;
12932 roll_fund   RECORD;
12933 deb         RECORD;
12934 detail      RECORD;
12935 --
12936 BEGIN
12937         --
12938         -- Sanity checks
12939         --
12940         IF old_year IS NULL THEN
12941                 RAISE EXCEPTION 'Input year argument is NULL';
12942     ELSIF old_year NOT BETWEEN 2008 and 2200 THEN
12943         RAISE EXCEPTION 'Input year is out of range';
12944         END IF;
12945         --
12946         IF user_id IS NULL THEN
12947                 RAISE EXCEPTION 'Input user id argument is NULL';
12948         END IF;
12949         --
12950         IF org_unit_id IS NULL THEN
12951                 RAISE EXCEPTION 'Org unit id argument is NULL';
12952         ELSE
12953                 --
12954                 -- Validate the org unit
12955                 --
12956                 SELECT TRUE
12957                 INTO org_found
12958                 FROM actor.org_unit
12959                 WHERE id = org_unit_id;
12960                 --
12961                 IF org_found IS NULL THEN
12962                         RAISE EXCEPTION 'Org unit id % is invalid', org_unit_id;
12963                 END IF;
12964         END IF;
12965         --
12966         -- Loop over the propagable funds to identify the details
12967         -- from the old fund plus the id of the new one, if it exists.
12968         --
12969         FOR roll_fund in
12970         SELECT
12971             oldf.id AS old_fund,
12972             oldf.org,
12973             oldf.name,
12974             oldf.currency_type,
12975             oldf.code,
12976                 oldf.rollover,
12977             newf.id AS new_fund_id
12978         FROM
12979         acq.fund AS oldf
12980         LEFT JOIN acq.fund AS newf
12981                 ON ( oldf.code = newf.code )
12982         WHERE
12983                     oldf.year = old_year
12984                 AND oldf.propagate
12985         AND newf.year = new_year
12986                 AND oldf.org in (
12987                         SELECT id FROM actor.org_unit_descendants( org_unit_id )
12988                 )
12989         LOOP
12990                 --RAISE NOTICE 'Processing fund %', roll_fund.old_fund;
12991                 --
12992                 IF roll_fund.new_fund_id IS NULL THEN
12993                         --
12994                         -- The old fund hasn't been propagated yet.  Propagate it now.
12995                         --
12996                         INSERT INTO acq.fund (
12997                                 org,
12998                                 name,
12999                                 year,
13000                                 currency_type,
13001                                 code,
13002                                 rollover,
13003                                 propagate,
13004                                 balance_warning_percent,
13005                                 balance_stop_percent
13006                         ) VALUES (
13007                                 roll_fund.org,
13008                                 roll_fund.name,
13009                                 new_year,
13010                                 roll_fund.currency_type,
13011                                 roll_fund.code,
13012                                 true,
13013                                 true,
13014                                 roll_fund.balance_warning_percent,
13015                                 roll_fund.balance_stop_percent
13016                         )
13017                         RETURNING id INTO new_fund;
13018                 ELSE
13019                         new_fund = roll_fund.new_fund_id;
13020                 END IF;
13021                 --
13022                 -- Determine the amount to transfer
13023                 --
13024                 SELECT amount
13025                 INTO xfer_amount
13026                 FROM acq.fund_spent_balance
13027                 WHERE fund = roll_fund.old_fund;
13028                 --
13029                 IF xfer_amount <> 0 THEN
13030                         IF roll_fund.rollover THEN
13031                                 --
13032                                 -- Transfer balance from old fund to new
13033                                 --
13034                                 --RAISE NOTICE 'Transferring % from fund % to %', xfer_amount, roll_fund.old_fund, new_fund;
13035                                 --
13036                                 PERFORM acq.transfer_fund(
13037                                         roll_fund.old_fund,
13038                                         xfer_amount,
13039                                         new_fund,
13040                                         xfer_amount,
13041                                         user_id,
13042                                         'Rollover'
13043                                 );
13044                         ELSE
13045                                 --
13046                                 -- Transfer balance from old fund to the void
13047                                 --
13048                                 -- RAISE NOTICE 'Transferring % from fund % to the void', xfer_amount, roll_fund.old_fund;
13049                                 --
13050                                 PERFORM acq.transfer_fund(
13051                                         roll_fund.old_fund,
13052                                         xfer_amount,
13053                                         NULL,
13054                                         NULL,
13055                                         user_id,
13056                                         'Rollover'
13057                                 );
13058                         END IF;
13059                 END IF;
13060                 --
13061                 IF roll_fund.rollover THEN
13062                         --
13063                         -- Move any lineitems from the old fund to the new one
13064                         -- where the associated debit is an encumbrance.
13065                         --
13066                         -- Any other tables tying expenditure details to funds should
13067                         -- receive similar treatment.  At this writing there are none.
13068                         --
13069                         UPDATE acq.lineitem_detail
13070                         SET fund = new_fund
13071                         WHERE
13072                         fund = roll_fund.old_fund -- this condition may be redundant
13073                         AND fund_debit in
13074                         (
13075                                 SELECT id
13076                                 FROM acq.fund_debit
13077                                 WHERE
13078                                 fund = roll_fund.old_fund
13079                                 AND encumbrance
13080                         );
13081                         --
13082                         -- Move encumbrance debits from the old fund to the new fund
13083                         --
13084                         UPDATE acq.fund_debit
13085                         SET fund = new_fund
13086                         wHERE
13087                                 fund = roll_fund.old_fund
13088                                 AND encumbrance;
13089                 END IF;
13090                 --
13091                 -- Mark old fund as inactive, now that we've closed it
13092                 --
13093                 UPDATE acq.fund
13094                 SET active = FALSE
13095                 WHERE id = roll_fund.old_fund;
13096         END LOOP;
13097 END;
13098 $$ LANGUAGE plpgsql;
13099
13100 CREATE OR REPLACE FUNCTION public.remove_commas( TEXT ) RETURNS TEXT AS $$
13101     SELECT regexp_replace($1, ',', '', 'g');
13102 $$ LANGUAGE SQL STRICT IMMUTABLE;
13103
13104 CREATE OR REPLACE FUNCTION public.remove_whitespace( TEXT ) RETURNS TEXT AS $$
13105     SELECT regexp_replace(normalize_space($1), E'\\s+', '', 'g');
13106 $$ LANGUAGE SQL STRICT IMMUTABLE;
13107
13108 CREATE TABLE acq.distribution_formula_application (
13109     id BIGSERIAL PRIMARY KEY,
13110     creator INT NOT NULL REFERENCES actor.usr(id) DEFERRABLE INITIALLY DEFERRED,
13111     create_time TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(),
13112     formula INT NOT NULL
13113         REFERENCES acq.distribution_formula(id) DEFERRABLE INITIALLY DEFERRED,
13114     lineitem INT NOT NULL
13115         REFERENCES acq.lineitem( id )
13116                 ON DELETE CASCADE
13117                 DEFERRABLE INITIALLY DEFERRED
13118 );
13119
13120 CREATE INDEX acqdfa_df_idx
13121     ON acq.distribution_formula_application(formula);
13122 CREATE INDEX acqdfa_li_idx
13123     ON acq.distribution_formula_application(lineitem);
13124 CREATE INDEX acqdfa_creator_idx
13125     ON acq.distribution_formula_application(creator);
13126
13127 CREATE TABLE acq.user_request_type (
13128     id      SERIAL  PRIMARY KEY,
13129     label   TEXT    NOT NULL UNIQUE -- i18n-ize
13130 );
13131
13132 INSERT INTO acq.user_request_type (id,label) VALUES (1, oils_i18n_gettext('1', 'Books', 'aurt', 'label'));
13133 INSERT INTO acq.user_request_type (id,label) VALUES (2, oils_i18n_gettext('2', 'Journal/Magazine & Newspaper Articles', 'aurt', 'label'));
13134 INSERT INTO acq.user_request_type (id,label) VALUES (3, oils_i18n_gettext('3', 'Audiobooks', 'aurt', 'label'));
13135 INSERT INTO acq.user_request_type (id,label) VALUES (4, oils_i18n_gettext('4', 'Music', 'aurt', 'label'));
13136 INSERT INTO acq.user_request_type (id,label) VALUES (5, oils_i18n_gettext('5', 'DVDs', 'aurt', 'label'));
13137
13138 SELECT SETVAL('acq.user_request_type_id_seq'::TEXT, 6);
13139
13140 CREATE TABLE acq.cancel_reason (
13141         id            SERIAL            PRIMARY KEY,
13142         org_unit      INTEGER           NOT NULL REFERENCES actor.org_unit( id )
13143                                         DEFERRABLE INITIALLY DEFERRED,
13144         label         TEXT              NOT NULL,
13145         description   TEXT              NOT NULL,
13146         keep_debits   BOOL              NOT NULL DEFAULT FALSE,
13147         CONSTRAINT acq_cancel_reason_one_per_org_unit UNIQUE( org_unit, label )
13148 );
13149
13150 -- Reserve ids 1-999 for stock reasons
13151 -- Reserve ids 1000-1999 for EDI reasons
13152 -- 2000+ are available for staff to create
13153
13154 SELECT SETVAL('acq.cancel_reason_id_seq'::TEXT, 2000);
13155
13156 CREATE TABLE acq.user_request (
13157     id                  SERIAL  PRIMARY KEY,
13158     usr                 INT     NOT NULL REFERENCES actor.usr (id), -- requesting user
13159     hold                BOOL    NOT NULL DEFAULT TRUE,
13160
13161     pickup_lib          INT     NOT NULL REFERENCES actor.org_unit (id), -- pickup lib
13162     holdable_formats    TEXT,           -- nullable, for use in hold creation
13163     phone_notify        TEXT,
13164     email_notify        BOOL    NOT NULL DEFAULT TRUE,
13165     lineitem            INT     REFERENCES acq.lineitem (id) ON DELETE CASCADE,
13166     eg_bib              BIGINT  REFERENCES biblio.record_entry (id) ON DELETE CASCADE,
13167     request_date        TIMESTAMPTZ NOT NULL DEFAULT NOW(), -- when they requested it
13168     need_before         TIMESTAMPTZ,    -- don't create holds after this
13169     max_fee             TEXT,
13170
13171     request_type        INT     NOT NULL REFERENCES acq.user_request_type (id), 
13172     isxn                TEXT,
13173     title               TEXT,
13174     volume              TEXT,
13175     author              TEXT,
13176     article_title       TEXT,
13177     article_pages       TEXT,
13178     publisher           TEXT,
13179     location            TEXT,
13180     pubdate             TEXT,
13181     mentioned           TEXT,
13182     other_info          TEXT,
13183         cancel_reason       INT              REFERENCES acq.cancel_reason( id )
13184                                              DEFERRABLE INITIALLY DEFERRED
13185 );
13186
13187 CREATE TABLE acq.lineitem_alert_text (
13188         id               SERIAL         PRIMARY KEY,
13189         code             TEXT           NOT NULL,
13190         description      TEXT,
13191         owning_lib       INT            NOT NULL
13192                                         REFERENCES actor.org_unit(id)
13193                                         DEFERRABLE INITIALLY DEFERRED,
13194         CONSTRAINT alert_one_code_per_org UNIQUE (code, owning_lib)
13195 );
13196
13197 ALTER TABLE acq.lineitem_note
13198         ADD COLUMN alert_text    INT     REFERENCES acq.lineitem_alert_text(id)
13199                                          DEFERRABLE INITIALLY DEFERRED;
13200
13201 -- add ON DELETE CASCADE clause
13202
13203 ALTER TABLE acq.lineitem_note
13204         DROP CONSTRAINT lineitem_note_lineitem_fkey;
13205
13206 ALTER TABLE acq.lineitem_note
13207         ADD FOREIGN KEY (lineitem) REFERENCES acq.lineitem( id )
13208                 ON DELETE CASCADE
13209                 DEFERRABLE INITIALLY DEFERRED;
13210
13211 ALTER TABLE acq.lineitem_note
13212         ADD COLUMN vendor_public BOOLEAN NOT NULL DEFAULT FALSE;
13213
13214 CREATE TABLE acq.invoice_method (
13215     code    TEXT    PRIMARY KEY,
13216     name    TEXT    NOT NULL -- i18n-ize
13217 );
13218 INSERT INTO acq.invoice_method (code,name) VALUES ('EDI',oils_i18n_gettext('EDI', 'EDI', 'acqim', 'name'));
13219 INSERT INTO acq.invoice_method (code,name) VALUES ('PPR',oils_i18n_gettext('PPR', 'Paper', 'acqit', 'name'));
13220
13221 CREATE TABLE acq.invoice_payment_method (
13222         code      TEXT     PRIMARY KEY,
13223         name      TEXT     NOT NULL
13224 );
13225
13226 CREATE TABLE acq.invoice (
13227     id             SERIAL      PRIMARY KEY,
13228     receiver       INT         NOT NULL REFERENCES actor.org_unit (id),
13229     provider       INT         NOT NULL REFERENCES acq.provider (id),
13230     shipper        INT         NOT NULL REFERENCES acq.provider (id),
13231     recv_date      TIMESTAMPTZ NOT NULL DEFAULT NOW(),
13232     recv_method    TEXT        NOT NULL REFERENCES acq.invoice_method (code) DEFAULT 'EDI',
13233     inv_type       TEXT,       -- A "type" field is desired, but no idea what goes here
13234     inv_ident      TEXT        NOT NULL, -- vendor-supplied invoice id/number
13235         payment_auth   TEXT,
13236         payment_method TEXT        REFERENCES acq.invoice_payment_method (code)
13237                                    DEFERRABLE INITIALLY DEFERRED,
13238         note           TEXT,
13239     complete       BOOL        NOT NULL DEFAULT FALSE,
13240     CONSTRAINT inv_ident_once_per_provider UNIQUE(provider, inv_ident)
13241 );
13242
13243 CREATE TABLE acq.invoice_entry (
13244     id              SERIAL      PRIMARY KEY,
13245     invoice         INT         NOT NULL REFERENCES acq.invoice (id) ON DELETE CASCADE,
13246     purchase_order  INT         REFERENCES acq.purchase_order (id) ON UPDATE CASCADE ON DELETE SET NULL,
13247     lineitem        INT         REFERENCES acq.lineitem (id) ON UPDATE CASCADE ON DELETE SET NULL,
13248     inv_item_count  INT         NOT NULL, -- How many acqlids did they say they sent
13249     phys_item_count INT, -- and how many did staff count
13250     note            TEXT,
13251     billed_per_item BOOL,
13252     cost_billed     NUMERIC(8,2),
13253     actual_cost     NUMERIC(8,2),
13254         amount_paid     NUMERIC (8,2)
13255 );
13256
13257 CREATE TABLE acq.invoice_item_type (
13258     code    TEXT    PRIMARY KEY,
13259     name    TEXT    NOT NULL, -- i18n-ize
13260         prorate BOOL    NOT NULL DEFAULT FALSE
13261 );
13262
13263 INSERT INTO acq.invoice_item_type (code,name) VALUES ('TAX',oils_i18n_gettext('TAX', 'Tax', 'aiit', 'name'));
13264 INSERT INTO acq.invoice_item_type (code,name) VALUES ('PRO',oils_i18n_gettext('PRO', 'Processing Fee', 'aiit', 'name'));
13265 INSERT INTO acq.invoice_item_type (code,name) VALUES ('SHP',oils_i18n_gettext('SHP', 'Shipping Charge', 'aiit', 'name'));
13266 INSERT INTO acq.invoice_item_type (code,name) VALUES ('HND',oils_i18n_gettext('HND', 'Handling Charge', 'aiit', 'name'));
13267 INSERT INTO acq.invoice_item_type (code,name) VALUES ('ITM',oils_i18n_gettext('ITM', 'Non-library Item', 'aiit', 'name'));
13268 INSERT INTO acq.invoice_item_type (code,name) VALUES ('SUB',oils_i18n_gettext('SUB', 'Serial Subscription', 'aiit', 'name'));
13269
13270 CREATE TABLE acq.po_item (
13271         id              SERIAL      PRIMARY KEY,
13272         purchase_order  INT         REFERENCES acq.purchase_order (id)
13273                                     ON UPDATE CASCADE ON DELETE SET NULL
13274                                     DEFERRABLE INITIALLY DEFERRED,
13275         fund_debit      INT         REFERENCES acq.fund_debit (id)
13276                                     DEFERRABLE INITIALLY DEFERRED,
13277         inv_item_type   TEXT        NOT NULL
13278                                     REFERENCES acq.invoice_item_type (code)
13279                                     DEFERRABLE INITIALLY DEFERRED,
13280         title           TEXT,
13281         author          TEXT,
13282         note            TEXT,
13283         estimated_cost  NUMERIC(8,2),
13284         fund            INT         REFERENCES acq.fund (id)
13285                                     DEFERRABLE INITIALLY DEFERRED,
13286         target          BIGINT
13287 );
13288
13289 CREATE TABLE acq.invoice_item ( -- for invoice-only debits: taxes/fees/non-bib items/etc
13290     id              SERIAL      PRIMARY KEY,
13291     invoice         INT         NOT NULL REFERENCES acq.invoice (id) ON UPDATE CASCADE ON DELETE CASCADE,
13292     purchase_order  INT         REFERENCES acq.purchase_order (id) ON UPDATE CASCADE ON DELETE SET NULL,
13293     fund_debit      INT         REFERENCES acq.fund_debit (id),
13294     inv_item_type   TEXT        NOT NULL REFERENCES acq.invoice_item_type (code),
13295     title           TEXT,
13296     author          TEXT,
13297     note            TEXT,
13298     cost_billed     NUMERIC(8,2),
13299     actual_cost     NUMERIC(8,2),
13300     fund            INT         REFERENCES acq.fund (id)
13301                                 DEFERRABLE INITIALLY DEFERRED,
13302     amount_paid     NUMERIC (8,2),
13303     po_item         INT         REFERENCES acq.po_item (id)
13304                                 DEFERRABLE INITIALLY DEFERRED,
13305     target          BIGINT
13306 );
13307
13308 CREATE TABLE acq.edi_message (
13309     id               SERIAL          PRIMARY KEY,
13310     account          INTEGER         REFERENCES acq.edi_account(id)
13311                                      DEFERRABLE INITIALLY DEFERRED,
13312     remote_file      TEXT,
13313     create_time      TIMESTAMPTZ     NOT NULL DEFAULT now(),
13314     translate_time   TIMESTAMPTZ,
13315     process_time     TIMESTAMPTZ,
13316     error_time       TIMESTAMPTZ,
13317     status           TEXT            NOT NULL DEFAULT 'new'
13318                                      CONSTRAINT status_value CHECK
13319                                      ( status IN (
13320                                         'new',          -- needs to be translated
13321                                         'translated',   -- needs to be processed
13322                                         'trans_error',  -- error in translation step
13323                                         'processed',    -- needs to have remote_file deleted
13324                                         'proc_error',   -- error in processing step
13325                                         'delete_error', -- error in deletion
13326                                         'retry',        -- need to retry
13327                                         'complete'      -- done
13328                                      )),
13329     edi              TEXT,
13330     jedi             TEXT,
13331     error            TEXT,
13332     purchase_order   INT             REFERENCES acq.purchase_order
13333                                      DEFERRABLE INITIALLY DEFERRED,
13334     message_type     TEXT            NOT NULL CONSTRAINT valid_message_type
13335                                      CHECK ( message_type IN (
13336                                         'ORDERS',
13337                                         'ORDRSP',
13338                                         'INVOIC',
13339                                         'OSTENQ',
13340                                         'OSTRPT'
13341                                      ))
13342 );
13343
13344 ALTER TABLE actor.org_address ADD COLUMN san TEXT;
13345
13346 ALTER TABLE acq.provider_address
13347         ADD COLUMN fax_phone TEXT;
13348
13349 ALTER TABLE acq.provider_contact_address
13350         ADD COLUMN fax_phone TEXT;
13351
13352 CREATE TABLE acq.provider_note (
13353     id      SERIAL              PRIMARY KEY,
13354     provider    INT             NOT NULL REFERENCES acq.provider (id) DEFERRABLE INITIALLY DEFERRED,
13355     creator     INT             NOT NULL REFERENCES actor.usr (id) DEFERRABLE INITIALLY DEFERRED,
13356     editor      INT             NOT NULL REFERENCES actor.usr (id) DEFERRABLE INITIALLY DEFERRED,
13357     create_time TIMESTAMP WITH TIME ZONE    NOT NULL DEFAULT NOW(),
13358     edit_time   TIMESTAMP WITH TIME ZONE    NOT NULL DEFAULT NOW(),
13359     value       TEXT            NOT NULL
13360 );
13361 CREATE INDEX acq_pro_note_pro_idx      ON acq.provider_note ( provider );
13362 CREATE INDEX acq_pro_note_creator_idx  ON acq.provider_note ( creator );
13363 CREATE INDEX acq_pro_note_editor_idx   ON acq.provider_note ( editor );
13364
13365 -- For each fund: the total allocation from all sources, in the
13366 -- currency of the fund (or 0 if there are no allocations)
13367
13368 CREATE VIEW acq.all_fund_allocation_total AS
13369 SELECT
13370     f.id AS fund,
13371     COALESCE( SUM( a.amount * acq.exchange_ratio(
13372         s.currency_type, f.currency_type))::numeric(100,2), 0 )
13373     AS amount
13374 FROM
13375     acq.fund f
13376         LEFT JOIN acq.fund_allocation a
13377             ON a.fund = f.id
13378         LEFT JOIN acq.funding_source s
13379             ON a.funding_source = s.id
13380 GROUP BY
13381     f.id;
13382
13383 -- For every fund: the total encumbrances (or 0 if none),
13384 -- in the currency of the fund.
13385
13386 CREATE VIEW acq.all_fund_encumbrance_total AS
13387 SELECT
13388         f.id AS fund,
13389         COALESCE( encumb.amount, 0 ) AS amount
13390 FROM
13391         acq.fund AS f
13392                 LEFT JOIN (
13393                         SELECT
13394                                 fund,
13395                                 sum( amount ) AS amount
13396                         FROM
13397                                 acq.fund_debit
13398                         WHERE
13399                                 encumbrance
13400                         GROUP BY fund
13401                 ) AS encumb
13402                         ON f.id = encumb.fund;
13403
13404 -- For every fund: the total spent (or 0 if none),
13405 -- in the currency of the fund.
13406
13407 CREATE VIEW acq.all_fund_spent_total AS
13408 SELECT
13409     f.id AS fund,
13410     COALESCE( spent.amount, 0 ) AS amount
13411 FROM
13412     acq.fund AS f
13413         LEFT JOIN (
13414             SELECT
13415                 fund,
13416                 sum( amount ) AS amount
13417             FROM
13418                 acq.fund_debit
13419             WHERE
13420                 NOT encumbrance
13421             GROUP BY fund
13422         ) AS spent
13423             ON f.id = spent.fund;
13424
13425 -- For each fund: the amount not yet spent, in the currency
13426 -- of the fund.  May include encumbrances.
13427
13428 CREATE VIEW acq.all_fund_spent_balance AS
13429 SELECT
13430         c.fund,
13431         c.amount - d.amount AS amount
13432 FROM acq.all_fund_allocation_total c
13433     LEFT JOIN acq.all_fund_spent_total d USING (fund);
13434
13435 -- For each fund: the amount neither spent nor encumbered,
13436 -- in the currency of the fund
13437
13438 CREATE VIEW acq.all_fund_combined_balance AS
13439 SELECT
13440      a.fund,
13441      a.amount - COALESCE( c.amount, 0 ) AS amount
13442 FROM
13443      acq.all_fund_allocation_total a
13444         LEFT OUTER JOIN (
13445             SELECT
13446                 fund,
13447                 SUM( amount ) AS amount
13448             FROM
13449                 acq.fund_debit
13450             GROUP BY
13451                 fund
13452         ) AS c USING ( fund );
13453
13454 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 $$
13455 DECLARE
13456         suffix TEXT;
13457         bucket_row RECORD;
13458         picklist_row RECORD;
13459         queue_row RECORD;
13460         folder_row RECORD;
13461 BEGIN
13462
13463     -- do some initial cleanup 
13464     UPDATE actor.usr SET card = NULL WHERE id = src_usr;
13465     UPDATE actor.usr SET mailing_address = NULL WHERE id = src_usr;
13466     UPDATE actor.usr SET billing_address = NULL WHERE id = src_usr;
13467
13468     -- actor.*
13469     IF del_cards THEN
13470         DELETE FROM actor.card where usr = src_usr;
13471     ELSE
13472         IF deactivate_cards THEN
13473             UPDATE actor.card SET active = 'f' WHERE usr = src_usr;
13474         END IF;
13475         UPDATE actor.card SET usr = dest_usr WHERE usr = src_usr;
13476     END IF;
13477
13478
13479     IF del_addrs THEN
13480         DELETE FROM actor.usr_address WHERE usr = src_usr;
13481     ELSE
13482         UPDATE actor.usr_address SET usr = dest_usr WHERE usr = src_usr;
13483     END IF;
13484
13485     UPDATE actor.usr_note SET usr = dest_usr WHERE usr = src_usr;
13486     -- dupes are technically OK in actor.usr_standing_penalty, should manually delete them...
13487     UPDATE actor.usr_standing_penalty SET usr = dest_usr WHERE usr = src_usr;
13488     PERFORM actor.usr_merge_rows('actor.usr_org_unit_opt_in', 'usr', src_usr, dest_usr);
13489     PERFORM actor.usr_merge_rows('actor.usr_setting', 'usr', src_usr, dest_usr);
13490
13491     -- permission.*
13492     PERFORM actor.usr_merge_rows('permission.usr_perm_map', 'usr', src_usr, dest_usr);
13493     PERFORM actor.usr_merge_rows('permission.usr_object_perm_map', 'usr', src_usr, dest_usr);
13494     PERFORM actor.usr_merge_rows('permission.usr_grp_map', 'usr', src_usr, dest_usr);
13495     PERFORM actor.usr_merge_rows('permission.usr_work_ou_map', 'usr', src_usr, dest_usr);
13496
13497
13498     -- container.*
13499         
13500         -- For each *_bucket table: transfer every bucket belonging to src_usr
13501         -- into the custody of dest_usr.
13502         --
13503         -- In order to avoid colliding with an existing bucket owned by
13504         -- the destination user, append the source user's id (in parenthesese)
13505         -- to the name.  If you still get a collision, add successive
13506         -- spaces to the name and keep trying until you succeed.
13507         --
13508         FOR bucket_row in
13509                 SELECT id, name
13510                 FROM   container.biblio_record_entry_bucket
13511                 WHERE  owner = src_usr
13512         LOOP
13513                 suffix := ' (' || src_usr || ')';
13514                 LOOP
13515                         BEGIN
13516                                 UPDATE  container.biblio_record_entry_bucket
13517                                 SET     owner = dest_usr, name = name || suffix
13518                                 WHERE   id = bucket_row.id;
13519                         EXCEPTION WHEN unique_violation THEN
13520                                 suffix := suffix || ' ';
13521                                 CONTINUE;
13522                         END;
13523                         EXIT;
13524                 END LOOP;
13525         END LOOP;
13526
13527         FOR bucket_row in
13528                 SELECT id, name
13529                 FROM   container.call_number_bucket
13530                 WHERE  owner = src_usr
13531         LOOP
13532                 suffix := ' (' || src_usr || ')';
13533                 LOOP
13534                         BEGIN
13535                                 UPDATE  container.call_number_bucket
13536                                 SET     owner = dest_usr, name = name || suffix
13537                                 WHERE   id = bucket_row.id;
13538                         EXCEPTION WHEN unique_violation THEN
13539                                 suffix := suffix || ' ';
13540                                 CONTINUE;
13541                         END;
13542                         EXIT;
13543                 END LOOP;
13544         END LOOP;
13545
13546         FOR bucket_row in
13547                 SELECT id, name
13548                 FROM   container.copy_bucket
13549                 WHERE  owner = src_usr
13550         LOOP
13551                 suffix := ' (' || src_usr || ')';
13552                 LOOP
13553                         BEGIN
13554                                 UPDATE  container.copy_bucket
13555                                 SET     owner = dest_usr, name = name || suffix
13556                                 WHERE   id = bucket_row.id;
13557                         EXCEPTION WHEN unique_violation THEN
13558                                 suffix := suffix || ' ';
13559                                 CONTINUE;
13560                         END;
13561                         EXIT;
13562                 END LOOP;
13563         END LOOP;
13564
13565         FOR bucket_row in
13566                 SELECT id, name
13567                 FROM   container.user_bucket
13568                 WHERE  owner = src_usr
13569         LOOP
13570                 suffix := ' (' || src_usr || ')';
13571                 LOOP
13572                         BEGIN
13573                                 UPDATE  container.user_bucket
13574                                 SET     owner = dest_usr, name = name || suffix
13575                                 WHERE   id = bucket_row.id;
13576                         EXCEPTION WHEN unique_violation THEN
13577                                 suffix := suffix || ' ';
13578                                 CONTINUE;
13579                         END;
13580                         EXIT;
13581                 END LOOP;
13582         END LOOP;
13583
13584         UPDATE container.user_bucket_item SET target_user = dest_usr WHERE target_user = src_usr;
13585
13586     -- vandelay.*
13587         -- transfer queues the same way we transfer buckets (see above)
13588         FOR queue_row in
13589                 SELECT id, name
13590                 FROM   vandelay.queue
13591                 WHERE  owner = src_usr
13592         LOOP
13593                 suffix := ' (' || src_usr || ')';
13594                 LOOP
13595                         BEGIN
13596                                 UPDATE  vandelay.queue
13597                                 SET     owner = dest_usr, name = name || suffix
13598                                 WHERE   id = queue_row.id;
13599                         EXCEPTION WHEN unique_violation THEN
13600                                 suffix := suffix || ' ';
13601                                 CONTINUE;
13602                         END;
13603                         EXIT;
13604                 END LOOP;
13605         END LOOP;
13606
13607     -- money.*
13608     PERFORM actor.usr_merge_rows('money.collections_tracker', 'usr', src_usr, dest_usr);
13609     PERFORM actor.usr_merge_rows('money.collections_tracker', 'collector', src_usr, dest_usr);
13610     UPDATE money.billable_xact SET usr = dest_usr WHERE usr = src_usr;
13611     UPDATE money.billing SET voider = dest_usr WHERE voider = src_usr;
13612     UPDATE money.bnm_payment SET accepting_usr = dest_usr WHERE accepting_usr = src_usr;
13613
13614     -- action.*
13615     UPDATE action.circulation SET usr = dest_usr WHERE usr = src_usr;
13616     UPDATE action.circulation SET circ_staff = dest_usr WHERE circ_staff = src_usr;
13617     UPDATE action.circulation SET checkin_staff = dest_usr WHERE checkin_staff = src_usr;
13618
13619     UPDATE action.hold_request SET usr = dest_usr WHERE usr = src_usr;
13620     UPDATE action.hold_request SET fulfillment_staff = dest_usr WHERE fulfillment_staff = src_usr;
13621     UPDATE action.hold_request SET requestor = dest_usr WHERE requestor = src_usr;
13622     UPDATE action.hold_notification SET notify_staff = dest_usr WHERE notify_staff = src_usr;
13623
13624     UPDATE action.in_house_use SET staff = dest_usr WHERE staff = src_usr;
13625     UPDATE action.non_cataloged_circulation SET staff = dest_usr WHERE staff = src_usr;
13626     UPDATE action.non_cataloged_circulation SET patron = dest_usr WHERE patron = src_usr;
13627     UPDATE action.non_cat_in_house_use SET staff = dest_usr WHERE staff = src_usr;
13628     UPDATE action.survey_response SET usr = dest_usr WHERE usr = src_usr;
13629
13630     -- acq.*
13631     UPDATE acq.fund_allocation SET allocator = dest_usr WHERE allocator = src_usr;
13632         UPDATE acq.fund_transfer SET transfer_user = dest_usr WHERE transfer_user = src_usr;
13633
13634         -- transfer picklists the same way we transfer buckets (see above)
13635         FOR picklist_row in
13636                 SELECT id, name
13637                 FROM   acq.picklist
13638                 WHERE  owner = src_usr
13639         LOOP
13640                 suffix := ' (' || src_usr || ')';
13641                 LOOP
13642                         BEGIN
13643                                 UPDATE  acq.picklist
13644                                 SET     owner = dest_usr, name = name || suffix
13645                                 WHERE   id = picklist_row.id;
13646                         EXCEPTION WHEN unique_violation THEN
13647                                 suffix := suffix || ' ';
13648                                 CONTINUE;
13649                         END;
13650                         EXIT;
13651                 END LOOP;
13652         END LOOP;
13653
13654     UPDATE acq.purchase_order SET owner = dest_usr WHERE owner = src_usr;
13655     UPDATE acq.po_note SET creator = dest_usr WHERE creator = src_usr;
13656     UPDATE acq.po_note SET editor = dest_usr WHERE editor = src_usr;
13657     UPDATE acq.provider_note SET creator = dest_usr WHERE creator = src_usr;
13658     UPDATE acq.provider_note SET editor = dest_usr WHERE editor = src_usr;
13659     UPDATE acq.lineitem_note SET creator = dest_usr WHERE creator = src_usr;
13660     UPDATE acq.lineitem_note SET editor = dest_usr WHERE editor = src_usr;
13661     UPDATE acq.lineitem_usr_attr_definition SET usr = dest_usr WHERE usr = src_usr;
13662
13663     -- asset.*
13664     UPDATE asset.copy SET creator = dest_usr WHERE creator = src_usr;
13665     UPDATE asset.copy SET editor = dest_usr WHERE editor = src_usr;
13666     UPDATE asset.copy_note SET creator = dest_usr WHERE creator = src_usr;
13667     UPDATE asset.call_number SET creator = dest_usr WHERE creator = src_usr;
13668     UPDATE asset.call_number SET editor = dest_usr WHERE editor = src_usr;
13669     UPDATE asset.call_number_note SET creator = dest_usr WHERE creator = src_usr;
13670
13671     -- serial.*
13672     UPDATE serial.record_entry SET creator = dest_usr WHERE creator = src_usr;
13673     UPDATE serial.record_entry SET editor = dest_usr WHERE editor = src_usr;
13674
13675     -- reporter.*
13676     -- It's not uncommon to define the reporter schema in a replica 
13677     -- DB only, so don't assume these tables exist in the write DB.
13678     BEGIN
13679         UPDATE reporter.template SET owner = dest_usr WHERE owner = src_usr;
13680     EXCEPTION WHEN undefined_table THEN
13681         -- do nothing
13682     END;
13683     BEGIN
13684         UPDATE reporter.report SET owner = dest_usr WHERE owner = src_usr;
13685     EXCEPTION WHEN undefined_table THEN
13686         -- do nothing
13687     END;
13688     BEGIN
13689         UPDATE reporter.schedule SET runner = dest_usr WHERE runner = src_usr;
13690     EXCEPTION WHEN undefined_table THEN
13691         -- do nothing
13692     END;
13693     BEGIN
13694                 -- transfer folders the same way we transfer buckets (see above)
13695                 FOR folder_row in
13696                         SELECT id, name
13697                         FROM   reporter.template_folder
13698                         WHERE  owner = src_usr
13699                 LOOP
13700                         suffix := ' (' || src_usr || ')';
13701                         LOOP
13702                                 BEGIN
13703                                         UPDATE  reporter.template_folder
13704                                         SET     owner = dest_usr, name = name || suffix
13705                                         WHERE   id = folder_row.id;
13706                                 EXCEPTION WHEN unique_violation THEN
13707                                         suffix := suffix || ' ';
13708                                         CONTINUE;
13709                                 END;
13710                                 EXIT;
13711                         END LOOP;
13712                 END LOOP;
13713     EXCEPTION WHEN undefined_table THEN
13714         -- do nothing
13715     END;
13716     BEGIN
13717                 -- transfer folders the same way we transfer buckets (see above)
13718                 FOR folder_row in
13719                         SELECT id, name
13720                         FROM   reporter.report_folder
13721                         WHERE  owner = src_usr
13722                 LOOP
13723                         suffix := ' (' || src_usr || ')';
13724                         LOOP
13725                                 BEGIN
13726                                         UPDATE  reporter.report_folder
13727                                         SET     owner = dest_usr, name = name || suffix
13728                                         WHERE   id = folder_row.id;
13729                                 EXCEPTION WHEN unique_violation THEN
13730                                         suffix := suffix || ' ';
13731                                         CONTINUE;
13732                                 END;
13733                                 EXIT;
13734                         END LOOP;
13735                 END LOOP;
13736     EXCEPTION WHEN undefined_table THEN
13737         -- do nothing
13738     END;
13739     BEGIN
13740                 -- transfer folders the same way we transfer buckets (see above)
13741                 FOR folder_row in
13742                         SELECT id, name
13743                         FROM   reporter.output_folder
13744                         WHERE  owner = src_usr
13745                 LOOP
13746                         suffix := ' (' || src_usr || ')';
13747                         LOOP
13748                                 BEGIN
13749                                         UPDATE  reporter.output_folder
13750                                         SET     owner = dest_usr, name = name || suffix
13751                                         WHERE   id = folder_row.id;
13752                                 EXCEPTION WHEN unique_violation THEN
13753                                         suffix := suffix || ' ';
13754                                         CONTINUE;
13755                                 END;
13756                                 EXIT;
13757                         END LOOP;
13758                 END LOOP;
13759     EXCEPTION WHEN undefined_table THEN
13760         -- do nothing
13761     END;
13762
13763     -- Finally, delete the source user
13764     DELETE FROM actor.usr WHERE id = src_usr;
13765
13766 END;
13767 $$ LANGUAGE plpgsql;
13768
13769 -- The "add" trigger functions should protect against existing NULLed values, just in case
13770 CREATE OR REPLACE FUNCTION money.materialized_summary_billing_add () RETURNS TRIGGER AS $$
13771 BEGIN
13772     IF NOT NEW.voided THEN
13773         UPDATE  money.materialized_billable_xact_summary
13774           SET   total_owed = COALESCE(total_owed, 0.0::numeric) + NEW.amount,
13775             last_billing_ts = NEW.billing_ts,
13776             last_billing_note = NEW.note,
13777             last_billing_type = NEW.billing_type,
13778             balance_owed = balance_owed + NEW.amount
13779           WHERE id = NEW.xact;
13780     END IF;
13781
13782     RETURN NEW;
13783 END;
13784 $$ LANGUAGE PLPGSQL;
13785
13786 CREATE OR REPLACE FUNCTION money.materialized_summary_payment_add () RETURNS TRIGGER AS $$
13787 BEGIN
13788     IF NOT NEW.voided THEN
13789         UPDATE  money.materialized_billable_xact_summary
13790           SET   total_paid = COALESCE(total_paid, 0.0::numeric) + NEW.amount,
13791             last_payment_ts = NEW.payment_ts,
13792             last_payment_note = NEW.note,
13793             last_payment_type = TG_ARGV[0],
13794             balance_owed = balance_owed - NEW.amount
13795           WHERE id = NEW.xact;
13796     END IF;
13797
13798     RETURN NEW;
13799 END;
13800 $$ LANGUAGE PLPGSQL;
13801
13802 -- Refresh the mat view with the corrected underlying view
13803 TRUNCATE money.materialized_billable_xact_summary;
13804 INSERT INTO money.materialized_billable_xact_summary SELECT * FROM money.billable_xact_summary;
13805
13806 -- Now redefine the view as a window onto the materialized view
13807 CREATE OR REPLACE VIEW money.billable_xact_summary AS
13808     SELECT * FROM money.materialized_billable_xact_summary;
13809
13810 CREATE OR REPLACE FUNCTION permission.usr_has_perm_at_nd(
13811     user_id    IN INTEGER,
13812     perm_code  IN TEXT
13813 )
13814 RETURNS SETOF INTEGER AS $$
13815 --
13816 -- Return a set of all the org units for which a given user has a given
13817 -- permission, granted directly (not through inheritance from a parent
13818 -- org unit).
13819 --
13820 -- The permissions apply to a minimum depth of the org unit hierarchy,
13821 -- for the org unit(s) to which the user is assigned.  (They also apply
13822 -- to the subordinates of those org units, but we don't report the
13823 -- subordinates here.)
13824 --
13825 -- For purposes of this function, the permission.usr_work_ou_map table
13826 -- defines which users belong to which org units.  I.e. we ignore the
13827 -- home_ou column of actor.usr.
13828 --
13829 -- The result set may contain duplicates, which should be eliminated
13830 -- by a DISTINCT clause.
13831 --
13832 DECLARE
13833     b_super       BOOLEAN;
13834     n_perm        INTEGER;
13835     n_min_depth   INTEGER;
13836     n_work_ou     INTEGER;
13837     n_curr_ou     INTEGER;
13838     n_depth       INTEGER;
13839     n_curr_depth  INTEGER;
13840 BEGIN
13841     --
13842     -- Check for superuser
13843     --
13844     SELECT INTO b_super
13845         super_user
13846     FROM
13847         actor.usr
13848     WHERE
13849         id = user_id;
13850     --
13851     IF NOT FOUND THEN
13852         return;             -- No user?  No permissions.
13853     ELSIF b_super THEN
13854         --
13855         -- Super user has all permissions everywhere
13856         --
13857         FOR n_work_ou IN
13858             SELECT
13859                 id
13860             FROM
13861                 actor.org_unit
13862             WHERE
13863                 parent_ou IS NULL
13864         LOOP
13865             RETURN NEXT n_work_ou;
13866         END LOOP;
13867         RETURN;
13868     END IF;
13869     --
13870     -- Translate the permission name
13871     -- to a numeric permission id
13872     --
13873     SELECT INTO n_perm
13874         id
13875     FROM
13876         permission.perm_list
13877     WHERE
13878         code = perm_code;
13879     --
13880     IF NOT FOUND THEN
13881         RETURN;               -- No such permission
13882     END IF;
13883     --
13884     -- Find the highest-level org unit (i.e. the minimum depth)
13885     -- to which the permission is applied for this user
13886     --
13887     -- This query is modified from the one in permission.usr_perms().
13888     --
13889     SELECT INTO n_min_depth
13890         min( depth )
13891     FROM    (
13892         SELECT depth
13893           FROM permission.usr_perm_map upm
13894          WHERE upm.usr = user_id
13895            AND (upm.perm = n_perm OR upm.perm = -1)
13896                     UNION
13897         SELECT  gpm.depth
13898           FROM  permission.grp_perm_map gpm
13899           WHERE (gpm.perm = n_perm OR gpm.perm = -1)
13900             AND gpm.grp IN (
13901                SELECT   (permission.grp_ancestors(
13902                     (SELECT profile FROM actor.usr WHERE id = user_id)
13903                 )).id
13904             )
13905                     UNION
13906         SELECT  p.depth
13907           FROM  permission.grp_perm_map p
13908           WHERE (p.perm = n_perm OR p.perm = -1)
13909             AND p.grp IN (
13910                 SELECT (permission.grp_ancestors(m.grp)).id
13911                 FROM   permission.usr_grp_map m
13912                 WHERE  m.usr = user_id
13913             )
13914     ) AS x;
13915     --
13916     IF NOT FOUND THEN
13917         RETURN;                -- No such permission for this user
13918     END IF;
13919     --
13920     -- Identify the org units to which the user is assigned.  Note that
13921     -- we pay no attention to the home_ou column in actor.usr.
13922     --
13923     FOR n_work_ou IN
13924         SELECT
13925             work_ou
13926         FROM
13927             permission.usr_work_ou_map
13928         WHERE
13929             usr = user_id
13930     LOOP            -- For each org unit to which the user is assigned
13931         --
13932         -- Determine the level of the org unit by a lookup in actor.org_unit_type.
13933         -- We take it on faith that this depth agrees with the actual hierarchy
13934         -- defined in actor.org_unit.
13935         --
13936         SELECT INTO n_depth
13937             type.depth
13938         FROM
13939             actor.org_unit_type type
13940                 INNER JOIN actor.org_unit ou
13941                     ON ( ou.ou_type = type.id )
13942         WHERE
13943             ou.id = n_work_ou;
13944         --
13945         IF NOT FOUND THEN
13946             CONTINUE;        -- Maybe raise exception?
13947         END IF;
13948         --
13949         -- Compare the depth of the work org unit to the
13950         -- minimum depth, and branch accordingly
13951         --
13952         IF n_depth = n_min_depth THEN
13953             --
13954             -- The org unit is at the right depth, so return it.
13955             --
13956             RETURN NEXT n_work_ou;
13957         ELSIF n_depth > n_min_depth THEN
13958             --
13959             -- Traverse the org unit tree toward the root,
13960             -- until you reach the minimum depth determined above
13961             --
13962             n_curr_depth := n_depth;
13963             n_curr_ou := n_work_ou;
13964             WHILE n_curr_depth > n_min_depth LOOP
13965                 SELECT INTO n_curr_ou
13966                     parent_ou
13967                 FROM
13968                     actor.org_unit
13969                 WHERE
13970                     id = n_curr_ou;
13971                 --
13972                 IF FOUND THEN
13973                     n_curr_depth := n_curr_depth - 1;
13974                 ELSE
13975                     --
13976                     -- This can happen only if the hierarchy defined in
13977                     -- actor.org_unit is corrupted, or out of sync with
13978                     -- the depths defined in actor.org_unit_type.
13979                     -- Maybe we should raise an exception here, instead
13980                     -- of silently ignoring the problem.
13981                     --
13982                     n_curr_ou = NULL;
13983                     EXIT;
13984                 END IF;
13985             END LOOP;
13986             --
13987             IF n_curr_ou IS NOT NULL THEN
13988                 RETURN NEXT n_curr_ou;
13989             END IF;
13990         ELSE
13991             --
13992             -- The permission applies only at a depth greater than the work org unit.
13993             -- Use connectby() to find all dependent org units at the specified depth.
13994             --
13995             FOR n_curr_ou IN
13996                 SELECT ou::INTEGER
13997                 FROM connectby(
13998                         'actor.org_unit',         -- table name
13999                         'id',                     -- key column
14000                         'parent_ou',              -- recursive foreign key
14001                         n_work_ou::TEXT,          -- id of starting point
14002                         (n_min_depth - n_depth)   -- max depth to search, relative
14003                     )                             --   to starting point
14004                     AS t(
14005                         ou text,            -- dependent org unit
14006                         parent_ou text,     -- (ignore)
14007                         level int           -- depth relative to starting point
14008                     )
14009                 WHERE
14010                     level = n_min_depth - n_depth
14011             LOOP
14012                 RETURN NEXT n_curr_ou;
14013             END LOOP;
14014         END IF;
14015         --
14016     END LOOP;
14017     --
14018     RETURN;
14019     --
14020 END;
14021 $$ LANGUAGE 'plpgsql';
14022
14023 ALTER TABLE acq.purchase_order
14024         ADD COLUMN cancel_reason INT
14025                 REFERENCES acq.cancel_reason( id )
14026             DEFERRABLE INITIALLY DEFERRED,
14027         ADD COLUMN prepayment_required BOOLEAN NOT NULL DEFAULT FALSE;
14028
14029 -- Build the history table and lifecycle view
14030 -- for acq.purchase_order
14031
14032 SELECT acq.create_acq_auditor ( 'acq', 'purchase_order' );
14033
14034 CREATE INDEX acq_po_hist_id_idx            ON acq.acq_purchase_order_history( id );
14035
14036 ALTER TABLE acq.lineitem
14037         ADD COLUMN cancel_reason INT
14038                 REFERENCES acq.cancel_reason( id )
14039             DEFERRABLE INITIALLY DEFERRED,
14040         ADD COLUMN estimated_unit_price NUMERIC,
14041         ADD COLUMN claim_policy INT
14042                 REFERENCES acq.claim_policy
14043                 DEFERRABLE INITIALLY DEFERRED,
14044         ALTER COLUMN eg_bib_id SET DATA TYPE bigint;
14045
14046 -- Build the history table and lifecycle view
14047 -- for acq.lineitem
14048
14049 SELECT acq.create_acq_auditor ( 'acq', 'lineitem' );
14050 CREATE INDEX acq_lineitem_hist_id_idx            ON acq.acq_lineitem_history( id );
14051
14052 ALTER TABLE acq.lineitem_detail
14053         ADD COLUMN cancel_reason        INT REFERENCES acq.cancel_reason( id )
14054                                             DEFERRABLE INITIALLY DEFERRED;
14055
14056 ALTER TABLE acq.lineitem_detail
14057         DROP CONSTRAINT lineitem_detail_lineitem_fkey;
14058
14059 ALTER TABLE acq.lineitem_detail
14060         ADD FOREIGN KEY (lineitem) REFERENCES acq.lineitem( id )
14061                 ON DELETE CASCADE
14062                 DEFERRABLE INITIALLY DEFERRED;
14063
14064 ALTER TABLE acq.lineitem_detail DROP CONSTRAINT lineitem_detail_eg_copy_id_fkey;
14065
14066 INSERT INTO acq.cancel_reason ( id, org_unit, label, description ) VALUES (
14067         1, 1, 'invalid_isbn', oils_i18n_gettext( 1, 'ISBN is unrecognizable', 'acqcr', 'label' ));
14068
14069 INSERT INTO acq.cancel_reason ( id, org_unit, label, description ) VALUES (
14070         2, 1, 'postpone', oils_i18n_gettext( 2, 'Title has been postponed', 'acqcr', 'label' ));
14071
14072 CREATE OR REPLACE FUNCTION vandelay.add_field ( target_xml TEXT, source_xml TEXT, field TEXT, force_add INT ) RETURNS TEXT AS $_$
14073
14074     use MARC::Record;
14075     use MARC::File::XML (BinaryEncoding => 'UTF-8');
14076     use strict;
14077
14078     my $target_xml = shift;
14079     my $source_xml = shift;
14080     my $field_spec = shift;
14081     my $force_add = shift || 0;
14082
14083     my $target_r = MARC::Record->new_from_xml( $target_xml );
14084     my $source_r = MARC::Record->new_from_xml( $source_xml );
14085
14086     return $target_xml unless ($target_r && $source_r);
14087
14088     my @field_list = split(',', $field_spec);
14089
14090     my %fields;
14091     for my $f (@field_list) {
14092         $f =~ s/^\s*//; $f =~ s/\s*$//;
14093         if ($f =~ /^(.{3})(\w*)(?:\[([^]]*)\])?$/) {
14094             my $field = $1;
14095             $field =~ s/\s+//;
14096             my $sf = $2;
14097             $sf =~ s/\s+//;
14098             my $match = $3;
14099             $match =~ s/^\s*//; $match =~ s/\s*$//;
14100             $fields{$field} = { sf => [ split('', $sf) ] };
14101             if ($match) {
14102                 my ($msf,$mre) = split('~', $match);
14103                 if (length($msf) > 0 and length($mre) > 0) {
14104                     $msf =~ s/^\s*//; $msf =~ s/\s*$//;
14105                     $mre =~ s/^\s*//; $mre =~ s/\s*$//;
14106                     $fields{$field}{match} = { sf => $msf, re => qr/$mre/ };
14107                 }
14108             }
14109         }
14110     }
14111
14112     for my $f ( keys %fields) {
14113         if ( @{$fields{$f}{sf}} ) {
14114             for my $from_field ($source_r->field( $f )) {
14115                 my @tos = $target_r->field( $f );
14116                 if (!@tos) {
14117                     next if (exists($fields{$f}{match}) and !$force_add);
14118                     my @new_fields = map { $_->clone } $source_r->field( $f );
14119                     $target_r->insert_fields_ordered( @new_fields );
14120                 } else {
14121                     for my $to_field (@tos) {
14122                         if (exists($fields{$f}{match})) {
14123                             next unless (grep { $_ =~ $fields{$f}{match}{re} } $to_field->subfield($fields{$f}{match}{sf}));
14124                         }
14125                         my @new_sf = map { ($_ => $from_field->subfield($_)) } @{$fields{$f}{sf}};
14126                         $to_field->add_subfields( @new_sf );
14127                     }
14128                 }
14129             }
14130         } else {
14131             my @new_fields = map { $_->clone } $source_r->field( $f );
14132             $target_r->insert_fields_ordered( @new_fields );
14133         }
14134     }
14135
14136     $target_xml = $target_r->as_xml_record;
14137     $target_xml =~ s/^<\?.+?\?>$//mo;
14138     $target_xml =~ s/\n//sgo;
14139     $target_xml =~ s/>\s+</></sgo;
14140
14141     return $target_xml;
14142
14143 $_$ LANGUAGE PLPERLU;
14144
14145 CREATE OR REPLACE FUNCTION vandelay.add_field ( target_xml TEXT, source_xml TEXT, field TEXT ) RETURNS TEXT AS $_$
14146     SELECT vandelay.add_field( $1, $2, $3, 0 );
14147 $_$ LANGUAGE SQL;
14148
14149 CREATE OR REPLACE FUNCTION vandelay.strip_field ( xml TEXT, field TEXT ) RETURNS TEXT AS $_$
14150
14151     use MARC::Record;
14152     use MARC::File::XML (BinaryEncoding => 'UTF-8');
14153     use strict;
14154
14155     my $xml = shift;
14156     my $r = MARC::Record->new_from_xml( $xml );
14157
14158     return $xml unless ($r);
14159
14160     my $field_spec = shift;
14161     my @field_list = split(',', $field_spec);
14162
14163     my %fields;
14164     for my $f (@field_list) {
14165         $f =~ s/^\s*//; $f =~ s/\s*$//;
14166         if ($f =~ /^(.{3})(\w*)(?:\[([^]]*)\])?$/) {
14167             my $field = $1;
14168             $field =~ s/\s+//;
14169             my $sf = $2;
14170             $sf =~ s/\s+//;
14171             my $match = $3;
14172             $match =~ s/^\s*//; $match =~ s/\s*$//;
14173             $fields{$field} = { sf => [ split('', $sf) ] };
14174             if ($match) {
14175                 my ($msf,$mre) = split('~', $match);
14176                 if (length($msf) > 0 and length($mre) > 0) {
14177                     $msf =~ s/^\s*//; $msf =~ s/\s*$//;
14178                     $mre =~ s/^\s*//; $mre =~ s/\s*$//;
14179                     $fields{$field}{match} = { sf => $msf, re => qr/$mre/ };
14180                 }
14181             }
14182         }
14183     }
14184
14185     for my $f ( keys %fields) {
14186         for my $to_field ($r->field( $f )) {
14187             if (exists($fields{$f}{match})) {
14188                 next unless (grep { $_ =~ $fields{$f}{match}{re} } $to_field->subfield($fields{$f}{match}{sf}));
14189             }
14190
14191             if ( @{$fields{$f}{sf}} ) {
14192                 $to_field->delete_subfield(code => $fields{$f}{sf});
14193             } else {
14194                 $r->delete_field( $to_field );
14195             }
14196         }
14197     }
14198
14199     $xml = $r->as_xml_record;
14200     $xml =~ s/^<\?.+?\?>$//mo;
14201     $xml =~ s/\n//sgo;
14202     $xml =~ s/>\s+</></sgo;
14203
14204     return $xml;
14205
14206 $_$ LANGUAGE PLPERLU;
14207
14208 CREATE OR REPLACE FUNCTION vandelay.replace_field ( target_xml TEXT, source_xml TEXT, field TEXT ) RETURNS TEXT AS $_$
14209 DECLARE
14210     xml_output TEXT;
14211 BEGIN
14212     xml_output := vandelay.strip_field( target_xml, field);
14213
14214     IF xml_output <> target_xml  AND field ~ E'~' THEN
14215         -- we removed something, and there was a regexp restriction in the field definition, so proceed
14216         xml_output := vandelay.add_field( xml_output, source_xml, field, 1 );
14217     ELSIF field !~ E'~' THEN
14218         -- No regexp restriction, add the field
14219         xml_output := vandelay.add_field( xml_output, source_xml, field, 0 );
14220     END IF;
14221
14222     RETURN xml_output;
14223 END;
14224 $_$ LANGUAGE PLPGSQL;
14225
14226 CREATE OR REPLACE FUNCTION vandelay.preserve_field ( incumbent_xml TEXT, incoming_xml TEXT, field TEXT ) RETURNS TEXT AS $_$
14227     SELECT vandelay.add_field( vandelay.strip_field( $2, $3), $1, $3 );
14228 $_$ LANGUAGE SQL;
14229
14230 CREATE VIEW action.unfulfilled_hold_max_loop AS
14231         SELECT  hold,
14232                 max(count) AS max
14233         FROM    action.unfulfilled_hold_loops
14234         GROUP BY 1;
14235
14236 ALTER TABLE acq.lineitem_attr
14237         DROP CONSTRAINT lineitem_attr_lineitem_fkey;
14238
14239 ALTER TABLE acq.lineitem_attr
14240         ADD FOREIGN KEY (lineitem) REFERENCES acq.lineitem( id )
14241                 ON DELETE CASCADE
14242                 DEFERRABLE INITIALLY DEFERRED;
14243
14244 ALTER TABLE acq.po_note
14245         ADD COLUMN vendor_public BOOLEAN NOT NULL DEFAULT FALSE;
14246
14247 CREATE TABLE vandelay.merge_profile (
14248     id              BIGSERIAL   PRIMARY KEY,
14249     owner           INT         NOT NULL REFERENCES actor.org_unit (id) ON UPDATE CASCADE ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED,
14250     name            TEXT        NOT NULL,
14251     add_spec        TEXT,
14252     replace_spec    TEXT,
14253     strip_spec      TEXT,
14254     preserve_spec   TEXT,
14255     CONSTRAINT vand_merge_prof_owner_name_idx UNIQUE (owner,name),
14256     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))
14257 );
14258
14259 CREATE OR REPLACE FUNCTION vandelay.match_bib_record ( ) RETURNS TRIGGER AS $func$
14260 DECLARE
14261     attr        RECORD;
14262     attr_def    RECORD;
14263     eg_rec      RECORD;
14264     id_value    TEXT;
14265     exact_id    BIGINT;
14266 BEGIN
14267
14268     DELETE FROM vandelay.bib_match WHERE queued_record = NEW.id;
14269
14270     SELECT * INTO attr_def FROM vandelay.bib_attr_definition WHERE xpath = '//*[@tag="901"]/*[@code="c"]' ORDER BY id LIMIT 1;
14271
14272     IF attr_def IS NOT NULL AND attr_def.id IS NOT NULL THEN
14273         id_value := extract_marc_field('vandelay.queued_bib_record', NEW.id, attr_def.xpath, attr_def.remove);
14274
14275         IF id_value IS NOT NULL AND id_value <> '' AND id_value ~ $r$^\d+$$r$ THEN
14276             SELECT id INTO exact_id FROM biblio.record_entry WHERE id = id_value::BIGINT AND NOT deleted;
14277             SELECT * INTO attr FROM vandelay.queued_bib_record_attr WHERE record = NEW.id and field = attr_def.id LIMIT 1;
14278             IF exact_id IS NOT NULL THEN
14279                 INSERT INTO vandelay.bib_match (field_type, matched_attr, queued_record, eg_record) VALUES ('id', attr.id, NEW.id, exact_id);
14280             END IF;
14281         END IF;
14282     END IF;
14283
14284     IF exact_id IS NULL THEN
14285         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
14286
14287             -- All numbers? check for an id match
14288             IF (attr.attr_value ~ $r$^\d+$$r$) THEN
14289                 FOR eg_rec IN SELECT * FROM biblio.record_entry WHERE id = attr.attr_value::BIGINT AND deleted IS FALSE LOOP
14290                     INSERT INTO vandelay.bib_match (field_type, matched_attr, queued_record, eg_record) VALUES ('id', attr.id, NEW.id, eg_rec.id);
14291                 END LOOP;
14292             END IF;
14293
14294             -- Looks like an ISBN? check for an isbn match
14295             IF (attr.attr_value ~* $r$^[0-9x]+$$r$ AND character_length(attr.attr_value) IN (10,13)) THEN
14296                 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
14297                     PERFORM id FROM biblio.record_entry WHERE id = eg_rec.record AND deleted IS FALSE;
14298                     IF FOUND THEN
14299                         INSERT INTO vandelay.bib_match (field_type, matched_attr, queued_record, eg_record) VALUES ('isbn', attr.id, NEW.id, eg_rec.record);
14300                     END IF;
14301                 END LOOP;
14302
14303                 -- subcheck for isbn-as-tcn
14304                 FOR eg_rec IN SELECT * FROM biblio.record_entry WHERE tcn_value = 'i' || attr.attr_value AND deleted IS FALSE LOOP
14305                     INSERT INTO vandelay.bib_match (field_type, matched_attr, queued_record, eg_record) VALUES ('tcn_value', attr.id, NEW.id, eg_rec.id);
14306                 END LOOP;
14307             END IF;
14308
14309             -- check for an OCLC tcn_value match
14310             IF (attr.attr_value ~ $r$^o\d+$$r$) THEN
14311                 FOR eg_rec IN SELECT * FROM biblio.record_entry WHERE tcn_value = regexp_replace(attr.attr_value,'^o','ocm') AND deleted IS FALSE LOOP
14312                     INSERT INTO vandelay.bib_match (field_type, matched_attr, queued_record, eg_record) VALUES ('tcn_value', attr.id, NEW.id, eg_rec.id);
14313                 END LOOP;
14314             END IF;
14315
14316             -- check for a direct tcn_value match
14317             FOR eg_rec IN SELECT * FROM biblio.record_entry WHERE tcn_value = attr.attr_value AND deleted IS FALSE LOOP
14318                 INSERT INTO vandelay.bib_match (field_type, matched_attr, queued_record, eg_record) VALUES ('tcn_value', attr.id, NEW.id, eg_rec.id);
14319             END LOOP;
14320
14321             -- check for a direct item barcode match
14322             FOR eg_rec IN
14323                     SELECT  DISTINCT b.*
14324                       FROM  biblio.record_entry b
14325                             JOIN asset.call_number cn ON (cn.record = b.id)
14326                             JOIN asset.copy cp ON (cp.call_number = cn.id)
14327                       WHERE cp.barcode = attr.attr_value AND cp.deleted IS FALSE
14328             LOOP
14329                 INSERT INTO vandelay.bib_match (field_type, matched_attr, queued_record, eg_record) VALUES ('id', attr.id, NEW.id, eg_rec.id);
14330             END LOOP;
14331
14332         END LOOP;
14333     END IF;
14334
14335     RETURN NULL;
14336 END;
14337 $func$ LANGUAGE PLPGSQL;
14338
14339 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 $_$
14340     SELECT vandelay.replace_field( vandelay.add_field( vandelay.strip_field( $1, $5) , $2, $3 ), $2, $4);
14341 $_$ LANGUAGE SQL;
14342
14343 CREATE TYPE vandelay.compile_profile AS (add_rule TEXT, replace_rule TEXT, preserve_rule TEXT, strip_rule TEXT);
14344 CREATE OR REPLACE FUNCTION vandelay.compile_profile ( incoming_xml TEXT ) RETURNS vandelay.compile_profile AS $_$
14345 DECLARE
14346     output              vandelay.compile_profile%ROWTYPE;
14347     profile             vandelay.merge_profile%ROWTYPE;
14348     profile_tmpl        TEXT;
14349     profile_tmpl_owner  TEXT;
14350     add_rule            TEXT := '';
14351     strip_rule          TEXT := '';
14352     replace_rule        TEXT := '';
14353     preserve_rule       TEXT := '';
14354
14355 BEGIN
14356
14357     profile_tmpl := (oils_xpath('//*[@tag="905"]/*[@code="t"]/text()',incoming_xml))[1];
14358     profile_tmpl_owner := (oils_xpath('//*[@tag="905"]/*[@code="o"]/text()',incoming_xml))[1];
14359
14360     IF profile_tmpl IS NOT NULL AND profile_tmpl <> '' AND profile_tmpl_owner IS NOT NULL AND profile_tmpl_owner <> '' THEN
14361         SELECT  p.* INTO profile
14362           FROM  vandelay.merge_profile p
14363                 JOIN actor.org_unit u ON (u.id = p.owner)
14364           WHERE p.name = profile_tmpl
14365                 AND u.shortname = profile_tmpl_owner;
14366
14367         IF profile.id IS NOT NULL THEN
14368             add_rule := COALESCE(profile.add_spec,'');
14369             strip_rule := COALESCE(profile.strip_spec,'');
14370             replace_rule := COALESCE(profile.replace_spec,'');
14371             preserve_rule := COALESCE(profile.preserve_spec,'');
14372         END IF;
14373     END IF;
14374
14375     add_rule := add_rule || ',' || COALESCE(ARRAY_TO_STRING(oils_xpath('//*[@tag="905"]/*[@code="a"]/text()',incoming_xml),','),'');
14376     strip_rule := strip_rule || ',' || COALESCE(ARRAY_TO_STRING(oils_xpath('//*[@tag="905"]/*[@code="d"]/text()',incoming_xml),','),'');
14377     replace_rule := replace_rule || ',' || COALESCE(ARRAY_TO_STRING(oils_xpath('//*[@tag="905"]/*[@code="r"]/text()',incoming_xml),','),'');
14378     preserve_rule := preserve_rule || ',' || COALESCE(ARRAY_TO_STRING(oils_xpath('//*[@tag="905"]/*[@code="p"]/text()',incoming_xml),','),'');
14379
14380     output.add_rule := BTRIM(add_rule,',');
14381     output.replace_rule := BTRIM(replace_rule,',');
14382     output.strip_rule := BTRIM(strip_rule,',');
14383     output.preserve_rule := BTRIM(preserve_rule,',');
14384
14385     RETURN output;
14386 END;
14387 $_$ LANGUAGE PLPGSQL;
14388
14389 -- Template-based marc munging functions
14390 CREATE OR REPLACE FUNCTION vandelay.template_overlay_bib_record ( v_marc TEXT, eg_id BIGINT, merge_profile_id INT ) RETURNS BOOL AS $$
14391 DECLARE
14392     merge_profile   vandelay.merge_profile%ROWTYPE;
14393     dyn_profile     vandelay.compile_profile%ROWTYPE;
14394     editor_string   TEXT;
14395     editor_id       INT;
14396     source_marc     TEXT;
14397     target_marc     TEXT;
14398     eg_marc         TEXT;
14399     replace_rule    TEXT;
14400     match_count     INT;
14401 BEGIN
14402
14403     SELECT  b.marc INTO eg_marc
14404       FROM  biblio.record_entry b
14405       WHERE b.id = eg_id
14406       LIMIT 1;
14407
14408     IF eg_marc IS NULL OR v_marc IS NULL THEN
14409         -- RAISE NOTICE 'no marc for template or bib record';
14410         RETURN FALSE;
14411     END IF;
14412
14413     dyn_profile := vandelay.compile_profile( v_marc );
14414
14415     IF merge_profile_id IS NOT NULL THEN
14416         SELECT * INTO merge_profile FROM vandelay.merge_profile WHERE id = merge_profile_id;
14417         IF FOUND THEN
14418             dyn_profile.add_rule := BTRIM( dyn_profile.add_rule || ',' || COALESCE(merge_profile.add_spec,''), ',');
14419             dyn_profile.strip_rule := BTRIM( dyn_profile.strip_rule || ',' || COALESCE(merge_profile.strip_spec,''), ',');
14420             dyn_profile.replace_rule := BTRIM( dyn_profile.replace_rule || ',' || COALESCE(merge_profile.replace_spec,''), ',');
14421             dyn_profile.preserve_rule := BTRIM( dyn_profile.preserve_rule || ',' || COALESCE(merge_profile.preserve_spec,''), ',');
14422         END IF;
14423     END IF;
14424
14425     IF dyn_profile.replace_rule <> '' AND dyn_profile.preserve_rule <> '' THEN
14426         -- RAISE NOTICE 'both replace [%] and preserve [%] specified', dyn_profile.replace_rule, dyn_profile.preserve_rule;
14427         RETURN FALSE;
14428     END IF;
14429
14430     IF dyn_profile.replace_rule <> '' THEN
14431         source_marc = v_marc;
14432         target_marc = eg_marc;
14433         replace_rule = dyn_profile.replace_rule;
14434     ELSE
14435         source_marc = eg_marc;
14436         target_marc = v_marc;
14437         replace_rule = dyn_profile.preserve_rule;
14438     END IF;
14439
14440     UPDATE  biblio.record_entry
14441       SET   marc = vandelay.merge_record_xml( target_marc, source_marc, dyn_profile.add_rule, replace_rule, dyn_profile.strip_rule )
14442       WHERE id = eg_id;
14443
14444     IF NOT FOUND THEN
14445         -- RAISE NOTICE 'update of biblio.record_entry failed';
14446         RETURN FALSE;
14447     END IF;
14448
14449     RETURN TRUE;
14450
14451 END;
14452 $$ LANGUAGE PLPGSQL;
14453
14454 CREATE OR REPLACE FUNCTION vandelay.template_overlay_bib_record ( v_marc TEXT, eg_id BIGINT) RETURNS BOOL AS $$
14455     SELECT vandelay.template_overlay_bib_record( $1, $2, NULL);
14456 $$ LANGUAGE SQL;
14457
14458 CREATE OR REPLACE FUNCTION vandelay.overlay_bib_record ( import_id BIGINT, eg_id BIGINT, merge_profile_id INT ) RETURNS BOOL AS $$
14459 DECLARE
14460     merge_profile   vandelay.merge_profile%ROWTYPE;
14461     dyn_profile     vandelay.compile_profile%ROWTYPE;
14462     editor_string   TEXT;
14463     editor_id       INT;
14464     source_marc     TEXT;
14465     target_marc     TEXT;
14466     eg_marc         TEXT;
14467     v_marc          TEXT;
14468     replace_rule    TEXT;
14469     match_count     INT;
14470 BEGIN
14471
14472     SELECT  q.marc INTO v_marc
14473       FROM  vandelay.queued_record q
14474             JOIN vandelay.bib_match m ON (m.queued_record = q.id AND q.id = import_id)
14475       LIMIT 1;
14476
14477     IF v_marc IS NULL THEN
14478         -- RAISE NOTICE 'no marc for vandelay or bib record';
14479         RETURN FALSE;
14480     END IF;
14481
14482     IF vandelay.template_overlay_bib_record( v_marc, eg_id, merge_profile_id) THEN
14483         UPDATE  vandelay.queued_bib_record
14484           SET   imported_as = eg_id,
14485                 import_time = NOW()
14486           WHERE id = import_id;
14487
14488         editor_string := (oils_xpath('//*[@tag="905"]/*[@code="u"]/text()',v_marc))[1];
14489
14490         IF editor_string IS NOT NULL AND editor_string <> '' THEN
14491             SELECT usr INTO editor_id FROM actor.card WHERE barcode = editor_string;
14492
14493             IF editor_id IS NULL THEN
14494                 SELECT id INTO editor_id FROM actor.usr WHERE usrname = editor_string;
14495             END IF;
14496
14497             IF editor_id IS NOT NULL THEN
14498                 UPDATE biblio.record_entry SET editor = editor_id WHERE id = eg_id;
14499             END IF;
14500         END IF;
14501
14502         RETURN TRUE;
14503     END IF;
14504
14505     -- RAISE NOTICE 'update of biblio.record_entry failed';
14506
14507     RETURN FALSE;
14508
14509 END;
14510 $$ LANGUAGE PLPGSQL;
14511
14512 CREATE OR REPLACE FUNCTION vandelay.auto_overlay_bib_record ( import_id BIGINT, merge_profile_id INT ) RETURNS BOOL AS $$
14513 DECLARE
14514     eg_id           BIGINT;
14515     match_count     INT;
14516     match_attr      vandelay.bib_attr_definition%ROWTYPE;
14517 BEGIN
14518
14519     PERFORM * FROM vandelay.queued_bib_record WHERE import_time IS NOT NULL AND id = import_id;
14520
14521     IF FOUND THEN
14522         -- RAISE NOTICE 'already imported, cannot auto-overlay'
14523         RETURN FALSE;
14524     END IF;
14525
14526     SELECT COUNT(*) INTO match_count FROM vandelay.bib_match WHERE queued_record = import_id;
14527
14528     IF match_count <> 1 THEN
14529         -- RAISE NOTICE 'not an exact match';
14530         RETURN FALSE;
14531     END IF;
14532
14533     SELECT  d.* INTO match_attr
14534       FROM  vandelay.bib_attr_definition d
14535             JOIN vandelay.queued_bib_record_attr a ON (a.field = d.id)
14536             JOIN vandelay.bib_match m ON (m.matched_attr = a.id)
14537       WHERE m.queued_record = import_id;
14538
14539     IF NOT (match_attr.xpath ~ '@tag="901"' AND match_attr.xpath ~ '@code="c"') THEN
14540         -- RAISE NOTICE 'not a 901c match: %', match_attr.xpath;
14541         RETURN FALSE;
14542     END IF;
14543
14544     SELECT  m.eg_record INTO eg_id
14545       FROM  vandelay.bib_match m
14546       WHERE m.queued_record = import_id
14547       LIMIT 1;
14548
14549     IF eg_id IS NULL THEN
14550         RETURN FALSE;
14551     END IF;
14552
14553     RETURN vandelay.overlay_bib_record( import_id, eg_id, merge_profile_id );
14554 END;
14555 $$ LANGUAGE PLPGSQL;
14556
14557 CREATE OR REPLACE FUNCTION vandelay.auto_overlay_bib_queue ( queue_id BIGINT, merge_profile_id INT ) RETURNS SETOF BIGINT AS $$
14558 DECLARE
14559     queued_record   vandelay.queued_bib_record%ROWTYPE;
14560 BEGIN
14561
14562     FOR queued_record IN SELECT * FROM vandelay.queued_bib_record WHERE queue = queue_id AND import_time IS NULL LOOP
14563
14564         IF vandelay.auto_overlay_bib_record( queued_record.id, merge_profile_id ) THEN
14565             RETURN NEXT queued_record.id;
14566         END IF;
14567
14568     END LOOP;
14569
14570     RETURN;
14571
14572 END;
14573 $$ LANGUAGE PLPGSQL;
14574
14575 CREATE OR REPLACE FUNCTION vandelay.auto_overlay_bib_queue ( queue_id BIGINT ) RETURNS SETOF BIGINT AS $$
14576     SELECT * FROM vandelay.auto_overlay_bib_queue( $1, NULL );
14577 $$ LANGUAGE SQL;
14578
14579 CREATE OR REPLACE FUNCTION vandelay.overlay_authority_record ( import_id BIGINT, eg_id BIGINT, merge_profile_id INT ) RETURNS BOOL AS $$
14580 DECLARE
14581     merge_profile   vandelay.merge_profile%ROWTYPE;
14582     dyn_profile     vandelay.compile_profile%ROWTYPE;
14583     source_marc     TEXT;
14584     target_marc     TEXT;
14585     eg_marc         TEXT;
14586     v_marc          TEXT;
14587     replace_rule    TEXT;
14588     match_count     INT;
14589 BEGIN
14590
14591     SELECT  b.marc INTO eg_marc
14592       FROM  authority.record_entry b
14593             JOIN vandelay.authority_match m ON (m.eg_record = b.id AND m.queued_record = import_id)
14594       LIMIT 1;
14595
14596     SELECT  q.marc INTO v_marc
14597       FROM  vandelay.queued_record q
14598             JOIN vandelay.authority_match m ON (m.queued_record = q.id AND q.id = import_id)
14599       LIMIT 1;
14600
14601     IF eg_marc IS NULL OR v_marc IS NULL THEN
14602         -- RAISE NOTICE 'no marc for vandelay or authority record';
14603         RETURN FALSE;
14604     END IF;
14605
14606     dyn_profile := vandelay.compile_profile( v_marc );
14607
14608     IF merge_profile_id IS NOT NULL THEN
14609         SELECT * INTO merge_profile FROM vandelay.merge_profile WHERE id = merge_profile_id;
14610         IF FOUND THEN
14611             dyn_profile.add_rule := BTRIM( dyn_profile.add_rule || ',' || COALESCE(merge_profile.add_spec,''), ',');
14612             dyn_profile.strip_rule := BTRIM( dyn_profile.strip_rule || ',' || COALESCE(merge_profile.strip_spec,''), ',');
14613             dyn_profile.replace_rule := BTRIM( dyn_profile.replace_rule || ',' || COALESCE(merge_profile.replace_spec,''), ',');
14614             dyn_profile.preserve_rule := BTRIM( dyn_profile.preserve_rule || ',' || COALESCE(merge_profile.preserve_spec,''), ',');
14615         END IF;
14616     END IF;
14617
14618     IF dyn_profile.replace_rule <> '' AND dyn_profile.preserve_rule <> '' THEN
14619         -- RAISE NOTICE 'both replace [%] and preserve [%] specified', dyn_profile.replace_rule, dyn_profile.preserve_rule;
14620         RETURN FALSE;
14621     END IF;
14622
14623     IF dyn_profile.replace_rule <> '' THEN
14624         source_marc = v_marc;
14625         target_marc = eg_marc;
14626         replace_rule = dyn_profile.replace_rule;
14627     ELSE
14628         source_marc = eg_marc;
14629         target_marc = v_marc;
14630         replace_rule = dyn_profile.preserve_rule;
14631     END IF;
14632
14633     UPDATE  authority.record_entry
14634       SET   marc = vandelay.merge_record_xml( target_marc, source_marc, dyn_profile.add_rule, replace_rule, dyn_profile.strip_rule )
14635       WHERE id = eg_id;
14636
14637     IF FOUND THEN
14638         UPDATE  vandelay.queued_authority_record
14639           SET   imported_as = eg_id,
14640                 import_time = NOW()
14641           WHERE id = import_id;
14642         RETURN TRUE;
14643     END IF;
14644
14645     -- RAISE NOTICE 'update of authority.record_entry failed';
14646
14647     RETURN FALSE;
14648
14649 END;
14650 $$ LANGUAGE PLPGSQL;
14651
14652 CREATE OR REPLACE FUNCTION vandelay.auto_overlay_authority_record ( import_id BIGINT, merge_profile_id INT ) RETURNS BOOL AS $$
14653 DECLARE
14654     eg_id           BIGINT;
14655     match_count     INT;
14656 BEGIN
14657     SELECT COUNT(*) INTO match_count FROM vandelay.authority_match WHERE queued_record = import_id;
14658
14659     IF match_count <> 1 THEN
14660         -- RAISE NOTICE 'not an exact match';
14661         RETURN FALSE;
14662     END IF;
14663
14664     SELECT  m.eg_record INTO eg_id
14665       FROM  vandelay.authority_match m
14666       WHERE m.queued_record = import_id
14667       LIMIT 1;
14668
14669     IF eg_id IS NULL THEN
14670         RETURN FALSE;
14671     END IF;
14672
14673     RETURN vandelay.overlay_authority_record( import_id, eg_id, merge_profile_id );
14674 END;
14675 $$ LANGUAGE PLPGSQL;
14676
14677 CREATE OR REPLACE FUNCTION vandelay.auto_overlay_authority_queue ( queue_id BIGINT, merge_profile_id INT ) RETURNS SETOF BIGINT AS $$
14678 DECLARE
14679     queued_record   vandelay.queued_authority_record%ROWTYPE;
14680 BEGIN
14681
14682     FOR queued_record IN SELECT * FROM vandelay.queued_authority_record WHERE queue = queue_id AND import_time IS NULL LOOP
14683
14684         IF vandelay.auto_overlay_authority_record( queued_record.id, merge_profile_id ) THEN
14685             RETURN NEXT queued_record.id;
14686         END IF;
14687
14688     END LOOP;
14689
14690     RETURN;
14691
14692 END;
14693 $$ LANGUAGE PLPGSQL;
14694
14695 CREATE OR REPLACE FUNCTION vandelay.auto_overlay_authority_queue ( queue_id BIGINT ) RETURNS SETOF BIGINT AS $$
14696     SELECT * FROM vandelay.auto_overlay_authority_queue( $1, NULL );
14697 $$ LANGUAGE SQL;
14698
14699 CREATE TYPE vandelay.tcn_data AS (tcn TEXT, tcn_source TEXT, used BOOL);
14700 CREATE OR REPLACE FUNCTION vandelay.find_bib_tcn_data ( xml TEXT ) RETURNS SETOF vandelay.tcn_data AS $_$
14701 DECLARE
14702     eg_tcn          TEXT;
14703     eg_tcn_source   TEXT;
14704     output          vandelay.tcn_data%ROWTYPE;
14705 BEGIN
14706
14707     -- 001/003
14708     eg_tcn := BTRIM((oils_xpath('//*[@tag="001"]/text()',xml))[1]);
14709     IF eg_tcn IS NOT NULL AND eg_tcn <> '' THEN
14710
14711         eg_tcn_source := BTRIM((oils_xpath('//*[@tag="003"]/text()',xml))[1]);
14712         IF eg_tcn_source IS NULL OR eg_tcn_source = '' THEN
14713             eg_tcn_source := 'System Local';
14714         END IF;
14715
14716         PERFORM id FROM biblio.record_entry WHERE tcn_value = eg_tcn  AND NOT deleted;
14717
14718         IF NOT FOUND THEN
14719             output.used := FALSE;
14720         ELSE
14721             output.used := TRUE;
14722         END IF;
14723
14724         output.tcn := eg_tcn;
14725         output.tcn_source := eg_tcn_source;
14726         RETURN NEXT output;
14727
14728     END IF;
14729
14730     -- 901 ab
14731     eg_tcn := BTRIM((oils_xpath('//*[@tag="901"]/*[@code="a"]/text()',xml))[1]);
14732     IF eg_tcn IS NOT NULL AND eg_tcn <> '' THEN
14733
14734         eg_tcn_source := BTRIM((oils_xpath('//*[@tag="901"]/*[@code="b"]/text()',xml))[1]);
14735         IF eg_tcn_source IS NULL OR eg_tcn_source = '' THEN
14736             eg_tcn_source := 'System Local';
14737         END IF;
14738
14739         PERFORM id FROM biblio.record_entry WHERE tcn_value = eg_tcn  AND NOT deleted;
14740
14741         IF NOT FOUND THEN
14742             output.used := FALSE;
14743         ELSE
14744             output.used := TRUE;
14745         END IF;
14746
14747         output.tcn := eg_tcn;
14748         output.tcn_source := eg_tcn_source;
14749         RETURN NEXT output;
14750
14751     END IF;
14752
14753     -- 039 ab
14754     eg_tcn := BTRIM((oils_xpath('//*[@tag="039"]/*[@code="a"]/text()',xml))[1]);
14755     IF eg_tcn IS NOT NULL AND eg_tcn <> '' THEN
14756
14757         eg_tcn_source := BTRIM((oils_xpath('//*[@tag="039"]/*[@code="b"]/text()',xml))[1]);
14758         IF eg_tcn_source IS NULL OR eg_tcn_source = '' THEN
14759             eg_tcn_source := 'System Local';
14760         END IF;
14761
14762         PERFORM id FROM biblio.record_entry WHERE tcn_value = eg_tcn  AND NOT deleted;
14763
14764         IF NOT FOUND THEN
14765             output.used := FALSE;
14766         ELSE
14767             output.used := TRUE;
14768         END IF;
14769
14770         output.tcn := eg_tcn;
14771         output.tcn_source := eg_tcn_source;
14772         RETURN NEXT output;
14773
14774     END IF;
14775
14776     -- 020 a
14777     eg_tcn := REGEXP_REPLACE((oils_xpath('//*[@tag="020"]/*[@code="a"]/text()',xml))[1], $re$^(\w+).*?$$re$, $re$\1$re$);
14778     IF eg_tcn IS NOT NULL AND eg_tcn <> '' THEN
14779
14780         eg_tcn_source := 'ISBN';
14781
14782         PERFORM id FROM biblio.record_entry WHERE tcn_value = eg_tcn  AND NOT deleted;
14783
14784         IF NOT FOUND THEN
14785             output.used := FALSE;
14786         ELSE
14787             output.used := TRUE;
14788         END IF;
14789
14790         output.tcn := eg_tcn;
14791         output.tcn_source := eg_tcn_source;
14792         RETURN NEXT output;
14793
14794     END IF;
14795
14796     -- 022 a
14797     eg_tcn := REGEXP_REPLACE((oils_xpath('//*[@tag="022"]/*[@code="a"]/text()',xml))[1], $re$^(\w+).*?$$re$, $re$\1$re$);
14798     IF eg_tcn IS NOT NULL AND eg_tcn <> '' THEN
14799
14800         eg_tcn_source := 'ISSN';
14801
14802         PERFORM id FROM biblio.record_entry WHERE tcn_value = eg_tcn  AND NOT deleted;
14803
14804         IF NOT FOUND THEN
14805             output.used := FALSE;
14806         ELSE
14807             output.used := TRUE;
14808         END IF;
14809
14810         output.tcn := eg_tcn;
14811         output.tcn_source := eg_tcn_source;
14812         RETURN NEXT output;
14813
14814     END IF;
14815
14816     -- 010 a
14817     eg_tcn := REGEXP_REPLACE((oils_xpath('//*[@tag="010"]/*[@code="a"]/text()',xml))[1], $re$^(\w+).*?$$re$, $re$\1$re$);
14818     IF eg_tcn IS NOT NULL AND eg_tcn <> '' THEN
14819
14820         eg_tcn_source := 'LCCN';
14821
14822         PERFORM id FROM biblio.record_entry WHERE tcn_value = eg_tcn  AND NOT deleted;
14823
14824         IF NOT FOUND THEN
14825             output.used := FALSE;
14826         ELSE
14827             output.used := TRUE;
14828         END IF;
14829
14830         output.tcn := eg_tcn;
14831         output.tcn_source := eg_tcn_source;
14832         RETURN NEXT output;
14833
14834     END IF;
14835
14836     -- 035 a
14837     eg_tcn := REGEXP_REPLACE((oils_xpath('//*[@tag="035"]/*[@code="a"]/text()',xml))[1], $re$^.*?(\w+)$$re$, $re$\1$re$);
14838     IF eg_tcn IS NOT NULL AND eg_tcn <> '' THEN
14839
14840         eg_tcn_source := 'System Legacy';
14841
14842         PERFORM id FROM biblio.record_entry WHERE tcn_value = eg_tcn  AND NOT deleted;
14843
14844         IF NOT FOUND THEN
14845             output.used := FALSE;
14846         ELSE
14847             output.used := TRUE;
14848         END IF;
14849
14850         output.tcn := eg_tcn;
14851         output.tcn_source := eg_tcn_source;
14852         RETURN NEXT output;
14853
14854     END IF;
14855
14856     RETURN;
14857 END;
14858 $_$ LANGUAGE PLPGSQL;
14859
14860 CREATE INDEX claim_lid_idx ON acq.claim( lineitem_detail );
14861
14862 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);
14863
14864 CREATE INDEX metabib_title_field_entry_value_idx ON metabib.title_field_entry (SUBSTRING(value,1,1024)) WHERE index_vector = ''::TSVECTOR;
14865 CREATE INDEX metabib_author_field_entry_value_idx ON metabib.author_field_entry (SUBSTRING(value,1,1024)) WHERE index_vector = ''::TSVECTOR;
14866 CREATE INDEX metabib_subject_field_entry_value_idx ON metabib.subject_field_entry (SUBSTRING(value,1,1024)) WHERE index_vector = ''::TSVECTOR;
14867 CREATE INDEX metabib_keyword_field_entry_value_idx ON metabib.keyword_field_entry (SUBSTRING(value,1,1024)) WHERE index_vector = ''::TSVECTOR;
14868 CREATE INDEX metabib_series_field_entry_value_idx ON metabib.series_field_entry (SUBSTRING(value,1,1024)) WHERE index_vector = ''::TSVECTOR;
14869
14870 CREATE INDEX metabib_author_field_entry_source_idx ON metabib.author_field_entry (source);
14871 CREATE INDEX metabib_keyword_field_entry_source_idx ON metabib.keyword_field_entry (source);
14872 CREATE INDEX metabib_title_field_entry_source_idx ON metabib.title_field_entry (source);
14873 CREATE INDEX metabib_series_field_entry_source_idx ON metabib.series_field_entry (source);
14874
14875 ALTER TABLE metabib.series_field_entry
14876         ADD CONSTRAINT metabib_series_field_entry_source_pkey FOREIGN KEY (source)
14877                 REFERENCES biblio.record_entry (id)
14878                 ON DELETE CASCADE
14879                 DEFERRABLE INITIALLY DEFERRED;
14880
14881 ALTER TABLE metabib.series_field_entry
14882         ADD CONSTRAINT metabib_series_field_entry_field_pkey FOREIGN KEY (field)
14883                 REFERENCES config.metabib_field (id)
14884                 ON DELETE CASCADE
14885                 DEFERRABLE INITIALLY DEFERRED;
14886
14887 CREATE TABLE acq.claim_policy_action (
14888         id              SERIAL       PRIMARY KEY,
14889         claim_policy    INT          NOT NULL REFERENCES acq.claim_policy
14890                                  ON DELETE CASCADE
14891                                      DEFERRABLE INITIALLY DEFERRED,
14892         action_interval INTERVAL     NOT NULL,
14893         action          INT          NOT NULL REFERENCES acq.claim_event_type
14894                                      DEFERRABLE INITIALLY DEFERRED,
14895         CONSTRAINT action_sequence UNIQUE (claim_policy, action_interval)
14896 );
14897
14898 CREATE OR REPLACE FUNCTION public.ingest_acq_marc ( ) RETURNS TRIGGER AS $function$
14899 DECLARE
14900     value       TEXT; 
14901     atype       TEXT; 
14902     prov        INT;
14903     pos         INT;
14904     adef        RECORD;
14905     xpath_string    TEXT;
14906 BEGIN
14907     FOR adef IN SELECT *,tableoid FROM acq.lineitem_attr_definition LOOP
14908     
14909         SELECT relname::TEXT INTO atype FROM pg_class WHERE oid = adef.tableoid;
14910       
14911         IF (atype NOT IN ('lineitem_usr_attr_definition','lineitem_local_attr_definition')) THEN
14912             IF (atype = 'lineitem_provider_attr_definition') THEN
14913                 SELECT provider INTO prov FROM acq.lineitem_provider_attr_definition WHERE id = adef.id;
14914                 CONTINUE WHEN NEW.provider IS NULL OR prov <> NEW.provider;
14915             END IF;
14916             
14917             IF (atype = 'lineitem_provider_attr_definition') THEN
14918                 SELECT xpath INTO xpath_string FROM acq.lineitem_provider_attr_definition WHERE id = adef.id;
14919             ELSIF (atype = 'lineitem_marc_attr_definition') THEN
14920                 SELECT xpath INTO xpath_string FROM acq.lineitem_marc_attr_definition WHERE id = adef.id;
14921             ELSIF (atype = 'lineitem_generated_attr_definition') THEN
14922                 SELECT xpath INTO xpath_string FROM acq.lineitem_generated_attr_definition WHERE id = adef.id;
14923             END IF;
14924       
14925             xpath_string := REGEXP_REPLACE(xpath_string,$re$//?text\(\)$$re$,'');
14926
14927             IF (adef.code = 'title' OR adef.code = 'author') THEN
14928                 -- title and author should not be split
14929                 -- FIXME: once oils_xpath can grok XPATH 2.0 functions, we can use
14930                 -- string-join in the xpath and remove this special case
14931                 SELECT extract_acq_marc_field(id, xpath_string, adef.remove) INTO value FROM acq.lineitem WHERE id = NEW.id;
14932                 IF (value IS NOT NULL AND value <> '') THEN
14933                     INSERT INTO acq.lineitem_attr (lineitem, definition, attr_type, attr_name, attr_value)
14934                         VALUES (NEW.id, adef.id, atype, adef.code, value);
14935                 END IF;
14936             ELSE
14937                 pos := 1;
14938
14939                 LOOP
14940                     SELECT extract_acq_marc_field(id, xpath_string || '[' || pos || ']', adef.remove) INTO value FROM acq.lineitem WHERE id = NEW.id;
14941       
14942                     IF (value IS NOT NULL AND value <> '') THEN
14943                         INSERT INTO acq.lineitem_attr (lineitem, definition, attr_type, attr_name, attr_value)
14944                             VALUES (NEW.id, adef.id, atype, adef.code, value);
14945                     ELSE
14946                         EXIT;
14947                     END IF;
14948
14949                     pos := pos + 1;
14950                 END LOOP;
14951             END IF;
14952
14953         END IF;
14954
14955     END LOOP;
14956
14957     RETURN NULL;
14958 END;
14959 $function$ LANGUAGE PLPGSQL;
14960
14961 UPDATE config.metabib_field SET label = name;
14962 ALTER TABLE config.metabib_field ALTER COLUMN label SET NOT NULL;
14963
14964 ALTER TABLE config.metabib_field ADD CONSTRAINT metabib_field_field_class_fkey
14965          FOREIGN KEY (field_class) REFERENCES config.metabib_class (name);
14966
14967 ALTER TABLE config.metabib_field DROP CONSTRAINT metabib_field_field_class_check;
14968
14969 ALTER TABLE config.metabib_field ADD CONSTRAINT metabib_field_format_fkey FOREIGN KEY (format) REFERENCES config.xml_transform (name);
14970
14971 CREATE TABLE config.metabib_search_alias (
14972     alias       TEXT    PRIMARY KEY,
14973     field_class TEXT    NOT NULL REFERENCES config.metabib_class (name),
14974     field       INT     REFERENCES config.metabib_field (id)
14975 );
14976
14977 INSERT INTO config.metabib_search_alias (alias,field_class) VALUES ('kw','keyword');
14978 INSERT INTO config.metabib_search_alias (alias,field_class) VALUES ('eg.keyword','keyword');
14979 INSERT INTO config.metabib_search_alias (alias,field_class) VALUES ('dc.publisher','keyword');
14980 INSERT INTO config.metabib_search_alias (alias,field_class) VALUES ('dc.identifier','keyword');
14981 INSERT INTO config.metabib_search_alias (alias,field_class) VALUES ('bib.subjecttitle','keyword');
14982 INSERT INTO config.metabib_search_alias (alias,field_class) VALUES ('bib.genre','keyword');
14983 INSERT INTO config.metabib_search_alias (alias,field_class) VALUES ('bib.edition','keyword');
14984 INSERT INTO config.metabib_search_alias (alias,field_class) VALUES ('srw.serverchoice','keyword');
14985
14986 INSERT INTO config.metabib_search_alias (alias,field_class) VALUES ('au','author');
14987 INSERT INTO config.metabib_search_alias (alias,field_class) VALUES ('name','author');
14988 INSERT INTO config.metabib_search_alias (alias,field_class) VALUES ('creator','author');
14989 INSERT INTO config.metabib_search_alias (alias,field_class) VALUES ('eg.author','author');
14990 INSERT INTO config.metabib_search_alias (alias,field_class) VALUES ('eg.name','author');
14991 INSERT INTO config.metabib_search_alias (alias,field_class) VALUES ('dc.creator','author');
14992 INSERT INTO config.metabib_search_alias (alias,field_class) VALUES ('dc.contributor','author');
14993 INSERT INTO config.metabib_search_alias (alias,field_class) VALUES ('bib.name','author');
14994 INSERT INTO config.metabib_search_alias (alias,field_class,field) VALUES ('bib.namepersonal','author',8);
14995 INSERT INTO config.metabib_search_alias (alias,field_class,field) VALUES ('bib.namepersonalfamily','author',8);
14996 INSERT INTO config.metabib_search_alias (alias,field_class,field) VALUES ('bib.namepersonalgiven','author',8);
14997 INSERT INTO config.metabib_search_alias (alias,field_class,field) VALUES ('bib.namecorporate','author',7);
14998 INSERT INTO config.metabib_search_alias (alias,field_class,field) VALUES ('bib.nameconference','author',9);
14999
15000 INSERT INTO config.metabib_search_alias (alias,field_class) VALUES ('ti','title');
15001 INSERT INTO config.metabib_search_alias (alias,field_class) VALUES ('eg.title','title');
15002 INSERT INTO config.metabib_search_alias (alias,field_class) VALUES ('dc.title','title');
15003 INSERT INTO config.metabib_search_alias (alias,field_class,field) VALUES ('bib.titleabbreviated','title',2);
15004 INSERT INTO config.metabib_search_alias (alias,field_class,field) VALUES ('bib.titleuniform','title',5);
15005 INSERT INTO config.metabib_search_alias (alias,field_class,field) VALUES ('bib.titletranslated','title',3);
15006 INSERT INTO config.metabib_search_alias (alias,field_class,field) VALUES ('bib.titlealternative','title',4);
15007 INSERT INTO config.metabib_search_alias (alias,field_class,field) VALUES ('bib.title','title',2);
15008
15009 INSERT INTO config.metabib_search_alias (alias,field_class) VALUES ('su','subject');
15010 INSERT INTO config.metabib_search_alias (alias,field_class) VALUES ('eg.subject','subject');
15011 INSERT INTO config.metabib_search_alias (alias,field_class) VALUES ('dc.subject','subject');
15012 INSERT INTO config.metabib_search_alias (alias,field_class,field) VALUES ('bib.subjectplace','subject',11);
15013 INSERT INTO config.metabib_search_alias (alias,field_class,field) VALUES ('bib.subjectname','subject',12);
15014 INSERT INTO config.metabib_search_alias (alias,field_class,field) VALUES ('bib.subjectoccupation','subject',16);
15015
15016 INSERT INTO config.metabib_search_alias (alias,field_class) VALUES ('se','series');
15017 INSERT INTO config.metabib_search_alias (alias,field_class) VALUES ('eg.series','series');
15018 INSERT INTO config.metabib_search_alias (alias,field_class,field) VALUES ('bib.titleseries','series',1);
15019
15020 UPDATE config.metabib_field SET facet_field=TRUE WHERE id = 1;
15021 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;
15022 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;
15023 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;
15024 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;
15025
15026 UPDATE config.metabib_field SET facet_field=TRUE WHERE id = 11;
15027 UPDATE config.metabib_field SET facet_field=TRUE , facet_xpath=$$*[local-name()='namePart']$$ WHERE id = 12;
15028 UPDATE config.metabib_field SET facet_field=TRUE WHERE id = 13;
15029 UPDATE config.metabib_field SET facet_field=TRUE WHERE id = 14;
15030
15031 CREATE INDEX metabib_rec_descriptor_item_type_idx ON metabib.rec_descriptor (item_type);
15032 CREATE INDEX metabib_rec_descriptor_item_form_idx ON metabib.rec_descriptor (item_form);
15033 CREATE INDEX metabib_rec_descriptor_bib_level_idx ON metabib.rec_descriptor (bib_level);
15034 CREATE INDEX metabib_rec_descriptor_control_type_idx ON metabib.rec_descriptor (control_type);
15035 CREATE INDEX metabib_rec_descriptor_char_encoding_idx ON metabib.rec_descriptor (char_encoding);
15036 CREATE INDEX metabib_rec_descriptor_enc_level_idx ON metabib.rec_descriptor (enc_level);
15037 CREATE INDEX metabib_rec_descriptor_audience_idx ON metabib.rec_descriptor (audience);
15038 CREATE INDEX metabib_rec_descriptor_lit_form_idx ON metabib.rec_descriptor (lit_form);
15039 CREATE INDEX metabib_rec_descriptor_cat_form_idx ON metabib.rec_descriptor (cat_form);
15040 CREATE INDEX metabib_rec_descriptor_pub_status_idx ON metabib.rec_descriptor (pub_status);
15041 CREATE INDEX metabib_rec_descriptor_item_lang_idx ON metabib.rec_descriptor (item_lang);
15042 CREATE INDEX metabib_rec_descriptor_vr_format_idx ON metabib.rec_descriptor (vr_format);
15043 CREATE INDEX metabib_rec_descriptor_date1_idx ON metabib.rec_descriptor (date1);
15044 CREATE INDEX metabib_rec_descriptor_dates_idx ON metabib.rec_descriptor (date1,date2);
15045
15046 CREATE TABLE asset.opac_visible_copies (
15047   id        BIGINT primary key, -- copy id
15048   record    BIGINT,
15049   circ_lib  INTEGER
15050 );
15051 COMMENT ON TABLE asset.opac_visible_copies IS $$
15052 Materialized view of copies that are visible in the OPAC, used by
15053 search.query_parser_fts() to speed up OPAC visibility checks on large
15054 databases.  Contents are maintained by a set of triggers.
15055 $$;
15056 CREATE INDEX opac_visible_copies_idx1 on asset.opac_visible_copies (record, circ_lib);
15057
15058 CREATE OR REPLACE FUNCTION search.query_parser_fts (
15059
15060     param_search_ou INT,
15061     param_depth     INT,
15062     param_query     TEXT,
15063     param_statuses  INT[],
15064     param_locations INT[],
15065     param_offset    INT,
15066     param_check     INT,
15067     param_limit     INT,
15068     metarecord      BOOL,
15069     staff           BOOL
15070  
15071 ) RETURNS SETOF search.search_result AS $func$
15072 DECLARE
15073
15074     current_res         search.search_result%ROWTYPE;
15075     search_org_list     INT[];
15076
15077     check_limit         INT;
15078     core_limit          INT;
15079     core_offset         INT;
15080     tmp_int             INT;
15081
15082     core_result         RECORD;
15083     core_cursor         REFCURSOR;
15084     core_rel_query      TEXT;
15085
15086     total_count         INT := 0;
15087     check_count         INT := 0;
15088     deleted_count       INT := 0;
15089     visible_count       INT := 0;
15090     excluded_count      INT := 0;
15091
15092 BEGIN
15093
15094     check_limit := COALESCE( param_check, 1000 );
15095     core_limit  := COALESCE( param_limit, 25000 );
15096     core_offset := COALESCE( param_offset, 0 );
15097
15098     -- core_skip_chk := COALESCE( param_skip_chk, 1 );
15099
15100     IF param_search_ou > 0 THEN
15101         IF param_depth IS NOT NULL THEN
15102             SELECT array_accum(distinct id) INTO search_org_list FROM actor.org_unit_descendants( param_search_ou, param_depth );
15103         ELSE
15104             SELECT array_accum(distinct id) INTO search_org_list FROM actor.org_unit_descendants( param_search_ou );
15105         END IF;
15106     ELSIF param_search_ou < 0 THEN
15107         SELECT array_accum(distinct org_unit) INTO search_org_list FROM actor.org_lasso_map WHERE lasso = -param_search_ou;
15108     ELSIF param_search_ou = 0 THEN
15109         -- reserved for user lassos (ou_buckets/type='lasso') with ID passed in depth ... hack? sure.
15110     END IF;
15111
15112     OPEN core_cursor FOR EXECUTE param_query;
15113
15114     LOOP
15115
15116         FETCH core_cursor INTO core_result;
15117         EXIT WHEN NOT FOUND;
15118         EXIT WHEN total_count >= core_limit;
15119
15120         total_count := total_count + 1;
15121
15122         CONTINUE WHEN total_count NOT BETWEEN  core_offset + 1 AND check_limit + core_offset;
15123
15124         check_count := check_count + 1;
15125
15126         PERFORM 1 FROM biblio.record_entry b WHERE NOT b.deleted AND b.id IN ( SELECT * FROM search.explode_array( core_result.records ) );
15127         IF NOT FOUND THEN
15128             -- RAISE NOTICE ' % were all deleted ... ', core_result.records;
15129             deleted_count := deleted_count + 1;
15130             CONTINUE;
15131         END IF;
15132
15133         PERFORM 1
15134           FROM  biblio.record_entry b
15135                 JOIN config.bib_source s ON (b.source = s.id)
15136           WHERE s.transcendant
15137                 AND b.id IN ( SELECT * FROM search.explode_array( core_result.records ) );
15138
15139         IF FOUND THEN
15140             -- RAISE NOTICE ' % were all transcendant ... ', core_result.records;
15141             visible_count := visible_count + 1;
15142
15143             current_res.id = core_result.id;
15144             current_res.rel = core_result.rel;
15145
15146             tmp_int := 1;
15147             IF metarecord THEN
15148                 SELECT COUNT(DISTINCT s.source) INTO tmp_int FROM metabib.metarecord_source_map s WHERE s.metarecord = core_result.id;
15149             END IF;
15150
15151             IF tmp_int = 1 THEN
15152                 current_res.record = core_result.records[1];
15153             ELSE
15154                 current_res.record = NULL;
15155             END IF;
15156
15157             RETURN NEXT current_res;
15158
15159             CONTINUE;
15160         END IF;
15161
15162         PERFORM 1
15163           FROM  asset.call_number cn
15164                 JOIN asset.uri_call_number_map map ON (map.call_number = cn.id)
15165                 JOIN asset.uri uri ON (map.uri = uri.id)
15166           WHERE NOT cn.deleted
15167                 AND cn.label = '##URI##'
15168                 AND uri.active
15169                 AND ( param_locations IS NULL OR array_upper(param_locations, 1) IS NULL )
15170                 AND cn.record IN ( SELECT * FROM search.explode_array( core_result.records ) )
15171                 AND cn.owning_lib IN ( SELECT * FROM search.explode_array( search_org_list ) )
15172           LIMIT 1;
15173
15174         IF FOUND THEN
15175             -- RAISE NOTICE ' % have at least one URI ... ', core_result.records;
15176             visible_count := visible_count + 1;
15177
15178             current_res.id = core_result.id;
15179             current_res.rel = core_result.rel;
15180
15181             tmp_int := 1;
15182             IF metarecord THEN
15183                 SELECT COUNT(DISTINCT s.source) INTO tmp_int FROM metabib.metarecord_source_map s WHERE s.metarecord = core_result.id;
15184             END IF;
15185
15186             IF tmp_int = 1 THEN
15187                 current_res.record = core_result.records[1];
15188             ELSE
15189                 current_res.record = NULL;
15190             END IF;
15191
15192             RETURN NEXT current_res;
15193
15194             CONTINUE;
15195         END IF;
15196
15197         IF param_statuses IS NOT NULL AND array_upper(param_statuses, 1) > 0 THEN
15198
15199             PERFORM 1
15200               FROM  asset.call_number cn
15201                     JOIN asset.copy cp ON (cp.call_number = cn.id)
15202               WHERE NOT cn.deleted
15203                     AND NOT cp.deleted
15204                     AND cp.status IN ( SELECT * FROM search.explode_array( param_statuses ) )
15205                     AND cn.record IN ( SELECT * FROM search.explode_array( core_result.records ) )
15206                     AND cp.circ_lib IN ( SELECT * FROM search.explode_array( search_org_list ) )
15207               LIMIT 1;
15208
15209             IF NOT FOUND THEN
15210                 -- RAISE NOTICE ' % were all status-excluded ... ', core_result.records;
15211                 excluded_count := excluded_count + 1;
15212                 CONTINUE;
15213             END IF;
15214
15215         END IF;
15216
15217         IF param_locations IS NOT NULL AND array_upper(param_locations, 1) > 0 THEN
15218
15219             PERFORM 1
15220               FROM  asset.call_number cn
15221                     JOIN asset.copy cp ON (cp.call_number = cn.id)
15222               WHERE NOT cn.deleted
15223                     AND NOT cp.deleted
15224                     AND cp.location IN ( SELECT * FROM search.explode_array( param_locations ) )
15225                     AND cn.record IN ( SELECT * FROM search.explode_array( core_result.records ) )
15226                     AND cp.circ_lib IN ( SELECT * FROM search.explode_array( search_org_list ) )
15227               LIMIT 1;
15228
15229             IF NOT FOUND THEN
15230                 -- RAISE NOTICE ' % were all copy_location-excluded ... ', core_result.records;
15231                 excluded_count := excluded_count + 1;
15232                 CONTINUE;
15233             END IF;
15234
15235         END IF;
15236
15237         IF staff IS NULL OR NOT staff THEN
15238
15239             PERFORM 1
15240               FROM  asset.opac_visible_copies
15241               WHERE circ_lib IN ( SELECT * FROM search.explode_array( search_org_list ) )
15242                     AND record IN ( SELECT * FROM search.explode_array( core_result.records ) )
15243               LIMIT 1;
15244
15245             IF NOT FOUND THEN
15246                 -- RAISE NOTICE ' % were all visibility-excluded ... ', core_result.records;
15247                 excluded_count := excluded_count + 1;
15248                 CONTINUE;
15249             END IF;
15250
15251         ELSE
15252
15253             PERFORM 1
15254               FROM  asset.call_number cn
15255                     JOIN asset.copy cp ON (cp.call_number = cn.id)
15256                     JOIN actor.org_unit a ON (cp.circ_lib = a.id)
15257               WHERE NOT cn.deleted
15258                     AND NOT cp.deleted
15259                     AND cp.circ_lib IN ( SELECT * FROM search.explode_array( search_org_list ) )
15260                     AND cn.record IN ( SELECT * FROM search.explode_array( core_result.records ) )
15261               LIMIT 1;
15262
15263             IF NOT FOUND THEN
15264
15265                 PERFORM 1
15266                   FROM  asset.call_number cn
15267                   WHERE cn.record IN ( SELECT * FROM search.explode_array( core_result.records ) )
15268                   LIMIT 1;
15269
15270                 IF FOUND THEN
15271                     -- RAISE NOTICE ' % were all visibility-excluded ... ', core_result.records;
15272                     excluded_count := excluded_count + 1;
15273                     CONTINUE;
15274                 END IF;
15275
15276             END IF;
15277
15278         END IF;
15279
15280         visible_count := visible_count + 1;
15281
15282         current_res.id = core_result.id;
15283         current_res.rel = core_result.rel;
15284
15285         tmp_int := 1;
15286         IF metarecord THEN
15287             SELECT COUNT(DISTINCT s.source) INTO tmp_int FROM metabib.metarecord_source_map s WHERE s.metarecord = core_result.id;
15288         END IF;
15289
15290         IF tmp_int = 1 THEN
15291             current_res.record = core_result.records[1];
15292         ELSE
15293             current_res.record = NULL;
15294         END IF;
15295
15296         RETURN NEXT current_res;
15297
15298         IF visible_count % 1000 = 0 THEN
15299             -- RAISE NOTICE ' % visible so far ... ', visible_count;
15300         END IF;
15301
15302     END LOOP;
15303
15304     current_res.id = NULL;
15305     current_res.rel = NULL;
15306     current_res.record = NULL;
15307     current_res.total = total_count;
15308     current_res.checked = check_count;
15309     current_res.deleted = deleted_count;
15310     current_res.visible = visible_count;
15311     current_res.excluded = excluded_count;
15312
15313     CLOSE core_cursor;
15314
15315     RETURN NEXT current_res;
15316
15317 END;
15318 $func$ LANGUAGE PLPGSQL;
15319
15320 ALTER TABLE biblio.record_entry ADD COLUMN owner INT;
15321 ALTER TABLE biblio.record_entry
15322          ADD CONSTRAINT biblio_record_entry_owner_fkey FOREIGN KEY (owner)
15323          REFERENCES actor.org_unit (id)
15324          DEFERRABLE INITIALLY DEFERRED;
15325
15326 ALTER TABLE biblio.record_entry ADD COLUMN share_depth INT;
15327
15328 ALTER TABLE auditor.biblio_record_entry_history ADD COLUMN owner INT;
15329 ALTER TABLE auditor.biblio_record_entry_history ADD COLUMN share_depth INT;
15330
15331 DROP VIEW auditor.biblio_record_entry_lifecycle;
15332
15333 SELECT auditor.create_auditor_lifecycle( 'biblio', 'record_entry' );
15334
15335 CREATE OR REPLACE FUNCTION public.first_word ( TEXT ) RETURNS TEXT AS $$
15336         SELECT COALESCE(SUBSTRING( $1 FROM $_$^\S+$_$), '');
15337 $$ LANGUAGE SQL STRICT IMMUTABLE;
15338
15339 CREATE OR REPLACE FUNCTION public.normalize_space( TEXT ) RETURNS TEXT AS $$
15340     SELECT regexp_replace(regexp_replace(regexp_replace($1, E'\\n', ' ', 'g'), E'(?:^\\s+)|(\\s+$)', '', 'g'), E'\\s+', ' ', 'g');
15341 $$ LANGUAGE SQL STRICT IMMUTABLE;
15342
15343 CREATE OR REPLACE FUNCTION public.lowercase( TEXT ) RETURNS TEXT AS $$
15344     return lc(shift);
15345 $$ LANGUAGE PLPERLU STRICT IMMUTABLE;
15346
15347 CREATE OR REPLACE FUNCTION public.uppercase( TEXT ) RETURNS TEXT AS $$
15348     return uc(shift);
15349 $$ LANGUAGE PLPERLU STRICT IMMUTABLE;
15350
15351 CREATE OR REPLACE FUNCTION public.remove_diacritics( TEXT ) RETURNS TEXT AS $$
15352     use Unicode::Normalize;
15353
15354     my $x = NFD(shift);
15355     $x =~ s/\pM+//go;
15356     return $x;
15357
15358 $$ LANGUAGE PLPERLU STRICT IMMUTABLE;
15359
15360 CREATE OR REPLACE FUNCTION public.entityize( TEXT ) RETURNS TEXT AS $$
15361     use Unicode::Normalize;
15362
15363     my $x = NFC(shift);
15364     $x =~ s/([\x{0080}-\x{fffd}])/sprintf('&#x%X;',ord($1))/sgoe;
15365     return $x;
15366
15367 $$ LANGUAGE PLPERLU STRICT IMMUTABLE;
15368
15369 CREATE OR REPLACE FUNCTION actor.org_unit_ancestor_setting( setting_name TEXT, org_id INT ) RETURNS SETOF actor.org_unit_setting AS $$
15370 DECLARE
15371     setting RECORD;
15372     cur_org INT;
15373 BEGIN
15374     cur_org := org_id;
15375     LOOP
15376         SELECT INTO setting * FROM actor.org_unit_setting WHERE org_unit = cur_org AND name = setting_name;
15377         IF FOUND THEN
15378             RETURN NEXT setting;
15379         END IF;
15380         SELECT INTO cur_org parent_ou FROM actor.org_unit WHERE id = cur_org;
15381         EXIT WHEN cur_org IS NULL;
15382     END LOOP;
15383     RETURN;
15384 END;
15385 $$ LANGUAGE plpgsql STABLE;
15386
15387 CREATE OR REPLACE FUNCTION acq.extract_holding_attr_table (lineitem int, tag text) RETURNS SETOF acq.flat_lineitem_holding_subfield AS $$
15388 DECLARE
15389     counter INT;
15390     lida    acq.flat_lineitem_holding_subfield%ROWTYPE;
15391 BEGIN
15392
15393     SELECT  COUNT(*) INTO counter
15394       FROM  oils_xpath_table(
15395                 'id',
15396                 'marc',
15397                 'acq.lineitem',
15398                 '//*[@tag="' || tag || '"]',
15399                 'id=' || lineitem
15400             ) as t(i int,c text);
15401
15402     FOR i IN 1 .. counter LOOP
15403         FOR lida IN
15404             SELECT  *
15405               FROM  (   SELECT  id,i,t,v
15406                           FROM  oils_xpath_table(
15407                                     'id',
15408                                     'marc',
15409                                     'acq.lineitem',
15410                                     '//*[@tag="' || tag || '"][position()=' || i || ']/*/@code|' ||
15411                                         '//*[@tag="' || tag || '"][position()=' || i || ']/*[@code]',
15412                                     'id=' || lineitem
15413                                 ) as t(id int,t text,v text)
15414                     )x
15415         LOOP
15416             RETURN NEXT lida;
15417         END LOOP;
15418     END LOOP;
15419
15420     RETURN;
15421 END;
15422 $$ LANGUAGE PLPGSQL;
15423
15424 CREATE OR REPLACE FUNCTION oils_i18n_xlate ( keytable TEXT, keyclass TEXT, keycol TEXT, identcol TEXT, keyvalue TEXT, raw_locale TEXT ) RETURNS TEXT AS $func$
15425 DECLARE
15426     locale      TEXT := REGEXP_REPLACE( REGEXP_REPLACE( raw_locale, E'[;, ].+$', '' ), E'_', '-', 'g' );
15427     language    TEXT := REGEXP_REPLACE( locale, E'-.+$', '' );
15428     result      config.i18n_core%ROWTYPE;
15429     fallback    TEXT;
15430     keyfield    TEXT := keyclass || '.' || keycol;
15431 BEGIN
15432
15433     -- Try the full locale
15434     SELECT  * INTO result
15435       FROM  config.i18n_core
15436       WHERE fq_field = keyfield
15437             AND identity_value = keyvalue
15438             AND translation = locale;
15439
15440     -- Try just the language
15441     IF NOT FOUND THEN
15442         SELECT  * INTO result
15443           FROM  config.i18n_core
15444           WHERE fq_field = keyfield
15445                 AND identity_value = keyvalue
15446                 AND translation = language;
15447     END IF;
15448
15449     -- Fall back to the string we passed in in the first place
15450     IF NOT FOUND THEN
15451     EXECUTE
15452             'SELECT ' ||
15453                 keycol ||
15454             ' FROM ' || keytable ||
15455             ' WHERE ' || identcol || ' = ' || quote_literal(keyvalue)
15456                 INTO fallback;
15457         RETURN fallback;
15458     END IF;
15459
15460     RETURN result.string;
15461 END;
15462 $func$ LANGUAGE PLPGSQL STABLE;
15463
15464 SELECT auditor.create_auditor ( 'acq', 'invoice' );
15465
15466 SELECT auditor.create_auditor ( 'acq', 'invoice_item' );
15467
15468 SELECT auditor.create_auditor ( 'acq', 'invoice_entry' );
15469
15470 INSERT INTO acq.cancel_reason ( id, org_unit, label, description, keep_debits ) VALUES (
15471     3, 1, 'delivered_but_lost',
15472     oils_i18n_gettext( 2, 'Delivered but not received; presumed lost', 'acqcr', 'label' ), TRUE );
15473
15474 CREATE TABLE config.global_flag (
15475     label   TEXT    NOT NULL
15476 ) INHERITS (config.internal_flag);
15477 ALTER TABLE config.global_flag ADD PRIMARY KEY (name);
15478
15479 INSERT INTO config.global_flag (name, label, enabled)
15480     VALUES (
15481         'cat.bib.use_id_for_tcn',
15482         oils_i18n_gettext(
15483             'cat.bib.use_id_for_tcn',
15484             'Cat: Use Internal ID for TCN Value',
15485             'cgf', 
15486             'label'
15487         ),
15488         TRUE
15489     );
15490
15491 -- resolves performance issue noted by EG Indiana
15492
15493 CREATE INDEX scecm_owning_copy_idx ON asset.stat_cat_entry_copy_map(owning_copy);
15494
15495 INSERT INTO config.metabib_class ( name, label ) VALUES ( 'identifier', oils_i18n_gettext('identifier', 'Identifier', 'cmc', 'name') );
15496
15497 INSERT INTO config.metabib_field ( id, field_class, name, label, format, xpath, facet_field ) VALUES
15498     (17, 'identifier', 'accession', oils_i18n_gettext(17, 'Accession Number', 'cmf', 'label'), 'marcxml', $$//marcxml:datafield[tag="001"]/text()$$, TRUE );
15499 INSERT INTO config.metabib_field ( id, field_class, name, label, format, xpath, facet_field ) VALUES
15500     (18, 'identifier', 'isbn', oils_i18n_gettext(18, 'ISBN', 'cmf', 'label'), 'marcxml', $$//marcxml:datafield[tag="020"]/marcxml:subfield[code="a" or code="z"]/text()$$, TRUE );
15501 INSERT INTO config.metabib_field ( id, field_class, name, label, format, xpath, facet_field ) VALUES
15502     (19, 'identifier', 'issn', oils_i18n_gettext(19, 'ISSN', 'cmf', 'label'), 'marcxml', $$//marcxml:datafield[tag="022"]/marcxml:subfield[code="a" or code="z"]/text()$$, TRUE );
15503 INSERT INTO config.metabib_field ( id, field_class, name, label, format, xpath, facet_field ) VALUES
15504     (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 );
15505 INSERT INTO config.metabib_field ( id, field_class, name, label, format, xpath, facet_field ) VALUES
15506     (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 );
15507 INSERT INTO config.metabib_field ( id, field_class, name, label, format, xpath, facet_field ) VALUES
15508     (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 );
15509 INSERT INTO config.metabib_field ( id, field_class, name, label, format, xpath, facet_field ) VALUES
15510     (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 );
15511 INSERT INTO config.metabib_field ( id, field_class, name, label, format, xpath, facet_field ) VALUES
15512     (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 );
15513 INSERT INTO config.metabib_field ( id, field_class, name, label, format, xpath, facet_field ) VALUES
15514     (25, 'identifier', 'bibcn', oils_i18n_gettext(25, 'Local Free-Text Call Number', 'cmf', 'label'), 'marcxml', $$//marcxml:datafield[tag="099"]//text()$$, TRUE );
15515
15516 SELECT SETVAL('config.metabib_field_id_seq'::TEXT, (SELECT MAX(id) FROM config.metabib_field), TRUE);
15517  
15518
15519 DELETE FROM config.metabib_search_alias WHERE alias = 'dc.identifier';
15520
15521 INSERT INTO config.metabib_search_alias (alias,field_class) VALUES ('id','identifier');
15522 INSERT INTO config.metabib_search_alias (alias,field_class) VALUES ('dc.identifier','identifier');
15523 INSERT INTO config.metabib_search_alias (alias,field_class,field) VALUES ('eg.isbn','identifier', 18);
15524 INSERT INTO config.metabib_search_alias (alias,field_class,field) VALUES ('eg.issn','identifier', 19);
15525 INSERT INTO config.metabib_search_alias (alias,field_class,field) VALUES ('eg.upc','identifier', 20);
15526 INSERT INTO config.metabib_search_alias (alias,field_class,field) VALUES ('eg.callnumber','identifier', 25);
15527
15528 CREATE TABLE metabib.identifier_field_entry (
15529         id              BIGSERIAL       PRIMARY KEY,
15530         source          BIGINT          NOT NULL,
15531         field           INT             NOT NULL,
15532         value           TEXT            NOT NULL,
15533         index_vector    tsvector        NOT NULL
15534 );
15535 CREATE TRIGGER metabib_identifier_field_entry_fti_trigger
15536         BEFORE UPDATE OR INSERT ON metabib.identifier_field_entry
15537         FOR EACH ROW EXECUTE PROCEDURE oils_tsearch2('keyword');
15538
15539 CREATE INDEX metabib_identifier_field_entry_index_vector_idx ON metabib.identifier_field_entry USING GIST (index_vector);
15540 CREATE INDEX metabib_identifier_field_entry_value_idx ON metabib.identifier_field_entry
15541     (SUBSTRING(value,1,1024)) WHERE index_vector = ''::TSVECTOR;
15542 CREATE INDEX metabib_identifier_field_entry_source_idx ON metabib.identifier_field_entry (source);
15543
15544 ALTER TABLE metabib.identifier_field_entry ADD CONSTRAINT metabib_identifier_field_entry_source_pkey
15545     FOREIGN KEY (source) REFERENCES biblio.record_entry (id) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED;
15546 ALTER TABLE metabib.identifier_field_entry ADD CONSTRAINT metabib_identifier_field_entry_field_pkey
15547     FOREIGN KEY (field) REFERENCES config.metabib_field (id) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED;
15548
15549 CREATE OR REPLACE FUNCTION public.translate_isbn1013( TEXT ) RETURNS TEXT AS $func$
15550     use Business::ISBN;
15551     use strict;
15552     use warnings;
15553
15554     # For each ISBN found in a single string containing a set of ISBNs:
15555     #   * Normalize an incoming ISBN to have the correct checksum and no hyphens
15556     #   * Convert an incoming ISBN10 or ISBN13 to its counterpart and return
15557
15558     my $input = shift;
15559     my $output = '';
15560
15561     foreach my $word (split(/\s/, $input)) {
15562         my $isbn = Business::ISBN->new($word);
15563
15564         # First check the checksum; if it is not valid, fix it and add the original
15565         # bad-checksum ISBN to the output
15566         if ($isbn && $isbn->is_valid_checksum() == Business::ISBN::BAD_CHECKSUM) {
15567             $output .= $isbn->isbn() . " ";
15568             $isbn->fix_checksum();
15569         }
15570
15571         # If we now have a valid ISBN, convert it to its counterpart ISBN10/ISBN13
15572         # and add the normalized original ISBN to the output
15573         if ($isbn && $isbn->is_valid()) {
15574             my $isbn_xlated = ($isbn->type eq "ISBN13") ? $isbn->as_isbn10 : $isbn->as_isbn13;
15575             $output .= $isbn->isbn . " ";
15576
15577             # If we successfully converted the ISBN to its counterpart, add the
15578             # converted ISBN to the output as well
15579             $output .= ($isbn_xlated->isbn . " ") if ($isbn_xlated);
15580         }
15581     }
15582     return $output if $output;
15583
15584     # If there were no valid ISBNs, just return the raw input
15585     return $input;
15586 $func$ LANGUAGE PLPERLU;
15587
15588 COMMENT ON FUNCTION public.translate_isbn1013(TEXT) IS $$
15589 /*
15590  * Copyright (C) 2010 Merrimack Valley Library Consortium
15591  * Jason Stephenson <jstephenson@mvlc.org>
15592  * Copyright (C) 2010 Laurentian University
15593  * Dan Scott <dscott@laurentian.ca>
15594  *
15595  * The translate_isbn1013 function takes an input ISBN and returns the
15596  * following in a single space-delimited string if the input ISBN is valid:
15597  *   - The normalized input ISBN (hyphens stripped)
15598  *   - The normalized input ISBN with a fixed checksum if the checksum was bad
15599  *   - The ISBN converted to its ISBN10 or ISBN13 counterpart, if possible
15600  */
15601 $$;
15602
15603 UPDATE config.metabib_field SET facet_field = FALSE WHERE id BETWEEN 17 AND 25;
15604 UPDATE config.metabib_field SET xpath = REPLACE(xpath,'marcxml','marc') WHERE id BETWEEN 17 AND 25;
15605 UPDATE config.metabib_field SET xpath = REPLACE(xpath,'tag','@tag') WHERE id BETWEEN 17 AND 25;
15606 UPDATE config.metabib_field SET xpath = REPLACE(xpath,'code','@code') WHERE id BETWEEN 17 AND 25;
15607 UPDATE config.metabib_field SET xpath = REPLACE(xpath,'"',E'\'') WHERE id BETWEEN 17 AND 25;
15608 UPDATE config.metabib_field SET xpath = REPLACE(xpath,'/text()','') WHERE id BETWEEN 17 AND 24;
15609
15610 INSERT INTO config.index_normalizer (name, description, func, param_count) VALUES (
15611         'ISBN 10/13 conversion',
15612         'Translate ISBN10 to ISBN13, and vice versa, for indexing purposes.',
15613         'translate_isbn1013',
15614         0
15615 );
15616
15617 INSERT INTO config.index_normalizer (name, description, func, param_count) VALUES (
15618         'Replace',
15619         'Replace all occurences of first parameter in the string with the second parameter.',
15620         'replace',
15621         2
15622 );
15623
15624 INSERT INTO config.metabib_field_index_norm_map (field,norm,pos)
15625     SELECT  m.id, i.id, 1
15626       FROM  config.metabib_field m,
15627             config.index_normalizer i
15628       WHERE i.func IN ('first_word')
15629             AND m.id IN (18);
15630
15631 INSERT INTO config.metabib_field_index_norm_map (field,norm,pos)
15632     SELECT  m.id, i.id, 2
15633       FROM  config.metabib_field m,
15634             config.index_normalizer i
15635       WHERE i.func IN ('translate_isbn1013')
15636             AND m.id IN (18);
15637
15638 INSERT INTO config.metabib_field_index_norm_map (field,norm,params)
15639     SELECT  m.id, i.id, $$['-','']$$
15640       FROM  config.metabib_field m,
15641             config.index_normalizer i
15642       WHERE i.func IN ('replace')
15643             AND m.id IN (19);
15644
15645 INSERT INTO config.metabib_field_index_norm_map (field,norm,params)
15646     SELECT  m.id, i.id, $$[' ','']$$
15647       FROM  config.metabib_field m,
15648             config.index_normalizer i
15649       WHERE i.func IN ('replace')
15650             AND m.id IN (19);
15651
15652 DELETE FROM config.metabib_field_index_norm_map WHERE norm IN (1,2) and field > 16;
15653
15654 UPDATE  config.metabib_field_index_norm_map
15655   SET   params = REPLACE(params,E'\'','"')
15656   WHERE params IS NOT NULL AND params <> '';
15657
15658 DROP TRIGGER IF EXISTS metabib_identifier_field_entry_fti_trigger ON metabib.identifier_field_entry;
15659
15660 CREATE TEXT SEARCH CONFIGURATION identifier ( COPY = title );
15661
15662 ALTER TABLE config.circ_modifier
15663         ADD COLUMN avg_wait_time INTERVAL;
15664
15665 --CREATE TABLE actor.usr_password_reset (
15666 --  id SERIAL PRIMARY KEY,
15667 --  uuid TEXT NOT NULL, 
15668 --  usr BIGINT NOT NULL REFERENCES actor.usr(id) DEFERRABLE INITIALLY DEFERRED, 
15669 --  request_time TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(), 
15670 --  has_been_reset BOOL NOT NULL DEFAULT false
15671 --);
15672 --COMMENT ON TABLE actor.usr_password_reset IS $$
15673 --/*
15674 -- * Copyright (C) 2010 Laurentian University
15675 -- * Dan Scott <dscott@laurentian.ca>
15676 -- *
15677 -- * Self-serve password reset requests
15678 -- *
15679 -- * ****
15680 -- *
15681 -- * This program is free software; you can redistribute it and/or
15682 -- * modify it under the terms of the GNU General Public License
15683 -- * as published by the Free Software Foundation; either version 2
15684 -- * of the License, or (at your option) any later version.
15685 -- *
15686 -- * This program is distributed in the hope that it will be useful,
15687 -- * but WITHOUT ANY WARRANTY; without even the implied warranty of
15688 -- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15689 -- * GNU General Public License for more details.
15690 -- */
15691 --$$;
15692 --CREATE UNIQUE INDEX actor_usr_password_reset_uuid_idx ON actor.usr_password_reset (uuid);
15693 --CREATE INDEX actor_usr_password_reset_usr_idx ON actor.usr_password_reset (usr);
15694 --CREATE INDEX actor_usr_password_reset_request_time_idx ON actor.usr_password_reset (request_time);
15695 --CREATE INDEX actor_usr_password_reset_has_been_reset_idx ON actor.usr_password_reset (has_been_reset);
15696
15697 -- Use the identifier search class tsconfig
15698 DROP TRIGGER IF EXISTS metabib_identifier_field_entry_fti_trigger ON metabib.identifier_field_entry;
15699 CREATE TRIGGER metabib_identifier_field_entry_fti_trigger
15700     BEFORE INSERT OR UPDATE ON metabib.identifier_field_entry
15701     FOR EACH ROW
15702     EXECUTE PROCEDURE public.oils_tsearch2('identifier');
15703
15704 INSERT INTO config.global_flag (name,label,enabled)
15705     VALUES ('history.circ.retention_age',oils_i18n_gettext('history.circ.retention_age', 'Historical Circulation Retention Age', 'cgf', 'label'), TRUE);
15706 INSERT INTO config.global_flag (name,label,enabled)
15707     VALUES ('history.circ.retention_count',oils_i18n_gettext('history.circ.retention_count', 'Historical Circulations per Copy', 'cgf', 'label'), TRUE);
15708
15709 -- turn a JSON scalar into an SQL TEXT value
15710 CREATE OR REPLACE FUNCTION oils_json_to_text( TEXT ) RETURNS TEXT AS $f$
15711     use JSON::XS;                    
15712     my $json = shift();
15713     my $txt;
15714     eval { $txt = JSON::XS->new->allow_nonref->decode( $json ) };   
15715     return undef if ($@);
15716     return $txt
15717 $f$ LANGUAGE PLPERLU;
15718
15719 -- Return the list of circ chain heads in xact_start order that the user has chosen to "retain"
15720 CREATE OR REPLACE FUNCTION action.usr_visible_circs (usr_id INT) RETURNS SETOF action.circulation AS $func$
15721 DECLARE
15722     c               action.circulation%ROWTYPE;
15723     view_age        INTERVAL;
15724     usr_view_age    actor.usr_setting%ROWTYPE;
15725     usr_view_start  actor.usr_setting%ROWTYPE;
15726 BEGIN
15727     SELECT * INTO usr_view_age FROM actor.usr_setting WHERE usr = usr_id AND name = 'history.circ.retention_age';
15728     SELECT * INTO usr_view_start FROM actor.usr_setting WHERE usr = usr_id AND name = 'history.circ.retention_start';
15729
15730     IF usr_view_age.value IS NOT NULL AND usr_view_start.value IS NOT NULL THEN
15731         -- User opted in and supplied a retention age
15732         IF oils_json_to_text(usr_view_age.value)::INTERVAL > AGE(NOW(), oils_json_to_text(usr_view_start.value)::TIMESTAMPTZ) THEN
15733             view_age := AGE(NOW(), oils_json_to_text(usr_view_start.value)::TIMESTAMPTZ);
15734         ELSE
15735             view_age := oils_json_to_text(usr_view_age.value)::INTERVAL;
15736         END IF;
15737     ELSIF usr_view_start.value IS NOT NULL THEN
15738         -- User opted in
15739         view_age := AGE(NOW(), oils_json_to_text(usr_view_start.value)::TIMESTAMPTZ);
15740     ELSE
15741         -- User did not opt in
15742         RETURN;
15743     END IF;
15744
15745     FOR c IN
15746         SELECT  *
15747           FROM  action.circulation
15748           WHERE usr = usr_id
15749                 AND parent_circ IS NULL
15750                 AND xact_start > NOW() - view_age
15751           ORDER BY xact_start
15752     LOOP
15753         RETURN NEXT c;
15754     END LOOP;
15755
15756     RETURN;
15757 END;
15758 $func$ LANGUAGE PLPGSQL;
15759
15760 CREATE OR REPLACE FUNCTION action.purge_circulations () RETURNS INT AS $func$
15761 DECLARE
15762     usr_keep_age    actor.usr_setting%ROWTYPE;
15763     usr_keep_start  actor.usr_setting%ROWTYPE;
15764     org_keep_age    INTERVAL;
15765     org_keep_count  INT;
15766
15767     keep_age        INTERVAL;
15768
15769     target_acp      RECORD;
15770     circ_chain_head action.circulation%ROWTYPE;
15771     circ_chain_tail action.circulation%ROWTYPE;
15772
15773     purge_position  INT;
15774     count_purged    INT;
15775 BEGIN
15776
15777     count_purged := 0;
15778
15779     SELECT value::INTERVAL INTO org_keep_age FROM config.global_flag WHERE name = 'history.circ.retention_age' AND enabled;
15780
15781     SELECT value::INT INTO org_keep_count FROM config.global_flag WHERE name = 'history.circ.retention_count' AND enabled;
15782     IF org_keep_count IS NULL THEN
15783         RETURN count_purged; -- Gimme a count to keep, or I keep them all, forever
15784     END IF;
15785
15786     -- First, find copies with more than keep_count non-renewal circs
15787     FOR target_acp IN
15788         SELECT  target_copy,
15789                 COUNT(*) AS total_real_circs
15790           FROM  action.circulation
15791           WHERE parent_circ IS NULL
15792                 AND xact_finish IS NOT NULL
15793           GROUP BY target_copy
15794           HAVING COUNT(*) > org_keep_count
15795     LOOP
15796         purge_position := 0;
15797         -- And, for those, select circs that are finished and older than keep_age
15798         FOR circ_chain_head IN
15799             SELECT  *
15800               FROM  action.circulation
15801               WHERE target_copy = target_acp.target_copy
15802                     AND parent_circ IS NULL
15803               ORDER BY xact_start
15804         LOOP
15805
15806             -- Stop once we've purged enough circs to hit org_keep_count
15807             EXIT WHEN target_acp.total_real_circs - purge_position <= org_keep_count;
15808
15809             SELECT * INTO circ_chain_tail FROM action.circ_chain(circ_chain_head.id) ORDER BY xact_start DESC LIMIT 1;
15810             EXIT WHEN circ_chain_tail.xact_finish IS NULL;
15811
15812             -- Now get the user settings, if any, to block purging if the user wants to keep more circs
15813             usr_keep_age.value := NULL;
15814             SELECT * INTO usr_keep_age FROM actor.usr_setting WHERE usr = circ_chain_head.usr AND name = 'history.circ.retention_age';
15815
15816             usr_keep_start.value := NULL;
15817             SELECT * INTO usr_keep_start FROM actor.usr_setting WHERE usr = circ_chain_head.usr AND name = 'history.circ.retention_start';
15818
15819             IF usr_keep_age.value IS NOT NULL AND usr_keep_start.value IS NOT NULL THEN
15820                 IF oils_json_to_text(usr_keep_age.value)::INTERVAL > AGE(NOW(), oils_json_to_text(usr_keep_start.value)::TIMESTAMPTZ) THEN
15821                     keep_age := AGE(NOW(), oils_json_to_text(usr_keep_start.value)::TIMESTAMPTZ);
15822                 ELSE
15823                     keep_age := oils_json_to_text(usr_keep_age.value)::INTERVAL;
15824                 END IF;
15825             ELSIF usr_keep_start.value IS NOT NULL THEN
15826                 keep_age := AGE(NOW(), oils_json_to_text(usr_keep_start.value)::TIMESTAMPTZ);
15827             ELSE
15828                 keep_age := COALESCE( org_keep_age::INTERVAL, '2000 years'::INTERVAL );
15829             END IF;
15830
15831             EXIT WHEN AGE(NOW(), circ_chain_tail.xact_finish) < keep_age;
15832
15833             -- We've passed the purging tests, purge the circ chain starting at the end
15834             DELETE FROM action.circulation WHERE id = circ_chain_tail.id;
15835             WHILE circ_chain_tail.parent_circ IS NOT NULL LOOP
15836                 SELECT * INTO circ_chain_tail FROM action.circulation WHERE id = circ_chain_tail.parent_circ;
15837                 DELETE FROM action.circulation WHERE id = circ_chain_tail.id;
15838             END LOOP;
15839
15840             count_purged := count_purged + 1;
15841             purge_position := purge_position + 1;
15842
15843         END LOOP;
15844     END LOOP;
15845 END;
15846 $func$ LANGUAGE PLPGSQL;
15847
15848 CREATE OR REPLACE FUNCTION action.usr_visible_holds (usr_id INT) RETURNS SETOF action.hold_request AS $func$
15849 DECLARE
15850     h               action.hold_request%ROWTYPE;
15851     view_age        INTERVAL;
15852     view_count      INT;
15853     usr_view_count  actor.usr_setting%ROWTYPE;
15854     usr_view_age    actor.usr_setting%ROWTYPE;
15855     usr_view_start  actor.usr_setting%ROWTYPE;
15856 BEGIN
15857     SELECT * INTO usr_view_count FROM actor.usr_setting WHERE usr = usr_id AND name = 'history.hold.retention_count';
15858     SELECT * INTO usr_view_age FROM actor.usr_setting WHERE usr = usr_id AND name = 'history.hold.retention_age';
15859     SELECT * INTO usr_view_start FROM actor.usr_setting WHERE usr = usr_id AND name = 'history.hold.retention_start';
15860
15861     FOR h IN
15862         SELECT  *
15863           FROM  action.hold_request
15864           WHERE usr = usr_id
15865                 AND fulfillment_time IS NULL
15866                 AND cancel_time IS NULL
15867           ORDER BY request_time DESC
15868     LOOP
15869         RETURN NEXT h;
15870     END LOOP;
15871
15872     IF usr_view_start.value IS NULL THEN
15873         RETURN;
15874     END IF;
15875
15876     IF usr_view_age.value IS NOT NULL THEN
15877         -- User opted in and supplied a retention age
15878         IF oils_json_to_string(usr_view_age.value)::INTERVAL > AGE(NOW(), oils_json_to_string(usr_view_start.value)::TIMESTAMPTZ) THEN
15879             view_age := AGE(NOW(), oils_json_to_string(usr_view_start.value)::TIMESTAMPTZ);
15880         ELSE
15881             view_age := oils_json_to_string(usr_view_age.value)::INTERVAL;
15882         END IF;
15883     ELSE
15884         -- User opted in
15885         view_age := AGE(NOW(), oils_json_to_string(usr_view_start.value)::TIMESTAMPTZ);
15886     END IF;
15887
15888     IF usr_view_count.value IS NOT NULL THEN
15889         view_count := oils_json_to_text(usr_view_count.value)::INT;
15890     ELSE
15891         view_count := 1000;
15892     END IF;
15893
15894     -- show some fulfilled/canceled holds
15895     FOR h IN
15896         SELECT  *
15897           FROM  action.hold_request
15898           WHERE usr = usr_id
15899                 AND ( fulfillment_time IS NOT NULL OR cancel_time IS NOT NULL )
15900                 AND request_time > NOW() - view_age
15901           ORDER BY request_time DESC
15902           LIMIT view_count
15903     LOOP
15904         RETURN NEXT h;
15905     END LOOP;
15906
15907     RETURN;
15908 END;
15909 $func$ LANGUAGE PLPGSQL;
15910
15911 DROP TABLE IF EXISTS serial.bib_summary CASCADE;
15912
15913 DROP TABLE IF EXISTS serial.index_summary CASCADE;
15914
15915 DROP TABLE IF EXISTS serial.sup_summary CASCADE;
15916
15917 DROP TABLE IF EXISTS serial.issuance CASCADE;
15918
15919 DROP TABLE IF EXISTS serial.binding_unit CASCADE;
15920
15921 DROP TABLE IF EXISTS serial.subscription CASCADE;
15922
15923 CREATE TABLE asset.copy_template (
15924         id             SERIAL   PRIMARY KEY,
15925         owning_lib     INT      NOT NULL
15926                                 REFERENCES actor.org_unit (id)
15927                                 DEFERRABLE INITIALLY DEFERRED,
15928         creator        BIGINT   NOT NULL
15929                                 REFERENCES actor.usr (id)
15930                                 DEFERRABLE INITIALLY DEFERRED,
15931         editor         BIGINT   NOT NULL
15932                                 REFERENCES actor.usr (id)
15933                                 DEFERRABLE INITIALLY DEFERRED,
15934         create_date    TIMESTAMP WITH TIME ZONE    DEFAULT NOW(),
15935         edit_date      TIMESTAMP WITH TIME ZONE    DEFAULT NOW(),
15936         name           TEXT     NOT NULL,
15937         -- columns above this point are attributes of the template itself
15938         -- columns after this point are attributes of the copy this template modifies/creates
15939         circ_lib       INT      REFERENCES actor.org_unit (id)
15940                                 DEFERRABLE INITIALLY DEFERRED,
15941         status         INT      REFERENCES config.copy_status (id)
15942                                 DEFERRABLE INITIALLY DEFERRED,
15943         location       INT      REFERENCES asset.copy_location (id)
15944                                 DEFERRABLE INITIALLY DEFERRED,
15945         loan_duration  INT      CONSTRAINT valid_loan_duration CHECK (
15946                                     loan_duration IS NULL OR loan_duration IN (1,2,3)),
15947         fine_level     INT      CONSTRAINT valid_fine_level CHECK (
15948                                     fine_level IS NULL OR loan_duration IN (1,2,3)),
15949         age_protect    INT,
15950         circulate      BOOL,
15951         deposit        BOOL,
15952         ref            BOOL,
15953         holdable       BOOL,
15954         deposit_amount NUMERIC(6,2),
15955         price          NUMERIC(8,2),
15956         circ_modifier  TEXT,
15957         circ_as_type   TEXT,
15958         alert_message  TEXT,
15959         opac_visible   BOOL,
15960         floating       BOOL,
15961         mint_condition BOOL
15962 );
15963
15964 CREATE TABLE serial.subscription (
15965         id                     SERIAL       PRIMARY KEY,
15966         owning_lib             INT          NOT NULL DEFAULT 1
15967                                             REFERENCES actor.org_unit (id)
15968                                             ON DELETE SET NULL
15969                                             DEFERRABLE INITIALLY DEFERRED,
15970         start_date             TIMESTAMP WITH TIME ZONE     NOT NULL,
15971         end_date               TIMESTAMP WITH TIME ZONE,    -- interpret NULL as current subscription
15972         record_entry           BIGINT       REFERENCES biblio.record_entry (id)
15973                                             ON DELETE SET NULL
15974                                             DEFERRABLE INITIALLY DEFERRED,
15975         expected_date_offset   INTERVAL
15976         -- acquisitions/business-side tables link to here
15977 );
15978 CREATE INDEX serial_subscription_record_idx ON serial.subscription (record_entry);
15979 CREATE INDEX serial_subscription_owner_idx ON serial.subscription (owning_lib);
15980
15981 --at least one distribution per org_unit holding issues
15982 CREATE TABLE serial.distribution (
15983         id                    SERIAL  PRIMARY KEY,
15984         record_entry          BIGINT  REFERENCES serial.record_entry (id)
15985                                       ON DELETE SET NULL
15986                                       DEFERRABLE INITIALLY DEFERRED,
15987         summary_method        TEXT    CONSTRAINT sdist_summary_method_check CHECK (
15988                                           summary_method IS NULL
15989                                           OR summary_method IN ( 'add_to_sre',
15990                                           'merge_with_sre', 'use_sre_only',
15991                                           'use_sdist_only')),
15992         subscription          INT     NOT NULL
15993                                       REFERENCES serial.subscription (id)
15994                                                                   ON DELETE CASCADE
15995                                                                   DEFERRABLE INITIALLY DEFERRED,
15996         holding_lib           INT     NOT NULL
15997                                       REFERENCES actor.org_unit (id)
15998                                                                   DEFERRABLE INITIALLY DEFERRED,
15999         label                 TEXT    NOT NULL,
16000         receive_call_number   BIGINT  REFERENCES asset.call_number (id)
16001                                       DEFERRABLE INITIALLY DEFERRED,
16002         receive_unit_template INT     REFERENCES asset.copy_template (id)
16003                                       DEFERRABLE INITIALLY DEFERRED,
16004         bind_call_number      BIGINT  REFERENCES asset.call_number (id)
16005                                       DEFERRABLE INITIALLY DEFERRED,
16006         bind_unit_template    INT     REFERENCES asset.copy_template (id)
16007                                       DEFERRABLE INITIALLY DEFERRED,
16008         unit_label_prefix     TEXT,
16009         unit_label_suffix     TEXT
16010 );
16011 CREATE INDEX serial_distribution_sub_idx ON serial.distribution (subscription);
16012 CREATE INDEX serial_distribution_holding_lib_idx ON serial.distribution (holding_lib);
16013
16014 CREATE UNIQUE INDEX one_dist_per_sre_idx ON serial.distribution (record_entry);
16015
16016 CREATE TABLE serial.stream (
16017         id              SERIAL  PRIMARY KEY,
16018         distribution    INT     NOT NULL
16019                                 REFERENCES serial.distribution (id)
16020                                 ON DELETE CASCADE
16021                                 DEFERRABLE INITIALLY DEFERRED,
16022         routing_label   TEXT
16023 );
16024 CREATE INDEX serial_stream_dist_idx ON serial.stream (distribution);
16025
16026 CREATE UNIQUE INDEX label_once_per_dist
16027         ON serial.stream (distribution, routing_label)
16028         WHERE routing_label IS NOT NULL;
16029
16030 CREATE TABLE serial.routing_list_user (
16031         id             SERIAL       PRIMARY KEY,
16032         stream         INT          NOT NULL
16033                                     REFERENCES serial.stream
16034                                     ON DELETE CASCADE
16035                                     DEFERRABLE INITIALLY DEFERRED,
16036         pos            INT          NOT NULL DEFAULT 1,
16037         reader         INT          REFERENCES actor.usr
16038                                     ON DELETE CASCADE
16039                                     DEFERRABLE INITIALLY DEFERRED,
16040         department     TEXT,
16041         note           TEXT,
16042         CONSTRAINT one_pos_per_routing_list UNIQUE ( stream, pos ),
16043         CONSTRAINT reader_or_dept CHECK
16044         (
16045             -- Recipient is a person or a department, but not both
16046                 (reader IS NOT NULL AND department IS NULL) OR
16047                 (reader IS NULL AND department IS NOT NULL)
16048         )
16049 );
16050 CREATE INDEX serial_routing_list_user_stream_idx ON serial.routing_list_user (stream);
16051 CREATE INDEX serial_routing_list_user_reader_idx ON serial.routing_list_user (reader);
16052
16053 CREATE TABLE serial.caption_and_pattern (
16054         id           SERIAL       PRIMARY KEY,
16055         subscription INT          NOT NULL REFERENCES serial.subscription (id)
16056                                   ON DELETE CASCADE
16057                                   DEFERRABLE INITIALLY DEFERRED,
16058         type         TEXT         NOT NULL
16059                                   CONSTRAINT cap_type CHECK ( type in
16060                                   ( 'basic', 'supplement', 'index' )),
16061         create_date  TIMESTAMPTZ  NOT NULL DEFAULT now(),
16062         start_date   TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(),
16063         end_date     TIMESTAMP WITH TIME ZONE,
16064         active       BOOL         NOT NULL DEFAULT FALSE,
16065         pattern_code TEXT         NOT NULL,       -- must contain JSON
16066         enum_1       TEXT,
16067         enum_2       TEXT,
16068         enum_3       TEXT,
16069         enum_4       TEXT,
16070         enum_5       TEXT,
16071         enum_6       TEXT,
16072         chron_1      TEXT,
16073         chron_2      TEXT,
16074         chron_3      TEXT,
16075         chron_4      TEXT,
16076         chron_5      TEXT
16077 );
16078 CREATE INDEX serial_caption_and_pattern_sub_idx ON serial.caption_and_pattern (subscription);
16079
16080 CREATE TABLE serial.issuance (
16081         id              SERIAL    PRIMARY KEY,
16082         creator         INT       NOT NULL
16083                                   REFERENCES actor.usr (id)
16084                                                           DEFERRABLE INITIALLY DEFERRED,
16085         editor          INT       NOT NULL
16086                                   REFERENCES actor.usr (id)
16087                                   DEFERRABLE INITIALLY DEFERRED,
16088         create_date     TIMESTAMP WITH TIME ZONE        NOT NULL DEFAULT now(),
16089         edit_date       TIMESTAMP WITH TIME ZONE        NOT NULL DEFAULT now(),
16090         subscription    INT       NOT NULL
16091                                   REFERENCES serial.subscription (id)
16092                                   ON DELETE CASCADE
16093                                   DEFERRABLE INITIALLY DEFERRED,
16094         label           TEXT,
16095         date_published  TIMESTAMP WITH TIME ZONE,
16096         caption_and_pattern  INT  REFERENCES serial.caption_and_pattern (id)
16097                               DEFERRABLE INITIALLY DEFERRED,
16098         holding_code    TEXT,
16099         holding_type    TEXT      CONSTRAINT valid_holding_type CHECK
16100                                   (
16101                                       holding_type IS NULL
16102                                       OR holding_type IN ('basic','supplement','index')
16103                                   ),
16104         holding_link_id INT
16105         -- TODO: add columns for separate enumeration/chronology values
16106 );
16107 CREATE INDEX serial_issuance_sub_idx ON serial.issuance (subscription);
16108 CREATE INDEX serial_issuance_caption_and_pattern_idx ON serial.issuance (caption_and_pattern);
16109 CREATE INDEX serial_issuance_date_published_idx ON serial.issuance (date_published);
16110
16111 CREATE TABLE serial.unit (
16112         label           TEXT,
16113         label_sort_key  TEXT,
16114         contents        TEXT    NOT NULL
16115 ) INHERITS (asset.copy);
16116 CREATE UNIQUE INDEX unit_barcode_key ON serial.unit (barcode) WHERE deleted = FALSE OR deleted IS FALSE;
16117 CREATE INDEX unit_cn_idx ON serial.unit (call_number);
16118 CREATE INDEX unit_avail_cn_idx ON serial.unit (call_number);
16119 CREATE INDEX unit_creator_idx  ON serial.unit ( creator );
16120 CREATE INDEX unit_editor_idx   ON serial.unit ( editor );
16121
16122 ALTER TABLE serial.unit ADD PRIMARY KEY (id);
16123
16124 ALTER TABLE serial.unit ADD CONSTRAINT serial_unit_call_number_fkey FOREIGN KEY (call_number) REFERENCES asset.call_number (id) DEFERRABLE INITIALLY DEFERRED;
16125
16126 ALTER TABLE serial.unit ADD CONSTRAINT serial_unit_creator_fkey FOREIGN KEY (creator) REFERENCES actor.usr (id) ON DELETE SET NULL DEFERRABLE INITIALLY DEFERRED;
16127
16128 ALTER TABLE serial.unit ADD CONSTRAINT serial_unit_editor_fkey FOREIGN KEY (editor) REFERENCES actor.usr (id) ON DELETE SET NULL DEFERRABLE INITIALLY DEFERRED;
16129
16130 CREATE TABLE serial.item (
16131         id              SERIAL  PRIMARY KEY,
16132         creator         INT     NOT NULL
16133                                 REFERENCES actor.usr (id)
16134                                 DEFERRABLE INITIALLY DEFERRED,
16135         editor          INT     NOT NULL
16136                                 REFERENCES actor.usr (id)
16137                                 DEFERRABLE INITIALLY DEFERRED,
16138         create_date     TIMESTAMP WITH TIME ZONE        NOT NULL DEFAULT now(),
16139         edit_date       TIMESTAMP WITH TIME ZONE        NOT NULL DEFAULT now(),
16140         issuance        INT     NOT NULL
16141                                 REFERENCES serial.issuance (id)
16142                                 ON DELETE CASCADE
16143                                 DEFERRABLE INITIALLY DEFERRED,
16144         stream          INT     NOT NULL
16145                                 REFERENCES serial.stream (id)
16146                                 ON DELETE CASCADE
16147                                 DEFERRABLE INITIALLY DEFERRED,
16148         unit            INT     REFERENCES serial.unit (id)
16149                                 ON DELETE SET NULL
16150                                 DEFERRABLE INITIALLY DEFERRED,
16151         uri             INT     REFERENCES asset.uri (id)
16152                                 ON DELETE SET NULL
16153                                 DEFERRABLE INITIALLY DEFERRED,
16154         date_expected   TIMESTAMP WITH TIME ZONE,
16155         date_received   TIMESTAMP WITH TIME ZONE,
16156         status          TEXT    CONSTRAINT valid_status CHECK (
16157                                status IN ( 'Bindery', 'Bound', 'Claimed', 'Discarded',
16158                                'Expected', 'Not Held', 'Not Published', 'Received'))
16159                             DEFAULT 'Expected',
16160         shadowed        BOOL    NOT NULL DEFAULT FALSE
16161 );
16162 CREATE INDEX serial_item_stream_idx ON serial.item (stream);
16163 CREATE INDEX serial_item_issuance_idx ON serial.item (issuance);
16164 CREATE INDEX serial_item_unit_idx ON serial.item (unit);
16165 CREATE INDEX serial_item_uri_idx ON serial.item (uri);
16166 CREATE INDEX serial_item_date_received_idx ON serial.item (date_received);
16167 CREATE INDEX serial_item_status_idx ON serial.item (status);
16168
16169 CREATE TABLE serial.item_note (
16170         id          SERIAL  PRIMARY KEY,
16171         item        INT     NOT NULL
16172                             REFERENCES serial.item (id)
16173                             ON DELETE CASCADE
16174                             DEFERRABLE INITIALLY DEFERRED,
16175         creator     INT     NOT NULL
16176                             REFERENCES actor.usr (id)
16177                             DEFERRABLE INITIALLY DEFERRED,
16178         create_date TIMESTAMP WITH TIME ZONE    DEFAULT NOW(),
16179         pub         BOOL    NOT NULL    DEFAULT FALSE,
16180         title       TEXT    NOT NULL,
16181         value       TEXT    NOT NULL
16182 );
16183 CREATE INDEX serial_item_note_item_idx ON serial.item_note (item);
16184
16185 CREATE TABLE serial.basic_summary (
16186         id                  SERIAL  PRIMARY KEY,
16187         distribution        INT     NOT NULL
16188                                     REFERENCES serial.distribution (id)
16189                                     ON DELETE CASCADE
16190                                     DEFERRABLE INITIALLY DEFERRED,
16191         generated_coverage  TEXT    NOT NULL,
16192         textual_holdings    TEXT,
16193         show_generated      BOOL    NOT NULL DEFAULT TRUE
16194 );
16195 CREATE INDEX serial_basic_summary_dist_idx ON serial.basic_summary (distribution);
16196
16197 CREATE TABLE serial.supplement_summary (
16198         id                  SERIAL  PRIMARY KEY,
16199         distribution        INT     NOT NULL
16200                                     REFERENCES serial.distribution (id)
16201                                     ON DELETE CASCADE
16202                                     DEFERRABLE INITIALLY DEFERRED,
16203         generated_coverage  TEXT    NOT NULL,
16204         textual_holdings    TEXT,
16205         show_generated      BOOL    NOT NULL DEFAULT TRUE
16206 );
16207 CREATE INDEX serial_supplement_summary_dist_idx ON serial.supplement_summary (distribution);
16208
16209 CREATE TABLE serial.index_summary (
16210         id                  SERIAL  PRIMARY KEY,
16211         distribution        INT     NOT NULL
16212                                     REFERENCES serial.distribution (id)
16213                                     ON DELETE CASCADE
16214                                     DEFERRABLE INITIALLY DEFERRED,
16215         generated_coverage  TEXT    NOT NULL,
16216         textual_holdings    TEXT,
16217         show_generated      BOOL    NOT NULL DEFAULT TRUE
16218 );
16219 CREATE INDEX serial_index_summary_dist_idx ON serial.index_summary (distribution);
16220
16221 -- 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.
16222
16223 DROP INDEX IF EXISTS authority.authority_record_unique_tcn;
16224 CREATE UNIQUE INDEX authority_record_unique_tcn ON authority.record_entry (arn_source,arn_value) WHERE deleted = FALSE OR deleted IS FALSE;
16225
16226 DROP INDEX IF EXISTS asset.asset_call_number_label_once_per_lib;
16227 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;
16228
16229 DROP INDEX IF EXISTS biblio.biblio_record_unique_tcn;
16230 CREATE UNIQUE INDEX biblio_record_unique_tcn ON biblio.record_entry (tcn_value) WHERE deleted = FALSE OR deleted IS FALSE;
16231
16232 CREATE OR REPLACE FUNCTION config.interval_to_seconds( interval_val INTERVAL )
16233 RETURNS INTEGER AS $$
16234 BEGIN
16235         RETURN EXTRACT( EPOCH FROM interval_val );
16236 END;
16237 $$ LANGUAGE plpgsql;
16238
16239 CREATE OR REPLACE FUNCTION config.interval_to_seconds( interval_string TEXT )
16240 RETURNS INTEGER AS $$
16241 BEGIN
16242         RETURN config.interval_to_seconds( interval_string::INTERVAL );
16243 END;
16244 $$ LANGUAGE plpgsql;
16245
16246 INSERT INTO container.biblio_record_entry_bucket_type( code, label ) VALUES (
16247     'temp',
16248     oils_i18n_gettext(
16249         'temp',
16250         'Temporary bucket which gets deleted after use.',
16251         'cbrebt',
16252         'label'
16253     )
16254 );
16255
16256 -- 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.
16257
16258 CREATE OR REPLACE FUNCTION biblio.check_marcxml_well_formed () RETURNS TRIGGER AS $func$
16259 BEGIN
16260
16261     IF xml_is_well_formed(NEW.marc) THEN
16262         RETURN NEW;
16263     ELSE
16264         RAISE EXCEPTION 'Attempted to % MARCXML that is not well formed', TG_OP;
16265     END IF;
16266     
16267 END;
16268 $func$ LANGUAGE PLPGSQL;
16269
16270 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();
16271
16272 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();
16273
16274 ALTER TABLE serial.record_entry
16275         ALTER COLUMN marc DROP NOT NULL;
16276
16277 insert INTO CONFIG.xml_transform(name, namespace_uri, prefix, xslt)
16278 VALUES ('marc21expand880', 'http://www.loc.gov/MARC21/slim', 'marc', $$<?xml version="1.0" encoding="UTF-8"?>
16279 <xsl:stylesheet
16280     xmlns:xsl="http://www.w3.org/1999/XSL/Transform" 
16281     xmlns:marc="http://www.loc.gov/MARC21/slim"
16282     version="1.0">
16283 <!--
16284 Copyright (C) 2010  Equinox Software, Inc.
16285 Galen Charlton <gmc@esilibrary.cOM.
16286
16287 This program is free software; you can redistribute it and/or
16288 modify it under the terms of the GNU General Public License
16289 as published by the Free Software Foundation; either version 2
16290 of the License, or (at your option) any later version.
16291
16292 This program is distributed in the hope that it will be useful,
16293 but WITHOUT ANY WARRANTY; without even the implied warranty of
16294 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16295 GNU General Public License for more details.
16296
16297 marc21_expand_880.xsl - stylesheet used during indexing to
16298                         map alternative graphical representations
16299                         of MARC fields stored in 880 fields
16300                         to the corresponding tag name and value.
16301
16302 For example, if a MARC record for a Chinese book has
16303
16304 245.00 $6 880-01 $a Ba shi san nian duan pian xiao shuo xuan
16305 880.00 $6 245-01/$1 $a八十三年短篇小說選
16306
16307 this stylesheet will transform it to the equivalent of
16308
16309 245.00 $6 880-01 $a Ba shi san nian duan pian xiao shuo xuan
16310 245.00 $6 245-01/$1 $a八十三年短篇小說選
16311
16312 -->
16313     <xsl:output encoding="UTF-8" indent="yes" method="xml"/>
16314
16315     <xsl:template match="@*|node()">
16316         <xsl:copy>
16317             <xsl:apply-templates select="@*|node()"/>
16318         </xsl:copy>
16319     </xsl:template>
16320
16321     <xsl:template match="//marc:datafield[@tag='880']">
16322         <xsl:if test="./marc:subfield[@code='6'] and string-length(./marc:subfield[@code='6']) &gt;= 6">
16323             <marc:datafield>
16324                 <xsl:attribute name="tag">
16325                     <xsl:value-of select="substring(./marc:subfield[@code='6'], 1, 3)" />
16326                 </xsl:attribute>
16327                 <xsl:attribute name="ind1">
16328                     <xsl:value-of select="@ind1" />
16329                 </xsl:attribute>
16330                 <xsl:attribute name="ind2">
16331                     <xsl:value-of select="@ind2" />
16332                 </xsl:attribute>
16333                 <xsl:apply-templates />
16334             </marc:datafield>
16335         </xsl:if>
16336     </xsl:template>
16337     
16338 </xsl:stylesheet>$$);
16339
16340 -- Splitting the ingest trigger up into little bits
16341
16342 CREATE TEMPORARY TABLE eg_0301_check_if_has_contents (
16343     flag INTEGER PRIMARY KEY
16344 ) ON COMMIT DROP;
16345 INSERT INTO eg_0301_check_if_has_contents VALUES (1);
16346
16347 -- cause failure if either of the tables we want to drop have rows
16348 INSERT INTO eg_0301_check_if_has_contents SELECT 1 FROM asset.copy_transparency LIMIT 1;
16349 INSERT INTO eg_0301_check_if_has_contents SELECT 1 FROM asset.copy_transparency_map LIMIT 1;
16350
16351 DROP TABLE IF EXISTS asset.copy_transparency_map;
16352 DROP TABLE IF EXISTS asset.copy_transparency;
16353
16354 UPDATE config.metabib_field SET facet_xpath = '//' || facet_xpath WHERE facet_xpath IS NOT NULL;
16355
16356 -- We won't necessarily use all of these, but they are here for completeness.
16357 -- Source is the EDI spec 1229 codelist, eg: http://www.stylusstudio.com/edifact/D04B/1229.htm
16358 -- Values are the EDI code value + 1000
16359
16360 INSERT INTO acq.cancel_reason (keep_debits, id, org_unit, label, description) VALUES 
16361 ('t',(  1+1000), 1, 'Added',     'The information is to be or has been added.'),
16362 ('f',(  2+1000), 1, 'Deleted',   'The information is to be or has been deleted.'),
16363 ('t',(  3+1000), 1, 'Changed',   'The information is to be or has been changed.'),
16364 ('t',(  4+1000), 1, 'No action',                  'This line item is not affected by the actual message.'),
16365 ('t',(  5+1000), 1, 'Accepted without amendment', 'This line item is entirely accepted by the seller.'),
16366 ('t',(  6+1000), 1, 'Accepted with amendment',    'This line item is accepted but amended by the seller.'),
16367 ('f',(  7+1000), 1, 'Not accepted',               'This line item is not accepted by the seller.'),
16368 ('t',(  8+1000), 1, 'Schedule only', 'Code specifying that the message is a schedule only.'),
16369 ('t',(  9+1000), 1, 'Amendments',    'Code specifying that amendments are requested/notified.'),
16370 ('f',( 10+1000), 1, 'Not found',   'This line item is not found in the referenced message.'),
16371 ('t',( 11+1000), 1, 'Not amended', 'This line is not amended by the buyer.'),
16372 ('t',( 12+1000), 1, 'Line item numbers changed', 'Code specifying that the line item numbers have changed.'),
16373 ('t',( 13+1000), 1, 'Buyer has deducted amount', 'Buyer has deducted amount from payment.'),
16374 ('t',( 14+1000), 1, 'Buyer claims against invoice', 'Buyer has a claim against an outstanding invoice.'),
16375 ('t',( 15+1000), 1, 'Charge back by seller', 'Factor has been requested to charge back the outstanding item.'),
16376 ('t',( 16+1000), 1, 'Seller will issue credit note', 'Seller agrees to issue a credit note.'),
16377 ('t',( 17+1000), 1, 'Terms changed for new terms', 'New settlement terms have been agreed.'),
16378 ('t',( 18+1000), 1, 'Abide outcome of negotiations', 'Factor agrees to abide by the outcome of negotiations between seller and buyer.'),
16379 ('t',( 19+1000), 1, 'Seller rejects dispute', 'Seller does not accept validity of dispute.'),
16380 ('t',( 20+1000), 1, 'Settlement', 'The reported situation is settled.'),
16381 ('t',( 21+1000), 1, 'No delivery', 'Code indicating that no delivery will be required.'),
16382 ('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).'),
16383 ('t',( 23+1000), 1, 'Proposed amendment', 'A code used to indicate an amendment suggested by the sender.'),
16384 ('t',( 24+1000), 1, 'Accepted with amendment, no confirmation required', 'Accepted with changes which require no confirmation.'),
16385 ('t',( 25+1000), 1, 'Equipment provisionally repaired', 'The equipment or component has been provisionally repaired.'),
16386 ('t',( 26+1000), 1, 'Included', 'Code indicating that the entity is included.'),
16387 ('t',( 27+1000), 1, 'Verified documents for coverage', 'Upon receipt and verification of documents we shall cover you when due as per your instructions.'),
16388 ('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.'),
16389 ('t',( 29+1000), 1, 'Authenticated advice for coverage',      'On receipt of your authenticated advice we shall cover you when due as per your instructions.'),
16390 ('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.'),
16391 ('t',( 31+1000), 1, 'Authenticated advice for credit',        'On receipt of your authenticated advice we shall credit your account with us when due.'),
16392 ('t',( 32+1000), 1, 'Credit advice requested for direct debit',           'A credit advice is requested for the direct debit.'),
16393 ('t',( 33+1000), 1, 'Credit advice and acknowledgement for direct debit', 'A credit advice and acknowledgement are requested for the direct debit.'),
16394 ('t',( 34+1000), 1, 'Inquiry',     'Request for information.'),
16395 ('t',( 35+1000), 1, 'Checked',     'Checked.'),
16396 ('t',( 36+1000), 1, 'Not checked', 'Not checked.'),
16397 ('f',( 37+1000), 1, 'Cancelled',   'Discontinued.'),
16398 ('t',( 38+1000), 1, 'Replaced',    'Provide a replacement.'),
16399 ('t',( 39+1000), 1, 'New',         'Not existing before.'),
16400 ('t',( 40+1000), 1, 'Agreed',      'Consent.'),
16401 ('t',( 41+1000), 1, 'Proposed',    'Put forward for consideration.'),
16402 ('t',( 42+1000), 1, 'Already delivered', 'Delivery has taken place.'),
16403 ('t',( 43+1000), 1, 'Additional subordinate structures will follow', 'Additional subordinate structures will follow the current hierarchy level.'),
16404 ('t',( 44+1000), 1, 'Additional subordinate structures will not follow', 'No additional subordinate structures will follow the current hierarchy level.'),
16405 ('t',( 45+1000), 1, 'Result opposed',         'A notification that the result is opposed.'),
16406 ('t',( 46+1000), 1, 'Auction held',           'A notification that an auction was held.'),
16407 ('t',( 47+1000), 1, 'Legal action pursued',   'A notification that legal action has been pursued.'),
16408 ('t',( 48+1000), 1, 'Meeting held',           'A notification that a meeting was held.'),
16409 ('t',( 49+1000), 1, 'Result set aside',       'A notification that the result has been set aside.'),
16410 ('t',( 50+1000), 1, 'Result disputed',        'A notification that the result has been disputed.'),
16411 ('t',( 51+1000), 1, 'Countersued',            'A notification that a countersuit has been filed.'),
16412 ('t',( 52+1000), 1, 'Pending',                'A notification that an action is awaiting settlement.'),
16413 ('f',( 53+1000), 1, 'Court action dismissed', 'A notification that a court action will no longer be heard.'),
16414 ('t',( 54+1000), 1, 'Referred item, accepted', 'The item being referred to has been accepted.'),
16415 ('f',( 55+1000), 1, 'Referred item, rejected', 'The item being referred to has been rejected.'),
16416 ('t',( 56+1000), 1, 'Debit advice statement line',  'Notification that the statement line is a debit advice.'),
16417 ('t',( 57+1000), 1, 'Credit advice statement line', 'Notification that the statement line is a credit advice.'),
16418 ('t',( 58+1000), 1, 'Grouped credit advices',       'Notification that the credit advices are grouped.'),
16419 ('t',( 59+1000), 1, 'Grouped debit advices',        'Notification that the debit advices are grouped.'),
16420 ('t',( 60+1000), 1, 'Registered', 'The name is registered.'),
16421 ('f',( 61+1000), 1, 'Payment denied', 'The payment has been denied.'),
16422 ('t',( 62+1000), 1, 'Approved as amended', 'Approved with modifications.'),
16423 ('t',( 63+1000), 1, 'Approved as submitted', 'The request has been approved as submitted.'),
16424 ('f',( 64+1000), 1, 'Cancelled, no activity', 'Cancelled due to the lack of activity.'),
16425 ('t',( 65+1000), 1, 'Under investigation', 'Investigation is being done.'),
16426 ('t',( 66+1000), 1, 'Initial claim received', 'Notification that the initial claim was received.'),
16427 ('f',( 67+1000), 1, 'Not in process', 'Not in process.'),
16428 ('f',( 68+1000), 1, 'Rejected, duplicate', 'Rejected because it is a duplicate.'),
16429 ('f',( 69+1000), 1, 'Rejected, resubmit with corrections', 'Rejected but may be resubmitted when corrected.'),
16430 ('t',( 70+1000), 1, 'Pending, incomplete', 'Pending because of incomplete information.'),
16431 ('t',( 71+1000), 1, 'Under field office investigation', 'Investigation by the field is being done.'),
16432 ('t',( 72+1000), 1, 'Pending, awaiting additional material', 'Pending awaiting receipt of additional material.'),
16433 ('t',( 73+1000), 1, 'Pending, awaiting review', 'Pending while awaiting review.'),
16434 ('t',( 74+1000), 1, 'Reopened', 'Opened again.'),
16435 ('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).'),
16436 ('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).'),
16437 ('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).'),
16438 ('t',( 78+1000), 1, 'Previous payment decision reversed', 'A previous payment decision has been reversed.'),
16439 ('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).'),
16440 ('t',( 80+1000), 1, 'Transferred to correct insurance carrier', 'The request has been transferred to the correct insurance carrier for processing.'),
16441 ('t',( 81+1000), 1, 'Not paid, predetermination pricing only', 'Payment has not been made and the enclosed response is predetermination pricing only.'),
16442 ('t',( 82+1000), 1, 'Documentation claim', 'The claim is for documentation purposes only, no payment required.'),
16443 ('t',( 83+1000), 1, 'Reviewed', 'Assessed.'),
16444 ('f',( 84+1000), 1, 'Repriced', 'This price was changed.'),
16445 ('t',( 85+1000), 1, 'Audited', 'An official examination has occurred.'),
16446 ('t',( 86+1000), 1, 'Conditionally paid', 'Payment has been conditionally made.'),
16447 ('t',( 87+1000), 1, 'On appeal', 'Reconsideration of the decision has been applied for.'),
16448 ('t',( 88+1000), 1, 'Closed', 'Shut.'),
16449 ('t',( 89+1000), 1, 'Reaudited', 'A subsequent official examination has occurred.'),
16450 ('t',( 90+1000), 1, 'Reissued', 'Issued again.'),
16451 ('t',( 91+1000), 1, 'Closed after reopening', 'Reopened and then closed.'),
16452 ('t',( 92+1000), 1, 'Redetermined', 'Determined again or differently.'),
16453 ('t',( 93+1000), 1, 'Processed as primary',   'Processed as the first.'),
16454 ('t',( 94+1000), 1, 'Processed as secondary', 'Processed as the second.'),
16455 ('t',( 95+1000), 1, 'Processed as tertiary',  'Processed as the third.'),
16456 ('t',( 96+1000), 1, 'Correction of error', 'A correction to information previously communicated which contained an error.'),
16457 ('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.'),
16458 ('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.'),
16459 ('t',( 99+1000), 1, 'Interim response', 'The response is an interim one.'),
16460 ('t',(100+1000), 1, 'Final response',   'The response is an final one.'),
16461 ('t',(101+1000), 1, 'Debit advice requested', 'A debit advice is requested for the transaction.'),
16462 ('t',(102+1000), 1, 'Transaction not impacted', 'Advice that the transaction is not impacted.'),
16463 ('t',(103+1000), 1, 'Patient to be notified',                    'The action to take is to notify the patient.'),
16464 ('t',(104+1000), 1, 'Healthcare provider to be notified',        'The action to take is to notify the healthcare provider.'),
16465 ('t',(105+1000), 1, 'Usual general practitioner to be notified', 'The action to take is to notify the usual general practitioner.'),
16466 ('t',(106+1000), 1, 'Advice without details', 'An advice without details is requested or notified.'),
16467 ('t',(107+1000), 1, 'Advice with details', 'An advice with details is requested or notified.'),
16468 ('t',(108+1000), 1, 'Amendment requested', 'An amendment is requested.'),
16469 ('t',(109+1000), 1, 'For information', 'Included for information only.'),
16470 ('f',(110+1000), 1, 'Withdraw', 'A code indicating discontinuance or retraction.'),
16471 ('t',(111+1000), 1, 'Delivery date change', 'The action / notiification is a change of the delivery date.'),
16472 ('f',(112+1000), 1, 'Quantity change',      'The action / notification is a change of quantity.'),
16473 ('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.'),
16474 ('t',(114+1000), 1, 'Resale',           'The identified items have been sold by the distributor to the end customer.'),
16475 ('t',(115+1000), 1, 'Prior addition', 'This existing line item becomes available at an earlier date.');
16476
16477 INSERT INTO config.metabib_field ( id, field_class, name, label, format, xpath, facet_field, search_field ) VALUES
16478     (26, 'identifier', 'arcn', oils_i18n_gettext(26, 'Authority record control number', 'cmf', 'label'), 'marcxml', $$//marc:subfield[@code='0']$$, TRUE, FALSE );
16479  
16480 SELECT SETVAL('config.metabib_field_id_seq'::TEXT, (SELECT MAX(id) FROM config.metabib_field), TRUE);
16481  
16482 INSERT INTO config.index_normalizer (name, description, func, param_count) VALUES (
16483         'Remove Parenthesized Substring',
16484         'Remove any parenthesized substrings from the extracted text, such as the agency code preceding authority record control numbers in subfield 0.',
16485         'remove_paren_substring',
16486         0
16487 );
16488
16489 INSERT INTO config.index_normalizer (name, description, func, param_count) VALUES (
16490         'Trim Surrounding Space',
16491         'Trim leading and trailing spaces from extracted text.',
16492         'btrim',
16493         0
16494 );
16495
16496 INSERT INTO config.metabib_field_index_norm_map (field,norm,pos)
16497     SELECT  m.id,
16498             i.id,
16499             -2
16500       FROM  config.metabib_field m,
16501             config.index_normalizer i
16502       WHERE i.func IN ('remove_paren_substring')
16503             AND m.id IN (26);
16504
16505 INSERT INTO config.metabib_field_index_norm_map (field,norm,pos)
16506     SELECT  m.id,
16507             i.id,
16508             -1
16509       FROM  config.metabib_field m,
16510             config.index_normalizer i
16511       WHERE i.func IN ('btrim')
16512             AND m.id IN (26);
16513
16514 -- Function that takes, and returns, marcxml and compiles an embedded ruleset for you, and they applys it
16515 CREATE OR REPLACE FUNCTION vandelay.merge_record_xml ( target_marc TEXT, template_marc TEXT ) RETURNS TEXT AS $$
16516 DECLARE
16517     dyn_profile     vandelay.compile_profile%ROWTYPE;
16518     replace_rule    TEXT;
16519     tmp_marc        TEXT;
16520     trgt_marc        TEXT;
16521     tmpl_marc        TEXT;
16522     match_count     INT;
16523 BEGIN
16524
16525     IF target_marc IS NULL OR template_marc IS NULL THEN
16526         -- RAISE NOTICE 'no marc for target or template record';
16527         RETURN NULL;
16528     END IF;
16529
16530     dyn_profile := vandelay.compile_profile( template_marc );
16531
16532     IF dyn_profile.replace_rule <> '' AND dyn_profile.preserve_rule <> '' THEN
16533         -- RAISE NOTICE 'both replace [%] and preserve [%] specified', dyn_profile.replace_rule, dyn_profile.preserve_rule;
16534         RETURN NULL;
16535     END IF;
16536
16537     IF dyn_profile.replace_rule <> '' THEN
16538         trgt_marc = target_marc;
16539         tmpl_marc = template_marc;
16540         replace_rule = dyn_profile.replace_rule;
16541     ELSE
16542         tmp_marc = target_marc;
16543         trgt_marc = template_marc;
16544         tmpl_marc = tmp_marc;
16545         replace_rule = dyn_profile.preserve_rule;
16546     END IF;
16547
16548     RETURN vandelay.merge_record_xml( trgt_marc, tmpl_marc, dyn_profile.add_rule, replace_rule, dyn_profile.strip_rule );
16549
16550 END;
16551 $$ LANGUAGE PLPGSQL;
16552
16553 -- Function to generate an ephemeral overlay template from an authority record
16554 CREATE OR REPLACE FUNCTION authority.generate_overlay_template ( TEXT, BIGINT ) RETURNS TEXT AS $func$
16555
16556     use MARC::Record;
16557     use MARC::File::XML (BinaryEncoding => 'UTF-8');
16558
16559     my $xml = shift;
16560     my $r = MARC::Record->new_from_xml( $xml );
16561
16562     return undef unless ($r);
16563
16564     my $id = shift() || $r->subfield( '901' => 'c' );
16565     $id =~ s/^\s*(?:\([^)]+\))?\s*(.+)\s*?$/$1/;
16566     return undef unless ($id); # We need an ID!
16567
16568     my $tmpl = MARC::Record->new();
16569
16570     my @rule_fields;
16571     for my $field ( $r->field( '1..' ) ) { # Get main entry fields from the authority record
16572
16573         my $tag = $field->tag;
16574         my $i1 = $field->indicator(1);
16575         my $i2 = $field->indicator(2);
16576         my $sf = join '', map { $_->[0] } $field->subfields;
16577         my @data = map { @$_ } $field->subfields;
16578
16579         my @replace_them;
16580
16581         # Map the authority field to bib fields it can control.
16582         if ($tag >= 100 and $tag <= 111) {       # names
16583             @replace_them = map { $tag + $_ } (0, 300, 500, 600, 700);
16584         } elsif ($tag eq '130') {                # uniform title
16585             @replace_them = qw/130 240 440 730 830/;
16586         } elsif ($tag >= 150 and $tag <= 155) {  # subjects
16587             @replace_them = ($tag + 500);
16588         } elsif ($tag >= 180 and $tag <= 185) {  # floating subdivisions
16589             @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/;
16590         } else {
16591             next;
16592         }
16593
16594         # Dummy up the bib-side data
16595         $tmpl->append_fields(
16596             map {
16597                 MARC::Field->new( $_, $i1, $i2, @data )
16598             } @replace_them
16599         );
16600
16601         # Construct some 'replace' rules
16602         push @rule_fields, map { $_ . $sf . '[0~\)' .$id . '$]' } @replace_them;
16603     }
16604
16605     # Insert the replace rules into the template
16606     $tmpl->append_fields(
16607         MARC::Field->new( '905' => ' ' => ' ' => 'r' => join(',', @rule_fields ) )
16608     );
16609
16610     $xml = $tmpl->as_xml_record;
16611     $xml =~ s/^<\?.+?\?>$//mo;
16612     $xml =~ s/\n//sgo;
16613     $xml =~ s/>\s+</></sgo;
16614
16615     return $xml;
16616
16617 $func$ LANGUAGE PLPERLU;
16618
16619 CREATE OR REPLACE FUNCTION authority.generate_overlay_template ( BIGINT ) RETURNS TEXT AS $func$
16620     SELECT authority.generate_overlay_template( marc, id ) FROM authority.record_entry WHERE id = $1;
16621 $func$ LANGUAGE SQL;
16622
16623 CREATE OR REPLACE FUNCTION authority.generate_overlay_template ( TEXT ) RETURNS TEXT AS $func$
16624     SELECT authority.generate_overlay_template( $1, NULL );
16625 $func$ LANGUAGE SQL;
16626
16627 DELETE FROM config.metabib_field_index_norm_map WHERE field = 26;
16628 DELETE FROM config.metabib_field WHERE id = 26;
16629
16630 -- Making this a global_flag (UI accessible) instead of an internal_flag
16631 INSERT INTO config.global_flag (name, label) -- defaults to enabled=FALSE
16632     VALUES (
16633         'ingest.disable_authority_linking',
16634         oils_i18n_gettext(
16635             'ingest.disable_authority_linking',
16636             'Authority Automation: Disable bib-authority link tracking',
16637             'cgf', 
16638             'label'
16639         )
16640     );
16641 UPDATE config.global_flag SET enabled = (SELECT enabled FROM ONLY config.internal_flag WHERE name = 'ingest.disable_authority_linking');
16642 DELETE FROM config.internal_flag WHERE name = 'ingest.disable_authority_linking';
16643
16644 INSERT INTO config.global_flag (name, label) -- defaults to enabled=FALSE
16645     VALUES (
16646         'ingest.disable_authority_auto_update',
16647         oils_i18n_gettext(
16648             'ingest.disable_authority_auto_update',
16649             'Authority Automation: Disable automatic authority updating (requires link tracking)',
16650             'cgf', 
16651             'label'
16652         )
16653     );
16654
16655 -- Enable automated ingest of authority records; just insert the row into
16656 -- authority.record_entry and authority.full_rec will automatically be populated
16657
16658 CREATE OR REPLACE FUNCTION authority.propagate_changes (aid BIGINT, bid BIGINT) RETURNS BIGINT AS $func$
16659     UPDATE  biblio.record_entry
16660       SET   marc = vandelay.merge_record_xml( marc, authority.generate_overlay_template( $1 ) )
16661       WHERE id = $2;
16662     SELECT $1;
16663 $func$ LANGUAGE SQL;
16664
16665 CREATE OR REPLACE FUNCTION authority.propagate_changes (aid BIGINT) RETURNS SETOF BIGINT AS $func$
16666     SELECT authority.propagate_changes( authority, bib ) FROM authority.bib_linking WHERE authority = $1;
16667 $func$ LANGUAGE SQL;
16668
16669 CREATE OR REPLACE FUNCTION authority.flatten_marc ( TEXT ) RETURNS SETOF authority.full_rec AS $func$
16670
16671 use MARC::Record;
16672 use MARC::File::XML (BinaryEncoding => 'UTF-8');
16673
16674 my $xml = shift;
16675 my $r = MARC::Record->new_from_xml( $xml );
16676
16677 return_next( { tag => 'LDR', value => $r->leader } );
16678
16679 for my $f ( $r->fields ) {
16680     if ($f->is_control_field) {
16681         return_next({ tag => $f->tag, value => $f->data });
16682     } else {
16683         for my $s ($f->subfields) {
16684             return_next({
16685                 tag      => $f->tag,
16686                 ind1     => $f->indicator(1),
16687                 ind2     => $f->indicator(2),
16688                 subfield => $s->[0],
16689                 value    => $s->[1]
16690             });
16691
16692         }
16693     }
16694 }
16695
16696 return undef;
16697
16698 $func$ LANGUAGE PLPERLU;
16699
16700 CREATE OR REPLACE FUNCTION authority.flatten_marc ( rid BIGINT ) RETURNS SETOF authority.full_rec AS $func$
16701 DECLARE
16702     auth    authority.record_entry%ROWTYPE;
16703     output    authority.full_rec%ROWTYPE;
16704     field    RECORD;
16705 BEGIN
16706     SELECT INTO auth * FROM authority.record_entry WHERE id = rid;
16707
16708     FOR field IN SELECT * FROM authority.flatten_marc( auth.marc ) LOOP
16709         output.record := rid;
16710         output.ind1 := field.ind1;
16711         output.ind2 := field.ind2;
16712         output.tag := field.tag;
16713         output.subfield := field.subfield;
16714         IF field.subfield IS NOT NULL THEN
16715             output.value := naco_normalize(field.value, field.subfield);
16716         ELSE
16717             output.value := field.value;
16718         END IF;
16719
16720         CONTINUE WHEN output.value IS NULL;
16721
16722         RETURN NEXT output;
16723     END LOOP;
16724 END;
16725 $func$ LANGUAGE PLPGSQL;
16726
16727 -- authority.rec_descriptor appears to be unused currently
16728 CREATE OR REPLACE FUNCTION authority.reingest_authority_rec_descriptor( auth_id BIGINT ) RETURNS VOID AS $func$
16729 BEGIN
16730     DELETE FROM authority.rec_descriptor WHERE record = auth_id;
16731 --    INSERT INTO authority.rec_descriptor (record, record_status, char_encoding)
16732 --        SELECT  auth_id, ;
16733
16734     RETURN;
16735 END;
16736 $func$ LANGUAGE PLPGSQL;
16737
16738 CREATE OR REPLACE FUNCTION authority.reingest_authority_full_rec( auth_id BIGINT ) RETURNS VOID AS $func$
16739 BEGIN
16740     DELETE FROM authority.full_rec WHERE record = auth_id;
16741     INSERT INTO authority.full_rec (record, tag, ind1, ind2, subfield, value)
16742         SELECT record, tag, ind1, ind2, subfield, value FROM authority.flatten_marc( auth_id );
16743
16744     RETURN;
16745 END;
16746 $func$ LANGUAGE PLPGSQL;
16747
16748 -- AFTER UPDATE OR INSERT trigger for authority.record_entry
16749 CREATE OR REPLACE FUNCTION authority.indexing_ingest_or_delete () RETURNS TRIGGER AS $func$
16750 BEGIN
16751
16752     IF NEW.deleted IS TRUE THEN -- If this authority is deleted
16753         DELETE FROM authority.bib_linking WHERE authority = NEW.id; -- Avoid updating fields in bibs that are no longer visible
16754         DELETE FROM authority.full_rec WHERE record = NEW.id; -- Avoid validating fields against deleted authority records
16755           -- Should remove matching $0 from controlled fields at the same time?
16756         RETURN NEW; -- and we're done
16757     END IF;
16758
16759     IF TG_OP = 'UPDATE' THEN -- re-ingest?
16760         PERFORM * FROM config.internal_flag WHERE name = 'ingest.reingest.force_on_same_marc' AND enabled;
16761
16762         IF NOT FOUND AND OLD.marc = NEW.marc THEN -- don't do anything if the MARC didn't change
16763             RETURN NEW;
16764         END IF;
16765     END IF;
16766
16767     -- Flatten and insert the afr data
16768     PERFORM * FROM config.internal_flag WHERE name = 'ingest.disable_authority_full_rec' AND enabled;
16769     IF NOT FOUND THEN
16770         PERFORM authority.reingest_authority_full_rec(NEW.id);
16771 -- authority.rec_descriptor is not currently used
16772 --        PERFORM * FROM config.internal_flag WHERE name = 'ingest.disable_authority_rec_descriptor' AND enabled;
16773 --        IF NOT FOUND THEN
16774 --            PERFORM authority.reingest_authority_rec_descriptor(NEW.id);
16775 --        END IF;
16776     END IF;
16777
16778     RETURN NEW;
16779 END;
16780 $func$ LANGUAGE PLPGSQL;
16781
16782 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 ();
16783
16784 -- Some records manage to get XML namespace declarations into each element,
16785 -- like <datafield xmlns:marc="http://www.loc.gov/MARC21/slim"
16786 -- This broke the old maintain_901(), so we'll make the regex more robust
16787
16788 CREATE OR REPLACE FUNCTION maintain_901 () RETURNS TRIGGER AS $func$
16789 BEGIN
16790     -- Remove any existing 901 fields before we insert the authoritative one
16791     NEW.marc := REGEXP_REPLACE(NEW.marc, E'<datafield\s*[^<>]*?\s*tag="901".+?</datafield>', '', 'g');
16792     IF TG_TABLE_SCHEMA = 'biblio' THEN
16793         NEW.marc := REGEXP_REPLACE(
16794             NEW.marc,
16795             E'(</(?:[^:]*?:)?record>)',
16796             E'<datafield tag="901" ind1=" " ind2=" ">' ||
16797                 '<subfield code="a">' || NEW.tcn_value || E'</subfield>' ||
16798                 '<subfield code="b">' || NEW.tcn_source || E'</subfield>' ||
16799                 '<subfield code="c">' || NEW.id || E'</subfield>' ||
16800                 '<subfield code="t">' || TG_TABLE_SCHEMA || E'</subfield>' ||
16801                 CASE WHEN NEW.owner IS NOT NULL THEN '<subfield code="o">' || NEW.owner || E'</subfield>' ELSE '' END ||
16802                 CASE WHEN NEW.share_depth IS NOT NULL THEN '<subfield code="d">' || NEW.share_depth || E'</subfield>' ELSE '' END ||
16803              E'</datafield>\\1'
16804         );
16805     ELSIF TG_TABLE_SCHEMA = 'authority' THEN
16806         NEW.marc := REGEXP_REPLACE(
16807             NEW.marc,
16808             E'(</(?:[^:]*?:)?record>)',
16809             E'<datafield tag="901" ind1=" " ind2=" ">' ||
16810                 '<subfield code="c">' || NEW.id || E'</subfield>' ||
16811                 '<subfield code="t">' || TG_TABLE_SCHEMA || E'</subfield>' ||
16812              E'</datafield>\\1'
16813         );
16814     ELSIF TG_TABLE_SCHEMA = 'serial' THEN
16815         NEW.marc := REGEXP_REPLACE(
16816             NEW.marc,
16817             E'(</(?:[^:]*?:)?record>)',
16818             E'<datafield tag="901" ind1=" " ind2=" ">' ||
16819                 '<subfield code="c">' || NEW.id || E'</subfield>' ||
16820                 '<subfield code="t">' || TG_TABLE_SCHEMA || E'</subfield>' ||
16821                 '<subfield code="o">' || NEW.owning_lib || E'</subfield>' ||
16822                 CASE WHEN NEW.record IS NOT NULL THEN '<subfield code="r">' || NEW.record || E'</subfield>' ELSE '' END ||
16823              E'</datafield>\\1'
16824         );
16825     ELSE
16826         NEW.marc := REGEXP_REPLACE(
16827             NEW.marc,
16828             E'(</(?:[^:]*?:)?record>)',
16829             E'<datafield tag="901" ind1=" " ind2=" ">' ||
16830                 '<subfield code="c">' || NEW.id || E'</subfield>' ||
16831                 '<subfield code="t">' || TG_TABLE_SCHEMA || E'</subfield>' ||
16832              E'</datafield>\\1'
16833         );
16834     END IF;
16835
16836     RETURN NEW;
16837 END;
16838 $func$ LANGUAGE PLPGSQL;
16839
16840 CREATE TRIGGER b_maintain_901 BEFORE INSERT OR UPDATE ON biblio.record_entry FOR EACH ROW EXECUTE PROCEDURE maintain_901();
16841 CREATE TRIGGER b_maintain_901 BEFORE INSERT OR UPDATE ON authority.record_entry FOR EACH ROW EXECUTE PROCEDURE maintain_901();
16842 CREATE TRIGGER b_maintain_901 BEFORE INSERT OR UPDATE ON serial.record_entry FOR EACH ROW EXECUTE PROCEDURE maintain_901();
16843  
16844 -- In booking, elbow room defines:
16845 --  a) how far in the future you must make a reservation on a given item if
16846 --      that item will have to transit somewhere to fulfill the reservation.
16847 --  b) how soon a reservation must be starting for the reserved item to
16848 --      be op-captured by the checkin interface.
16849
16850 -- We don't want to clobber any default_elbow room at any level:
16851
16852 CREATE OR REPLACE FUNCTION pg_temp.default_elbow() RETURNS INTEGER AS $$
16853 DECLARE
16854     existing    actor.org_unit_setting%ROWTYPE;
16855 BEGIN
16856     SELECT INTO existing id FROM actor.org_unit_setting WHERE name = 'circ.booking_reservation.default_elbow_room';
16857     IF NOT FOUND THEN
16858         INSERT INTO actor.org_unit_setting (org_unit, name, value) VALUES (
16859             (SELECT id FROM actor.org_unit WHERE parent_ou IS NULL),
16860             'circ.booking_reservation.default_elbow_room',
16861             '"1 day"'
16862         );
16863         RETURN 1;
16864     END IF;
16865     RETURN 0;
16866 END;
16867 $$ LANGUAGE plpgsql;
16868
16869 SELECT pg_temp.default_elbow();
16870
16871 DROP FUNCTION IF EXISTS action.usr_visible_circ_copies( INTEGER );
16872
16873 -- returns the distinct set of target copy IDs from a user's visible circulation history
16874 CREATE OR REPLACE FUNCTION action.usr_visible_circ_copies( INTEGER ) RETURNS SETOF BIGINT AS $$
16875     SELECT DISTINCT(target_copy) FROM action.usr_visible_circs($1)
16876 $$ LANGUAGE SQL;
16877
16878 ALTER TABLE action.in_house_use DROP CONSTRAINT in_house_use_item_fkey;
16879 ALTER TABLE action.transit_copy DROP CONSTRAINT transit_copy_target_copy_fkey;
16880 ALTER TABLE action.hold_transit_copy DROP CONSTRAINT ahtc_tc_fkey;
16881 ALTER TABLE action.hold_copy_map DROP CONSTRAINT hold_copy_map_target_copy_fkey;
16882
16883 ALTER TABLE asset.stat_cat_entry_copy_map DROP CONSTRAINT a_sc_oc_fkey;
16884
16885 ALTER TABLE authority.record_entry ADD COLUMN owner INT;
16886 ALTER TABLE serial.record_entry ADD COLUMN owner INT;
16887
16888 INSERT INTO config.global_flag (name, label, enabled)
16889     VALUES (
16890         'cat.maintain_control_numbers',
16891         oils_i18n_gettext(
16892             'cat.maintain_control_numbers',
16893             'Cat: Maintain 001/003/035 according to the MARC21 specification',
16894             'cgf', 
16895             'label'
16896         ),
16897         TRUE
16898     );
16899
16900 INSERT INTO config.global_flag (name, label, enabled)
16901     VALUES (
16902         'circ.holds.empty_issuance_ok',
16903         oils_i18n_gettext(
16904             'circ.holds.empty_issuance_ok',
16905             'Holds: Allow holds on empty issuances',
16906             'cgf',
16907             'label'
16908         ),
16909         TRUE
16910     );
16911
16912 INSERT INTO config.global_flag (name, label, enabled)
16913     VALUES (
16914         'circ.holds.usr_not_requestor',
16915         oils_i18n_gettext(
16916             'circ.holds.usr_not_requestor',
16917             'Holds: When testing hold matrix matchpoints, use the profile group of the receiving user instead of that of the requestor (affects staff-placed holds)',
16918             'cgf',
16919             'label'
16920         ),
16921         TRUE
16922     );
16923
16924 CREATE OR REPLACE FUNCTION maintain_control_numbers() RETURNS TRIGGER AS $func$
16925 use strict;
16926 use MARC::Record;
16927 use MARC::File::XML (BinaryEncoding => 'UTF-8');
16928 use Encode;
16929 use Unicode::Normalize;
16930
16931 my $record = MARC::Record->new_from_xml($_TD->{new}{marc});
16932 my $schema = $_TD->{table_schema};
16933 my $rec_id = $_TD->{new}{id};
16934
16935 # Short-circuit if maintaining control numbers per MARC21 spec is not enabled
16936 my $enable = spi_exec_query("SELECT enabled FROM config.global_flag WHERE name = 'cat.maintain_control_numbers'");
16937 if (!($enable->{processed}) or $enable->{rows}[0]->{enabled} eq 'f') {
16938     return;
16939 }
16940
16941 # Get the control number identifier from an OU setting based on $_TD->{new}{owner}
16942 my $ou_cni = 'EVRGRN';
16943
16944 my $owner;
16945 if ($schema eq 'serial') {
16946     $owner = $_TD->{new}{owning_lib};
16947 } else {
16948     # are.owner and bre.owner can be null, so fall back to the consortial setting
16949     $owner = $_TD->{new}{owner} || 1;
16950 }
16951
16952 my $ous_rv = spi_exec_query("SELECT value FROM actor.org_unit_ancestor_setting('cat.marc_control_number_identifier', $owner)");
16953 if ($ous_rv->{processed}) {
16954     $ou_cni = $ous_rv->{rows}[0]->{value};
16955     $ou_cni =~ s/"//g; # Stupid VIM syntax highlighting"
16956 } else {
16957     # Fall back to the shortname of the OU if there was no OU setting
16958     $ous_rv = spi_exec_query("SELECT shortname FROM actor.org_unit WHERE id = $owner");
16959     if ($ous_rv->{processed}) {
16960         $ou_cni = $ous_rv->{rows}[0]->{shortname};
16961     }
16962 }
16963
16964 my ($create, $munge) = (0, 0);
16965 my ($orig_001, $orig_003) = ('', '');
16966
16967 # Incoming MARC records may have multiple 001s or 003s, despite the spec
16968 my @control_ids = $record->field('003');
16969 my @scns = $record->field('035');
16970
16971 foreach my $id_field ('001', '003') {
16972     my $spec_value;
16973     my @controls = $record->field($id_field);
16974
16975     if ($id_field eq '001') {
16976         $spec_value = $rec_id;
16977     } else {
16978         $spec_value = $ou_cni;
16979     }
16980
16981     # Create the 001/003 if none exist
16982     if (scalar(@controls) == 0) {
16983         $record->insert_fields_ordered(MARC::Field->new($id_field, $spec_value));
16984         $create = 1;
16985     } elsif (scalar(@controls) > 1) {
16986         # Do we already have the right 001/003 value in the existing set?
16987         unless (grep $_->data() eq $spec_value, @controls) {
16988             $munge = 1;
16989         }
16990
16991         # Delete the other fields, as with more than 1 001/003 we do not know which 003/001 to match
16992         foreach my $control (@controls) {
16993             unless ($control->data() eq $spec_value) {
16994                 $record->delete_field($control);
16995             }
16996         }
16997     } else {
16998         # Only one field; check to see if we need to munge it
16999         unless (grep $_->data() eq $spec_value, @controls) {
17000             $munge = 1;
17001         }
17002     }
17003 }
17004
17005 # Now, if we need to munge the 001, we will first push the existing 001/003 into the 035
17006 if ($munge) {
17007     my $scn = "(" . $record->field('003')->data() . ")" . $record->field('001')->data();
17008
17009     # Do not create duplicate 035 fields
17010     unless (grep $_->subfield('a') eq $scn, @scns) {
17011         $record->insert_fields_ordered(MARC::Field->new('035', '', '', 'a' => $scn));
17012     }
17013 }
17014
17015 # Set the 001/003 and update the MARC
17016 if ($create or $munge) {
17017     $record->field('001')->data($rec_id);
17018     $record->field('003')->data($ou_cni);
17019
17020     my $xml = $record->as_xml_record();
17021     $xml =~ s/\n//sgo;
17022     $xml =~ s/^<\?xml.+\?\s*>//go;
17023     $xml =~ s/>\s+</></go;
17024     $xml =~ s/\p{Cc}//go;
17025
17026     # Embed a version of OpenILS::Application::AppUtils->entityize()
17027     # to avoid having to set PERL5LIB for PostgreSQL as well
17028
17029     # If we are going to convert non-ASCII characters to XML entities,
17030     # we had better be dealing with a UTF8 string to begin with
17031     $xml = decode_utf8($xml);
17032
17033     $xml = NFC($xml);
17034
17035     # Convert raw ampersands to entities
17036     $xml =~ s/&(?!\S+;)/&amp;/gso;
17037
17038     # Convert Unicode characters to entities
17039     $xml =~ s/([\x{0080}-\x{fffd}])/sprintf('&#x%X;',ord($1))/sgoe;
17040
17041     $xml =~ s/[\x00-\x1f]//go;
17042     $_TD->{new}{marc} = $xml;
17043
17044     return "MODIFY";
17045 }
17046
17047 return;
17048 $func$ LANGUAGE PLPERLU;
17049
17050 CREATE TRIGGER c_maintain_control_numbers BEFORE INSERT OR UPDATE ON authority.record_entry FOR EACH ROW EXECUTE PROCEDURE maintain_control_numbers();
17051 CREATE TRIGGER c_maintain_control_numbers BEFORE INSERT OR UPDATE ON biblio.record_entry FOR EACH ROW EXECUTE PROCEDURE maintain_control_numbers();
17052 CREATE TRIGGER c_maintain_control_numbers BEFORE INSERT OR UPDATE ON serial.record_entry FOR EACH ROW EXECUTE PROCEDURE maintain_control_numbers();
17053
17054 INSERT INTO metabib.facet_entry (source, field, value)
17055     SELECT source, field, value FROM (
17056         SELECT * FROM metabib.author_field_entry
17057             UNION ALL
17058         SELECT * FROM metabib.keyword_field_entry
17059             UNION ALL
17060         SELECT * FROM metabib.identifier_field_entry
17061             UNION ALL
17062         SELECT * FROM metabib.title_field_entry
17063             UNION ALL
17064         SELECT * FROM metabib.subject_field_entry
17065             UNION ALL
17066         SELECT * FROM metabib.series_field_entry
17067         )x
17068     WHERE x.index_vector = '';
17069         
17070 DELETE FROM metabib.author_field_entry WHERE index_vector = '';
17071 DELETE FROM metabib.keyword_field_entry WHERE index_vector = '';
17072 DELETE FROM metabib.identifier_field_entry WHERE index_vector = '';
17073 DELETE FROM metabib.title_field_entry WHERE index_vector = '';
17074 DELETE FROM metabib.subject_field_entry WHERE index_vector = '';
17075 DELETE FROM metabib.series_field_entry WHERE index_vector = '';
17076
17077 CREATE INDEX metabib_facet_entry_field_idx ON metabib.facet_entry (field);
17078 CREATE INDEX metabib_facet_entry_value_idx ON metabib.facet_entry (SUBSTRING(value,1,1024));
17079 CREATE INDEX metabib_facet_entry_source_idx ON metabib.facet_entry (source);
17080
17081 -- copy OPAC visibility materialized view
17082 CREATE OR REPLACE FUNCTION asset.refresh_opac_visible_copies_mat_view () RETURNS VOID AS $$
17083
17084     TRUNCATE TABLE asset.opac_visible_copies;
17085
17086     INSERT INTO asset.opac_visible_copies (id, circ_lib, record)
17087     SELECT  cp.id, cp.circ_lib, cn.record
17088     FROM  asset.copy cp
17089         JOIN asset.call_number cn ON (cn.id = cp.call_number)
17090         JOIN actor.org_unit a ON (cp.circ_lib = a.id)
17091         JOIN asset.copy_location cl ON (cp.location = cl.id)
17092         JOIN config.copy_status cs ON (cp.status = cs.id)
17093         JOIN biblio.record_entry b ON (cn.record = b.id)
17094     WHERE NOT cp.deleted
17095         AND NOT cn.deleted
17096         AND NOT b.deleted
17097         AND cs.opac_visible
17098         AND cl.opac_visible
17099         AND cp.opac_visible
17100         AND a.opac_visible;
17101
17102 $$ LANGUAGE SQL;
17103 COMMENT ON FUNCTION asset.refresh_opac_visible_copies_mat_view() IS $$
17104 Rebuild the copy OPAC visibility cache.  Useful during migrations.
17105 $$;
17106
17107 -- and actually populate the table
17108 SELECT asset.refresh_opac_visible_copies_mat_view();
17109
17110 CREATE OR REPLACE FUNCTION asset.cache_copy_visibility () RETURNS TRIGGER as $func$
17111 DECLARE
17112     add_query       TEXT;
17113     remove_query    TEXT;
17114     do_add          BOOLEAN := false;
17115     do_remove       BOOLEAN := false;
17116 BEGIN
17117     add_query := $$
17118             INSERT INTO asset.opac_visible_copies (id, circ_lib, record)
17119                 SELECT  cp.id, cp.circ_lib, cn.record
17120                   FROM  asset.copy cp
17121                         JOIN asset.call_number cn ON (cn.id = cp.call_number)
17122                         JOIN actor.org_unit a ON (cp.circ_lib = a.id)
17123                         JOIN asset.copy_location cl ON (cp.location = cl.id)
17124                         JOIN config.copy_status cs ON (cp.status = cs.id)
17125                         JOIN biblio.record_entry b ON (cn.record = b.id)
17126                   WHERE NOT cp.deleted
17127                         AND NOT cn.deleted
17128                         AND NOT b.deleted
17129                         AND cs.opac_visible
17130                         AND cl.opac_visible
17131                         AND cp.opac_visible
17132                         AND a.opac_visible
17133     $$;
17134  
17135     remove_query := $$ DELETE FROM asset.opac_visible_copies WHERE id IN ( SELECT id FROM asset.copy WHERE $$;
17136
17137     IF TG_OP = 'INSERT' THEN
17138
17139         IF TG_TABLE_NAME IN ('copy', 'unit') THEN
17140             add_query := add_query || 'AND cp.id = ' || NEW.id || ';';
17141             EXECUTE add_query;
17142         END IF;
17143
17144         RETURN NEW;
17145
17146     END IF;
17147
17148     -- handle items first, since with circulation activity
17149     -- their statuses change frequently
17150     IF TG_TABLE_NAME IN ('copy', 'unit') THEN
17151
17152         IF OLD.location    <> NEW.location OR
17153            OLD.call_number <> NEW.call_number OR
17154            OLD.status      <> NEW.status OR
17155            OLD.circ_lib    <> NEW.circ_lib THEN
17156             -- any of these could change visibility, but
17157             -- we'll save some queries and not try to calculate
17158             -- the change directly
17159             do_remove := true;
17160             do_add := true;
17161         ELSE
17162
17163             IF OLD.deleted <> NEW.deleted THEN
17164                 IF NEW.deleted THEN
17165                     do_remove := true;
17166                 ELSE
17167                     do_add := true;
17168                 END IF;
17169             END IF;
17170
17171             IF OLD.opac_visible <> NEW.opac_visible THEN
17172                 IF OLD.opac_visible THEN
17173                     do_remove := true;
17174                 ELSIF NOT do_remove THEN -- handle edge case where deleted item
17175                                         -- is also marked opac_visible
17176                     do_add := true;
17177                 END IF;
17178             END IF;
17179
17180         END IF;
17181
17182         IF do_remove THEN
17183             DELETE FROM asset.opac_visible_copies WHERE id = NEW.id;
17184         END IF;
17185         IF do_add THEN
17186             add_query := add_query || 'AND cp.id = ' || NEW.id || ';';
17187             EXECUTE add_query;
17188         END IF;
17189
17190         RETURN NEW;
17191
17192     END IF;
17193
17194     IF TG_TABLE_NAME IN ('call_number', 'record_entry') THEN -- these have a 'deleted' column
17195  
17196         IF OLD.deleted AND NEW.deleted THEN -- do nothing
17197
17198             RETURN NEW;
17199  
17200         ELSIF NEW.deleted THEN -- remove rows
17201  
17202             IF TG_TABLE_NAME = 'call_number' THEN
17203                 DELETE FROM asset.opac_visible_copies WHERE id IN (SELECT id FROM asset.copy WHERE call_number = NEW.id);
17204             ELSIF TG_TABLE_NAME = 'record_entry' THEN
17205                 DELETE FROM asset.opac_visible_copies WHERE record = NEW.id;
17206             END IF;
17207  
17208             RETURN NEW;
17209  
17210         ELSIF OLD.deleted THEN -- add rows
17211  
17212             IF TG_TABLE_NAME IN ('copy','unit') THEN
17213                 add_query := add_query || 'AND cp.id = ' || NEW.id || ';';
17214             ELSIF TG_TABLE_NAME = 'call_number' THEN
17215                 add_query := add_query || 'AND cp.call_number = ' || NEW.id || ';';
17216             ELSIF TG_TABLE_NAME = 'record_entry' THEN
17217                 add_query := add_query || 'AND cn.record = ' || NEW.id || ';';
17218             END IF;
17219  
17220             EXECUTE add_query;
17221             RETURN NEW;
17222  
17223         END IF;
17224  
17225     END IF;
17226
17227     IF TG_TABLE_NAME = 'call_number' THEN
17228
17229         IF OLD.record <> NEW.record THEN
17230             -- call number is linked to different bib
17231             remove_query := remove_query || 'call_number = ' || NEW.id || ');';
17232             EXECUTE remove_query;
17233             add_query := add_query || 'AND cp.call_number = ' || NEW.id || ';';
17234             EXECUTE add_query;
17235         END IF;
17236
17237         RETURN NEW;
17238
17239     END IF;
17240
17241     IF TG_TABLE_NAME IN ('record_entry') THEN
17242         RETURN NEW; -- don't have 'opac_visible'
17243     END IF;
17244
17245     -- actor.org_unit, asset.copy_location, asset.copy_status
17246     IF NEW.opac_visible = OLD.opac_visible THEN -- do nothing
17247
17248         RETURN NEW;
17249
17250     ELSIF NEW.opac_visible THEN -- add rows
17251
17252         IF TG_TABLE_NAME = 'org_unit' THEN
17253             add_query := add_query || 'AND cp.circ_lib = ' || NEW.id || ';';
17254         ELSIF TG_TABLE_NAME = 'copy_location' THEN
17255             add_query := add_query || 'AND cp.location = ' || NEW.id || ';';
17256         ELSIF TG_TABLE_NAME = 'copy_status' THEN
17257             add_query := add_query || 'AND cp.status = ' || NEW.id || ';';
17258         END IF;
17259  
17260         EXECUTE add_query;
17261  
17262     ELSE -- delete rows
17263
17264         IF TG_TABLE_NAME = 'org_unit' THEN
17265             remove_query := 'DELETE FROM asset.opac_visible_copies WHERE circ_lib = ' || NEW.id || ';';
17266         ELSIF TG_TABLE_NAME = 'copy_location' THEN
17267             remove_query := remove_query || 'location = ' || NEW.id || ');';
17268         ELSIF TG_TABLE_NAME = 'copy_status' THEN
17269             remove_query := remove_query || 'status = ' || NEW.id || ');';
17270         END IF;
17271  
17272         EXECUTE remove_query;
17273  
17274     END IF;
17275  
17276     RETURN NEW;
17277 END;
17278 $func$ LANGUAGE PLPGSQL;
17279 COMMENT ON FUNCTION asset.cache_copy_visibility() IS $$
17280 Trigger function to update the copy OPAC visiblity cache.
17281 $$;
17282 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();
17283 CREATE TRIGGER a_opac_vis_mat_view_tgr AFTER INSERT OR UPDATE ON asset.copy FOR EACH ROW EXECUTE PROCEDURE asset.cache_copy_visibility();
17284 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();
17285 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();
17286 CREATE TRIGGER a_opac_vis_mat_view_tgr AFTER INSERT OR UPDATE ON serial.unit FOR EACH ROW EXECUTE PROCEDURE asset.cache_copy_visibility();
17287 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();
17288 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();
17289
17290 -- must create this rule explicitly; it is not inherited from asset.copy
17291 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;
17292
17293 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);
17294
17295 CREATE OR REPLACE FUNCTION authority.merge_records ( target_record BIGINT, source_record BIGINT ) RETURNS INT AS $func$
17296 DECLARE
17297     moved_objects INT := 0;
17298     bib_id        INT := 0;
17299     bib_rec       biblio.record_entry%ROWTYPE;
17300     auth_link     authority.bib_linking%ROWTYPE;
17301     ingest_same   boolean;
17302 BEGIN
17303
17304     -- 1. Update all bib records with the ID from target_record in their $0
17305     FOR bib_rec IN SELECT bre.* FROM biblio.record_entry bre
17306       INNER JOIN authority.bib_linking abl ON abl.bib = bre.id
17307       WHERE abl.authority = source_record LOOP
17308
17309         UPDATE biblio.record_entry
17310           SET marc = REGEXP_REPLACE(marc,
17311             E'(<subfield\\s+code="0"\\s*>[^<]*?\\))' || source_record || '<',
17312             E'\\1' || target_record || '<', 'g')
17313           WHERE id = bib_rec.id;
17314
17315           moved_objects := moved_objects + 1;
17316     END LOOP;
17317
17318     -- 2. Grab the current value of reingest on same MARC flag
17319     SELECT enabled INTO ingest_same
17320       FROM config.internal_flag
17321       WHERE name = 'ingest.reingest.force_on_same_marc'
17322     ;
17323
17324     -- 3. Temporarily set reingest on same to TRUE
17325     UPDATE config.internal_flag
17326       SET enabled = TRUE
17327       WHERE name = 'ingest.reingest.force_on_same_marc'
17328     ;
17329
17330     -- 4. Make a harmless update to target_record to trigger auto-update
17331     --    in linked bibliographic records
17332     UPDATE authority.record_entry
17333       SET DELETED = FALSE
17334       WHERE id = source_record;
17335
17336     -- 5. "Delete" source_record
17337     DELETE FROM authority.record_entry
17338       WHERE id = source_record;
17339
17340     -- 6. Set "reingest on same MARC" flag back to initial value
17341     UPDATE config.internal_flag
17342       SET enabled = ingest_same
17343       WHERE name = 'ingest.reingest.force_on_same_marc'
17344     ;
17345
17346     RETURN moved_objects;
17347 END;
17348 $func$ LANGUAGE plpgsql;
17349
17350 -- serial.record_entry already had an owner column spelled "owning_lib"
17351 -- Adjust the table and affected functions accordingly
17352
17353 ALTER TABLE serial.record_entry DROP COLUMN owner;
17354
17355 CREATE TABLE actor.usr_saved_search (
17356     id              SERIAL          PRIMARY KEY,
17357         owner           INT             NOT NULL REFERENCES actor.usr (id)
17358                                         ON DELETE CASCADE
17359                                         DEFERRABLE INITIALLY DEFERRED,
17360         name            TEXT            NOT NULL,
17361         create_date     TIMESTAMPTZ     NOT NULL DEFAULT now(),
17362         query_text      TEXT            NOT NULL,
17363         query_type      TEXT            NOT NULL
17364                                         CONSTRAINT valid_query_text CHECK (
17365                                         query_type IN ( 'URL' )) DEFAULT 'URL',
17366                                         -- we may add other types someday
17367         target          TEXT            NOT NULL
17368                                         CONSTRAINT valid_target CHECK (
17369                                         target IN ( 'record', 'metarecord', 'callnumber' )),
17370         CONSTRAINT name_once_per_user UNIQUE (owner, name)
17371 );
17372
17373 -- Apply Dan Wells' changes to the serial schema, from the
17374 -- seials-integration branch
17375
17376 CREATE TABLE serial.subscription_note (
17377         id           SERIAL PRIMARY KEY,
17378         subscription INT    NOT NULL
17379                             REFERENCES serial.subscription (id)
17380                             ON DELETE CASCADE
17381                             DEFERRABLE INITIALLY DEFERRED,
17382         creator      INT    NOT NULL
17383                             REFERENCES actor.usr (id)
17384                             DEFERRABLE INITIALLY DEFERRED,
17385         create_date  TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
17386         pub          BOOL   NOT NULL DEFAULT FALSE,
17387         title        TEXT   NOT NULL,
17388         value        TEXT   NOT NULL
17389 );
17390 CREATE INDEX serial_subscription_note_sub_idx ON serial.subscription_note (subscription);
17391
17392 CREATE TABLE serial.distribution_note (
17393         id           SERIAL PRIMARY KEY,
17394         distribution INT    NOT NULL
17395                             REFERENCES serial.distribution (id)
17396                             ON DELETE CASCADE
17397                             DEFERRABLE INITIALLY DEFERRED,
17398         creator      INT    NOT NULL
17399                             REFERENCES actor.usr (id)
17400                             DEFERRABLE INITIALLY DEFERRED,
17401         create_date  TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
17402         pub          BOOL   NOT NULL DEFAULT FALSE,
17403         title        TEXT   NOT NULL,
17404         value        TEXT   NOT NULL
17405 );
17406 CREATE INDEX serial_distribution_note_dist_idx ON serial.distribution_note (distribution);
17407
17408 ------- Begin surgery on serial.unit
17409
17410 ALTER TABLE serial.unit
17411         DROP COLUMN label;
17412
17413 ALTER TABLE serial.unit
17414         RENAME COLUMN label_sort_key TO sort_key;
17415
17416 ALTER TABLE serial.unit
17417         RENAME COLUMN contents TO detailed_contents;
17418
17419 ALTER TABLE serial.unit
17420         ADD COLUMN summary_contents TEXT;
17421
17422 UPDATE serial.unit
17423 SET summary_contents = detailed_contents;
17424
17425 ALTER TABLE serial.unit
17426         ALTER column summary_contents SET NOT NULL;
17427
17428 ------- End surgery on serial.unit
17429
17430 -- 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' );
17431
17432 -- Now rebuild the constraints dropped via cascade.
17433 -- ALTER TABLE acq.provider    ADD CONSTRAINT provider_edi_default_fkey FOREIGN KEY (edi_default) REFERENCES acq.edi_account (id) DEFERRABLE INITIALLY DEFERRED;
17434 DROP INDEX IF EXISTS money.money_mat_summary_id_idx;
17435 ALTER TABLE money.materialized_billable_xact_summary ADD PRIMARY KEY (id);
17436
17437 -- ALTER TABLE staging.billing_address_stage ADD PRIMARY KEY (row_id);
17438
17439 DELETE FROM config.metabib_field_index_norm_map
17440     WHERE norm IN (
17441         SELECT id 
17442             FROM config.index_normalizer
17443             WHERE func IN ('first_word', 'naco_normalize', 'split_date_range')
17444     )
17445     AND field = 18
17446 ;
17447
17448 -- We won't necessarily use all of these, but they are here for completeness.
17449 -- Source is the EDI spec 6063 codelist, eg: http://www.stylusstudio.com/edifact/D04B/6063.htm
17450 -- Values are the EDI code value + 1200
17451
17452 INSERT INTO acq.cancel_reason (org_unit, keep_debits, id, label, description) VALUES 
17453 (1, 't', 1201, 'Discrete quantity', 'Individually separated and distinct quantity.'),
17454 (1, 't', 1202, 'Charge', 'Quantity relevant for charge.'),
17455 (1, 't', 1203, 'Cumulative quantity', 'Quantity accumulated.'),
17456 (1, 't', 1204, 'Interest for overdrawn account', 'Interest for overdrawing the account.'),
17457 (1, 't', 1205, 'Active ingredient dose per unit', 'The dosage of active ingredient per unit.'),
17458 (1, 't', 1206, 'Auditor', 'The number of entities that audit accounts.'),
17459 (1, 't', 1207, 'Branch locations, leased', 'The number of branch locations being leased by an entity.'),
17460 (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.'),
17461 (1, 't', 1209, 'Branch locations, owned', 'The number of branch locations owned by an entity.'),
17462 (1, 't', 1210, 'Judgements registered', 'The number of judgements registered against an entity.'),
17463 (1, 't', 1211, 'Split quantity', 'Part of the whole quantity.'),
17464 (1, 't', 1212, 'Despatch quantity', 'Quantity despatched by the seller.'),
17465 (1, 't', 1213, 'Liens registered', 'The number of liens registered against an entity.'),
17466 (1, 't', 1214, 'Livestock', 'The number of animals kept for use or profit.'),
17467 (1, 't', 1215, 'Insufficient funds returned cheques', 'The number of cheques returned due to insufficient funds.'),
17468 (1, 't', 1216, 'Stolen cheques', 'The number of stolen cheques.'),
17469 (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.'),
17470 (1, 't', 1218, 'Previous quantity', 'Quantity previously referenced.'),
17471 (1, 't', 1219, 'Paid-in security shares', 'The number of security shares issued and for which full payment has been made.'),
17472 (1, 't', 1220, 'Unusable quantity', 'Quantity not usable.'),
17473 (1, 't', 1221, 'Ordered quantity', '[6024] The quantity which has been ordered.'),
17474 (1, 't', 1222, 'Quantity at 100%', 'Equivalent quantity at 100% purity.'),
17475 (1, 't', 1223, 'Active ingredient', 'Quantity at 100% active agent content.'),
17476 (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.'),
17477 (1, 't', 1225, 'Retail sales', 'Quantity of retail point of sale activity.'),
17478 (1, 't', 1226, 'Promotion quantity', 'A quantity associated with a promotional event.'),
17479 (1, 't', 1227, 'On hold for shipment', 'Article received which cannot be shipped in its present form.'),
17480 (1, 't', 1228, 'Military sales quantity', 'Quantity of goods or services sold to a military organization.'),
17481 (1, 't', 1229, 'On premises sales',  'Sale of product in restaurants or bars.'),
17482 (1, 't', 1230, 'Off premises sales', 'Sale of product directly to a store.'),
17483 (1, 't', 1231, 'Estimated annual volume', 'Volume estimated for a year.'),
17484 (1, 't', 1232, 'Minimum delivery batch', 'Minimum quantity of goods delivered at one time.'),
17485 (1, 't', 1233, 'Maximum delivery batch', 'Maximum quantity of goods delivered at one time.'),
17486 (1, 't', 1234, 'Pipes', 'The number of tubes used to convey a substance.'),
17487 (1, 't', 1235, 'Price break from', 'The minimum quantity of a quantity range for a specified (unit) price.'),
17488 (1, 't', 1236, 'Price break to', 'Maximum quantity to which the price break applies.'),
17489 (1, 't', 1237, 'Poultry', 'The number of domestic fowl.'),
17490 (1, 't', 1238, 'Secured charges registered', 'The number of secured charges registered against an entity.'),
17491 (1, 't', 1239, 'Total properties owned', 'The total number of properties owned by an entity.'),
17492 (1, 't', 1240, 'Normal delivery', 'Quantity normally delivered by the seller.'),
17493 (1, 't', 1241, 'Sales quantity not included in the replenishment', 'calculation Sales which will not be included in the calculation of replenishment requirements.'),
17494 (1, 't', 1242, 'Maximum supply quantity, supplier endorsed', 'Maximum supply quantity endorsed by a supplier.'),
17495 (1, 't', 1243, 'Buyer', 'The number of buyers.'),
17496 (1, 't', 1244, 'Debenture bond', 'The number of fixed-interest bonds of an entity backed by general credit rather than specified assets.'),
17497 (1, 't', 1245, 'Debentures filed against directors', 'The number of notices of indebtedness filed against an entity''s directors.'),
17498 (1, 't', 1246, 'Pieces delivered', 'Number of pieces actually received at the final destination.'),
17499 (1, 't', 1247, 'Invoiced quantity', 'The quantity as per invoice.'),
17500 (1, 't', 1248, 'Received quantity', 'The quantity which has been received.'),
17501 (1, 't', 1249, 'Chargeable distance', '[6110] The distance between two points for which a specific tariff applies.'),
17502 (1, 't', 1250, 'Disposition undetermined quantity', 'Product quantity that has not yet had its disposition determined.'),
17503 (1, 't', 1251, 'Inventory category transfer', 'Inventory that has been moved from one inventory category to another.'),
17504 (1, 't', 1252, 'Quantity per pack', 'Quantity for each pack.'),
17505 (1, 't', 1253, 'Minimum order quantity', 'Minimum quantity of goods for an order.'),
17506 (1, 't', 1254, 'Maximum order quantity', 'Maximum quantity of goods for an order.'),
17507 (1, 't', 1255, 'Total sales', 'The summation of total quantity sales.'),
17508 (1, 't', 1256, 'Wholesaler to wholesaler sales', 'Sale of product to other wholesalers by a wholesaler.'),
17509 (1, 't', 1257, 'In transit quantity', 'A quantity that is en route.'),
17510 (1, 't', 1258, 'Quantity withdrawn', 'Quantity withdrawn from a location.'),
17511 (1, 't', 1259, 'Numbers of consumer units in the traded unit', 'Number of units for consumer sales in a unit for trading.'),
17512 (1, 't', 1260, 'Current inventory quantity available for shipment', 'Current inventory quantity available for shipment.'),
17513 (1, 't', 1261, 'Return quantity', 'Quantity of goods returned.'),
17514 (1, 't', 1262, 'Sorted quantity', 'The quantity that is sorted.'),
17515 (1, 'f', 1263, 'Sorted quantity rejected', 'The sorted quantity that is rejected.'),
17516 (1, 't', 1264, 'Scrap quantity', 'Remainder of the total quantity after split deliveries.'),
17517 (1, 'f', 1265, 'Destroyed quantity', 'Quantity of goods destroyed.'),
17518 (1, 't', 1266, 'Committed quantity', 'Quantity a party is committed to.'),
17519 (1, 't', 1267, 'Estimated reading quantity', 'The value that is estimated to be the reading of a measuring device (e.g. meter).'),
17520 (1, 't', 1268, 'End quantity', 'The quantity recorded at the end of an agreement or period.'),
17521 (1, 't', 1269, 'Start quantity', 'The quantity recorded at the start of an agreement or period.'),
17522 (1, 't', 1270, 'Cumulative quantity received', 'Cumulative quantity of all deliveries of this article received by the buyer.'),
17523 (1, 't', 1271, 'Cumulative quantity ordered', 'Cumulative quantity of all deliveries, outstanding and scheduled orders.'),
17524 (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.'),
17525 (1, 't', 1273, 'Outstanding quantity', 'Difference between quantity ordered and quantity received.'),
17526 (1, 't', 1274, 'Latest cumulative quantity', 'Cumulative quantity after complete delivery of all scheduled quantities of the product.'),
17527 (1, 't', 1275, 'Previous highest cumulative quantity', 'Cumulative quantity after complete delivery of all scheduled quantities of the product from a prior schedule period.'),
17528 (1, 't', 1276, 'Adjusted corrector reading', 'A corrector reading after it has been adjusted.'),
17529 (1, 't', 1277, 'Work days', 'Number of work days, e.g. per respective period.'),
17530 (1, 't', 1278, 'Cumulative quantity scheduled', 'Adding the quantity actually scheduled to previous cumulative quantity.'),
17531 (1, 't', 1279, 'Previous cumulative quantity', 'Cumulative quantity prior the actual order.'),
17532 (1, 't', 1280, 'Unadjusted corrector reading', 'A corrector reading before it has been adjusted.'),
17533 (1, 't', 1281, 'Extra unplanned delivery', 'Non scheduled additional quantity.'),
17534 (1, 't', 1282, 'Quantity requirement for sample inspection', 'Required quantity for sample inspection.'),
17535 (1, 't', 1283, 'Backorder quantity', 'The quantity of goods that is on back-order.'),
17536 (1, 't', 1284, 'Urgent delivery quantity', 'Quantity for urgent delivery.'),
17537 (1, 'f', 1285, 'Previous order quantity to be cancelled', 'Quantity ordered previously to be cancelled.'),
17538 (1, 't', 1286, 'Normal reading quantity', 'The value recorded or read from a measuring device (e.g. meter) in the normal conditions.'),
17539 (1, 't', 1287, 'Customer reading quantity', 'The value recorded or read from a measuring device (e.g. meter) by the customer.'),
17540 (1, 't', 1288, 'Information reading quantity', 'The value recorded or read from a measuring device (e.g. meter) for information purposes.'),
17541 (1, 't', 1289, 'Quality control held', 'Quantity of goods held pending completion of a quality control assessment.'),
17542 (1, 't', 1290, 'As is quantity', 'Quantity as it is in the existing circumstances.'),
17543 (1, 't', 1291, 'Open quantity', 'Quantity remaining after partial delivery.'),
17544 (1, 't', 1292, 'Final delivery quantity', 'Quantity of final delivery to a respective order.'),
17545 (1, 't', 1293, 'Subsequent delivery quantity', 'Quantity delivered to a respective order after it''s final delivery.'),
17546 (1, 't', 1294, 'Substitutional quantity', 'Quantity delivered replacing previous deliveries.'),
17547 (1, 't', 1295, 'Redelivery after post processing', 'Quantity redelivered after post processing.'),
17548 (1, 'f', 1296, 'Quality control failed', 'Quantity of goods which have failed quality control.'),
17549 (1, 't', 1297, 'Minimum inventory', 'Minimum stock quantity on which replenishment is based.'),
17550 (1, 't', 1298, 'Maximum inventory', 'Maximum stock quantity on which replenishment is based.'),
17551 (1, 't', 1299, 'Estimated quantity', 'Quantity estimated.'),
17552 (1, 't', 1300, 'Chargeable weight', 'The weight on which charges are based.'),
17553 (1, 't', 1301, 'Chargeable gross weight', 'The gross weight on which charges are based.'),
17554 (1, 't', 1302, 'Chargeable tare weight', 'The tare weight on which charges are based.'),
17555 (1, 't', 1303, 'Chargeable number of axles', 'The number of axles on which charges are based.'),
17556 (1, 't', 1304, 'Chargeable number of containers', 'The number of containers on which charges are based.'),
17557 (1, 't', 1305, 'Chargeable number of rail wagons', 'The number of rail wagons on which charges are based.'),
17558 (1, 't', 1306, 'Chargeable number of packages', 'The number of packages on which charges are based.'),
17559 (1, 't', 1307, 'Chargeable number of units', 'The number of units on which charges are based.'),
17560 (1, 't', 1308, 'Chargeable period', 'The period of time on which charges are based.'),
17561 (1, 't', 1309, 'Chargeable volume', 'The volume on which charges are based.'),
17562 (1, 't', 1310, 'Chargeable cubic measurements', 'The cubic measurements on which charges are based.'),
17563 (1, 't', 1311, 'Chargeable surface', 'The surface area on which charges are based.'),
17564 (1, 't', 1312, 'Chargeable length', 'The length on which charges are based.'),
17565 (1, 't', 1313, 'Quantity to be delivered', 'The quantity to be delivered.'),
17566 (1, 't', 1314, 'Number of passengers', 'Total number of passengers on the conveyance.'),
17567 (1, 't', 1315, 'Number of crew', 'Total number of crew members on the conveyance.'),
17568 (1, 't', 1316, 'Number of transport documents', 'Total number of air waybills, bills of lading, etc. being reported for a specific conveyance.'),
17569 (1, 't', 1317, 'Quantity landed', 'Quantity of goods actually arrived.'),
17570 (1, 't', 1318, 'Quantity manifested', 'Quantity of goods contracted for delivery by the carrier.'),
17571 (1, 't', 1319, 'Short shipped', 'Indication that part of the consignment was not shipped.'),
17572 (1, 't', 1320, 'Split shipment', 'Indication that the consignment has been split into two or more shipments.'),
17573 (1, 't', 1321, 'Over shipped', 'The quantity of goods shipped that exceeds the quantity contracted.'),
17574 (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.'),
17575 (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.'),
17576 (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.'),
17577 (1, 'f', 1325, 'Pilferage goods', 'Quantity of goods stolen during transport.'),
17578 (1, 'f', 1326, 'Lost goods', 'Quantity of goods that disappeared in transport.'),
17579 (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.'),
17580 (1, 't', 1328, 'Quantity loaded', 'Quantity of goods loaded onto a means of transport.'),
17581 (1, 't', 1329, 'Units per unit price', 'Number of units per unit price.'),
17582 (1, 't', 1330, 'Allowance', 'Quantity relevant for allowance.'),
17583 (1, 't', 1331, 'Delivery quantity', 'Quantity required by buyer to be delivered.'),
17584 (1, 't', 1332, 'Cumulative quantity, preceding period, planned', 'Cumulative quantity originally planned for the preceding period.'),
17585 (1, 't', 1333, 'Cumulative quantity, preceding period, reached', 'Cumulative quantity reached in the preceding period.'),
17586 (1, 't', 1334, 'Cumulative quantity, actual planned',            'Cumulative quantity planned for now.'),
17587 (1, 't', 1335, 'Period quantity, planned', 'Quantity planned for this period.'),
17588 (1, 't', 1336, 'Period quantity, reached', 'Quantity reached during this period.'),
17589 (1, 't', 1337, 'Cumulative quantity, preceding period, estimated', 'Estimated cumulative quantity reached in the preceding period.'),
17590 (1, 't', 1338, 'Cumulative quantity, actual estimated',            'Estimated cumulative quantity reached now.'),
17591 (1, 't', 1339, 'Cumulative quantity, preceding period, measured', 'Surveyed cumulative quantity reached in the preceding period.'),
17592 (1, 't', 1340, 'Cumulative quantity, actual measured', 'Surveyed cumulative quantity reached now.'),
17593 (1, 't', 1341, 'Period quantity, measured',            'Surveyed quantity reached during this period.'),
17594 (1, 't', 1342, 'Total quantity, planned', 'Total quantity planned.'),
17595 (1, 't', 1343, 'Quantity, remaining', 'Quantity remaining.'),
17596 (1, 't', 1344, 'Tolerance', 'Plus or minus tolerance expressed as a monetary amount.'),
17597 (1, 't', 1345, 'Actual stock',          'The stock on hand, undamaged, and available for despatch, sale or use.'),
17598 (1, 't', 1346, 'Model or target stock', 'The stock quantity required or planned to have on hand, undamaged and available for use.'),
17599 (1, 't', 1347, 'Direct shipment quantity', 'Quantity to be shipped directly to a customer from a manufacturing site.'),
17600 (1, 't', 1348, 'Amortization total quantity',     'Indication of final quantity for amortization.'),
17601 (1, 't', 1349, 'Amortization order quantity',     'Indication of actual share of the order quantity for amortization.'),
17602 (1, 't', 1350, 'Amortization cumulated quantity', 'Indication of actual cumulated quantity of previous and actual amortization order quantity.'),
17603 (1, 't', 1351, 'Quantity advised',  'Quantity advised by supplier or shipper, in contrast to quantity actually received.'),
17604 (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.'),
17605 (1, 't', 1353, 'Statistical sales quantity', 'Quantity of goods sold in a specified period.'),
17606 (1, 't', 1354, 'Sales quantity planned',     'Quantity of goods required to meet future demands. - Market intelligence quantity.'),
17607 (1, 't', 1355, 'Replenishment quantity',     'Quantity required to maintain the requisite on-hand stock of goods.'),
17608 (1, 't', 1356, 'Inventory movement quantity', 'To specify the quantity of an inventory movement.'),
17609 (1, 't', 1357, 'Opening stock balance quantity', 'To specify the quantity of an opening stock balance.'),
17610 (1, 't', 1358, 'Closing stock balance quantity', 'To specify the quantity of a closing stock balance.'),
17611 (1, 't', 1359, 'Number of stops', 'Number of times a means of transport stops before arriving at destination.'),
17612 (1, 't', 1360, 'Minimum production batch', 'The quantity specified is the minimum output from a single production run.'),
17613 (1, 't', 1361, 'Dimensional sample quantity', 'The quantity defined is a sample for the purpose of validating dimensions.'),
17614 (1, 't', 1362, 'Functional sample quantity', 'The quantity defined is a sample for the purpose of validating function and performance.'),
17615 (1, 't', 1363, 'Pre-production quantity', 'Quantity of the referenced item required prior to full production.'),
17616 (1, 't', 1364, 'Delivery batch', 'Quantity of the referenced item which constitutes a standard batch for deliver purposes.'),
17617 (1, 't', 1365, 'Delivery batch multiple', 'The multiples in which delivery batches can be supplied.'),
17618 (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.'),
17619 (1, 't', 1367, 'Total delivery quantity',  'The total quantity required by the buyer to be delivered.'),
17620 (1, 't', 1368, 'Single delivery quantity', 'The quantity required by the buyer to be delivered in a single shipment.'),
17621 (1, 't', 1369, 'Supplied quantity',  'Quantity of the referenced item actually shipped.'),
17622 (1, 't', 1370, 'Allocated quantity', 'Quantity of the referenced item allocated from available stock for delivery.'),
17623 (1, 't', 1371, 'Maximum stackability', 'The number of pallets/handling units which can be safely stacked one on top of another.'),
17624 (1, 't', 1372, 'Amortisation quantity', 'The quantity of the referenced item which has a cost for tooling amortisation included in the item price.'),
17625 (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.'),
17626 (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.'),
17627 (1, 't', 1375, 'Number of moulds', 'The number of pressing moulds contained within a single piece of the referenced tooling.'),
17628 (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.'),
17629 (1, 't', 1377, 'Periodic capacity of tooling', 'Maximum production output of the referenced tool over a period of time.'),
17630 (1, 't', 1378, 'Lifetime capacity of tooling', 'Maximum production output of the referenced tool over its productive lifetime.'),
17631 (1, 't', 1379, 'Number of deliveries per despatch period', 'The number of deliveries normally expected to be despatched within each despatch period.'),
17632 (1, 't', 1380, 'Provided quantity', 'The quantity of a referenced component supplied by the buyer for manufacturing of an ordered item.'),
17633 (1, 't', 1381, 'Maximum production batch', 'The quantity specified is the maximum output from a single production run.'),
17634 (1, 'f', 1382, 'Cancelled quantity', 'Quantity of the referenced item which has previously been ordered and is now cancelled.'),
17635 (1, 't', 1383, 'No delivery requirement in this instruction', 'This delivery instruction does not contain any delivery requirements.'),
17636 (1, 't', 1384, 'Quantity of material in ordered time', 'Quantity of the referenced material within the ordered time.'),
17637 (1, 'f', 1385, 'Rejected quantity', 'The quantity of received goods rejected for quantity reasons.'),
17638 (1, 't', 1386, 'Cumulative quantity scheduled up to accumulation start date', 'The cumulative quantity scheduled up to the accumulation start date.'),
17639 (1, 't', 1387, 'Quantity scheduled', 'The quantity scheduled for delivery.'),
17640 (1, 't', 1388, 'Number of identical handling units', 'Number of identical handling units in terms of type and contents.'),
17641 (1, 't', 1389, 'Number of packages in handling unit', 'The number of packages contained in one handling unit.'),
17642 (1, 't', 1390, 'Despatch note quantity', 'The item quantity specified on the despatch note.'),
17643 (1, 't', 1391, 'Adjustment to inventory quantity', 'An adjustment to inventory quantity.'),
17644 (1, 't', 1392, 'Free goods quantity',    'Quantity of goods which are free of charge.'),
17645 (1, 't', 1393, 'Free quantity included', 'Quantity included to which no charge is applicable.'),
17646 (1, 't', 1394, 'Received and accepted',  'Quantity which has been received and accepted at a given location.'),
17647 (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.'),
17648 (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.'),
17649 (1, 't', 1397, 'Reordering level', 'Quantity at which an order may be triggered to replenish.'),
17650 (1, 't', 1399, 'Inventory withdrawal quantity', 'Quantity which has been withdrawn from inventory since the last inventory report.'),
17651 (1, 't', 1400, 'Free quantity not included', 'Free quantity not included in ordered quantity.'),
17652 (1, 't', 1401, 'Recommended overhaul and repair quantity', 'To indicate the recommended quantity of an article required to support overhaul and repair activities.'),
17653 (1, 't', 1402, 'Quantity per next higher assembly', 'To indicate the quantity required for the next higher assembly.'),
17654 (1, 't', 1403, 'Quantity per unit of issue', 'Provides the standard quantity of an article in which one unit can be issued.'),
17655 (1, 't', 1404, 'Cumulative scrap quantity',  'Provides the cumulative quantity of an item which has been identified as scrapped.'),
17656 (1, 't', 1405, 'Publication turn size', 'The quantity of magazines or newspapers grouped together with the spine facing alternate directions in a bundle.'),
17657 (1, 't', 1406, 'Recommended maintenance quantity', 'Recommended quantity of an article which is required to meet an agreed level of maintenance.'),
17658 (1, 't', 1407, 'Labour hours', 'Number of labour hours.'),
17659 (1, 't', 1408, 'Quantity requirement for maintenance and repair of', 'equipment Quantity of the material needed to maintain and repair equipment.'),
17660 (1, 't', 1409, 'Additional replenishment demand quantity', 'Incremental needs over and above normal replenishment calculations, but not intended to permanently change the model parameters.'),
17661 (1, 't', 1410, 'Returned by consumer quantity', 'Quantity returned by a consumer.'),
17662 (1, 't', 1411, 'Replenishment override quantity', 'Quantity to override the normal replenishment model calculations, but not intended to permanently change the model parameters.'),
17663 (1, 't', 1412, 'Quantity sold, net', 'Net quantity sold which includes returns of saleable inventory and other adjustments.'),
17664 (1, 't', 1413, 'Transferred out quantity',   'Quantity which was transferred out of this location.'),
17665 (1, 't', 1414, 'Transferred in quantity',    'Quantity which was transferred into this location.'),
17666 (1, 't', 1415, 'Unsaleable quantity',        'Quantity of inventory received which cannot be sold in its present condition.'),
17667 (1, 't', 1416, 'Consumer reserved quantity', 'Quantity reserved for consumer delivery or pickup and not yet withdrawn from inventory.'),
17668 (1, 't', 1417, 'Out of inventory quantity',  'Quantity of inventory which was requested but was not available.'),
17669 (1, 't', 1418, 'Quantity returned, defective or damaged', 'Quantity returned in a damaged or defective condition.'),
17670 (1, 't', 1419, 'Taxable quantity',           'Quantity subject to taxation.'),
17671 (1, 't', 1420, 'Meter reading', 'The numeric value of measure units counted by a meter.'),
17672 (1, 't', 1421, 'Maximum requestable quantity', 'The maximum quantity which may be requested.'),
17673 (1, 't', 1422, 'Minimum requestable quantity', 'The minimum quantity which may be requested.'),
17674 (1, 't', 1423, 'Daily average quantity', 'The quantity for a defined period divided by the number of days of the period.'),
17675 (1, 't', 1424, 'Budgeted hours',     'The number of budgeted hours.'),
17676 (1, 't', 1425, 'Actual hours',       'The number of actual hours.'),
17677 (1, 't', 1426, 'Earned value hours', 'The number of earned value hours.'),
17678 (1, 't', 1427, 'Estimated hours',    'The number of estimated hours.'),
17679 (1, 't', 1428, 'Level resource task quantity', 'Quantity of a resource that is level for the duration of the task.'),
17680 (1, 't', 1429, 'Available resource task quantity', 'Quantity of a resource available to complete a task.'),
17681 (1, 't', 1430, 'Work time units',   'Quantity of work units of time.'),
17682 (1, 't', 1431, 'Daily work shifts', 'Quantity of work shifts per day.'),
17683 (1, 't', 1432, 'Work time units per shift', 'Work units of time per work shift.'),
17684 (1, 't', 1433, 'Work calendar units',       'Work calendar units of time.'),
17685 (1, 't', 1434, 'Elapsed duration',   'Quantity representing the elapsed duration.'),
17686 (1, 't', 1435, 'Remaining duration', 'Quantity representing the remaining duration.'),
17687 (1, 't', 1436, 'Original duration',  'Quantity representing the original duration.'),
17688 (1, 't', 1437, 'Current duration',   'Quantity representing the current duration.'),
17689 (1, 't', 1438, 'Total float time',   'Quantity representing the total float time.'),
17690 (1, 't', 1439, 'Free float time',    'Quantity representing the free float time.'),
17691 (1, 't', 1440, 'Lag time',           'Quantity representing lag time.'),
17692 (1, 't', 1441, 'Lead time',          'Quantity representing lead time.'),
17693 (1, 't', 1442, 'Number of months', 'The number of months.'),
17694 (1, 't', 1443, 'Reserved quantity customer direct delivery sales', 'Quantity of products reserved for sales delivered direct to the customer.'),
17695 (1, 't', 1444, 'Reserved quantity retail sales', 'Quantity of products reserved for retail sales.'),
17696 (1, 't', 1445, 'Consolidated discount inventory', 'A quantity of inventory supplied at consolidated discount terms.'),
17697 (1, 't', 1446, 'Returns replacement quantity',    'A quantity of goods issued as a replacement for a returned quantity.'),
17698 (1, 't', 1447, 'Additional promotion sales forecast quantity', 'A forecast of additional quantity which will be sold during a period of promotional activity.'),
17699 (1, 't', 1448, 'Reserved quantity', 'Quantity reserved for specific purposes.'),
17700 (1, 't', 1449, 'Quantity displayed not available for sale', 'Quantity displayed within a retail outlet but not available for sale.'),
17701 (1, 't', 1450, 'Inventory discrepancy', 'The difference recorded between theoretical and physical inventory.'),
17702 (1, 't', 1451, 'Incremental order quantity', 'The incremental quantity by which ordering is carried out.'),
17703 (1, 't', 1452, 'Quantity requiring manipulation before despatch', 'A quantity of goods which needs manipulation before despatch.'),
17704 (1, 't', 1453, 'Quantity in quarantine',              'A quantity of goods which are held in a restricted area for quarantine purposes.'),
17705 (1, 't', 1454, 'Quantity withheld by owner of goods', 'A quantity of goods which has been withheld by the owner of the goods.'),
17706 (1, 't', 1455, 'Quantity not available for despatch', 'A quantity of goods not available for despatch.'),
17707 (1, 't', 1456, 'Quantity awaiting delivery', 'Quantity of goods which are awaiting delivery.'),
17708 (1, 't', 1457, 'Quantity in physical inventory',      'A quantity of goods held in physical inventory.'),
17709 (1, 't', 1458, 'Quantity held by logistic service provider', 'Quantity of goods under the control of a logistic service provider.'),
17710 (1, 't', 1459, 'Optimal quantity', 'The optimal quantity for a given purpose.'),
17711 (1, 't', 1460, 'Delivery quantity balance', 'The difference between the scheduled quantity and the quantity delivered to the consignee at a given date.'),
17712 (1, 't', 1461, 'Cumulative quantity shipped', 'Cumulative quantity of all shipments.'),
17713 (1, 't', 1462, 'Quantity suspended', 'The quantity of something which is suspended.'),
17714 (1, 't', 1463, 'Control quantity', 'The quantity designated for control purposes.'),
17715 (1, 't', 1464, 'Equipment quantity', 'A count of a quantity of equipment.'),
17716 (1, 't', 1465, 'Factor', 'Number by which the measured unit has to be multiplied to calculate the units used.'),
17717 (1, 't', 1466, 'Unsold quantity held by wholesaler', 'Unsold quantity held by the wholesaler.'),
17718 (1, 't', 1467, 'Quantity held by delivery vehicle', 'Quantity of goods held by the delivery vehicle.'),
17719 (1, 't', 1468, 'Quantity held by retail outlet', 'Quantity held by the retail outlet.'),
17720 (1, 'f', 1469, 'Rejected return quantity', 'A quantity for return which has been rejected.'),
17721 (1, 't', 1470, 'Accounts', 'The number of accounts.'),
17722 (1, 't', 1471, 'Accounts placed for collection', 'The number of accounts placed for collection.'),
17723 (1, 't', 1472, 'Activity codes', 'The number of activity codes.'),
17724 (1, 't', 1473, 'Agents', 'The number of agents.'),
17725 (1, 't', 1474, 'Airline attendants', 'The number of airline attendants.'),
17726 (1, 't', 1475, 'Authorised shares',  'The number of shares authorised for issue.'),
17727 (1, 't', 1476, 'Employee average',   'The average number of employees.'),
17728 (1, 't', 1477, 'Branch locations',   'The number of branch locations.'),
17729 (1, 't', 1478, 'Capital changes',    'The number of capital changes made.'),
17730 (1, 't', 1479, 'Clerks', 'The number of clerks.'),
17731 (1, 't', 1480, 'Companies in same activity', 'The number of companies doing business in the same activity category.'),
17732 (1, 't', 1481, 'Companies included in consolidated financial statement', 'The number of companies included in a consolidated financial statement.'),
17733 (1, 't', 1482, 'Cooperative shares', 'The number of cooperative shares.'),
17734 (1, 't', 1483, 'Creditors',   'The number of creditors.'),
17735 (1, 't', 1484, 'Departments', 'The number of departments.'),
17736 (1, 't', 1485, 'Design employees', 'The number of employees involved in the design process.'),
17737 (1, 't', 1486, 'Physicians', 'The number of medical doctors.'),
17738 (1, 't', 1487, 'Domestic affiliated companies', 'The number of affiliated companies located within the country.'),
17739 (1, 't', 1488, 'Drivers', 'The number of drivers.'),
17740 (1, 't', 1489, 'Employed at location',     'The number of employees at the specified location.'),
17741 (1, 't', 1490, 'Employed by this company', 'The number of employees at the specified company.'),
17742 (1, 't', 1491, 'Total employees',    'The total number of employees.'),
17743 (1, 't', 1492, 'Employees shared',   'The number of employees shared among entities.'),
17744 (1, 't', 1493, 'Engineers',          'The number of engineers.'),
17745 (1, 't', 1494, 'Estimated accounts', 'The estimated number of accounts.'),
17746 (1, 't', 1495, 'Estimated employees at location', 'The estimated number of employees at the specified location.'),
17747 (1, 't', 1496, 'Estimated total employees',       'The total estimated number of employees.'),
17748 (1, 't', 1497, 'Executives', 'The number of executives.'),
17749 (1, 't', 1498, 'Agricultural workers',   'The number of agricultural workers.'),
17750 (1, 't', 1499, 'Financial institutions', 'The number of financial institutions.'),
17751 (1, 't', 1500, 'Floors occupied', 'The number of floors occupied.'),
17752 (1, 't', 1501, 'Foreign related entities', 'The number of related entities located outside the country.'),
17753 (1, 't', 1502, 'Group employees',    'The number of employees within the group.'),
17754 (1, 't', 1503, 'Indirect employees', 'The number of employees not associated with direct production.'),
17755 (1, 't', 1504, 'Installers',    'The number of employees involved with the installation process.'),
17756 (1, 't', 1505, 'Invoices',      'The number of invoices.'),
17757 (1, 't', 1506, 'Issued shares', 'The number of shares actually issued.'),
17758 (1, 't', 1507, 'Labourers',     'The number of labourers.'),
17759 (1, 't', 1508, 'Manufactured units', 'The number of units manufactured.'),
17760 (1, 't', 1509, 'Maximum number of employees', 'The maximum number of people employed.'),
17761 (1, 't', 1510, 'Maximum number of employees at location', 'The maximum number of people employed at a location.'),
17762 (1, 't', 1511, 'Members in group', 'The number of members within a group.'),
17763 (1, 't', 1512, 'Minimum number of employees at location', 'The minimum number of people employed at a location.'),
17764 (1, 't', 1513, 'Minimum number of employees', 'The minimum number of people employed.'),
17765 (1, 't', 1514, 'Non-union employees', 'The number of employees not belonging to a labour union.'),
17766 (1, 't', 1515, 'Floors', 'The number of floors in a building.'),
17767 (1, 't', 1516, 'Nurses', 'The number of nurses.'),
17768 (1, 't', 1517, 'Office workers', 'The number of workers in an office.'),
17769 (1, 't', 1518, 'Other employees', 'The number of employees otherwise categorised.'),
17770 (1, 't', 1519, 'Part time employees', 'The number of employees working on a part time basis.'),
17771 (1, 't', 1520, 'Accounts payable average overdue days', 'The average number of days accounts payable are overdue.'),
17772 (1, 't', 1521, 'Pilots', 'The number of pilots.'),
17773 (1, 't', 1522, 'Plant workers', 'The number of workers within a plant.'),
17774 (1, 't', 1523, 'Previous number of accounts', 'The number of accounts which preceded the current count.'),
17775 (1, 't', 1524, 'Previous number of branch locations', 'The number of branch locations which preceded the current count.'),
17776 (1, 't', 1525, 'Principals included as employees', 'The number of principals which are included in the count of employees.'),
17777 (1, 't', 1526, 'Protested bills', 'The number of bills which are protested.'),
17778 (1, 't', 1527, 'Registered brands distributed', 'The number of registered brands which are being distributed.'),
17779 (1, 't', 1528, 'Registered brands manufactured', 'The number of registered brands which are being manufactured.'),
17780 (1, 't', 1529, 'Related business entities', 'The number of related business entities.'),
17781 (1, 't', 1530, 'Relatives employed', 'The number of relatives which are counted as employees.'),
17782 (1, 't', 1531, 'Rooms',        'The number of rooms.'),
17783 (1, 't', 1532, 'Salespersons', 'The number of salespersons.'),
17784 (1, 't', 1533, 'Seats',        'The number of seats.'),
17785 (1, 't', 1534, 'Shareholders', 'The number of shareholders.'),
17786 (1, 't', 1535, 'Shares of common stock', 'The number of shares of common stock.'),
17787 (1, 't', 1536, 'Shares of preferred stock', 'The number of shares of preferred stock.'),
17788 (1, 't', 1537, 'Silent partners', 'The number of silent partners.'),
17789 (1, 't', 1538, 'Subcontractors',  'The number of subcontractors.'),
17790 (1, 't', 1539, 'Subsidiaries',    'The number of subsidiaries.'),
17791 (1, 't', 1540, 'Law suits',       'The number of law suits.'),
17792 (1, 't', 1541, 'Suppliers',       'The number of suppliers.'),
17793 (1, 't', 1542, 'Teachers',        'The number of teachers.'),
17794 (1, 't', 1543, 'Technicians',     'The number of technicians.'),
17795 (1, 't', 1544, 'Trainees',        'The number of trainees.'),
17796 (1, 't', 1545, 'Union employees', 'The number of employees who are members of a labour union.'),
17797 (1, 't', 1546, 'Number of units', 'The quantity of units.'),
17798 (1, 't', 1547, 'Warehouse employees', 'The number of employees who work in a warehouse setting.'),
17799 (1, 't', 1548, 'Shareholders holding remainder of shares', 'Number of shareholders owning the remainder of shares.'),
17800 (1, 't', 1549, 'Payment orders filed', 'Number of payment orders filed.'),
17801 (1, 't', 1550, 'Uncovered cheques', 'Number of uncovered cheques.'),
17802 (1, 't', 1551, 'Auctions', 'Number of auctions.'),
17803 (1, 't', 1552, 'Units produced', 'The number of units produced.'),
17804 (1, 't', 1553, 'Added employees', 'Number of employees that were added to the workforce.'),
17805 (1, 't', 1554, 'Number of added locations', 'Number of locations that were added.'),
17806 (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.'),
17807 (1, 't', 1556, 'Number of closed locations', 'Number of locations that were closed.'),
17808 (1, 't', 1557, 'Counter clerks', 'The number of clerks that work behind a flat-topped fitment.'),
17809 (1, 't', 1558, 'Payment experiences in the last 3 months', 'The number of payment experiences received for an entity over the last 3 months.'),
17810 (1, 't', 1559, 'Payment experiences in the last 12 months', 'The number of payment experiences received for an entity over the last 12 months.'),
17811 (1, 't', 1560, 'Total number of subsidiaries not included in the financial', 'statement The total number of subsidiaries not included in the financial statement.'),
17812 (1, 't', 1561, 'Paid-in common shares', 'The number of paid-in common shares.'),
17813 (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.'),
17814 (1, 't', 1563, 'Total number of foreign subsidiaries included in financial statement', 'The total number of foreign subsidiaries included in the financial statement.'),
17815 (1, 't', 1564, 'Total number of domestic subsidiaries included in financial statement', 'The total number of domestic subsidiaries included in the financial statement.'),
17816 (1, 't', 1565, 'Total transactions', 'The total number of transactions.'),
17817 (1, 't', 1566, 'Paid-in preferred shares', 'The number of paid-in preferred shares.'),
17818 (1, 't', 1567, 'Employees', 'Code specifying the quantity of persons working for a company, whose services are used for pay.'),
17819 (1, 't', 1568, 'Active ingredient dose per unit, dispensed', 'The dosage of active ingredient per dispensed unit.'),
17820 (1, 't', 1569, 'Budget', 'Budget quantity.'),
17821 (1, 't', 1570, 'Budget, cumulative to date', 'Budget quantity, cumulative to date.'),
17822 (1, 't', 1571, 'Actual units', 'The number of actual units.'),
17823 (1, 't', 1572, 'Actual units, cumulative to date', 'The number of cumulative to date actual units.'),
17824 (1, 't', 1573, 'Earned value', 'Earned value quantity.'),
17825 (1, 't', 1574, 'Earned value, cumulative to date', 'Earned value quantity accumulated to date.'),
17826 (1, 't', 1575, 'At completion quantity, estimated', 'The estimated quantity when a project is complete.'),
17827 (1, 't', 1576, 'To complete quantity, estimated', 'The estimated quantity required to complete a project.'),
17828 (1, 't', 1577, 'Adjusted units', 'The number of adjusted units.'),
17829 (1, 't', 1578, 'Number of limited partnership shares', 'Number of shares held in a limited partnership.'),
17830 (1, 't', 1579, 'National business failure incidences', 'Number of firms in a country that discontinued with a loss to creditors.'),
17831 (1, 't', 1580, 'Industry business failure incidences', 'Number of firms in a specific industry that discontinued with a loss to creditors.'),
17832 (1, 't', 1581, 'Business class failure incidences', 'Number of firms in a specific class that discontinued with a loss to creditors.'),
17833 (1, 't', 1582, 'Mechanics', 'Number of mechanics.'),
17834 (1, 't', 1583, 'Messengers', 'Number of messengers.'),
17835 (1, 't', 1584, 'Primary managers', 'Number of primary managers.'),
17836 (1, 't', 1585, 'Secretaries', 'Number of secretaries.'),
17837 (1, 't', 1586, 'Detrimental legal filings', 'Number of detrimental legal filings.'),
17838 (1, 't', 1587, 'Branch office locations, estimated', 'Estimated number of branch office locations.'),
17839 (1, 't', 1588, 'Previous number of employees', 'The number of employees for a previous period.'),
17840 (1, 't', 1589, 'Asset seizers', 'Number of entities that seize assets of another entity.'),
17841 (1, 't', 1590, 'Out-turned quantity', 'The quantity discharged.'),
17842 (1, 't', 1591, 'Material on-board quantity, prior to loading', 'The material in vessel tanks, void spaces, and pipelines prior to loading.'),
17843 (1, 't', 1592, 'Supplier estimated previous meter reading', 'Previous meter reading estimated by the supplier.'),
17844 (1, 't', 1593, 'Supplier estimated latest meter reading',   'Latest meter reading estimated by the supplier.'),
17845 (1, 't', 1594, 'Customer estimated previous meter reading', 'Previous meter reading estimated by the customer.'),
17846 (1, 't', 1595, 'Customer estimated latest meter reading',   'Latest meter reading estimated by the customer.'),
17847 (1, 't', 1596, 'Supplier previous meter reading',           'Previous meter reading done by the supplier.'),
17848 (1, 't', 1597, 'Supplier latest meter reading',             'Latest meter reading recorded by the supplier.'),
17849 (1, 't', 1598, 'Maximum number of purchase orders allowed', 'Maximum number of purchase orders that are allowed.'),
17850 (1, 't', 1599, 'File size before compression', 'The size of a file before compression.'),
17851 (1, 't', 1600, 'File size after compression', 'The size of a file after compression.'),
17852 (1, 't', 1601, 'Securities shares', 'Number of shares of securities.'),
17853 (1, 't', 1602, 'Patients',         'Number of patients.'),
17854 (1, 't', 1603, 'Completed projects', 'Number of completed projects.'),
17855 (1, 't', 1604, 'Promoters',        'Number of entities who finance or organize an event or a production.'),
17856 (1, 't', 1605, 'Administrators',   'Number of administrators.'),
17857 (1, 't', 1606, 'Supervisors',      'Number of supervisors.'),
17858 (1, 't', 1607, 'Professionals',    'Number of professionals.'),
17859 (1, 't', 1608, 'Debt collectors',  'Number of debt collectors.'),
17860 (1, 't', 1609, 'Inspectors',       'Number of individuals who perform inspections.'),
17861 (1, 't', 1610, 'Operators',        'Number of operators.'),
17862 (1, 't', 1611, 'Trainers',         'Number of trainers.'),
17863 (1, 't', 1612, 'Active accounts',  'Number of accounts in a current or active status.'),
17864 (1, 't', 1613, 'Trademarks used',  'Number of trademarks used.'),
17865 (1, 't', 1614, 'Machines',         'Number of machines.'),
17866 (1, 't', 1615, 'Fuel pumps',       'Number of fuel pumps.'),
17867 (1, 't', 1616, 'Tables available', 'Number of tables available for use.'),
17868 (1, 't', 1617, 'Directors',        'Number of directors.'),
17869 (1, 't', 1618, 'Freelance debt collectors', 'Number of debt collectors who work on a freelance basis.'),
17870 (1, 't', 1619, 'Freelance salespersons',    'Number of salespersons who work on a freelance basis.'),
17871 (1, 't', 1620, 'Travelling employees',      'Number of travelling employees.'),
17872 (1, 't', 1621, 'Foremen', 'Number of workers with limited supervisory responsibilities.'),
17873 (1, 't', 1622, 'Production workers', 'Number of employees engaged in production.'),
17874 (1, 't', 1623, 'Employees not including owners', 'Number of employees excluding business owners.'),
17875 (1, 't', 1624, 'Beds', 'Number of beds.'),
17876 (1, 't', 1625, 'Resting quantity', 'A quantity of product that is at rest before it can be used.'),
17877 (1, 't', 1626, 'Production requirements', 'Quantity needed to meet production requirements.'),
17878 (1, 't', 1627, 'Corrected quantity', 'The quantity has been corrected.'),
17879 (1, 't', 1628, 'Operating divisions', 'Number of divisions operating.'),
17880 (1, 't', 1629, 'Quantitative incentive scheme base', 'Quantity constituting the base for the quantitative incentive scheme.'),
17881 (1, 't', 1630, 'Petitions filed', 'Number of petitions that have been filed.'),
17882 (1, 't', 1631, 'Bankruptcy petitions filed', 'Number of bankruptcy petitions that have been filed.'),
17883 (1, 't', 1632, 'Projects in process', 'Number of projects in process.'),
17884 (1, 't', 1633, 'Changes in capital structure', 'Number of modifications made to the capital structure of an entity.'),
17885 (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.'),
17886 (1, 't', 1635, 'Number of failed businesses of directors', 'The number of failed businesses with which the directors have been associated.'),
17887 (1, 't', 1636, 'Professor', 'The number of professors.'),
17888 (1, 't', 1637, 'Seller',    'The number of sellers.'),
17889 (1, 't', 1638, 'Skilled worker', 'The number of skilled workers.'),
17890 (1, 't', 1639, 'Trademark represented', 'The number of trademarks represented.'),
17891 (1, 't', 1640, 'Number of quantitative incentive scheme units', 'Number of units allocated to a quantitative incentive scheme.'),
17892 (1, 't', 1641, 'Quantity in manufacturing process', 'Quantity currently in the manufacturing process.'),
17893 (1, 't', 1642, 'Number of units in the width of a layer', 'Number of units which make up the width of a layer.'),
17894 (1, 't', 1643, 'Number of units in the depth of a layer', 'Number of units which make up the depth of a layer.'),
17895 (1, 't', 1644, 'Return to warehouse', 'A quantity of products sent back to the warehouse.'),
17896 (1, 't', 1645, 'Return to the manufacturer', 'A quantity of products sent back from the manufacturer.'),
17897 (1, 't', 1646, 'Delta quantity', 'An increment or decrement to a quantity.'),
17898 (1, 't', 1647, 'Quantity moved between outlets', 'A quantity of products moved between outlets.'),
17899 (1, 't', 1648, 'Pre-paid invoice annual consumption, estimated', 'The estimated annual consumption used for a prepayment invoice.'),
17900 (1, 't', 1649, 'Total quoted quantity', 'The sum of quoted quantities.'),
17901 (1, 't', 1650, 'Requests pertaining to entity in last 12 months', 'Number of requests received in last 12 months pertaining to the entity.'),
17902 (1, 't', 1651, 'Total inquiry matches', 'Number of instances which correspond with the inquiry.'),
17903 (1, 't', 1652, 'En route to warehouse quantity',   'A quantity of products that is en route to a warehouse.'),
17904 (1, 't', 1653, 'En route from warehouse quantity', 'A quantity of products that is en route from a warehouse.'),
17905 (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.'),
17906 (1, 't', 1655, 'Not yet ordered quantity', 'The quantity which has not yet been ordered.'),
17907 (1, 't', 1656, 'Net reserve power', 'The reserve power available for the net.'),
17908 (1, 't', 1657, 'Maximum number of units per shelf', 'Maximum number of units of a product that can be placed on a shelf.'),
17909 (1, 't', 1658, 'Stowaway', 'Number of stowaway(s) on a conveyance.'),
17910 (1, 't', 1659, 'Tug', 'The number of tugboat(s).'),
17911 (1, 't', 1660, 'Maximum quantity capability of the package', 'Maximum quantity of a product that can be contained in a package.'),
17912 (1, 't', 1661, 'Calculated', 'The calculated quantity.'),
17913 (1, 't', 1662, 'Monthly volume, estimated', 'Volume estimated for a month.'),
17914 (1, 't', 1663, 'Total number of persons', 'Quantity representing the total number of persons.'),
17915 (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.'),
17916 (1, 't', 1665, 'Deducted tariff quantity',   'Quantity deducted from tariff quantity to reckon duty/tax/fee assessment bases.'),
17917 (1, 't', 1666, 'Advised but not arrived',    'Goods are advised by the consignor or supplier, but have not yet arrived at the destination.'),
17918 (1, 't', 1667, 'Received but not available', 'Goods have been received in the arrival area but are not yet available.'),
17919 (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.'),
17920 (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.'),
17921 (1, 't', 1670, 'Chargeable number of trailers', 'The number of trailers on which charges are based.'),
17922 (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.'),
17923 (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.'),
17924 (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.'),
17925 (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.'),
17926 (1, 't', 1675, 'Agreed maximum buying quantity', 'The agreed maximum quantity of the trade item that may be purchased.'),
17927 (1, 't', 1676, 'Agreed minimum buying quantity', 'The agreed minimum quantity of the trade item that may be purchased.'),
17928 (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.'),
17929 (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.'),
17930 (1, 't', 1679, 'Marine Diesel Oil bunkers, loaded',                  'Number of Marine Diesel Oil (MDO) bunkers taken on in the port.'),
17931 (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.'),
17932 (1, 't', 1681, 'Intermediate Fuel Oil bunkers, loaded',              'Number of Intermediate Fuel Oil (IFO) bunkers taken on in the port.'),
17933 (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.'),
17934 (1, 't', 1683, 'Bunker C bunkers, loaded', 'Number of Bunker C, or Number 6 fuel oil bunkers, taken on in the port.'),
17935 (1, 't', 1684, 'Number of individual units within the smallest packaging', 'unit Total number of individual units contained within the smallest unit of packaging.'),
17936 (1, 't', 1685, 'Percentage of constituent element', 'The part of a product or material that is composed of the constituent element, as a percentage.'),
17937 (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).'),
17938 (1, 't', 1687, 'Regulated commodity count', 'The number of regulated items.'),
17939 (1, 't', 1688, 'Number of passengers, embarking', 'The number of passengers going aboard a conveyance.'),
17940 (1, 't', 1689, 'Number of passengers, disembarking', 'The number of passengers disembarking the conveyance.'),
17941 (1, 't', 1690, 'Constituent element or component quantity', 'The specific quantity of the identified constituent element.')
17942 ;
17943 -- ZZZ, 'Mutually defined', 'As agreed by the trading partners.'),
17944
17945 CREATE TABLE acq.serial_claim (
17946     id     SERIAL           PRIMARY KEY,
17947     type   INT              NOT NULL REFERENCES acq.claim_type
17948                                      DEFERRABLE INITIALLY DEFERRED,
17949     item    BIGINT          NOT NULL REFERENCES serial.item
17950                                      DEFERRABLE INITIALLY DEFERRED
17951 );
17952
17953 CREATE INDEX serial_claim_lid_idx ON acq.serial_claim( item );
17954
17955 CREATE TABLE acq.serial_claim_event (
17956     id             BIGSERIAL        PRIMARY KEY,
17957     type           INT              NOT NULL REFERENCES acq.claim_event_type
17958                                              DEFERRABLE INITIALLY DEFERRED,
17959     claim          SERIAL           NOT NULL REFERENCES acq.serial_claim
17960                                              DEFERRABLE INITIALLY DEFERRED,
17961     event_date     TIMESTAMPTZ      NOT NULL DEFAULT now(),
17962     creator        INT              NOT NULL REFERENCES actor.usr
17963                                              DEFERRABLE INITIALLY DEFERRED,
17964     note           TEXT
17965 );
17966
17967 CREATE INDEX serial_claim_event_claim_date_idx ON acq.serial_claim_event( claim, event_date );
17968
17969 ALTER TABLE asset.stat_cat ADD COLUMN required BOOL NOT NULL DEFAULT FALSE;
17970
17971 -- now what about the auditor.*_lifecycle views??
17972
17973 INSERT INTO config.metabib_field ( id, field_class, name, label, format, xpath ) VALUES
17974     (26, 'identifier', 'tcn', oils_i18n_gettext(26, 'Title Control Number', 'cmf', 'label'), 'marcxml', $$//marc:datafield[@tag='901']/marc:subfield[@code='a']$$ );
17975 INSERT INTO config.metabib_field ( id, field_class, name, label, format, xpath ) VALUES
17976     (27, 'identifier', 'bibid', oils_i18n_gettext(27, 'Internal ID', 'cmf', 'label'), 'marcxml', $$//marc:datafield[@tag='901']/marc:subfield[@code='c']$$ );
17977 INSERT INTO config.metabib_search_alias (alias,field_class,field) VALUES ('eg.tcn','identifier', 26);
17978 INSERT INTO config.metabib_search_alias (alias,field_class,field) VALUES ('eg.bibid','identifier', 27);
17979
17980 CREATE TABLE asset.call_number_class (
17981     id             bigserial     PRIMARY KEY,
17982     name           TEXT          NOT NULL,
17983     normalizer     TEXT          NOT NULL DEFAULT 'asset.normalize_generic',
17984     field          TEXT          NOT NULL DEFAULT '050ab,055ab,060ab,070ab,080ab,082ab,086ab,088ab,090,092,096,098,099'
17985 );
17986
17987 COMMENT ON TABLE asset.call_number_class IS $$
17988 Defines the call number normalization database functions in the "normalizer"
17989 column and the tag/subfield combinations to use to lookup the call number in
17990 the "field" column for a given classification scheme. Tag/subfield combinations
17991 are delimited by commas.
17992 $$;
17993
17994 INSERT INTO asset.call_number_class (name, normalizer) VALUES 
17995     ('Generic', 'asset.label_normalizer_generic'),
17996     ('Dewey (DDC)', 'asset.label_normalizer_dewey'),
17997     ('Library of Congress (LC)', 'asset.label_normalizer_lc')
17998 ;
17999
18000 -- Generic fields
18001 UPDATE asset.call_number_class
18002     SET field = '050ab,055ab,060ab,070ab,080ab,082ab,086ab,088ab,090,092,096,098,099'
18003     WHERE id = 1
18004 ;
18005
18006 -- Dewey fields
18007 UPDATE asset.call_number_class
18008     SET field = '080ab,082ab'
18009     WHERE id = 2
18010 ;
18011
18012 -- LC fields
18013 UPDATE asset.call_number_class
18014     SET field = '050ab,055ab'
18015     WHERE id = 3
18016 ;
18017  
18018 ALTER TABLE asset.call_number
18019         ADD COLUMN label_class BIGINT DEFAULT 1 NOT NULL
18020                 REFERENCES asset.call_number_class(id)
18021                 DEFERRABLE INITIALLY DEFERRED;
18022
18023 ALTER TABLE asset.call_number
18024         ADD COLUMN label_sortkey TEXT;
18025
18026 CREATE INDEX asset_call_number_label_sortkey
18027         ON asset.call_number(oils_text_as_bytea(label_sortkey));
18028
18029 ALTER TABLE auditor.asset_call_number_history
18030         ADD COLUMN label_class BIGINT;
18031
18032 ALTER TABLE auditor.asset_call_number_history
18033         ADD COLUMN label_sortkey TEXT;
18034
18035 -- Pick up the new columns in dependent views
18036
18037 DROP VIEW auditor.asset_call_number_lifecycle;
18038
18039 SELECT auditor.create_auditor_lifecycle( 'asset', 'call_number' );
18040
18041 DROP VIEW auditor.asset_call_number_lifecycle;
18042
18043 SELECT auditor.create_auditor_lifecycle( 'asset', 'call_number' );
18044
18045 DROP VIEW IF EXISTS stats.fleshed_call_number;
18046
18047 CREATE VIEW stats.fleshed_call_number AS
18048         SELECT  cn.*,
18049             CAST(cn.create_date AS DATE) AS create_date_day,
18050         CAST(cn.edit_date AS DATE) AS edit_date_day,
18051         DATE_TRUNC('hour', cn.create_date) AS create_date_hour,
18052         DATE_TRUNC('hour', cn.edit_date) AS edit_date_hour,
18053             rd.item_lang,
18054                 rd.item_type,
18055                 rd.item_form
18056         FROM    asset.call_number cn
18057                 JOIN metabib.rec_descriptor rd ON (rd.record = cn.record);
18058
18059 CREATE OR REPLACE FUNCTION asset.label_normalizer() RETURNS TRIGGER AS $func$
18060 DECLARE
18061     sortkey        TEXT := '';
18062 BEGIN
18063     sortkey := NEW.label_sortkey;
18064
18065     EXECUTE 'SELECT ' || acnc.normalizer || '(' || 
18066        quote_literal( NEW.label ) || ')'
18067        FROM asset.call_number_class acnc
18068        WHERE acnc.id = NEW.label_class
18069        INTO sortkey;
18070
18071     NEW.label_sortkey = sortkey;
18072
18073     RETURN NEW;
18074 END;
18075 $func$ LANGUAGE PLPGSQL;
18076
18077 CREATE OR REPLACE FUNCTION asset.label_normalizer_generic(TEXT) RETURNS TEXT AS $func$
18078     # Created after looking at the Koha C4::ClassSortRoutine::Generic module,
18079     # thus could probably be considered a derived work, although nothing was
18080     # directly copied - but to err on the safe side of providing attribution:
18081     # Copyright (C) 2007 LibLime
18082     # Licensed under the GPL v2 or later
18083
18084     use strict;
18085     use warnings;
18086
18087     # Converts the callnumber to uppercase
18088     # Strips spaces from start and end of the call number
18089     # Converts anything other than letters, digits, and periods into underscores
18090     # Collapses multiple underscores into a single underscore
18091     my $callnum = uc(shift);
18092     $callnum =~ s/^\s//g;
18093     $callnum =~ s/\s$//g;
18094     $callnum =~ s/[^A-Z0-9_.]/_/g;
18095     $callnum =~ s/_{2,}/_/g;
18096
18097     return $callnum;
18098 $func$ LANGUAGE PLPERLU;
18099
18100 CREATE OR REPLACE FUNCTION asset.label_normalizer_dewey(TEXT) RETURNS TEXT AS $func$
18101     # Derived from the Koha C4::ClassSortRoutine::Dewey module
18102     # Copyright (C) 2007 LibLime
18103     # Licensed under the GPL v2 or later
18104
18105     use strict;
18106     use warnings;
18107
18108     my $init = uc(shift);
18109     $init =~ s/^\s+//;
18110     $init =~ s/\s+$//;
18111     $init =~ s!/!!g;
18112     $init =~ s/^([\p{IsAlpha}]+)/$1 /;
18113     my @tokens = split /\.|\s+/, $init;
18114     my $digit_group_count = 0;
18115     for (my $i = 0; $i <= $#tokens; $i++) {
18116         if ($tokens[$i] =~ /^\d+$/) {
18117             $digit_group_count++;
18118             if (2 == $digit_group_count) {
18119                 $tokens[$i] = sprintf("%-15.15s", $tokens[$i]);
18120                 $tokens[$i] =~ tr/ /0/;
18121             }
18122         }
18123     }
18124     my $key = join("_", @tokens);
18125     $key =~ s/[^\p{IsAlnum}_]//g;
18126
18127     return $key;
18128
18129 $func$ LANGUAGE PLPERLU;
18130
18131 CREATE OR REPLACE FUNCTION asset.label_normalizer_lc(TEXT) RETURNS TEXT AS $func$
18132     use strict;
18133     use warnings;
18134
18135     # Library::CallNumber::LC is currently hosted at http://code.google.com/p/library-callnumber-lc/
18136     # The author hopes to upload it to CPAN some day, which would make our lives easier
18137     use Library::CallNumber::LC;
18138
18139     my $callnum = Library::CallNumber::LC->new(shift);
18140     return $callnum->normalize();
18141
18142 $func$ LANGUAGE PLPERLU;
18143
18144 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$
18145 DECLARE
18146     ans RECORD;
18147     trans INT;
18148 BEGIN
18149     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;
18150
18151     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
18152         RETURN QUERY
18153         SELECT  ans.depth,
18154                 ans.id,
18155                 COUNT( av.id ),
18156                 SUM( CASE WHEN cp.status IN (0,7,12) THEN 1 ELSE 0 END ),
18157                 COUNT( av.id ),
18158                 trans
18159           FROM
18160                 actor.org_unit_descendants(ans.id) d
18161                 JOIN asset.opac_visible_copies av ON (av.record = record AND av.circ_lib = d.id)
18162                 JOIN asset.copy cp ON (cp.id = av.id)
18163           GROUP BY 1,2,6;
18164
18165         IF NOT FOUND THEN
18166             RETURN QUERY SELECT ans.depth, ans.id, 0::BIGINT, 0::BIGINT, 0::BIGINT, trans;
18167         END IF;
18168
18169     END LOOP;
18170
18171     RETURN;
18172 END;
18173 $f$ LANGUAGE PLPGSQL;
18174
18175 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$
18176 DECLARE
18177     ans RECORD;
18178     trans INT;
18179 BEGIN
18180     SELECT 1 INTO trans FROM biblio.record_entry b JOIN config.bib_source src ON (b.source = src.id) WHERE src.transcendant AND b.id = record;
18181
18182     FOR ans IN SELECT u.org_unit AS id FROM actor.org_lasso_map AS u WHERE lasso = i_lasso LOOP
18183         RETURN QUERY
18184         SELECT  -1,
18185                 ans.id,
18186                 COUNT( av.id ),
18187                 SUM( CASE WHEN cp.status IN (0,7,12) THEN 1 ELSE 0 END ),
18188                 COUNT( av.id ),
18189                 trans
18190           FROM
18191                 actor.org_unit_descendants(ans.id) d
18192                 JOIN asset.opac_visible_copies av ON (av.record = record AND av.circ_lib = d.id)
18193                 JOIN asset.copy cp ON (cp.id = av.id)
18194           GROUP BY 1,2,6;
18195
18196         IF NOT FOUND THEN
18197             RETURN QUERY SELECT ans.depth, ans.id, 0::BIGINT, 0::BIGINT, 0::BIGINT, trans;
18198         END IF;
18199
18200     END LOOP;
18201
18202     RETURN;
18203 END;
18204 $f$ LANGUAGE PLPGSQL;
18205
18206 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$
18207 DECLARE
18208     ans RECORD;
18209     trans INT;
18210 BEGIN
18211     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;
18212
18213     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
18214         RETURN QUERY
18215         SELECT  ans.depth,
18216                 ans.id,
18217                 COUNT( cp.id ),
18218                 SUM( CASE WHEN cp.status IN (0,7,12) THEN 1 ELSE 0 END ),
18219                 COUNT( cp.id ),
18220                 trans
18221           FROM
18222                 actor.org_unit_descendants(ans.id) d
18223                 JOIN asset.copy cp ON (cp.circ_lib = d.id)
18224                 JOIN asset.call_number cn ON (cn.record = record AND cn.id = cp.call_number)
18225           GROUP BY 1,2,6;
18226
18227         IF NOT FOUND THEN
18228             RETURN QUERY SELECT ans.depth, ans.id, 0::BIGINT, 0::BIGINT, 0::BIGINT, trans;
18229         END IF;
18230
18231     END LOOP;
18232
18233     RETURN;
18234 END;
18235 $f$ LANGUAGE PLPGSQL;
18236
18237 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$
18238 DECLARE
18239     ans RECORD;
18240     trans INT;
18241 BEGIN
18242     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;
18243
18244     FOR ans IN SELECT u.org_unit AS id FROM actor.org_lasso_map AS u WHERE lasso = i_lasso LOOP
18245         RETURN QUERY
18246         SELECT  -1,
18247                 ans.id,
18248                 COUNT( cp.id ),
18249                 SUM( CASE WHEN cp.status IN (0,7,12) THEN 1 ELSE 0 END ),
18250                 COUNT( cp.id ),
18251                 trans
18252           FROM
18253                 actor.org_unit_descendants(ans.id) d
18254                 JOIN asset.copy cp ON (cp.circ_lib = d.id)
18255                 JOIN asset.call_number cn ON (cn.record = record AND cn.id = cp.call_number)
18256           GROUP BY 1,2,6;
18257
18258         IF NOT FOUND THEN
18259             RETURN QUERY SELECT ans.depth, ans.id, 0::BIGINT, 0::BIGINT, 0::BIGINT, trans;
18260         END IF;
18261
18262     END LOOP;
18263
18264     RETURN;
18265 END;
18266 $f$ LANGUAGE PLPGSQL;
18267
18268 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$
18269 BEGIN
18270     IF staff IS TRUE THEN
18271         IF place > 0 THEN
18272             RETURN QUERY SELECT * FROM asset.staff_ou_record_copy_count( place, record );
18273         ELSE
18274             RETURN QUERY SELECT * FROM asset.staff_lasso_record_copy_count( -place, record );
18275         END IF;
18276     ELSE
18277         IF place > 0 THEN
18278             RETURN QUERY SELECT * FROM asset.opac_ou_record_copy_count( place, record );
18279         ELSE
18280             RETURN QUERY SELECT * FROM asset.opac_lasso_record_copy_count( -place, record );
18281         END IF;
18282     END IF;
18283
18284     RETURN;
18285 END;
18286 $f$ LANGUAGE PLPGSQL;
18287
18288 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$
18289 DECLARE
18290     ans RECORD;
18291     trans INT;
18292 BEGIN
18293     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;
18294
18295     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
18296         RETURN QUERY
18297         SELECT  ans.depth,
18298                 ans.id,
18299                 COUNT( av.id ),
18300                 SUM( CASE WHEN cp.status IN (0,7,12) THEN 1 ELSE 0 END ),
18301                 COUNT( av.id ),
18302                 trans
18303           FROM
18304                 actor.org_unit_descendants(ans.id) d
18305                 JOIN asset.opac_visible_copies av ON (av.record = record AND av.circ_lib = d.id)
18306                 JOIN asset.copy cp ON (cp.id = av.id)
18307                 JOIN metabib.metarecord_source_map m ON (m.source = av.record)
18308           GROUP BY 1,2,6;
18309
18310         IF NOT FOUND THEN
18311             RETURN QUERY SELECT ans.depth, ans.id, 0::BIGINT, 0::BIGINT, 0::BIGINT, trans;
18312         END IF;
18313
18314     END LOOP;
18315
18316     RETURN;
18317 END;
18318 $f$ LANGUAGE PLPGSQL;
18319
18320 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$
18321 DECLARE
18322     ans RECORD;
18323     trans INT;
18324 BEGIN
18325     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;
18326
18327     FOR ans IN SELECT u.org_unit AS id FROM actor.org_lasso_map AS u WHERE lasso = i_lasso LOOP
18328         RETURN QUERY
18329         SELECT  -1,
18330                 ans.id,
18331                 COUNT( av.id ),
18332                 SUM( CASE WHEN cp.status IN (0,7,12) THEN 1 ELSE 0 END ),
18333                 COUNT( av.id ),
18334                 trans
18335           FROM
18336                 actor.org_unit_descendants(ans.id) d
18337                 JOIN asset.opac_visible_copies av ON (av.record = record AND av.circ_lib = d.id)
18338                 JOIN asset.copy cp ON (cp.id = av.id)
18339                 JOIN metabib.metarecord_source_map m ON (m.source = av.record)
18340           GROUP BY 1,2,6;
18341
18342         IF NOT FOUND THEN
18343             RETURN QUERY SELECT ans.depth, ans.id, 0::BIGINT, 0::BIGINT, 0::BIGINT, trans;
18344         END IF;
18345
18346     END LOOP;
18347
18348     RETURN;
18349 END;
18350 $f$ LANGUAGE PLPGSQL;
18351
18352 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$
18353 DECLARE
18354     ans RECORD;
18355     trans INT;
18356 BEGIN
18357     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;
18358
18359     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
18360         RETURN QUERY
18361         SELECT  ans.depth,
18362                 ans.id,
18363                 COUNT( cp.id ),
18364                 SUM( CASE WHEN cp.status IN (0,7,12) THEN 1 ELSE 0 END ),
18365                 COUNT( cp.id ),
18366                 trans
18367           FROM
18368                 actor.org_unit_descendants(ans.id) d
18369                 JOIN asset.copy cp ON (cp.circ_lib = d.id)
18370                 JOIN asset.call_number cn ON (cn.record = record AND cn.id = cp.call_number)
18371                 JOIN metabib.metarecord_source_map m ON (m.source = cn.record)
18372           GROUP BY 1,2,6;
18373
18374         IF NOT FOUND THEN
18375             RETURN QUERY SELECT ans.depth, ans.id, 0::BIGINT, 0::BIGINT, 0::BIGINT, trans;
18376         END IF;
18377
18378     END LOOP;
18379
18380     RETURN;
18381 END;
18382 $f$ LANGUAGE PLPGSQL;
18383
18384 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$
18385 DECLARE
18386     ans RECORD;
18387     trans INT;
18388 BEGIN
18389     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;
18390
18391     FOR ans IN SELECT u.org_unit AS id FROM actor.org_lasso_map AS u WHERE lasso = i_lasso LOOP
18392         RETURN QUERY
18393         SELECT  -1,
18394                 ans.id,
18395                 COUNT( cp.id ),
18396                 SUM( CASE WHEN cp.status IN (0,7,12) THEN 1 ELSE 0 END ),
18397                 COUNT( cp.id ),
18398                 trans
18399           FROM
18400                 actor.org_unit_descendants(ans.id) d
18401                 JOIN asset.copy cp ON (cp.circ_lib = d.id)
18402                 JOIN asset.call_number cn ON (cn.record = record AND cn.id = cp.call_number)
18403                 JOIN metabib.metarecord_source_map m ON (m.source = cn.record)
18404           GROUP BY 1,2,6;
18405
18406         IF NOT FOUND THEN
18407             RETURN QUERY SELECT ans.depth, ans.id, 0::BIGINT, 0::BIGINT, 0::BIGINT, trans;
18408         END IF;
18409
18410     END LOOP;
18411
18412     RETURN;
18413 END;
18414 $f$ LANGUAGE PLPGSQL;
18415
18416 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$
18417 BEGIN
18418     IF staff IS TRUE THEN
18419         IF place > 0 THEN
18420             RETURN QUERY SELECT * FROM asset.staff_ou_metarecord_copy_count( place, record );
18421         ELSE
18422             RETURN QUERY SELECT * FROM asset.staff_lasso_metarecord_copy_count( -place, record );
18423         END IF;
18424     ELSE
18425         IF place > 0 THEN
18426             RETURN QUERY SELECT * FROM asset.opac_ou_metarecord_copy_count( place, record );
18427         ELSE
18428             RETURN QUERY SELECT * FROM asset.opac_lasso_metarecord_copy_count( -place, record );
18429         END IF;
18430     END IF;
18431
18432     RETURN;
18433 END;
18434 $f$ LANGUAGE PLPGSQL;
18435
18436 -- No transaction is required
18437
18438 -- Triggers on the vandelay.queued_*_record tables delete entries from
18439 -- the associated vandelay.queued_*_record_attr tables based on the record's
18440 -- ID; create an index on that column to avoid sequential scans for each
18441 -- queued record that is deleted
18442 CREATE INDEX queued_bib_record_attr_record_idx ON vandelay.queued_bib_record_attr (record);
18443 CREATE INDEX queued_authority_record_attr_record_idx ON vandelay.queued_authority_record_attr (record);
18444
18445 -- Avoid sequential scans for queue retrieval operations by providing an
18446 -- index on the queue column
18447 CREATE INDEX queued_bib_record_queue_idx ON vandelay.queued_bib_record (queue);
18448 CREATE INDEX queued_authority_record_queue_idx ON vandelay.queued_authority_record (queue);
18449
18450 -- Start picking up call number label prefixes and suffixes
18451 -- from asset.copy_location
18452 ALTER TABLE asset.copy_location ADD COLUMN label_prefix TEXT;
18453 ALTER TABLE asset.copy_location ADD COLUMN label_suffix TEXT;
18454
18455 DROP VIEW auditor.asset_copy_lifecycle;
18456
18457 SELECT auditor.create_auditor_lifecycle( 'asset', 'copy' );
18458
18459 ALTER TABLE reporter.report RENAME COLUMN recurance TO recurrence;
18460
18461 -- Let's not break existing reports
18462 UPDATE reporter.template SET data = REGEXP_REPLACE(data, E'^(.*)recuring(.*)$', E'\\1recurring\\2') WHERE data LIKE '%recuring%';
18463 UPDATE reporter.template SET data = REGEXP_REPLACE(data, E'^(.*)recurance(.*)$', E'\\1recurrence\\2') WHERE data LIKE '%recurance%';
18464
18465 -- Need to recreate this view with DISTINCT calls to ARRAY_ACCUM, thus avoiding duplicated ISBN and ISSN values
18466 CREATE OR REPLACE VIEW reporter.old_super_simple_record AS
18467 SELECT  r.id,
18468     r.fingerprint,
18469     r.quality,
18470     r.tcn_source,
18471     r.tcn_value,
18472     FIRST(title.value) AS title,
18473     FIRST(author.value) AS author,
18474     ARRAY_TO_STRING(ARRAY_ACCUM( DISTINCT publisher.value), ', ') AS publisher,
18475     ARRAY_TO_STRING(ARRAY_ACCUM( DISTINCT SUBSTRING(pubdate.value FROM $$\d+$$) ), ', ') AS pubdate,
18476     ARRAY_ACCUM( DISTINCT SUBSTRING(isbn.value FROM $$^\S+$$) ) AS isbn,
18477     ARRAY_ACCUM( DISTINCT SUBSTRING(issn.value FROM $$^\S+$$) ) AS issn
18478   FROM  biblio.record_entry r
18479     LEFT JOIN metabib.full_rec title ON (r.id = title.record AND title.tag = '245' AND title.subfield = 'a')
18480     LEFT JOIN metabib.full_rec author ON (r.id = author.record AND author.tag IN ('100','110','111') AND author.subfield = 'a')
18481     LEFT JOIN metabib.full_rec publisher ON (r.id = publisher.record AND publisher.tag = '260' AND publisher.subfield = 'b')
18482     LEFT JOIN metabib.full_rec pubdate ON (r.id = pubdate.record AND pubdate.tag = '260' AND pubdate.subfield = 'c')
18483     LEFT JOIN metabib.full_rec isbn ON (r.id = isbn.record AND isbn.tag IN ('024', '020') AND isbn.subfield IN ('a','z'))
18484     LEFT JOIN metabib.full_rec issn ON (r.id = issn.record AND issn.tag = '022' AND issn.subfield = 'a')
18485   GROUP BY 1,2,3,4,5;
18486
18487 -- Correct the ISSN array definition for reporter.simple_record
18488
18489 CREATE OR REPLACE VIEW reporter.simple_record AS
18490 SELECT  r.id,
18491         s.metarecord,
18492         r.fingerprint,
18493         r.quality,
18494         r.tcn_source,
18495         r.tcn_value,
18496         title.value AS title,
18497         uniform_title.value AS uniform_title,
18498         author.value AS author,
18499         publisher.value AS publisher,
18500         SUBSTRING(pubdate.value FROM $$\d+$$) AS pubdate,
18501         series_title.value AS series_title,
18502         series_statement.value AS series_statement,
18503         summary.value AS summary,
18504         ARRAY_ACCUM( SUBSTRING(isbn.value FROM $$^\S+$$) ) AS isbn,
18505         ARRAY_ACCUM( REGEXP_REPLACE(issn.value, E'^\\S*(\\d{4})[-\\s](\\d{3,4}x?)', E'\\1 \\2') ) AS issn,
18506         ARRAY((SELECT DISTINCT value FROM metabib.full_rec WHERE tag = '650' AND subfield = 'a' AND record = r.id)) AS topic_subject,
18507         ARRAY((SELECT DISTINCT value FROM metabib.full_rec WHERE tag = '651' AND subfield = 'a' AND record = r.id)) AS geographic_subject,
18508         ARRAY((SELECT DISTINCT value FROM metabib.full_rec WHERE tag = '655' AND subfield = 'a' AND record = r.id)) AS genre,
18509         ARRAY((SELECT DISTINCT value FROM metabib.full_rec WHERE tag = '600' AND subfield = 'a' AND record = r.id)) AS name_subject,
18510         ARRAY((SELECT DISTINCT value FROM metabib.full_rec WHERE tag = '610' AND subfield = 'a' AND record = r.id)) AS corporate_subject,
18511         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
18512   FROM  biblio.record_entry r
18513         JOIN metabib.metarecord_source_map s ON (s.source = r.id)
18514         LEFT JOIN metabib.full_rec uniform_title ON (r.id = uniform_title.record AND uniform_title.tag = '240' AND uniform_title.subfield = 'a')
18515         LEFT JOIN metabib.full_rec title ON (r.id = title.record AND title.tag = '245' AND title.subfield = 'a')
18516         LEFT JOIN metabib.full_rec author ON (r.id = author.record AND author.tag = '100' AND author.subfield = 'a')
18517         LEFT JOIN metabib.full_rec publisher ON (r.id = publisher.record AND publisher.tag = '260' AND publisher.subfield = 'b')
18518         LEFT JOIN metabib.full_rec pubdate ON (r.id = pubdate.record AND pubdate.tag = '260' AND pubdate.subfield = 'c')
18519         LEFT JOIN metabib.full_rec isbn ON (r.id = isbn.record AND isbn.tag IN ('024', '020') AND isbn.subfield IN ('a','z'))
18520         LEFT JOIN metabib.full_rec issn ON (r.id = issn.record AND issn.tag = '022' AND issn.subfield = 'a')
18521         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')
18522         LEFT JOIN metabib.full_rec series_statement ON (r.id = series_statement.record AND series_statement.tag = '490' AND series_statement.subfield = 'a')
18523         LEFT JOIN metabib.full_rec summary ON (r.id = summary.record AND summary.tag = '520' AND summary.subfield = 'a')
18524   GROUP BY 1,2,3,4,5,6,7,8,9,10,11,12,13,14;
18525
18526 CREATE OR REPLACE FUNCTION reporter.disable_materialized_simple_record_trigger () RETURNS VOID AS $$
18527     DROP TRIGGER IF EXISTS zzz_update_materialized_simple_record_tgr ON metabib.real_full_rec;
18528 $$ LANGUAGE SQL;
18529
18530 CREATE OR REPLACE FUNCTION reporter.simple_rec_trigger () RETURNS TRIGGER AS $func$
18531 BEGIN
18532     IF TG_OP = 'DELETE' THEN
18533         PERFORM reporter.simple_rec_delete(NEW.id);
18534     ELSE
18535         PERFORM reporter.simple_rec_update(NEW.id);
18536     END IF;
18537
18538     RETURN NEW;
18539 END;
18540 $func$ LANGUAGE PLPGSQL;
18541
18542 CREATE TRIGGER bbb_simple_rec_trigger AFTER INSERT OR UPDATE ON biblio.record_entry FOR EACH ROW EXECUTE PROCEDURE reporter.simple_rec_trigger ();
18543
18544 ALTER TABLE extend_reporter.legacy_circ_count DROP CONSTRAINT legacy_circ_count_id_fkey;
18545
18546 CREATE INDEX asset_copy_note_owning_copy_idx ON asset.copy_note ( owning_copy );
18547
18548 UPDATE config.org_unit_setting_type
18549     SET view_perm = (SELECT id FROM permission.perm_list
18550         WHERE code = 'VIEW_CREDIT_CARD_PROCESSING' LIMIT 1)
18551     WHERE name LIKE 'credit.processor%' AND view_perm IS NULL;
18552
18553 UPDATE config.org_unit_setting_type
18554     SET update_perm = (SELECT id FROM permission.perm_list
18555         WHERE code = 'ADMIN_CREDIT_CARD_PROCESSING' LIMIT 1)
18556     WHERE name LIKE 'credit.processor%' AND update_perm IS NULL;
18557
18558 INSERT INTO config.org_unit_setting_type (name, label, description, datatype)
18559     VALUES (
18560         'opac.fully_compressed_serial_holdings',
18561         'OPAC: Use fully compressed serial holdings',
18562         'Show fully compressed serial holdings for all libraries at and below
18563         the current context unit',
18564         'bool'
18565     );
18566
18567 CREATE OR REPLACE FUNCTION authority.normalize_heading( TEXT ) RETURNS TEXT AS $func$
18568     use strict;
18569     use warnings;
18570
18571     use utf8;
18572     use MARC::Record;
18573     use MARC::File::XML (BinaryEncoding => 'UTF8');
18574     use UUID::Tiny ':std';
18575
18576     my $xml = shift() or return undef;
18577
18578     my $r;
18579
18580     # Prevent errors in XML parsing from blowing out ungracefully
18581     eval {
18582         $r = MARC::Record->new_from_xml( $xml );
18583         1;
18584     } or do {
18585        return 'BAD_MARCXML_' . create_uuid_as_string(UUID_MD5, $xml);
18586     };
18587
18588     if (!$r) {
18589        return 'BAD_MARCXML_' . create_uuid_as_string(UUID_MD5, $xml);
18590     }
18591
18592     # From http://www.loc.gov/standards/sourcelist/subject.html
18593     my $thes_code_map = {
18594         a => 'lcsh',
18595         b => 'lcshac',
18596         c => 'mesh',
18597         d => 'nal',
18598         k => 'cash',
18599         n => 'notapplicable',
18600         r => 'aat',
18601         s => 'sears',
18602         v => 'rvm',
18603     };
18604
18605     # Default to "No attempt to code" if the leader is horribly broken
18606     my $fixed_field = $r->field('008');
18607     my $thes_char = '|';
18608     if ($fixed_field) {
18609         $thes_char = substr($fixed_field->data(), 11, 1) || '|';
18610     }
18611
18612     my $thes_code = 'UNDEFINED';
18613
18614     if ($thes_char eq 'z') {
18615         # Grab the 040 $f per http://www.loc.gov/marc/authority/ad040.html
18616         $thes_code = $r->subfield('040', 'f') || 'UNDEFINED';
18617     } elsif ($thes_code_map->{$thes_char}) {
18618         $thes_code = $thes_code_map->{$thes_char};
18619     }
18620
18621     my $auth_txt = '';
18622     my $head = $r->field('1..');
18623     if ($head) {
18624         # Concatenate all of these subfields together, prefixed by their code
18625         # to prevent collisions along the lines of "Fiction, North Carolina"
18626         foreach my $sf ($head->subfields()) {
18627             $auth_txt .= '‡' . $sf->[0] . ' ' . $sf->[1];
18628         }
18629     }
18630
18631     # Perhaps better to parameterize the spi and pass as a parameter
18632     $auth_txt =~ s/'//go;
18633
18634     if ($auth_txt) {
18635         my $result = spi_exec_query("SELECT public.naco_normalize('$auth_txt') AS norm_text");
18636         my $norm_txt = $result->{rows}[0]->{norm_text};
18637         return $head->tag() . "_" . $thes_code . " " . $norm_txt;
18638     }
18639
18640     return 'NOHEADING_' . $thes_code . ' ' . create_uuid_as_string(UUID_MD5, $xml);
18641 $func$ LANGUAGE 'plperlu' IMMUTABLE;
18642
18643 COMMENT ON FUNCTION authority.normalize_heading( TEXT ) IS $$
18644 /**
18645 * Extract the authority heading, thesaurus, and NACO-normalized values
18646 * from an authority record. The primary purpose is to build a unique
18647 * index to defend against duplicated authority records from the same
18648 * thesaurus.
18649 */
18650 $$;
18651
18652 DROP INDEX authority.authority_record_unique_tcn;
18653 ALTER TABLE authority.record_entry DROP COLUMN arn_value;
18654 ALTER TABLE authority.record_entry DROP COLUMN arn_source;
18655
18656 ALTER TABLE acq.provider_contact
18657         ALTER COLUMN name SET NOT NULL;
18658
18659 ALTER TABLE actor.stat_cat
18660         ADD COLUMN usr_summary BOOL NOT NULL DEFAULT FALSE;
18661
18662 -- Recreate some foreign keys that were somehow dropped, probably
18663 -- by some kind of cascade from an inherited table:
18664
18665 ALTER TABLE action.reservation_transit_copy
18666         ADD CONSTRAINT artc_tc_fkey FOREIGN KEY (target_copy)
18667                 REFERENCES booking.resource(id)
18668                 ON DELETE CASCADE
18669                 DEFERRABLE INITIALLY DEFERRED,
18670         ADD CONSTRAINT reservation_transit_copy_reservation_fkey FOREIGN KEY (reservation)
18671                 REFERENCES booking.reservation(id)
18672                 ON DELETE SET NULL
18673                 DEFERRABLE INITIALLY DEFERRED;
18674
18675 CREATE INDEX user_bucket_item_target_user_idx
18676         ON container.user_bucket_item ( target_user );
18677
18678 CREATE INDEX m_c_t_collector_idx
18679         ON money.collections_tracker ( collector );
18680
18681 CREATE INDEX aud_actor_usr_address_hist_id_idx
18682         ON auditor.actor_usr_address_history ( id );
18683
18684 CREATE INDEX aud_actor_usr_hist_id_idx
18685         ON auditor.actor_usr_history ( id );
18686
18687 CREATE INDEX aud_asset_cn_hist_creator_idx
18688         ON auditor.asset_call_number_history ( creator );
18689
18690 CREATE INDEX aud_asset_cn_hist_editor_idx
18691         ON auditor.asset_call_number_history ( editor );
18692
18693 CREATE INDEX aud_asset_cp_hist_creator_idx
18694         ON auditor.asset_copy_history ( creator );
18695
18696 CREATE INDEX aud_asset_cp_hist_editor_idx
18697         ON auditor.asset_copy_history ( editor );
18698
18699 CREATE INDEX aud_bib_rec_entry_hist_creator_idx
18700         ON auditor.biblio_record_entry_history ( creator );
18701
18702 CREATE INDEX aud_bib_rec_entry_hist_editor_idx
18703         ON auditor.biblio_record_entry_history ( editor );
18704
18705 CREATE TABLE action.hold_request_note (
18706
18707     id     BIGSERIAL PRIMARY KEY,
18708     hold   BIGINT    NOT NULL REFERENCES action.hold_request (id)
18709                               ON DELETE CASCADE
18710                               DEFERRABLE INITIALLY DEFERRED,
18711     title  TEXT      NOT NULL,
18712     body   TEXT      NOT NULL,
18713     slip   BOOL      NOT NULL DEFAULT FALSE,
18714     pub    BOOL      NOT NULL DEFAULT FALSE,
18715     staff  BOOL      NOT NULL DEFAULT FALSE  -- created by staff
18716
18717 );
18718 CREATE INDEX ahrn_hold_idx ON action.hold_request_note (hold);
18719
18720 -- Tweak a constraint to add a CASCADE
18721
18722 ALTER TABLE action.hold_notification DROP CONSTRAINT hold_notification_hold_fkey;
18723
18724 ALTER TABLE action.hold_notification
18725         ADD CONSTRAINT hold_notification_hold_fkey
18726                 FOREIGN KEY (hold) REFERENCES action.hold_request (id)
18727                 ON DELETE CASCADE
18728                 DEFERRABLE INITIALLY DEFERRED;
18729
18730 CREATE TRIGGER asset_label_sortkey_trigger
18731     BEFORE UPDATE OR INSERT ON asset.call_number
18732     FOR EACH ROW EXECUTE PROCEDURE asset.label_normalizer();
18733
18734 CREATE OR REPLACE FUNCTION container.clear_all_expired_circ_history_items( )
18735 RETURNS VOID AS $$
18736 --
18737 -- Delete expired circulation bucket items for all users that have
18738 -- a setting for patron.max_reading_list_interval.
18739 --
18740 DECLARE
18741     today        TIMESTAMP WITH TIME ZONE;
18742     threshold    TIMESTAMP WITH TIME ZONE;
18743         usr_setting  RECORD;
18744 BEGIN
18745         SELECT date_trunc( 'day', now() ) INTO today;
18746         --
18747         FOR usr_setting in
18748                 SELECT
18749                         usr,
18750                         value
18751                 FROM
18752                         actor.usr_setting
18753                 WHERE
18754                         name = 'patron.max_reading_list_interval'
18755         LOOP
18756                 --
18757                 -- Make sure the setting is a valid interval
18758                 --
18759                 BEGIN
18760                         threshold := today - CAST( translate( usr_setting.value, '"', '' ) AS INTERVAL );
18761                 EXCEPTION
18762                         WHEN OTHERS THEN
18763                                 RAISE NOTICE 'Invalid setting patron.max_reading_list_interval for user %: ''%''',
18764                                         usr_setting.usr, usr_setting.value;
18765                                 CONTINUE;
18766                 END;
18767                 --
18768                 --RAISE NOTICE 'User % threshold %', usr_setting.usr, threshold;
18769                 --
18770         DELETE FROM container.copy_bucket_item
18771         WHERE
18772                 bucket IN
18773                 (
18774                     SELECT
18775                         id
18776                     FROM
18777                         container.copy_bucket
18778                     WHERE
18779                         owner = usr_setting.usr
18780                         AND btype = 'circ_history'
18781                 )
18782                 AND create_time < threshold;
18783         END LOOP;
18784         --
18785 END;
18786 $$ LANGUAGE plpgsql;
18787
18788 COMMENT ON FUNCTION container.clear_all_expired_circ_history_items( ) IS $$
18789 /*
18790  * Delete expired circulation bucket items for all users that have
18791  * a setting for patron.max_reading_list_interval.
18792 */
18793 $$;
18794
18795 CREATE OR REPLACE FUNCTION container.clear_expired_circ_history_items( 
18796          ac_usr IN INTEGER
18797 ) RETURNS VOID AS $$
18798 --
18799 -- Delete old circulation bucket items for a specified user.
18800 -- "Old" means older than the interval specified by a
18801 -- user-level setting, if it is so specified.
18802 --
18803 DECLARE
18804     threshold TIMESTAMP WITH TIME ZONE;
18805 BEGIN
18806         -- Sanity check
18807         IF ac_usr IS NULL THEN
18808                 RETURN;
18809         END IF;
18810         -- Determine the threshold date that defines "old".  Subtract the
18811         -- interval from the system date, then truncate to midnight.
18812         SELECT
18813                 date_trunc( 
18814                         'day',
18815                         now() - CAST( translate( value, '"', '' ) AS INTERVAL )
18816                 )
18817         INTO
18818                 threshold
18819         FROM
18820                 actor.usr_setting
18821         WHERE
18822                 usr = ac_usr
18823                 AND name = 'patron.max_reading_list_interval';
18824         --
18825         IF threshold is null THEN
18826                 -- No interval defined; don't delete anything
18827                 -- RAISE NOTICE 'No interval defined for user %', ac_usr;
18828                 return;
18829         END IF;
18830         --
18831         -- RAISE NOTICE 'Date threshold: %', threshold;
18832         --
18833         -- Threshold found; do the delete
18834         delete from container.copy_bucket_item
18835         where
18836                 bucket in
18837                 (
18838                         select
18839                                 id
18840                         from
18841                                 container.copy_bucket
18842                         where
18843                                 owner = ac_usr
18844                                 and btype = 'circ_history'
18845                 )
18846                 and create_time < threshold;
18847         --
18848         RETURN;
18849 END;
18850 $$ LANGUAGE plpgsql;
18851
18852 COMMENT ON FUNCTION container.clear_expired_circ_history_items( INTEGER ) IS $$
18853 /*
18854  * Delete old circulation bucket items for a specified user.
18855  * "Old" means older than the interval specified by a
18856  * user-level setting, if it is so specified.
18857 */
18858 $$;
18859
18860 CREATE OR REPLACE VIEW reporter.hold_request_record AS
18861 SELECT  id,
18862     target,
18863     hold_type,
18864     CASE
18865         WHEN hold_type = 'T'
18866             THEN target
18867         WHEN hold_type = 'I'
18868             THEN (SELECT ssub.record_entry FROM serial.subscription ssub JOIN serial.issuance si ON (si.subscription = ssub.id) WHERE si.id = ahr.target)
18869         WHEN hold_type = 'V'
18870             THEN (SELECT cn.record FROM asset.call_number cn WHERE cn.id = ahr.target)
18871         WHEN hold_type IN ('C','R','F')
18872             THEN (SELECT cn.record FROM asset.call_number cn JOIN asset.copy cp ON (cn.id = cp.call_number) WHERE cp.id = ahr.target)
18873         WHEN hold_type = 'M'
18874             THEN (SELECT mr.master_record FROM metabib.metarecord mr WHERE mr.id = ahr.target)
18875     END AS bib_record
18876   FROM  action.hold_request ahr;
18877
18878 UPDATE  metabib.rec_descriptor
18879   SET   date1=LPAD(NULLIF(REGEXP_REPLACE(NULLIF(date1, ''), E'\\D', '0', 'g')::INT,0)::TEXT,4,'0'),
18880         date2=LPAD(NULLIF(REGEXP_REPLACE(NULLIF(date2, ''), E'\\D', '9', 'g')::INT,9999)::TEXT,4,'0');
18881
18882 -- Change some ints to bigints:
18883
18884 ALTER TABLE container.biblio_record_entry_bucket_item
18885         ALTER COLUMN target_biblio_record_entry SET DATA TYPE bigint;
18886
18887 ALTER TABLE vandelay.queued_bib_record
18888         ALTER COLUMN imported_as SET DATA TYPE bigint;
18889
18890 ALTER TABLE action.hold_copy_map
18891         ALTER COLUMN id SET DATA TYPE bigint;
18892
18893 -- Make due times get pushed to 23:59:59 on insert OR update
18894 DROP TRIGGER IF EXISTS push_due_date_tgr ON action.circulation;
18895 CREATE TRIGGER push_due_date_tgr BEFORE INSERT OR UPDATE ON action.circulation FOR EACH ROW EXECUTE PROCEDURE action.push_circ_due_time();
18896
18897 INSERT INTO acq.lineitem_marc_attr_definition ( code, description, xpath, remove )
18898 SELECT 'upc', 'UPC', '//*[@tag="024" and @ind1="1"]/*[@code="a"]', $r$(?:-|\s.+$)$r$
18899 WHERE NOT EXISTS (
18900     SELECT 1 FROM acq.lineitem_marc_attr_definition WHERE code = 'upc'
18901 );  
18902
18903 COMMIT;
18904
18905 -- Some operations go outside of the transaction, because they may
18906 -- legitimately fail.
18907
18908 \qecho ALTERs of auditor.action_hold_request_history will fail if the table
18909 \qecho doesn't exist; ignore those errors if they occur.
18910
18911 ALTER TABLE auditor.action_hold_request_history ADD COLUMN cut_in_line BOOL;
18912
18913 ALTER TABLE auditor.action_hold_request_history
18914 ADD COLUMN mint_condition boolean NOT NULL DEFAULT TRUE;
18915
18916 ALTER TABLE auditor.action_hold_request_history
18917 ADD COLUMN shelf_expire_time TIMESTAMPTZ;
18918
18919 \qecho Outside of the transaction: adding indexes that may or may not exist.
18920 \qecho If any of these CREATE INDEX statements fails because the index already
18921 \qecho exists, ignore the failure.
18922
18923 CREATE INDEX acq_picklist_owner_idx   ON acq.picklist ( owner );
18924 CREATE INDEX acq_picklist_creator_idx ON acq.picklist ( creator );
18925 CREATE INDEX acq_picklist_editor_idx  ON acq.picklist ( editor );
18926 CREATE INDEX acq_po_note_creator_idx  ON acq.po_note ( creator );
18927 CREATE INDEX acq_po_note_editor_idx   ON acq.po_note ( editor );
18928 CREATE INDEX fund_alloc_allocator_idx ON acq.fund_allocation ( allocator );
18929 CREATE INDEX li_creator_idx   ON acq.lineitem ( creator );
18930 CREATE INDEX li_editor_idx    ON acq.lineitem ( editor );
18931 CREATE INDEX li_selector_idx  ON acq.lineitem ( selector );
18932 CREATE INDEX li_note_creator_idx  ON acq.lineitem_note ( creator );
18933 CREATE INDEX li_note_editor_idx   ON acq.lineitem_note ( editor );
18934 CREATE INDEX li_usr_attr_def_usr_idx  ON acq.lineitem_usr_attr_definition ( usr );
18935 CREATE INDEX po_editor_idx   ON acq.purchase_order ( editor );
18936 CREATE INDEX po_creator_idx  ON acq.purchase_order ( creator );
18937 CREATE INDEX acq_po_org_name_order_date_idx ON acq.purchase_order( ordering_agency, name, order_date );
18938 CREATE INDEX action_in_house_use_staff_idx  ON action.in_house_use ( staff );
18939 CREATE INDEX action_non_cat_circ_patron_idx ON action.non_cataloged_circulation ( patron );
18940 CREATE INDEX action_non_cat_circ_staff_idx  ON action.non_cataloged_circulation ( staff );
18941 CREATE INDEX action_survey_response_usr_idx ON action.survey_response ( usr );
18942 CREATE INDEX ahn_notify_staff_idx           ON action.hold_notification ( notify_staff );
18943 CREATE INDEX circ_all_usr_idx               ON action.circulation ( usr );
18944 CREATE INDEX circ_circ_staff_idx            ON action.circulation ( circ_staff );
18945 CREATE INDEX circ_checkin_staff_idx         ON action.circulation ( checkin_staff );
18946 CREATE INDEX hold_request_fulfillment_staff_idx ON action.hold_request ( fulfillment_staff );
18947 CREATE INDEX hold_request_requestor_idx     ON action.hold_request ( requestor );
18948 CREATE INDEX non_cat_in_house_use_staff_idx ON action.non_cat_in_house_use ( staff );
18949 CREATE INDEX actor_usr_note_creator_idx     ON actor.usr_note ( creator );
18950 CREATE INDEX actor_usr_standing_penalty_staff_idx ON actor.usr_standing_penalty ( staff );
18951 CREATE INDEX usr_org_unit_opt_in_staff_idx  ON actor.usr_org_unit_opt_in ( staff );
18952 CREATE INDEX asset_call_number_note_creator_idx ON asset.call_number_note ( creator );
18953 CREATE INDEX asset_copy_note_creator_idx    ON asset.copy_note ( creator );
18954 CREATE INDEX cp_creator_idx                 ON asset.copy ( creator );
18955 CREATE INDEX cp_editor_idx                  ON asset.copy ( editor );
18956
18957 CREATE INDEX actor_card_barcode_lower_idx ON actor.card (lower(barcode));
18958
18959 DROP INDEX IF EXISTS authority.unique_by_heading_and_thesaurus;
18960
18961 \qecho If the following CREATE INDEX fails, It will be necessary to do some
18962 \qecho data cleanup as described in the comments.
18963
18964 CREATE UNIQUE INDEX unique_by_heading_and_thesaurus
18965     ON authority.record_entry (authority.normalize_heading(marc))
18966         WHERE deleted IS FALSE or deleted = FALSE;
18967
18968 -- If the unique index fails, uncomment the following to create
18969 -- a regular index that will help find the duplicates in a hurry:
18970 --CREATE INDEX by_heading_and_thesaurus
18971 --    ON authority.record_entry (authority.normalize_heading(marc))
18972 --    WHERE deleted IS FALSE or deleted = FALSE
18973 --;
18974
18975 -- Then find the duplicates like so to get an idea of how much
18976 -- pain you're looking at to clean things up:
18977 --SELECT id, authority.normalize_heading(marc)
18978 --    FROM authority.record_entry
18979 --    WHERE authority.normalize_heading(marc) IN (
18980 --        SELECT authority.normalize_heading(marc)
18981 --        FROM authority.record_entry
18982 --        GROUP BY authority.normalize_heading(marc)
18983 --        HAVING COUNT(*) > 1
18984 --    )
18985 --;
18986
18987 -- Once you have removed the duplicates and the CREATE UNIQUE INDEX
18988 -- statement succeeds, drop the temporary index to avoid unnecessary
18989 -- duplication:
18990 -- DROP INDEX authority.by_heading_and_thesaurus;
18991
18992 -- 0448.data.trigger.circ.staff_age_to_lost.sql
18993
18994 INSERT INTO action_trigger.hook (key,core_type,description,passive) VALUES 
18995     (   'circ.staff_age_to_lost',
18996         'circ', 
18997         oils_i18n_gettext(
18998             'circ.staff_age_to_lost',
18999             'An overdue circulation should be aged to a Lost status.',
19000             'ath',
19001             'description'
19002         ), 
19003         TRUE
19004     )
19005 ;
19006
19007 INSERT INTO action_trigger.event_definition (
19008         id,
19009         active,
19010         owner,
19011         name,
19012         hook,
19013         validator,
19014         reactor,
19015         delay_field
19016     ) VALUES (
19017         36,
19018         FALSE,
19019         1,
19020         'circ.staff_age_to_lost',
19021         'circ.staff_age_to_lost',
19022         'CircIsOverdue',
19023         'MarkItemLost',
19024         'due_date'
19025     )
19026 ;
19027
19028
19029 \qecho Upgrade script completed.
19030