]> git.evergreen-ils.org Git - Evergreen.git/blob - Open-ILS/src/sql/Pg/1.6.1-2.0-upgrade-db.sql
keep trunk version of the 2.0 upgrade script in sync ... for now
[Evergreen.git] / Open-ILS / src / sql / Pg / 1.6.1-2.0-upgrade-db.sql
1 -- Before starting the transaction: drop some constraints that
2 -- may or may not exist.
3
4 CREATE OR REPLACE FUNCTION oils_text_as_bytea (TEXT) RETURNS BYTEA AS $_$
5     SELECT CAST(REGEXP_REPLACE(UPPER($1), $$\\$$, $$\\\\$$, 'g') AS BYTEA);
6 $_$ LANGUAGE SQL IMMUTABLE;
7
8 DROP INDEX asset.asset_call_number_upper_label_id_owning_lib_idx;
9 CREATE INDEX asset_call_number_upper_label_id_owning_lib_idx ON asset.call_number (oils_text_as_bytea(label),id,owning_lib);
10
11 \qecho Before starting the transaction: drop some constraints.
12 \qecho If a DROP fails because the constraint doesn't exist, ignore the failure.
13
14 ALTER TABLE permission.grp_perm_map        DROP CONSTRAINT grp_perm_map_perm_fkey;
15 ALTER TABLE permission.usr_perm_map        DROP CONSTRAINT usr_perm_map_perm_fkey;
16 ALTER TABLE permission.usr_object_perm_map DROP CONSTRAINT usr_object_perm_map_perm_fkey;
17 ALTER TABLE booking.resource_type          DROP CONSTRAINT brt_name_or_record_once_per_owner;
18 ALTER TABLE booking.resource_type          DROP CONSTRAINT brt_name_once_per_owner;
19
20 \qecho Beginning the transaction now
21
22 BEGIN;
23
24 -- Highest-numbered individual upgrade script incorporated herein:
25
26 INSERT INTO config.upgrade_log (version) VALUES ('0461');
27
28 -- Remove some uses of the connectby() function from the tablefunc contrib module
29 CREATE OR REPLACE FUNCTION actor.org_unit_descendants( INT, INT ) RETURNS SETOF actor.org_unit AS $$
30     WITH RECURSIVE descendant_depth AS (
31         SELECT  ou.id,
32                 ou.parent_ou,
33                 out.depth
34           FROM  actor.org_unit ou
35                 JOIN actor.org_unit_type out ON (out.id = ou.ou_type)
36                 JOIN anscestor_depth ad ON (ad.id = ou.id)
37           WHERE ad.depth = $2
38             UNION ALL
39         SELECT  ou.id,
40                 ou.parent_ou,
41                 out.depth
42           FROM  actor.org_unit ou
43                 JOIN actor.org_unit_type out ON (out.id = ou.ou_type)
44                 JOIN descendant_depth ot ON (ot.id = ou.parent_ou)
45     ), anscestor_depth AS (
46         SELECT  ou.id,
47                 ou.parent_ou,
48                 out.depth
49           FROM  actor.org_unit ou
50                 JOIN actor.org_unit_type out ON (out.id = ou.ou_type)
51           WHERE ou.id = $1
52             UNION ALL
53         SELECT  ou.id,
54                 ou.parent_ou,
55                 out.depth
56           FROM  actor.org_unit ou
57                 JOIN actor.org_unit_type out ON (out.id = ou.ou_type)
58                 JOIN anscestor_depth ot ON (ot.parent_ou = ou.id)
59     ) SELECT ou.* FROM actor.org_unit ou JOIN descendant_depth USING (id);
60 $$ LANGUAGE SQL;
61
62 CREATE OR REPLACE FUNCTION actor.org_unit_descendants( INT ) RETURNS SETOF actor.org_unit AS $$
63     WITH RECURSIVE descendant_depth AS (
64         SELECT  ou.id,
65                 ou.parent_ou,
66                 out.depth
67           FROM  actor.org_unit ou
68                 JOIN actor.org_unit_type out ON (out.id = ou.ou_type)
69           WHERE ou.id = $1
70             UNION ALL
71         SELECT  ou.id,
72                 ou.parent_ou,
73                 out.depth
74           FROM  actor.org_unit ou
75                 JOIN actor.org_unit_type out ON (out.id = ou.ou_type)
76                 JOIN descendant_depth ot ON (ot.id = ou.parent_ou)
77     ) SELECT ou.* FROM actor.org_unit ou JOIN descendant_depth USING (id);
78 $$ LANGUAGE SQL;
79
80 CREATE OR REPLACE FUNCTION actor.org_unit_ancestors( INT ) RETURNS SETOF actor.org_unit AS $$
81     WITH RECURSIVE anscestor_depth AS (
82         SELECT  ou.id,
83                 ou.parent_ou
84           FROM  actor.org_unit ou
85           WHERE ou.id = $1
86             UNION ALL
87         SELECT  ou.id,
88                 ou.parent_ou
89           FROM  actor.org_unit ou
90                 JOIN anscestor_depth ot ON (ot.parent_ou = ou.id)
91     ) SELECT ou.* FROM actor.org_unit ou JOIN anscestor_depth USING (id);
92 $$ LANGUAGE SQL;
93
94 -- Support merge template buckets
95 INSERT INTO container.biblio_record_entry_bucket_type (code,label) VALUES ('template_merge','Template Merge Container');
96
97 -- Recreate one of the constraints that we just dropped,
98 -- under a different name:
99
100 ALTER TABLE booking.resource_type
101         ADD CONSTRAINT brt_name_and_record_once_per_owner UNIQUE(owner, name, record);
102
103 -- Now upgrade permission.perm_list.  This is fairly complicated.
104
105 -- Add ON UPDATE CASCADE to some foreign keys so that, when we renumber the
106 -- permissions, the dependents will follow and stay in sync:
107
108 ALTER TABLE permission.grp_perm_map ADD CONSTRAINT grp_perm_map_perm_fkey FOREIGN KEY (perm)
109     REFERENCES permission.perm_list (id) ON UPDATE CASCADE ON DELETE RESTRICT DEFERRABLE INITIALLY DEFERRED;
110
111 ALTER TABLE permission.usr_perm_map ADD CONSTRAINT usr_perm_map_perm_fkey FOREIGN KEY (perm)
112     REFERENCES permission.perm_list (id) ON UPDATE CASCADE ON DELETE RESTRICT DEFERRABLE INITIALLY DEFERRED;
113
114 ALTER TABLE permission.usr_object_perm_map ADD CONSTRAINT usr_object_perm_map_perm_fkey FOREIGN KEY (perm)
115     REFERENCES permission.perm_list (id) ON UPDATE CASCADE ON DELETE RESTRICT DEFERRABLE INITIALLY DEFERRED;
116
117 UPDATE permission.perm_list
118     SET code = 'UPDATE_ORG_UNIT_SETTING.credit.payments.allow'
119     WHERE code = 'UPDATE_ORG_UNIT_SETTING.global.credit.allow';
120
121 -- The following UPDATES were originally in an individual upgrade script, but should
122 -- no longer be necessary now that the foreign key has an ON UPDATE CASCADE clause.
123 -- We retain the UPDATES here, commented out, as historical relics.
124
125 -- UPDATE permission.grp_perm_map SET perm = perm + 1000 WHERE perm NOT IN ( SELECT id FROM permission.perm_list );
126 -- UPDATE permission.usr_perm_map SET perm = perm + 1000 WHERE perm NOT IN ( SELECT id FROM permission.perm_list );
127
128 -- Spelling correction
129 UPDATE permission.perm_list SET code = 'ADMIN_RECURRING_FINE_RULE' WHERE code = 'ADMIN_RECURING_FINE_RULE';
130
131 -- Now we engage in a Great Renumbering of the permissions in permission.perm_list,
132 -- in order to clean up accumulated cruft.
133
134 -- The first step is to establish some triggers so that, when we change the id of a permission,
135 -- the associated translations are updated accordingly.
136
137 CREATE OR REPLACE FUNCTION oils_i18n_update_apply(old_ident TEXT, new_ident TEXT, hint TEXT) RETURNS VOID AS $_$
138 BEGIN
139
140     EXECUTE $$
141         UPDATE  config.i18n_core
142           SET   identity_value = $$ || quote_literal( new_ident ) || $$ 
143           WHERE fq_field LIKE '$$ || hint || $$.%' 
144                 AND identity_value = $$ || quote_literal( old_ident ) || $$;$$;
145
146     RETURN;
147
148 END;
149 $_$ LANGUAGE PLPGSQL;
150
151 CREATE OR REPLACE FUNCTION oils_i18n_id_tracking(/* hint */) RETURNS TRIGGER AS $_$
152 BEGIN
153     PERFORM oils_i18n_update_apply( OLD.id::TEXT, NEW.id::TEXT, TG_ARGV[0]::TEXT );
154     RETURN NEW;
155 END;
156 $_$ LANGUAGE PLPGSQL;
157
158 CREATE OR REPLACE FUNCTION oils_i18n_code_tracking(/* hint */) RETURNS TRIGGER AS $_$
159 BEGIN
160     PERFORM oils_i18n_update_apply( OLD.code::TEXT, NEW.code::TEXT, TG_ARGV[0]::TEXT );
161     RETURN NEW;
162 END;
163 $_$ LANGUAGE PLPGSQL;
164
165
166 CREATE TRIGGER maintain_perm_i18n_tgr
167     AFTER UPDATE ON permission.perm_list
168     FOR EACH ROW EXECUTE PROCEDURE oils_i18n_id_tracking('ppl');
169
170 -- Next, create a new table as a convenience for sloshing data back and forth,
171 -- and for recording which permission went where.  It looks just like
172 -- permission.perm_list, but with two extra columns: one for the old id, and one to
173 -- distinguish between predefined permissions and non-predefined permissions.
174
175 -- This table is, in effect, a temporary table, because we can drop it once the
176 -- upgrade is complete.  It is not technically temporary as far as PostgreSQL is
177 -- concerned, because we don't want it to disappear at the end of the session.
178 -- We keep it around so that we have a map showing the old id and the new id for
179 -- each permission.  However there is no IDL entry for it, nor is it defined
180 -- in the base sql files.
181
182 CREATE TABLE permission.temp_perm (
183         id          INT        PRIMARY KEY,
184         code        TEXT       UNIQUE,
185         description TEXT,
186         old_id      INT,
187         predefined  BOOL       NOT NULL DEFAULT TRUE
188 );
189
190 -- Populate the temp table with a definitive set of predefined permissions,
191 -- hard-coding the ids.
192
193 -- The first set of permissions is derived from the database, as loaded in a
194 -- loaded 1.6.1 database, plus a few changes previously applied in this upgrade
195 -- script.  The second set is derived from the IDL -- permissions that are referenced
196 -- in <permacrud> elements but not defined in the database.
197
198 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( -1, 'EVERYTHING',
199      '' );
200 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 1, 'OPAC_LOGIN',
201      'Allow a user to log in to the OPAC' );
202 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 2, 'STAFF_LOGIN',
203      'Allow a user to log in to the staff client' );
204 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 3, 'MR_HOLDS',
205      'Allow a user to create a metarecord holds' );
206 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 4, 'TITLE_HOLDS',
207      'Allow a user to place a hold at the title level' );
208 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 5, 'VOLUME_HOLDS',
209      'Allow a user to place a volume level hold' );
210 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 6, 'COPY_HOLDS',
211      'Allow a user to place a hold on a specific copy' );
212 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 7, 'REQUEST_HOLDS',
213      'Allow a user to create holds for another user (if true, we still check to make sure they have permission to make the type of hold they are requesting, for example, COPY_HOLDS)' );
214 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 8, 'REQUEST_HOLDS_OVERRIDE',
215      '* no longer applicable' );
216 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 9, 'VIEW_HOLD',
217      'Allow a user to view another user''s holds' );
218 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 10, 'DELETE_HOLDS',
219      '* no longer applicable' );
220 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 11, 'UPDATE_HOLD',
221      'Allow a user to update another user''s hold' );
222 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 12, 'RENEW_CIRC',
223      'Allow a user to renew items' );
224 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 13, 'VIEW_USER_FINES_SUMMARY',
225      'Allow a user to view bill details' );
226 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 14, 'VIEW_USER_TRANSACTIONS',
227      'Allow a user to see another user''s grocery or circulation transactions in the Bills Interface; duplicate of VIEW_TRANSACTION' );
228 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 15, 'UPDATE_MARC',
229      'Allow a user to edit a MARC record' );
230 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 16, 'CREATE_MARC',
231      'Allow a user to create new MARC records' );
232 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 17, 'IMPORT_MARC',
233      'Allow a user to import a MARC record via the Z39.50 interface' );
234 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 18, 'CREATE_VOLUME',
235      'Allow a user to create a volume' );
236 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 19, 'UPDATE_VOLUME',
237      'Allow a user to edit volumes - needed for merging records. This is a duplicate of VOLUME_UPDATE; user must have both permissions at appropriate level to merge records.' );
238 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 20, 'DELETE_VOLUME',
239      'Allow a user to delete a volume' );
240 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 21, 'CREATE_COPY',
241      'Allow a user to create a new copy object' );
242 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 22, 'UPDATE_COPY',
243      'Allow a user to edit a copy' );
244 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 23, 'DELETE_COPY',
245      'Allow a user to delete a copy' );
246 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 24, 'RENEW_HOLD_OVERRIDE',
247      'Allow a user to continue to renew an item even if it is required for a hold' );
248 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 25, 'CREATE_USER',
249      'Allow a user to create another user' );
250 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 26, 'UPDATE_USER',
251      'Allow a user to edit a user''s record' );
252 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 27, 'DELETE_USER',
253      'Allow a user to mark a user as deleted' );
254 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 28, 'VIEW_USER',
255      'Allow a user to view another user''s Patron Record' );
256 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 29, 'COPY_CHECKIN',
257      'Allow a user to check in a copy' );
258 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 30, 'CREATE_TRANSIT',
259      'Allow a user to place an item in transit' );
260 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 31, 'VIEW_PERMISSION',
261      'Allow a user to view user permissions within the user permissions editor' );
262 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 32, 'CHECKIN_BYPASS_HOLD_FULFILL',
263      '* no longer applicable' );
264 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 33, 'CREATE_PAYMENT',
265      'Allow a user to record payments in the Billing Interface' );
266 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 34, 'SET_CIRC_LOST',
267      'Allow a user to mark an item as ''lost''' );
268 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 35, 'SET_CIRC_MISSING',
269      'Allow a user to mark an item as ''missing''' );
270 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 36, 'SET_CIRC_CLAIMS_RETURNED',
271      'Allow a user to mark an item as ''claims returned''' );
272 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 37, 'CREATE_TRANSACTION',
273      'Allow a user to create a new billable transaction' );
274 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 38, 'VIEW_TRANSACTION',
275      'Allow a user may view another user''s transactions' );
276 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 39, 'CREATE_BILL',
277      'Allow a user to create a new bill on a transaction' );
278 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 40, 'VIEW_CONTAINER',
279      'Allow a user to view another user''s containers (buckets)' );
280 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 41, 'CREATE_CONTAINER',
281      'Allow a user to create a new container for another user' );
282 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 42, 'UPDATE_ORG_UNIT',
283      'Allow a user to change the settings for an organization unit' );
284 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 43, 'VIEW_CIRCULATIONS',
285      'Allow a user to see what another user has checked out' );
286 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 44, 'DELETE_CONTAINER',
287      'Allow a user to delete another user''s container' );
288 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 45, 'CREATE_CONTAINER_ITEM',
289      'Allow a user to create a container item for another user' );
290 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 46, 'CREATE_USER_GROUP_LINK',
291      'Allow a user to add other users to permission groups' );
292 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 47, 'REMOVE_USER_GROUP_LINK',
293      'Allow a user to remove other users from permission groups' );
294 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 48, 'VIEW_PERM_GROUPS',
295      'Allow a user to view other users'' permission groups' );
296 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 49, 'VIEW_PERMIT_CHECKOUT',
297      'Allow a user to determine whether another user can check out an item' );
298 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 50, 'UPDATE_BATCH_COPY',
299      'Allow a user to edit copies in batch' );
300 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 51, 'CREATE_PATRON_STAT_CAT',
301      'User may create a new patron statistical category' );
302 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 52, 'CREATE_COPY_STAT_CAT',
303      'User may create a copy statistical category' );
304 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 53, 'CREATE_PATRON_STAT_CAT_ENTRY',
305      'User may create an entry in a patron statistical category' );
306 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 54, 'CREATE_COPY_STAT_CAT_ENTRY',
307      'User may create an entry in a copy statistical category' );
308 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 55, 'UPDATE_PATRON_STAT_CAT',
309      'User may update a patron statistical category' );
310 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 56, 'UPDATE_COPY_STAT_CAT',
311      'User may update a copy statistical category' );
312 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 57, 'UPDATE_PATRON_STAT_CAT_ENTRY',
313      'User may update an entry in a patron statistical category' );
314 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 58, 'UPDATE_COPY_STAT_CAT_ENTRY',
315      'User may update an entry in a copy statistical category' );
316 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 59, 'CREATE_PATRON_STAT_CAT_ENTRY_MAP',
317      'User may link another user to an entry in a statistical category' );
318 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 60, 'CREATE_COPY_STAT_CAT_ENTRY_MAP',
319      'User may link a copy to an entry in a statistical category' );
320 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 61, 'DELETE_PATRON_STAT_CAT',
321      'User may delete a patron statistical category' );
322 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 62, 'DELETE_COPY_STAT_CAT',
323      'User may delete a copy statistical category' );
324 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 63, 'DELETE_PATRON_STAT_CAT_ENTRY',
325      'User may delete an entry from a patron statistical category' );
326 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 64, 'DELETE_COPY_STAT_CAT_ENTRY',
327      'User may delete an entry from a copy statistical category' );
328 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 65, 'DELETE_PATRON_STAT_CAT_ENTRY_MAP',
329      'User may delete a patron statistical category entry map' );
330 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 66, 'DELETE_COPY_STAT_CAT_ENTRY_MAP',
331      'User may delete a copy statistical category entry map' );
332 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 67, 'CREATE_NON_CAT_TYPE',
333      'Allow a user to create a new non-cataloged item type' );
334 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 68, 'UPDATE_NON_CAT_TYPE',
335      'Allow a user to update a non-cataloged item type' );
336 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 69, 'CREATE_IN_HOUSE_USE',
337      'Allow a user to create a new in-house-use ' );
338 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 70, 'COPY_CHECKOUT',
339      'Allow a user to check out a copy' );
340 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 71, 'CREATE_COPY_LOCATION',
341      'Allow a user to create a new copy location' );
342 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 72, 'UPDATE_COPY_LOCATION',
343      'Allow a user to update a copy location' );
344 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 73, 'DELETE_COPY_LOCATION',
345      'Allow a user to delete a copy location' );
346 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 74, 'CREATE_COPY_TRANSIT',
347      'Allow a user to create a transit_copy object for transiting a copy' );
348 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 75, 'COPY_TRANSIT_RECEIVE',
349      'Allow a user to close out a transit on a copy' );
350 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 76, 'VIEW_HOLD_PERMIT',
351      'Allow a user to see if another user has permission to place a hold on a given copy' );
352 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 77, 'VIEW_COPY_CHECKOUT_HISTORY',
353      'Allow a user to view which users have checked out a given copy' );
354 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 78, 'REMOTE_Z3950_QUERY',
355      'Allow a user to perform Z39.50 queries against remote servers' );
356 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 79, 'REGISTER_WORKSTATION',
357      'Allow a user to register a new workstation' );
358 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 80, 'VIEW_COPY_NOTES',
359      'Allow a user to view all notes attached to a copy' );
360 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 81, 'VIEW_VOLUME_NOTES',
361      'Allow a user to view all notes attached to a volume' );
362 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 82, 'VIEW_TITLE_NOTES',
363      'Allow a user to view all notes attached to a title' );
364 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 83, 'CREATE_COPY_NOTE',
365      'Allow a user to create a new copy note' );
366 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 84, 'CREATE_VOLUME_NOTE',
367      'Allow a user to create a new volume note' );
368 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 85, 'CREATE_TITLE_NOTE',
369      'Allow a user to create a new title note' );
370 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 86, 'DELETE_COPY_NOTE',
371      'Allow a user to delete another user''s copy notes' );
372 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 87, 'DELETE_VOLUME_NOTE',
373      'Allow a user to delete another user''s volume note' );
374 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 88, 'DELETE_TITLE_NOTE',
375      'Allow a user to delete another user''s title note' );
376 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 89, 'UPDATE_CONTAINER',
377      'Allow a user to update another user''s container' );
378 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 90, 'CREATE_MY_CONTAINER',
379      'Allow a user to create a container for themselves' );
380 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 91, 'VIEW_HOLD_NOTIFICATION',
381      'Allow a user to view notifications attached to a hold' );
382 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 92, 'CREATE_HOLD_NOTIFICATION',
383      'Allow a user to create new hold notifications' );
384 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 93, 'UPDATE_ORG_SETTING',
385      'Allow a user to update an organization unit setting' );
386 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 94, 'OFFLINE_UPLOAD',
387      'Allow a user to upload an offline script' );
388 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 95, 'OFFLINE_VIEW',
389      'Allow a user to view uploaded offline script information' );
390 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 96, 'OFFLINE_EXECUTE',
391      'Allow a user to execute an offline script batch' );
392 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 97, 'CIRC_OVERRIDE_DUE_DATE',
393      'Allow a user to change the due date on an item to any date' );
394 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 98, 'CIRC_PERMIT_OVERRIDE',
395      'Allow a user to bypass the circulation permit call for check out' );
396 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 99, 'COPY_IS_REFERENCE.override',
397      'Allow a user to override the copy_is_reference event' );
398 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 100, 'VOID_BILLING',
399      'Allow a user to void a bill' );
400 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 101, 'CIRC_CLAIMS_RETURNED.override',
401      'Allow a user to check in or check out an item that has a status of ''claims returned''' );
402 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 102, 'COPY_BAD_STATUS.override',
403      'Allow a user to check out an item in a non-circulatable status' );
404 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 103, 'COPY_ALERT_MESSAGE.override',
405      'Allow a user to check in/out an item that has an alert message' );
406 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 104, 'COPY_STATUS_LOST.override',
407      'Allow a user to remove the lost status from a copy' );
408 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 105, 'COPY_STATUS_MISSING.override',
409      'Allow a user to change the missing status on a copy' );
410 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 106, 'ABORT_TRANSIT',
411      'Allow a user to abort a copy transit if the user is at the transit destination or source' );
412 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 107, 'ABORT_REMOTE_TRANSIT',
413      'Allow a user to abort a copy transit if the user is not at the transit source or dest' );
414 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 108, 'VIEW_ZIP_DATA',
415      'Allow a user to query the ZIP code data method' );
416 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 109, 'CANCEL_HOLDS',
417      'Allow a user to cancel holds' );
418 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 110, 'CREATE_DUPLICATE_HOLDS',
419      'Allow a user to create duplicate holds (two or more holds on the same title)' );
420 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 111, 'actor.org_unit.closed_date.delete',
421      'Allow a user to remove a closed date interval for a given location' );
422 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 112, 'actor.org_unit.closed_date.update',
423      'Allow a user to update a closed date interval for a given location' );
424 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 113, 'actor.org_unit.closed_date.create',
425      'Allow a user to create a new closed date for a location' );
426 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 114, 'DELETE_NON_CAT_TYPE',
427      'Allow a user to delete a non cataloged type' );
428 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 115, 'money.collections_tracker.create',
429      'Allow a user to put someone into collections' );
430 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 116, 'money.collections_tracker.delete',
431      'Allow a user to remove someone from collections' );
432 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 117, 'BAR_PATRON',
433      'Allow a user to bar a patron' );
434 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 118, 'UNBAR_PATRON',
435      'Allow a user to un-bar a patron' );
436 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 119, 'DELETE_WORKSTATION',
437      'Allow a user to remove an existing workstation so a new one can replace it' );
438 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 120, 'group_application.user',
439      'Allow a user to add/remove users to/from the "User" group' );
440 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 121, 'group_application.user.patron',
441      'Allow a user to add/remove users to/from the "Patron" group' );
442 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 122, 'group_application.user.staff',
443      'Allow a user to add/remove users to/from the "Staff" group' );
444 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 123, 'group_application.user.staff.circ',
445      'Allow a user to add/remove users to/from the "Circulator" group' );
446 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 124, 'group_application.user.staff.cat',
447      'Allow a user to add/remove users to/from the "Cataloger" group' );
448 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 125, 'group_application.user.staff.admin.global_admin',
449      'Allow a user to add/remove users to/from the "GlobalAdmin" group' );
450 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 126, 'group_application.user.staff.admin.local_admin',
451      'Allow a user to add/remove users to/from the "LocalAdmin" group' );
452 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 127, 'group_application.user.staff.admin.lib_manager',
453      'Allow a user to add/remove users to/from the "LibraryManager" group' );
454 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 128, 'group_application.user.staff.cat.cat1',
455      'Allow a user to add/remove users to/from the "Cat1" group' );
456 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 129, 'group_application.user.staff.supercat',
457      'Allow a user to add/remove users to/from the "Supercat" group' );
458 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 130, 'group_application.user.sip_client',
459      'Allow a user to add/remove users to/from the "SIP-Client" group' );
460 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 131, 'group_application.user.vendor',
461      'Allow a user to add/remove users to/from the "Vendor" group' );
462 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 132, 'ITEM_AGE_PROTECTED.override',
463      'Allow a user to place a hold on an age-protected item' );
464 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 133, 'MAX_RENEWALS_REACHED.override',
465      'Allow a user to renew an item past the maximum renewal count' );
466 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 134, 'PATRON_EXCEEDS_CHECKOUT_COUNT.override',
467      'Allow staff to override checkout count failure' );
468 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 135, 'PATRON_EXCEEDS_OVERDUE_COUNT.override',
469      'Allow staff to override overdue count failure' );
470 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 136, 'PATRON_EXCEEDS_FINES.override',
471      'Allow staff to override fine amount checkout failure' );
472 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 137, 'CIRC_EXCEEDS_COPY_RANGE.override',
473      'Allow staff to override circulation copy range failure' );
474 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 138, 'ITEM_ON_HOLDS_SHELF.override',
475      'Allow staff to override item on holds shelf failure' );
476 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 139, 'COPY_NOT_AVAILABLE.override',
477      'Allow staff to force checkout of Missing/Lost type items' );
478 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 140, 'HOLD_EXISTS.override',
479      'Allow a user to place multiple holds on a single title' );
480 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 141, 'RUN_REPORTS',
481      'Allow a user to run reports' );
482 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 142, 'SHARE_REPORT_FOLDER',
483      'Allow a user to share report his own folders' );
484 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 143, 'VIEW_REPORT_OUTPUT',
485      'Allow a user to view report output' );
486 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 144, 'COPY_CIRC_NOT_ALLOWED.override',
487      'Allow a user to checkout an item that is marked as non-circ' );
488 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 145, 'DELETE_CONTAINER_ITEM',
489      'Allow a user to delete an item out of another user''s container' );
490 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 146, 'ASSIGN_WORK_ORG_UNIT',
491      'Allow a staff member to define where another staff member has their permissions' );
492 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 147, 'CREATE_FUNDING_SOURCE',
493      'Allow a user to create a new funding source' );
494 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 148, 'DELETE_FUNDING_SOURCE',
495      'Allow a user to delete a funding source' );
496 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 149, 'VIEW_FUNDING_SOURCE',
497      'Allow a user to view a funding source' );
498 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 150, 'UPDATE_FUNDING_SOURCE',
499      'Allow a user to update a funding source' );
500 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 151, 'CREATE_FUND',
501      'Allow a user to create a new fund' );
502 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 152, 'DELETE_FUND',
503      'Allow a user to delete a fund' );
504 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 153, 'VIEW_FUND',
505      'Allow a user to view a fund' );
506 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 154, 'UPDATE_FUND',
507      'Allow a user to update a fund' );
508 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 155, 'CREATE_FUND_ALLOCATION',
509      'Allow a user to create a new fund allocation' );
510 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 156, 'DELETE_FUND_ALLOCATION',
511      'Allow a user to delete a fund allocation' );
512 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 157, 'VIEW_FUND_ALLOCATION',
513      'Allow a user to view a fund allocation' );
514 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 158, 'UPDATE_FUND_ALLOCATION',
515      'Allow a user to update a fund allocation' );
516 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 159, 'GENERAL_ACQ',
517      'Lowest level permission required to access the ACQ interface' );
518 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 160, 'CREATE_PROVIDER',
519      'Allow a user to create a new provider' );
520 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 161, 'DELETE_PROVIDER',
521      'Allow a user to delate a provider' );
522 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 162, 'VIEW_PROVIDER',
523      'Allow a user to view a provider' );
524 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 163, 'UPDATE_PROVIDER',
525      'Allow a user to update a provider' );
526 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 164, 'ADMIN_FUNDING_SOURCE',
527      'Allow a user to create/view/update/delete a funding source' );
528 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 165, 'ADMIN_FUND',
529      '(Deprecated) Allow a user to create/view/update/delete a fund' );
530 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 166, 'MANAGE_FUNDING_SOURCE',
531      'Allow a user to view/credit/debit a funding source' );
532 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 167, 'MANAGE_FUND',
533      'Allow a user to view/credit/debit a fund' );
534 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 168, 'CREATE_PICKLIST',
535      'Allows a user to create a picklist' );
536 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 169, 'ADMIN_PROVIDER',
537      'Allow a user to create/view/update/delete a provider' );
538 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 170, 'MANAGE_PROVIDER',
539      'Allow a user to view and purchase from a provider' );
540 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 171, 'VIEW_PICKLIST',
541      'Allow a user to view another users picklist' );
542 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 172, 'DELETE_RECORD',
543      'Allow a staff member to directly remove a bibliographic record' );
544 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 173, 'ADMIN_CURRENCY_TYPE',
545      'Allow a user to create/view/update/delete a currency_type' );
546 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 174, 'MARK_BAD_DEBT',
547      'Allow a user to mark a transaction as bad (unrecoverable) debt' );
548 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 175, 'VIEW_BILLING_TYPE',
549      'Allow a user to view billing types' );
550 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 176, 'MARK_ITEM_AVAILABLE',
551      'Allow a user to mark an item status as ''available''' );
552 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 177, 'MARK_ITEM_CHECKED_OUT',
553      'Allow a user to mark an item status as ''checked out''' );
554 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 178, 'MARK_ITEM_BINDERY',
555      'Allow a user to mark an item status as ''bindery''' );
556 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 179, 'MARK_ITEM_LOST',
557      'Allow a user to mark an item status as ''lost''' );
558 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 180, 'MARK_ITEM_MISSING',
559      'Allow a user to mark an item status as ''missing''' );
560 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 181, 'MARK_ITEM_IN_PROCESS',
561      'Allow a user to mark an item status as ''in process''' );
562 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 182, 'MARK_ITEM_IN_TRANSIT',
563      'Allow a user to mark an item status as ''in transit''' );
564 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 183, 'MARK_ITEM_RESHELVING',
565      'Allow a user to mark an item status as ''reshelving''' );
566 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 184, 'MARK_ITEM_ON_HOLDS_SHELF',
567      'Allow a user to mark an item status as ''on holds shelf''' );
568 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 185, 'MARK_ITEM_ON_ORDER',
569      'Allow a user to mark an item status as ''on order''' );
570 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 186, 'MARK_ITEM_ILL',
571      'Allow a user to mark an item status as ''inter-library loan''' );
572 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 187, 'group_application.user.staff.acq',
573      'Allows a user to add/remove/edit users in the "ACQ" group' );
574 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 188, 'CREATE_PURCHASE_ORDER',
575      'Allows a user to create a purchase order' );
576 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 189, 'VIEW_PURCHASE_ORDER',
577      'Allows a user to view a purchase order' );
578 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 190, 'IMPORT_ACQ_LINEITEM_BIB_RECORD',
579      'Allows a user to import a bib record from the acq staging area (on-order record) into the ILS bib data set' );
580 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 191, 'RECEIVE_PURCHASE_ORDER',
581      'Allows a user to mark a purchase order, lineitem, or individual copy as received' );
582 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 192, 'VIEW_ORG_SETTINGS',
583      'Allows a user to view all org settings at the specified level' );
584 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 193, 'CREATE_MFHD_RECORD',
585      'Allows a user to create a new MFHD record' );
586 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 194, 'UPDATE_MFHD_RECORD',
587      'Allows a user to update an MFHD record' );
588 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 195, 'DELETE_MFHD_RECORD',
589      'Allows a user to delete an MFHD record' );
590 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 196, 'ADMIN_ACQ_FUND',
591      'Allow a user to create/view/update/delete a fund' );
592 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 197, 'group_application.user.staff.acq_admin',
593      'Allows a user to add/remove/edit users in the "Acquisitions Administrators" group' );
594 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 198, 'SET_CIRC_CLAIMS_RETURNED.override',
595      'Allows staff to override the max claims returned value for a patron' );
596 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 199, 'UPDATE_PATRON_CLAIM_RETURN_COUNT',
597      'Allows staff to manually change a patron''s claims returned count' );
598 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 200, 'UPDATE_BILL_NOTE',
599      'Allows staff to edit the note for a bill on a transaction' );
600 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 201, 'UPDATE_PAYMENT_NOTE',
601      'Allows staff to edit the note for a payment on a transaction' );
602 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 202, 'UPDATE_PATRON_CLAIM_NEVER_CHECKED_OUT_COUNT',
603      'Allows staff to manually change a patron''s claims never checkout out count' );
604 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 203, 'ADMIN_COPY_LOCATION_ORDER',
605      'Allow a user to create/view/update/delete a copy location order' );
606 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 204, 'ASSIGN_GROUP_PERM',
607      '' );
608 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 205, 'CREATE_AUDIENCE',
609      '' );
610 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 206, 'CREATE_BIB_LEVEL',
611      '' );
612 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 207, 'CREATE_CIRC_DURATION',
613      '' );
614 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 208, 'CREATE_CIRC_MOD',
615      '' );
616 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 209, 'CREATE_COPY_STATUS',
617      '' );
618 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 210, 'CREATE_HOURS_OF_OPERATION',
619      '' );
620 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 211, 'CREATE_ITEM_FORM',
621      '' );
622 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 212, 'CREATE_ITEM_TYPE',
623      '' );
624 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 213, 'CREATE_LANGUAGE',
625      '' );
626 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 214, 'CREATE_LASSO',
627      '' );
628 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 215, 'CREATE_LASSO_MAP',
629      '' );
630 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 216, 'CREATE_LIT_FORM',
631      '' );
632 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 217, 'CREATE_METABIB_FIELD',
633      '' );
634 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 218, 'CREATE_NET_ACCESS_LEVEL',
635      '' );
636 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 219, 'CREATE_ORG_ADDRESS',
637      '' );
638 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 220, 'CREATE_ORG_TYPE',
639      '' );
640 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 221, 'CREATE_ORG_UNIT',
641      '' );
642 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 222, 'CREATE_ORG_UNIT_CLOSING',
643      '' );
644 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 223, 'CREATE_PERM',
645      '' );
646 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 224, 'CREATE_RELEVANCE_ADJUSTMENT',
647      '' );
648 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 225, 'CREATE_SURVEY',
649      '' );
650 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 226, 'CREATE_VR_FORMAT',
651      '' );
652 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 227, 'CREATE_XML_TRANSFORM',
653      '' );
654 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 228, 'DELETE_AUDIENCE',
655      '' );
656 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 229, 'DELETE_BIB_LEVEL',
657      '' );
658 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 230, 'DELETE_CIRC_DURATION',
659      '' );
660 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 231, 'DELETE_CIRC_MOD',
661      '' );
662 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 232, 'DELETE_COPY_STATUS',
663      '' );
664 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 233, 'DELETE_HOURS_OF_OPERATION',
665      '' );
666 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 234, 'DELETE_ITEM_FORM',
667      '' );
668 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 235, 'DELETE_ITEM_TYPE',
669      '' );
670 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 236, 'DELETE_LANGUAGE',
671      '' );
672 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 237, 'DELETE_LASSO',
673      '' );
674 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 238, 'DELETE_LASSO_MAP',
675      '' );
676 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 239, 'DELETE_LIT_FORM',
677      '' );
678 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 240, 'DELETE_METABIB_FIELD',
679      '' );
680 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 241, 'DELETE_NET_ACCESS_LEVEL',
681      '' );
682 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 242, 'DELETE_ORG_ADDRESS',
683      '' );
684 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 243, 'DELETE_ORG_TYPE',
685      '' );
686 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 244, 'DELETE_ORG_UNIT',
687      '' );
688 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 245, 'DELETE_ORG_UNIT_CLOSING',
689      '' );
690 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 246, 'DELETE_PERM',
691      '' );
692 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 247, 'DELETE_RELEVANCE_ADJUSTMENT',
693      '' );
694 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 248, 'DELETE_SURVEY',
695      '' );
696 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 249, 'DELETE_TRANSIT',
697      '' );
698 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 250, 'DELETE_VR_FORMAT',
699      '' );
700 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 251, 'DELETE_XML_TRANSFORM',
701      '' );
702 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 252, 'REMOVE_GROUP_PERM',
703      '' );
704 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 253, 'TRANSIT_COPY',
705      '' );
706 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 254, 'UPDATE_AUDIENCE',
707      '' );
708 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 255, 'UPDATE_BIB_LEVEL',
709      '' );
710 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 256, 'UPDATE_CIRC_DURATION',
711      '' );
712 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 257, 'UPDATE_CIRC_MOD',
713      '' );
714 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 258, 'UPDATE_COPY_NOTE',
715      '' );
716 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 259, 'UPDATE_COPY_STATUS',
717      '' );
718 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 260, 'UPDATE_GROUP_PERM',
719      '' );
720 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 261, 'UPDATE_HOURS_OF_OPERATION',
721      '' );
722 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 262, 'UPDATE_ITEM_FORM',
723      '' );
724 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 263, 'UPDATE_ITEM_TYPE',
725      '' );
726 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 264, 'UPDATE_LANGUAGE',
727      '' );
728 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 265, 'UPDATE_LASSO',
729      '' );
730 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 266, 'UPDATE_LASSO_MAP',
731      '' );
732 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 267, 'UPDATE_LIT_FORM',
733      '' );
734 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 268, 'UPDATE_METABIB_FIELD',
735      '' );
736 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 269, 'UPDATE_NET_ACCESS_LEVEL',
737      '' );
738 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 270, 'UPDATE_ORG_ADDRESS',
739      '' );
740 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 271, 'UPDATE_ORG_TYPE',
741      '' );
742 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 272, 'UPDATE_ORG_UNIT_CLOSING',
743      '' );
744 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 273, 'UPDATE_PERM',
745      '' );
746 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 274, 'UPDATE_RELEVANCE_ADJUSTMENT',
747      '' );
748 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 275, 'UPDATE_SURVEY',
749      '' );
750 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 276, 'UPDATE_TRANSIT',
751      '' );
752 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 277, 'UPDATE_VOLUME_NOTE',
753      '' );
754 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 278, 'UPDATE_VR_FORMAT',
755      '' );
756 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 279, 'UPDATE_XML_TRANSFORM',
757      '' );
758 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 280, 'MERGE_BIB_RECORDS',
759      '' );
760 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 281, 'UPDATE_PICKUP_LIB_FROM_HOLDS_SHELF',
761      '' );
762 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 282, 'CREATE_ACQ_FUNDING_SOURCE',
763      '' );
764 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 283, 'CREATE_AUTHORITY_IMPORT_IMPORT_FIELD_DEF',
765      '' );
766 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 284, 'CREATE_AUTHORITY_IMPORT_QUEUE',
767      '' );
768 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 285, 'CREATE_AUTHORITY_RECORD_NOTE',
769      '' );
770 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 286, 'CREATE_BIB_IMPORT_FIELD_DEF',
771      '' );
772 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 287, 'CREATE_BIB_IMPORT_QUEUE',
773      '' );
774 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 288, 'CREATE_LOCALE',
775      '' );
776 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 289, 'CREATE_MARC_CODE',
777      '' );
778 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 290, 'CREATE_TRANSLATION',
779      '' );
780 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 291, 'DELETE_ACQ_FUNDING_SOURCE',
781      '' );
782 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 292, 'DELETE_AUTHORITY_IMPORT_IMPORT_FIELD_DEF',
783      '' );
784 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 293, 'DELETE_AUTHORITY_IMPORT_QUEUE',
785      '' );
786 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 294, 'DELETE_AUTHORITY_RECORD_NOTE',
787      '' );
788 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 295, 'DELETE_BIB_IMPORT_IMPORT_FIELD_DEF',
789      '' );
790 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 296, 'DELETE_BIB_IMPORT_QUEUE',
791      '' );
792 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 297, 'DELETE_LOCALE',
793      '' );
794 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 298, 'DELETE_MARC_CODE',
795      '' );
796 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 299, 'DELETE_TRANSLATION',
797      '' );
798 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 300, 'UPDATE_ACQ_FUNDING_SOURCE',
799      '' );
800 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 301, 'UPDATE_AUTHORITY_IMPORT_IMPORT_FIELD_DEF',
801      '' );
802 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 302, 'UPDATE_AUTHORITY_IMPORT_QUEUE',
803      '' );
804 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 303, 'UPDATE_AUTHORITY_RECORD_NOTE',
805      '' );
806 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 304, 'UPDATE_BIB_IMPORT_IMPORT_FIELD_DEF',
807      '' );
808 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 305, 'UPDATE_BIB_IMPORT_QUEUE',
809      '' );
810 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 306, 'UPDATE_LOCALE',
811      '' );
812 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 307, 'UPDATE_MARC_CODE',
813      '' );
814 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 308, 'UPDATE_TRANSLATION',
815      '' );
816 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 309, 'VIEW_ACQ_FUNDING_SOURCE',
817      '' );
818 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 310, 'VIEW_AUTHORITY_RECORD_NOTES',
819      '' );
820 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 311, 'CREATE_IMPORT_ITEM',
821      '' );
822 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 312, 'CREATE_IMPORT_ITEM_ATTR_DEF',
823      '' );
824 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 313, 'CREATE_IMPORT_TRASH_FIELD',
825      '' );
826 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 314, 'DELETE_IMPORT_ITEM',
827      '' );
828 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 315, 'DELETE_IMPORT_ITEM_ATTR_DEF',
829      '' );
830 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 316, 'DELETE_IMPORT_TRASH_FIELD',
831      '' );
832 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 317, 'UPDATE_IMPORT_ITEM',
833      '' );
834 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 318, 'UPDATE_IMPORT_ITEM_ATTR_DEF',
835      '' );
836 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 319, 'UPDATE_IMPORT_TRASH_FIELD',
837      '' );
838 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 320, 'UPDATE_ORG_UNIT_SETTING_ALL',
839      '' );
840 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 321, 'UPDATE_ORG_UNIT_SETTING.circ.lost_materials_processing_fee',
841      '' );
842 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 322, 'UPDATE_ORG_UNIT_SETTING.cat.default_item_price',
843      '' );
844 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 323, 'UPDATE_ORG_UNIT_SETTING.auth.opac_timeout',
845      '' );
846 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 324, 'UPDATE_ORG_UNIT_SETTING.auth.staff_timeout',
847      '' );
848 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 325, 'UPDATE_ORG_UNIT_SETTING.org.bounced_emails',
849      '' );
850 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 326, 'UPDATE_ORG_UNIT_SETTING.circ.hold_expire_alert_interval',
851      '' );
852 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 327, 'UPDATE_ORG_UNIT_SETTING.circ.hold_expire_interval',
853      '' );
854 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 328, 'UPDATE_ORG_UNIT_SETTING.credit.payments.allow',
855      '' );
856 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 329, 'UPDATE_ORG_UNIT_SETTING.circ.void_overdue_on_lost',
857      '' );
858 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 330, 'UPDATE_ORG_UNIT_SETTING.circ.hold_stalling.soft',
859      '' );
860 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 331, 'UPDATE_ORG_UNIT_SETTING.circ.hold_boundary.hard',
861      '' );
862 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 332, 'UPDATE_ORG_UNIT_SETTING.circ.hold_boundary.soft',
863      '' );
864 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 333, 'UPDATE_ORG_UNIT_SETTING.opac.barcode_regex',
865      '' );
866 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 334, 'UPDATE_ORG_UNIT_SETTING.global.password_regex',
867      '' );
868 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 335, 'UPDATE_ORG_UNIT_SETTING.circ.item_checkout_history.max',
869      '' );
870 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 336, 'UPDATE_ORG_UNIT_SETTING.circ.reshelving_complete.interval',
871      '' );
872 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 337, 'UPDATE_ORG_UNIT_SETTING.circ.selfcheck.patron_login_timeout',
873      '' );
874 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 338, 'UPDATE_ORG_UNIT_SETTING.circ.selfcheck.alert_on_checkout_event',
875      '' );
876 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 339, 'UPDATE_ORG_UNIT_SETTING.circ.selfcheck.require_patron_password',
877      '' );
878 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 340, 'UPDATE_ORG_UNIT_SETTING.global.juvenile_age_threshold',
879      '' );
880 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 341, 'UPDATE_ORG_UNIT_SETTING.cat.bib.keep_on_empty',
881      '' );
882 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 342, 'UPDATE_ORG_UNIT_SETTING.cat.bib.alert_on_empty',
883      '' );
884 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 343, 'UPDATE_ORG_UNIT_SETTING.patron.password.use_phone',
885      '' );
886 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 344, 'HOLD_ITEM_CHECKED_OUT.override',
887      'Allows a user to place a hold on an item that they already have checked out' );
888 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 345, 'ADMIN_ACQ_CANCEL_CAUSE',
889      'Allow a user to create/update/delete reasons for order cancellations' );
890 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 346, 'ACQ_XFER_MANUAL_DFUND_AMOUNT',
891      'Allow a user to transfer different amounts of money out of one fund and into another' );
892 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 347, 'OVERRIDE_HOLD_HAS_LOCAL_COPY',
893      'Allow a user to override the circ.holds.hold_has_copy_at.block setting' );
894 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 348, 'UPDATE_PICKUP_LIB_FROM_TRANSIT',
895      'Allow a user to change the pickup and transit destination for a captured hold item already in transit' );
896 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 349, 'COPY_NEEDED_FOR_HOLD.override',
897      'Allow a user to force renewal of an item that could fulfill a hold request' );
898 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 350, 'MERGE_AUTH_RECORDS',
899      'Allow a user to merge authority records together' );
900 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 351, 'ALLOW_ALT_TCN',
901      'Allows staff to import a record using an alternate TCN to avoid conflicts' );
902 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 352, 'ADMIN_TRIGGER_EVENT_DEF',
903      'Allow a user to administer trigger event definitions' );
904 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 353, 'ADMIN_TRIGGER_CLEANUP',
905      'Allow a user to create, delete, and update trigger cleanup entries' );
906 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 354, 'CREATE_TRIGGER_CLEANUP',
907      'Allow a user to create trigger cleanup entries' );
908 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 355, 'DELETE_TRIGGER_CLEANUP',
909      'Allow a user to delete trigger cleanup entries' );
910 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 356, 'UPDATE_TRIGGER_CLEANUP',
911      'Allow a user to update trigger cleanup entries' );
912 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 357, 'CREATE_TRIGGER_EVENT_DEF',
913      'Allow a user to create trigger event definitions' );
914 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 358, 'DELETE_TRIGGER_EVENT_DEF',
915      'Allow a user to delete trigger event definitions' );
916 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 359, 'UPDATE_TRIGGER_EVENT_DEF',
917      'Allow a user to update trigger event definitions' );
918 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 360, 'VIEW_TRIGGER_EVENT_DEF',
919      'Allow a user to view trigger event definitions' );
920 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 361, 'ADMIN_TRIGGER_HOOK',
921      'Allow a user to create, update, and delete trigger hooks' );
922 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 362, 'CREATE_TRIGGER_HOOK',
923      'Allow a user to create trigger hooks' );
924 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 363, 'DELETE_TRIGGER_HOOK',
925      'Allow a user to delete trigger hooks' );
926 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 364, 'UPDATE_TRIGGER_HOOK',
927      'Allow a user to update trigger hooks' );
928 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 365, 'ADMIN_TRIGGER_REACTOR',
929      'Allow a user to create, update, and delete trigger reactors' );
930 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 366, 'CREATE_TRIGGER_REACTOR',
931      'Allow a user to create trigger reactors' );
932 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 367, 'DELETE_TRIGGER_REACTOR',
933      'Allow a user to delete trigger reactors' );
934 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 368, 'UPDATE_TRIGGER_REACTOR',
935      'Allow a user to update trigger reactors' );
936 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 369, 'ADMIN_TRIGGER_TEMPLATE_OUTPUT',
937      'Allow a user to delete trigger template output' );
938 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 370, 'DELETE_TRIGGER_TEMPLATE_OUTPUT',
939      'Allow a user to delete trigger template output' );
940 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 371, 'ADMIN_TRIGGER_VALIDATOR',
941      'Allow a user to create, update, and delete trigger validators' );
942 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 372, 'CREATE_TRIGGER_VALIDATOR',
943      'Allow a user to create trigger validators' );
944 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 373, 'DELETE_TRIGGER_VALIDATOR',
945      'Allow a user to delete trigger validators' );
946 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 374, 'UPDATE_TRIGGER_VALIDATOR',
947      'Allow a user to update trigger validators' );
948 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 376, 'ADMIN_BOOKING_RESOURCE',
949      'Enables the user to create/update/delete booking resources' );
950 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 377, 'ADMIN_BOOKING_RESOURCE_TYPE',
951      'Enables the user to create/update/delete booking resource types' );
952 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 378, 'ADMIN_BOOKING_RESOURCE_ATTR',
953      'Enables the user to create/update/delete booking resource attributes' );
954 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 379, 'ADMIN_BOOKING_RESOURCE_ATTR_MAP',
955      'Enables the user to create/update/delete booking resource attribute maps' );
956 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 380, 'ADMIN_BOOKING_RESOURCE_ATTR_VALUE',
957      'Enables the user to create/update/delete booking resource attribute values' );
958 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 381, 'ADMIN_BOOKING_RESERVATION',
959      'Enables the user to create/update/delete booking reservations' );
960 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 382, 'ADMIN_BOOKING_RESERVATION_ATTR_VALUE_MAP',
961      'Enables the user to create/update/delete booking reservation attribute value maps' );
962 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 383, 'RETRIEVE_RESERVATION_PULL_LIST',
963      'Allows a user to retrieve a booking reservation pull list' );
964 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 384, 'CAPTURE_RESERVATION',
965      'Allows a user to capture booking reservations' );
966 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 385, 'UPDATE_RECORD',
967      '' );
968 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 386, 'UPDATE_ORG_UNIT_SETTING.circ.block_renews_for_holds',
969      '' );
970 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 387, 'MERGE_USERS',
971      'Allows user records to be merged' );
972 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 388, 'ISSUANCE_HOLDS',
973      'Allow a user to place holds on serials issuances' );
974 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 389, 'VIEW_CREDIT_CARD_PROCESSING',
975      'View org unit settings related to credit card processing' );
976 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 390, 'ADMIN_CREDIT_CARD_PROCESSING',
977      'Update org unit settings related to credit card processing' );
978 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 391, 'ADMIN_SERIAL_CAPTION_PATTERN',
979         'Create/update/delete serial caption and pattern objects' );
980 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 392, 'ADMIN_SERIAL_SUBSCRIPTION',
981         'Create/update/delete serial subscription objects' );
982 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 393, 'ADMIN_SERIAL_DISTRIBUTION',
983         'Create/update/delete serial distribution objects' );
984 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 394, 'ADMIN_SERIAL_STREAM',
985         'Create/update/delete serial stream objects' );
986 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 395, 'RECEIVE_SERIAL',
987         'Receive serial items' );
988
989 -- Now for the permissions from the IDL.  We don't have descriptions for them.
990
991 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 396, 'ADMIN_ACQ_CLAIM' );
992 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 397, 'ADMIN_ACQ_CLAIM_EVENT_TYPE' );
993 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 398, 'ADMIN_ACQ_CLAIM_TYPE' );
994 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 399, 'ADMIN_ACQ_DISTRIB_FORMULA' );
995 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 400, 'ADMIN_ACQ_FISCAL_YEAR' );
996 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 401, 'ADMIN_ACQ_FUND_ALLOCATION_PERCENT' );
997 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 402, 'ADMIN_ACQ_FUND_TAG' );
998 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 403, 'ADMIN_ACQ_LINEITEM_ALERT_TEXT' );
999 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 404, 'ADMIN_AGE_PROTECT_RULE' );
1000 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 405, 'ADMIN_ASSET_COPY_TEMPLATE' );
1001 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 406, 'ADMIN_BOOKING_RESERVATION_ATTR_MAP' );
1002 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 407, 'ADMIN_CIRC_MATRIX_MATCHPOINT' );
1003 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 408, 'ADMIN_CIRC_MOD' );
1004 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 409, 'ADMIN_CLAIM_POLICY' );
1005 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 410, 'ADMIN_CONFIG_REMOTE_ACCOUNT' );
1006 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 411, 'ADMIN_FIELD_DOC' );
1007 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 412, 'ADMIN_GLOBAL_FLAG' );
1008 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 413, 'ADMIN_GROUP_PENALTY_THRESHOLD' );
1009 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 414, 'ADMIN_HOLD_CANCEL_CAUSE' );
1010 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 415, 'ADMIN_HOLD_MATRIX_MATCHPOINT' );
1011 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 416, 'ADMIN_IDENT_TYPE' );
1012 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 417, 'ADMIN_IMPORT_ITEM_ATTR_DEF' );
1013 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 418, 'ADMIN_INDEX_NORMALIZER' );
1014 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 419, 'ADMIN_INVOICE' );
1015 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 420, 'ADMIN_INVOICE_METHOD' );
1016 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 421, 'ADMIN_INVOICE_PAYMENT_METHOD' );
1017 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 422, 'ADMIN_LINEITEM_MARC_ATTR_DEF' );
1018 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 423, 'ADMIN_MARC_CODE' );
1019 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 424, 'ADMIN_MAX_FINE_RULE' );
1020 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 425, 'ADMIN_MERGE_PROFILE' );
1021 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 426, 'ADMIN_ORG_UNIT_SETTING_TYPE' );
1022 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 427, 'ADMIN_RECURRING_FINE_RULE' );
1023 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 428, 'ADMIN_STANDING_PENALTY' );
1024 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 429, 'ADMIN_SURVEY' );
1025 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 430, 'ADMIN_USER_REQUEST_TYPE' );
1026 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 431, 'ADMIN_USER_SETTING_GROUP' );
1027 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 432, 'ADMIN_USER_SETTING_TYPE' );
1028 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 433, 'ADMIN_Z3950_SOURCE' );
1029 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 434, 'CREATE_BIB_BTYPE' );
1030 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 435, 'CREATE_BIBLIO_FINGERPRINT' );
1031 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 436, 'CREATE_BIB_SOURCE' );
1032 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 437, 'CREATE_BILLING_TYPE' );
1033 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 438, 'CREATE_CN_BTYPE' );
1034 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 439, 'CREATE_COPY_BTYPE' );
1035 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 440, 'CREATE_INVOICE' );
1036 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 441, 'CREATE_INVOICE_ITEM_TYPE' );
1037 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 442, 'CREATE_INVOICE_METHOD' );
1038 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 443, 'CREATE_MERGE_PROFILE' );
1039 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 444, 'CREATE_METABIB_CLASS' );
1040 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 445, 'CREATE_METABIB_SEARCH_ALIAS' );
1041 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 446, 'CREATE_USER_BTYPE' );
1042 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 447, 'DELETE_BIB_BTYPE' );
1043 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 448, 'DELETE_BIBLIO_FINGERPRINT' );
1044 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 449, 'DELETE_BIB_SOURCE' );
1045 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 450, 'DELETE_BILLING_TYPE' );
1046 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 451, 'DELETE_CN_BTYPE' );
1047 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 452, 'DELETE_COPY_BTYPE' );
1048 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 453, 'DELETE_INVOICE_ITEM_TYPE' );
1049 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 454, 'DELETE_INVOICE_METHOD' );
1050 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 455, 'DELETE_MERGE_PROFILE' );
1051 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 456, 'DELETE_METABIB_CLASS' );
1052 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 457, 'DELETE_METABIB_SEARCH_ALIAS' );
1053 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 458, 'DELETE_USER_BTYPE' );
1054 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 459, 'MANAGE_CLAIM' );
1055 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 460, 'UPDATE_BIB_BTYPE' );
1056 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 461, 'UPDATE_BIBLIO_FINGERPRINT' );
1057 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 462, 'UPDATE_BIB_SOURCE' );
1058 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 463, 'UPDATE_BILLING_TYPE' );
1059 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 464, 'UPDATE_CN_BTYPE' );
1060 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 465, 'UPDATE_COPY_BTYPE' );
1061 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 466, 'UPDATE_INVOICE_ITEM_TYPE' );
1062 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 467, 'UPDATE_INVOICE_METHOD' );
1063 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 468, 'UPDATE_MERGE_PROFILE' );
1064 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 469, 'UPDATE_METABIB_CLASS' );
1065 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 470, 'UPDATE_METABIB_SEARCH_ALIAS' );
1066 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 471, 'UPDATE_USER_BTYPE' );
1067 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 472, 'user_request.create' );
1068 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 473, 'user_request.delete' );
1069 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 474, 'user_request.update' );
1070 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 475, 'user_request.view' );
1071 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 476, 'VIEW_ACQ_FUND_ALLOCATION_PERCENT' );
1072 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 477, 'VIEW_CIRC_MATRIX_MATCHPOINT' );
1073 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 478, 'VIEW_CLAIM' );
1074 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 479, 'VIEW_GROUP_PENALTY_THRESHOLD' );
1075 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 480, 'VIEW_HOLD_MATRIX_MATCHPOINT' );
1076 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 481, 'VIEW_INVOICE' );
1077 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 482, 'VIEW_MERGE_PROFILE' );
1078 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 483, 'VIEW_SERIAL_SUBSCRIPTION' );
1079 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 484, 'VIEW_STANDING_PENALTY' );
1080
1081 -- For every permission in the temp_perm table that has a matching
1082 -- permission in the real table: record the original id.
1083
1084 UPDATE permission.temp_perm AS tp
1085 SET old_id =
1086         (
1087                 SELECT id
1088                 FROM permission.perm_list AS ppl
1089                 WHERE ppl.code = tp.code
1090         )
1091 WHERE code IN ( SELECT code FROM permission.perm_list );
1092
1093 -- Start juggling ids.
1094
1095 -- If any permissions have negative ids (with the special exception of -1),
1096 -- we need to move them into the positive range in order to avoid duplicate
1097 -- key problems (since we are going to use the negative range as a temporary
1098 -- staging area).
1099
1100 -- First, move any predefined permissions that have negative ids (again with
1101 -- the special exception of -1).  Temporarily give them positive ids based on
1102 -- the sequence.
1103
1104 UPDATE permission.perm_list
1105 SET id = NEXTVAL('permission.perm_list_id_seq'::regclass)
1106 WHERE id < -1
1107   AND code IN (SELECT code FROM permission.temp_perm);
1108
1109 -- Identify any non-predefined permissions whose ids are either negative
1110 -- or within the range (0-1000) reserved for predefined permissions.
1111 -- Assign them ids above 1000, based on the sequence.  Record the new
1112 -- ids in the temp_perm table.
1113
1114 INSERT INTO permission.temp_perm ( id, code, description, old_id, predefined )
1115 (
1116         SELECT NEXTVAL('permission.perm_list_id_seq'::regclass),
1117                 code, description, id, false
1118         FROM permission.perm_list
1119         WHERE  ( id < -1 OR id BETWEEN 0 AND 1000 )
1120         AND code NOT IN (SELECT code FROM permission.temp_perm)
1121 );
1122
1123 -- Now update the ids of those non-predefined permissions, using the
1124 -- values assigned in the previous step.
1125
1126 UPDATE permission.perm_list AS ppl
1127 SET id = (
1128                 SELECT id
1129                 FROM permission.temp_perm AS tp
1130                 WHERE tp.code = ppl.code
1131         )
1132 WHERE id IN ( SELECT old_id FROM permission.temp_perm WHERE NOT predefined );
1133
1134 -- Now the negative ids have been eliminated, except for -1.  Move all the
1135 -- predefined permissions temporarily into the negative range.
1136
1137 UPDATE permission.perm_list
1138 SET id = -1 - id
1139 WHERE id <> -1
1140 AND code IN ( SELECT code from permission.temp_perm WHERE predefined );
1141
1142 -- Apply the final ids to the existing predefined permissions.
1143
1144 UPDATE permission.perm_list AS ppl
1145 SET id =
1146         (
1147                 SELECT id
1148                 FROM permission.temp_perm AS tp
1149                 WHERE tp.code = ppl.code
1150         )
1151 WHERE
1152         id <> -1
1153         AND ppl.code IN
1154         (
1155                 SELECT code from permission.temp_perm
1156                 WHERE predefined
1157                 AND old_id IS NOT NULL
1158         );
1159
1160 -- If there are any predefined permissions that don't exist yet in
1161 -- permission.perm_list, insert them now.
1162
1163 INSERT INTO permission.perm_list ( id, code, description )
1164 (
1165         SELECT id, code, description
1166         FROM permission.temp_perm
1167         WHERE old_id IS NULL
1168 );
1169
1170 -- Reset the sequence to the lowest feasible value.  This may or may not
1171 -- accomplish anything, but it will do no harm.
1172
1173 SELECT SETVAL('permission.perm_list_id_seq'::TEXT, GREATEST( 
1174         (SELECT MAX(id) FROM permission.perm_list), 1000 ));
1175
1176 -- If any permission lacks a description, use the code as a description.
1177 -- It's better than nothing.
1178
1179 UPDATE permission.perm_list
1180 SET description = code
1181 WHERE description IS NULL
1182    OR description = '';
1183
1184 -- Thus endeth the Great Renumbering.
1185
1186 -- Having massaged the permissions, massage the way they are assigned, by inserting
1187 -- rows into permission.grp_perm_map.  Some of these permissions may have already
1188 -- been assigned, so we insert the rows only if they aren't already there.
1189
1190 -- for backwards compat, give everyone the permission
1191 INSERT INTO permission.grp_perm_map (grp, perm, depth, grantable)
1192     SELECT 1, id, 0, false FROM permission.perm_list AS perm
1193         WHERE code = 'HOLD_ITEM_CHECKED_OUT.override'
1194                 AND NOT EXISTS (
1195                         SELECT 1
1196                         FROM permission.grp_perm_map AS map
1197                         WHERE
1198                                 grp = 1
1199                                 AND map.perm = perm.id
1200                 );
1201
1202 -- Add trigger administration permissions to the Local System Administrator group.
1203 INSERT INTO permission.grp_perm_map (grp, perm, depth, grantable)
1204     SELECT 10, id, 1, false FROM permission.perm_list AS perm
1205     WHERE (
1206                 perm.code LIKE 'ADMIN_TRIGGER%'
1207         OR perm.code LIKE 'CREATE_TRIGGER%'
1208         OR perm.code LIKE 'DELETE_TRIGGER%'
1209         OR perm.code LIKE 'UPDATE_TRIGGER%'
1210         ) AND NOT EXISTS (
1211                 SELECT 1
1212                 FROM permission.grp_perm_map AS map
1213                 WHERE
1214                         grp = 10
1215                         AND map.perm = perm.id
1216         );
1217
1218 -- View trigger permissions are required at a consortial level for initial setup
1219 -- (as before, only if the row doesn't already exist)
1220 INSERT INTO permission.grp_perm_map (grp, perm, depth, grantable)
1221     SELECT 10, id, 0, false FROM permission.perm_list AS perm
1222         WHERE code LIKE 'VIEW_TRIGGER%'
1223                 AND NOT EXISTS (
1224                         SELECT 1
1225                         FROM permission.grp_perm_map AS map
1226                         WHERE
1227                                 grp = 10
1228                                 AND map.perm = perm.id
1229                 );
1230
1231 -- Permission for merging auth records may already be defined,
1232 -- so add it only if it isn't there.
1233 INSERT INTO permission.grp_perm_map (grp, perm, depth, grantable)
1234     SELECT 4, id, 1, false FROM permission.perm_list AS perm
1235         WHERE code = 'MERGE_AUTH_RECORDS'
1236                 AND NOT EXISTS (
1237                         SELECT 1
1238                         FROM permission.grp_perm_map AS map
1239                         WHERE
1240                                 grp = 4
1241                                 AND map.perm = perm.id
1242                 );
1243
1244 -- Create a reference table as parent to both
1245 -- config.org_unit_setting_type and config_usr_setting_type
1246
1247 CREATE TABLE config.settings_group (
1248     name    TEXT PRIMARY KEY,
1249     label   TEXT UNIQUE NOT NULL -- I18N
1250 );
1251
1252 -- org_unit setting types
1253 CREATE TABLE config.org_unit_setting_type (
1254     name            TEXT    PRIMARY KEY,
1255     label           TEXT    UNIQUE NOT NULL,
1256     grp             TEXT    REFERENCES config.settings_group (name),
1257     description     TEXT,
1258     datatype        TEXT    NOT NULL DEFAULT 'string',
1259     fm_class        TEXT,
1260     view_perm       INT,
1261     update_perm     INT,
1262     --
1263     -- define valid datatypes
1264     --
1265     CONSTRAINT coust_valid_datatype CHECK ( datatype IN
1266     ( 'bool', 'integer', 'float', 'currency', 'interval',
1267       'date', 'string', 'object', 'array', 'link' ) ),
1268     --
1269     -- fm_class is meaningful only for 'link' datatype
1270     --
1271     CONSTRAINT coust_no_empty_link CHECK
1272     ( ( datatype =  'link' AND fm_class IS NOT NULL ) OR
1273       ( datatype <> 'link' AND fm_class IS NULL ) ),
1274         CONSTRAINT view_perm_fkey FOREIGN KEY (view_perm) REFERENCES permission.perm_list (id)
1275                 ON UPDATE CASCADE
1276                 ON DELETE RESTRICT
1277                 DEFERRABLE INITIALLY DEFERRED,
1278         CONSTRAINT update_perm_fkey FOREIGN KEY (update_perm) REFERENCES permission.perm_list (id)
1279                 ON UPDATE CASCADE
1280                 DEFERRABLE INITIALLY DEFERRED
1281 );
1282
1283 CREATE TABLE config.usr_setting_type (
1284
1285     name TEXT PRIMARY KEY,
1286     opac_visible BOOL NOT NULL DEFAULT FALSE,
1287     label TEXT UNIQUE NOT NULL,
1288     description TEXT,
1289     grp             TEXT    REFERENCES config.settings_group (name),
1290     datatype TEXT NOT NULL DEFAULT 'string',
1291     fm_class TEXT,
1292
1293     --
1294     -- define valid datatypes
1295     --
1296     CONSTRAINT coust_valid_datatype CHECK ( datatype IN
1297     ( 'bool', 'integer', 'float', 'currency', 'interval',
1298         'date', 'string', 'object', 'array', 'link' ) ),
1299
1300     --
1301     -- fm_class is meaningful only for 'link' datatype
1302     --
1303     CONSTRAINT coust_no_empty_link CHECK
1304     ( ( datatype = 'link' AND fm_class IS NOT NULL ) OR
1305         ( datatype <> 'link' AND fm_class IS NULL ) )
1306
1307 );
1308
1309 --------------------------------------
1310 -- Seed data for org_unit_setting_type
1311 --------------------------------------
1312
1313 INSERT into config.org_unit_setting_type
1314 ( name, label, description, datatype ) VALUES
1315
1316 ( 'auth.opac_timeout',
1317   'OPAC Inactivity Timeout (in seconds)',
1318   null,
1319   'integer' ),
1320
1321 ( 'auth.staff_timeout',
1322   'Staff Login Inactivity Timeout (in seconds)',
1323   null,
1324   'integer' ),
1325
1326 ( 'circ.lost_materials_processing_fee',
1327   'Lost Materials Processing Fee',
1328   null,
1329   'currency' ),
1330
1331 ( 'cat.default_item_price',
1332   'Default Item Price',
1333   null,
1334   'currency' ),
1335
1336 ( 'org.bounced_emails',
1337   'Sending email address for patron notices',
1338   null,
1339   'string' ),
1340
1341 ( 'circ.hold_expire_alert_interval',
1342   'Holds: Expire Alert Interval',
1343   'Amount of time before a hold expires at which point the patron should be alerted',
1344   'interval' ),
1345
1346 ( 'circ.hold_expire_interval',
1347   'Holds: Expire Interval',
1348   'Amount of time after a hold is placed before the hold expires.  Example "100 days"',
1349   'interval' ),
1350
1351 ( 'credit.payments.allow',
1352   'Allow Credit Card Payments',
1353   'If enabled, patrons will be able to pay fines accrued at this location via credit card',
1354   'bool' ),
1355
1356 ( 'global.default_locale',
1357   'Global Default Locale',
1358   null,
1359   'string' ),
1360
1361 ( 'circ.void_overdue_on_lost',
1362   'Void overdue fines when items are marked lost',
1363   null,
1364   'bool' ),
1365
1366 ( 'circ.hold_stalling.soft',
1367   'Holds: Soft stalling interval',
1368   'How long to wait before allowing remote items to be opportunistically captured for a hold.  Example "5 days"',
1369   'interval' ),
1370
1371 ( 'circ.hold_stalling_hard',
1372   'Holds: Hard stalling interval',
1373   '',
1374   'interval' ),
1375
1376 ( 'circ.hold_boundary.hard',
1377   'Holds: Hard boundary',
1378   null,
1379   'integer' ),
1380
1381 ( 'circ.hold_boundary.soft',
1382   'Holds: Soft boundary',
1383   null,
1384   'integer' ),
1385
1386 ( 'opac.barcode_regex',
1387   'Patron barcode format',
1388   'Regular expression defining the patron barcode format',
1389   'string' ),
1390
1391 ( 'global.password_regex',
1392   'Password format',
1393   'Regular expression defining the password format',
1394   'string' ),
1395
1396 ( 'circ.item_checkout_history.max',
1397   'Maximum previous checkouts displayed',
1398   'This is the maximum number of previous circulations the staff client will display when investigating item details',
1399   'integer' ),
1400
1401 ( 'circ.reshelving_complete.interval',
1402   'Change reshelving status interval',
1403   'Amount of time to wait before changing an item from "reshelving" status to "available".  Examples: "1 day", "6 hours"',
1404   'interval' ),
1405
1406 ( 'circ.holds.default_estimated_wait_interval',
1407   'Holds: Default Estimated Wait',
1408   'When predicting the amount of time a patron will be waiting for a hold to be fulfilled, this is the default estimated length of time to assume an item will be checked out.',
1409   'interval' ),
1410
1411 ( 'circ.holds.min_estimated_wait_interval',
1412   'Holds: Minimum Estimated Wait',
1413   'When predicting the amount of time a patron will be waiting for a hold to be fulfilled, this is the minimum estimated length of time to assume an item will be checked out.',
1414   'interval' ),
1415
1416 ( 'circ.selfcheck.patron_login_timeout',
1417   'Selfcheck: Patron Login Timeout (in seconds)',
1418   'Number of seconds of inactivity before the patron is logged out of the selfcheck interface',
1419   'integer' ),
1420
1421 ( 'circ.selfcheck.alert.popup',
1422   'Selfcheck: Pop-up alert for errors',
1423   'If true, checkout/renewal errors will cause a pop-up window in addition to the on-screen message',
1424   'bool' ),
1425
1426 ( 'circ.selfcheck.require_patron_password',
1427   'Selfcheck: Require patron password',
1428   'If true, patrons will be required to enter their password in addition to their username/barcode to log into the selfcheck interface',
1429   'bool' ),
1430
1431 ( 'global.juvenile_age_threshold',
1432   'Juvenile Age Threshold',
1433   'The age at which a user is no long considered a juvenile.  For example, "18 years".',
1434   'interval' ),
1435
1436 ( 'cat.bib.keep_on_empty',
1437   'Retain empty bib records',
1438   'Retain a bib record even when all attached copies are deleted',
1439   'bool' ),
1440
1441 ( 'cat.bib.alert_on_empty',
1442   'Alert on empty bib records',
1443   'Alert staff when the last copy for a record is being deleted',
1444   'bool' ),
1445
1446 ( 'patron.password.use_phone',
1447   'Patron: password from phone #',
1448   'Use the last 4 digits of the patrons phone number as the default password when creating new users',
1449   'bool' ),
1450
1451 ( 'circ.charge_on_damaged',
1452   'Charge item price when marked damaged',
1453   'Charge item price when marked damaged',
1454   'bool' ),
1455
1456 ( 'circ.charge_lost_on_zero',
1457   'Charge lost on zero',
1458   '',
1459   'bool' ),
1460
1461 ( 'circ.damaged_item_processing_fee',
1462   'Charge processing fee for damaged items',
1463   'Charge processing fee for damaged items',
1464   'currency' ),
1465
1466 ( 'circ.void_lost_on_checkin',
1467   'Circ: Void lost item billing when returned',
1468   'Void lost item billing when returned',
1469   'bool' ),
1470
1471 ( 'circ.max_accept_return_of_lost',
1472   'Circ: Void lost max interval',
1473   'Items that have been lost this long will not result in voided billings when returned.  E.g. ''6 months''',
1474   'interval' ),
1475
1476 ( 'circ.void_lost_proc_fee_on_checkin',
1477   'Circ: Void processing fee on lost item return',
1478   'Void processing fee when lost item returned',
1479   'bool' ),
1480
1481 ( 'circ.restore_overdue_on_lost_return',
1482   'Circ: Restore overdues on lost item return',
1483   'Restore overdue fines on lost item return',
1484   'bool' ),
1485
1486 ( 'circ.lost_immediately_available',
1487   'Circ: Lost items usable on checkin',
1488   'Lost items are usable on checkin instead of going ''home'' first',
1489   'bool' ),
1490
1491 ( 'circ.holds_fifo',
1492   'Holds: FIFO',
1493   'Force holds to a more strict First-In, First-Out capture',
1494   'bool' ),
1495
1496 ( 'opac.allow_pending_address',
1497   'OPAC: Allow pending addresses',
1498   'If enabled, patrons can create and edit existing addresses.  Addresses are kept in a pending state until staff approves the changes',
1499   'bool' ),
1500
1501 ( 'ui.circ.show_billing_tab_on_bills',
1502   'Show billing tab first when bills are present',
1503   'If enabled and a patron has outstanding bills and the alert page is not required, show the billing tab by default, instead of the checkout tab, when a patron is loaded',
1504   'bool' ),
1505
1506 ( 'ui.general.idle_timeout',
1507     'GUI: Idle timeout',
1508     'If you want staff client windows to be minimized after a certain amount of system idle time, set this to the number of seconds of idle time that you want to allow before minimizing (requires staff client restart).',
1509     'integer' ),
1510
1511 ( 'ui.circ.in_house_use.entry_cap',
1512   'GUI: Record In-House Use: Maximum # of uses allowed per entry.',
1513   'The # of uses entry in the Record In-House Use interface may not exceed the value of this setting.',
1514   'integer' ),
1515
1516 ( 'ui.circ.in_house_use.entry_warn',
1517   'GUI: Record In-House Use: # of uses threshold for Are You Sure? dialog.',
1518   'In the Record In-House Use interface, a submission attempt will warn if the # of uses field exceeds the value of this setting.',
1519   'integer' ),
1520
1521 ( 'acq.default_circ_modifier',
1522   'Default circulation modifier',
1523   null,
1524   'string' ),
1525
1526 ( 'acq.tmp_barcode_prefix',
1527   'Temporary barcode prefix',
1528   null,
1529   'string' ),
1530
1531 ( 'acq.tmp_callnumber_prefix',
1532   'Temporary call number prefix',
1533   null,
1534   'string' ),
1535
1536 ( 'ui.circ.patron_summary.horizontal',
1537   'Patron circulation summary is horizontal',
1538   null,
1539   'bool' ),
1540
1541 ( 'ui.staff.require_initials',
1542   oils_i18n_gettext('ui.staff.require_initials', 'GUI: Require staff initials for entry/edit of item/patron/penalty notes/messages.', 'coust', 'label'),
1543   oils_i18n_gettext('ui.staff.require_initials', 'Appends staff initials and edit date into note content.', 'coust', 'description'),
1544   'bool' ),
1545
1546 ( 'ui.general.button_bar',
1547   'Button bar',
1548   null,
1549   'bool' ),
1550
1551 ( 'circ.hold_shelf_status_delay',
1552   'Hold Shelf Status Delay',
1553   'The purpose is to provide an interval of time after an item goes into the on-holds-shelf status before it appears to patrons that it is actually on the holds shelf.  This gives staff time to process the item before it shows as ready-for-pickup.',
1554   'interval' ),
1555
1556 ( 'circ.patron_invalid_address_apply_penalty',
1557   'Invalid patron address penalty',
1558   'When set, if a patron address is set to invalid, a penalty is applied.',
1559   'bool' ),
1560
1561 ( 'circ.checkout_fills_related_hold',
1562   'Checkout Fills Related Hold',
1563   'When a patron checks out an item and they have no holds that directly target the item, the system will attempt to find a hold for the patron that could be fulfilled by the checked out item and fulfills it',
1564   'bool'),
1565
1566 ( 'circ.selfcheck.auto_override_checkout_events',
1567   'Selfcheck override events list',
1568   'List of checkout/renewal events that the selfcheck interface should automatically override instead instead of alerting and stopping the transaction',
1569   'array' ),
1570
1571 ( 'circ.staff_client.do_not_auto_attempt_print',
1572   'Disable Automatic Print Attempt Type List',
1573   'Disable automatic print attempts from staff client interfaces for the receipt types in this list.  Possible values: "Checkout", "Bill Pay", "Hold Slip", "Transit Slip", and "Hold/Transit Slip".  This is different from the Auto-Print checkbox in the pertinent interfaces in that it disables automatic print attempts altogether, rather than encouraging silent printing by suppressing the print dialog.  The Auto-Print checkbox in these interfaces have no effect on the behavior for this setting.  In the case of the Hold, Transit, and Hold/Transit slips, this also suppresses the alert dialogs that precede the print dialog (the ones that offer Print and Do Not Print as options).',
1574   'array' ),
1575
1576 ( 'ui.patron.default_inet_access_level',
1577   'Default level of patrons'' internet access',
1578   null,
1579   'integer' ),
1580
1581 ( 'circ.max_patron_claim_return_count',
1582     'Max Patron Claims Returned Count',
1583     'When this count is exceeded, a staff override is required to mark the item as claims returned',
1584     'integer' ),
1585
1586 ( 'circ.obscure_dob',
1587     'Obscure the Date of Birth field',
1588     'When true, the Date of Birth column in patron lists will default to Not Visible, and in the Patron Summary sidebar the value will display as <Hidden> unless the field label is clicked.',
1589     'bool' ),
1590
1591 ( 'circ.auto_hide_patron_summary',
1592     'GUI: Toggle off the patron summary sidebar after first view.',
1593     'When true, the patron summary sidebar will collapse after a new patron sub-interface is selected.',
1594     'bool' ),
1595
1596 ( 'credit.processor.default',
1597     'Credit card processing: Name default credit processor',
1598     'This can be "AuthorizeNet", "PayPal" (for the Website Payment Pro API), or "PayflowPro".',
1599     'string' ),
1600
1601 ( 'credit.processor.authorizenet.enabled',
1602     'Credit card processing: AuthorizeNet enabled',
1603     '',
1604     'bool' ),
1605
1606 ( 'credit.processor.authorizenet.login',
1607     'Credit card processing: AuthorizeNet login',
1608     '',
1609     'string' ),
1610
1611 ( 'credit.processor.authorizenet.password',
1612     'Credit card processing: AuthorizeNet password',
1613     '',
1614     'string' ),
1615
1616 ( 'credit.processor.authorizenet.server',
1617     'Credit card processing: AuthorizeNet server',
1618     'Required if using a developer/test account with AuthorizeNet',
1619     'string' ),
1620
1621 ( 'credit.processor.authorizenet.testmode',
1622     'Credit card processing: AuthorizeNet test mode',
1623     '',
1624     'bool' ),
1625
1626 ( 'credit.processor.paypal.enabled',
1627     'Credit card processing: PayPal enabled',
1628     '',
1629     'bool' ),
1630 ( 'credit.processor.paypal.login',
1631     'Credit card processing: PayPal login',
1632     '',
1633     'string' ),
1634 ( 'credit.processor.paypal.password',
1635     'Credit card processing: PayPal password',
1636     '',
1637     'string' ),
1638 ( 'credit.processor.paypal.signature',
1639     'Credit card processing: PayPal signature',
1640     '',
1641     'string' ),
1642 ( 'credit.processor.paypal.testmode',
1643     'Credit card processing: PayPal test mode',
1644     '',
1645     'bool' ),
1646
1647 ( 'ui.admin.work_log.max_entries',
1648     oils_i18n_gettext('ui.admin.work_log.max_entries', 'GUI: Work Log: Maximum Actions Logged', 'coust', 'label'),
1649     oils_i18n_gettext('ui.admin.work_log.max_entries', 'Maximum entries for "Most Recent Staff Actions" section of the Work Log interface.', 'coust', 'description'),
1650   'interval' ),
1651
1652 ( 'ui.admin.patron_log.max_entries',
1653     oils_i18n_gettext('ui.admin.patron_log.max_entries', 'GUI: Work Log: Maximum Patrons Logged', 'coust', 'label'),
1654     oils_i18n_gettext('ui.admin.patron_log.max_entries', 'Maximum entries for "Most Recently Affected Patrons..." section of the Work Log interface.', 'coust', 'description'),
1655   'interval' ),
1656
1657 ( 'lib.courier_code',
1658     oils_i18n_gettext('lib.courier_code', 'Courier Code', 'coust', 'label'),
1659     oils_i18n_gettext('lib.courier_code', 'Courier Code for the library.  Available in transit slip templates as the %courier_code% macro.', 'coust', 'description'),
1660     'string'),
1661
1662 ( 'circ.block_renews_for_holds',
1663     oils_i18n_gettext('circ.block_renews_for_holds', 'Holds: Block Renewal of Items Needed for Holds', 'coust', 'label'),
1664     oils_i18n_gettext('circ.block_renews_for_holds', 'When an item could fulfill a hold, do not allow the current patron to renew', 'coust', 'description'),
1665     'bool' ),
1666
1667 ( 'circ.password_reset_request_per_user_limit',
1668     oils_i18n_gettext('circ.password_reset_request_per_user_limit', 'Circulation: Maximum concurrently active self-serve password reset requests per user', 'coust', 'label'),
1669     oils_i18n_gettext('circ.password_reset_request_per_user_limit', 'When a user has more than this number of concurrently active self-serve password reset requests for their account, prevent the user from creating any new self-serve password reset requests until the number of active requests for the user drops back below this number.', 'coust', 'description'),
1670     'string'),
1671
1672 ( 'circ.password_reset_request_time_to_live',
1673     oils_i18n_gettext('circ.password_reset_request_time_to_live', 'Circulation: Self-serve password reset request time-to-live', 'coust', 'label'),
1674     oils_i18n_gettext('circ.password_reset_request_time_to_live', 'Length of time (in seconds) a self-serve password reset request should remain active.', 'coust', 'description'),
1675     'string'),
1676
1677 ( 'circ.password_reset_request_throttle',
1678     oils_i18n_gettext('circ.password_reset_request_throttle', 'Circulation: Maximum concurrently active self-serve password reset requests', 'coust', 'label'),
1679     oils_i18n_gettext('circ.password_reset_request_throttle', 'Prevent the creation of new self-serve password reset requests until the number of active requests drops back below this number.', 'coust', 'description'),
1680     'string')
1681 ;
1682
1683 INSERT INTO config.org_unit_setting_type ( name, label, description, datatype ) VALUES (
1684         'ui.circ.suppress_checkin_popups',
1685         oils_i18n_gettext(
1686             'ui.circ.suppress_checkin_popups', 
1687             'Circ: Suppress popup-dialogs during check-in.', 
1688             'coust', 
1689             'label'),
1690         oils_i18n_gettext(
1691             'ui.circ.suppress_checkin_popups', 
1692             'Circ: Suppress popup-dialogs during check-in.', 
1693             'coust', 
1694             'description'),
1695         'bool'
1696 );
1697
1698 INSERT INTO config.org_unit_setting_type ( name, label, description, datatype ) VALUES (
1699         'format.date',
1700         oils_i18n_gettext(
1701             'format.date',
1702             'GUI: Format Dates with this pattern.', 
1703             'coust', 
1704             'label'),
1705         oils_i18n_gettext(
1706             'format.date',
1707             'GUI: Format Dates with this pattern (examples: "yyyy-MM-dd" for "2010-04-26", "MMM d, yyyy" for "Apr 26, 2010")', 
1708             'coust', 
1709             'description'),
1710         'string'
1711 ), (
1712         'format.time',
1713         oils_i18n_gettext(
1714             'format.time',
1715             'GUI: Format Times with this pattern.', 
1716             'coust', 
1717             'label'),
1718         oils_i18n_gettext(
1719             'format.time',
1720             'GUI: Format Times with this pattern (examples: "h:m:s.SSS a z" for "2:07:20.666 PM Eastern Daylight Time", "HH:mm" for "14:07")', 
1721             'coust', 
1722             'description'),
1723         'string'
1724 );
1725
1726 INSERT INTO config.org_unit_setting_type ( name, label, description, datatype ) VALUES (
1727         'cat.bib.delete_on_no_copy_via_acq_lineitem_cancel',
1728         oils_i18n_gettext(
1729             'cat.bib.delete_on_no_copy_via_acq_lineitem_cancel',
1730             'CAT: Delete bib if all copies are deleted via Acquisitions lineitem cancellation.', 
1731             'coust', 
1732             'label'),
1733         oils_i18n_gettext(
1734             'cat.bib.delete_on_no_copy_via_acq_lineitem_cancel',
1735             'CAT: Delete bib if all copies are deleted via Acquisitions lineitem cancellation.', 
1736             'coust', 
1737             'description'),
1738         'bool'
1739 );
1740
1741 INSERT INTO config.org_unit_setting_type ( name, label, description, datatype ) VALUES (
1742         'url.remote_column_settings',
1743         oils_i18n_gettext(
1744             'url.remote_column_settings',
1745             'GUI: URL for remote directory containing list column settings.', 
1746             'coust', 
1747             'label'),
1748         oils_i18n_gettext(
1749             'url.remote_column_settings',
1750             'GUI: URL for remote directory containing list column settings.  The format and naming convention for the files found in this directory match those in the local settings directory for a given workstation.  An administrator could create the desired settings locally and then copy all the tree_columns_for_* files to the remote directory.', 
1751             'coust', 
1752             'description'),
1753         'string'
1754 );
1755
1756 INSERT INTO config.org_unit_setting_type ( name, label, description, datatype ) VALUES (
1757         'gui.disable_local_save_columns',
1758         oils_i18n_gettext(
1759             'gui.disable_local_save_columns',
1760             'GUI: Disable the ability to save list column configurations locally.', 
1761             'coust', 
1762             'label'),
1763         oils_i18n_gettext(
1764             'gui.disable_local_save_columns',
1765             'GUI: Disable the ability to save list column configurations locally.  If set, columns may still be manipulated, however, the changes do not persist.  Also, existing local configurations are ignored if this setting is true.', 
1766             'coust', 
1767             'description'),
1768         'bool'
1769 );
1770
1771 INSERT INTO config.org_unit_setting_type ( name, label, description, datatype ) VALUES (
1772         'circ.password_reset_request_requires_matching_email',
1773         oils_i18n_gettext(
1774             'circ.password_reset_request_requires_matching_email',
1775             'Circulation: Require matching email address for password reset requests', 
1776             'coust', 
1777             'label'),
1778         oils_i18n_gettext(
1779             'circ.password_reset_request_requires_matching_email',
1780             'Circulation: Require matching email address for password reset requests', 
1781             'coust', 
1782             'description'),
1783         'bool'
1784 );
1785
1786 INSERT INTO config.org_unit_setting_type ( name, label, description, datatype ) VALUES (
1787         'circ.holds.expired_patron_block',
1788         oils_i18n_gettext(
1789             'circ.holds.expired_patron_block',
1790             'Circulation: Block hold request if hold recipient privileges have expired', 
1791             'coust', 
1792             'label'),
1793         oils_i18n_gettext(
1794             'circ.holds.expired_patron_block',
1795             'Circulation: Block hold request if hold recipient privileges have expired', 
1796             'coust', 
1797             'description'),
1798         'bool'
1799 );
1800
1801 INSERT INTO config.org_unit_setting_type
1802     (name, label, description, datatype) VALUES (
1803         'circ.booking_reservation.default_elbow_room',
1804         oils_i18n_gettext(
1805             'circ.booking_reservation.default_elbow_room',
1806             'Booking: Elbow room',
1807             'coust',
1808             'label'
1809         ),
1810         oils_i18n_gettext(
1811             'circ.booking_reservation.default_elbow_room',
1812             'Elbow room specifies how far in the future you must make a reservation on an item if that item will have to transit to reach its pickup location.  It secondarily defines how soon a reservation on a given item must start before the check-in process will opportunistically capture it for the reservation shelf.',
1813             'coust',
1814             'label'
1815         ),
1816         'interval'
1817     );
1818
1819 -- Org_unit_setting_type(s) that need an fm_class:
1820 INSERT into config.org_unit_setting_type
1821 ( name, label, description, datatype, fm_class ) VALUES
1822 ( 'acq.default_copy_location',
1823   'Default copy location',
1824   null,
1825   'link',
1826   'acpl' );
1827
1828 INSERT INTO config.org_unit_setting_type (name, label, description, datatype) VALUES (
1829     'circ.holds.org_unit_target_weight',
1830     'Holds: Org Unit Target Weight',
1831     'Org Units can be organized into hold target groups based on a weight.  Potential copies from org units with the same weight are chosen at random.',
1832     'integer'
1833 );
1834
1835 INSERT INTO config.org_unit_setting_type (name, label, description, datatype) VALUES (
1836     'circ.holds.target_holds_by_org_unit_weight',
1837     'Holds: Use weight-based hold targeting',
1838     'Use library weight based hold targeting',
1839     'bool'
1840 );
1841
1842 INSERT INTO config.org_unit_setting_type (name, label, description, datatype) VALUES (
1843     'circ.holds.max_org_unit_target_loops',
1844     'Holds: Maximum library target attempts',
1845     'When this value is set and greater than 0, the system will only attempt to find a copy at each possible branch the configured number of times',
1846     'integer'
1847 );
1848
1849
1850 -- Org setting for overriding the circ lib of a precat copy
1851 INSERT INTO config.org_unit_setting_type (name, label, description, datatype) VALUES (
1852     'circ.pre_cat_copy_circ_lib',
1853     'Pre-cat Item Circ Lib',
1854     'Override the default circ lib of "here" with a pre-configured circ lib for pre-cat items.  The value should be the "shortname" (aka policy name) of the org unit',
1855     'string'
1856 );
1857
1858 -- Circ auto-renew interval setting
1859 INSERT INTO config.org_unit_setting_type (name, label, description, datatype) VALUES (
1860     'circ.checkout_auto_renew_age',
1861     'Checkout auto renew age',
1862     'When an item has been checked out for at least this amount of time, an attempt to check out the item to the patron that it is already checked out to will simply renew the circulation',
1863     'interval'
1864 );
1865
1866 -- Setting for behind the desk hold pickups
1867 INSERT INTO config.org_unit_setting_type (name, label, description, datatype) VALUES (
1868     'circ.holds.behind_desk_pickup_supported',
1869     'Holds: Behind Desk Pickup Supported',
1870     'If a branch supports both a public holds shelf and behind-the-desk pickups, set this value to true.  This gives the patron the option to enable behind-the-desk pickups for their holds',
1871     'bool'
1872 );
1873
1874 INSERT INTO config.org_unit_setting_type ( name, label, description, datatype ) VALUES (
1875         'acq.holds.allow_holds_from_purchase_request',
1876         oils_i18n_gettext(
1877             'acq.holds.allow_holds_from_purchase_request', 
1878             'Allows patrons to create automatic holds from purchase requests.', 
1879             'coust', 
1880             'label'),
1881         oils_i18n_gettext(
1882             'acq.holds.allow_holds_from_purchase_request', 
1883             'Allows patrons to create automatic holds from purchase requests.', 
1884             'coust', 
1885             'description'),
1886         'bool'
1887 );
1888
1889 INSERT INTO config.org_unit_setting_type (name, label, description, datatype) VALUES (
1890     'circ.holds.target_skip_me',
1891     'Skip For Hold Targeting',
1892     'When true, don''t target any copies at this org unit for holds',
1893     'bool'
1894 );
1895
1896 -- claims returned mark item missing 
1897 INSERT INTO
1898     config.org_unit_setting_type ( name, label, description, datatype )
1899     VALUES (
1900         'circ.claim_return.mark_missing',
1901         'Claim Return: Mark copy as missing', 
1902         'When a circ is marked as claims-returned, also mark the copy as missing',
1903         'bool'
1904     );
1905
1906 -- claims never checked out mark item missing 
1907 INSERT INTO
1908     config.org_unit_setting_type ( name, label, description, datatype )
1909     VALUES (
1910         'circ.claim_never_checked_out.mark_missing',
1911         'Claim Never Checked Out: Mark copy as missing', 
1912         'When a circ is marked as claims-never-checked-out, mark the copy as missing',
1913         'bool'
1914     );
1915
1916 -- mark damaged void overdue setting
1917 INSERT INTO
1918     config.org_unit_setting_type ( name, label, description, datatype )
1919     VALUES (
1920         'circ.damaged.void_ovedue',
1921         'Mark item damaged voids overdues',
1922         'When an item is marked damaged, overdue fines on the most recent circulation are voided.',
1923         'bool'
1924     );
1925
1926 -- hold cancel display limits
1927 INSERT INTO config.org_unit_setting_type ( name, label, description, datatype )
1928     VALUES (
1929         'circ.holds.canceled.display_count',
1930         'Holds: Canceled holds display count',
1931         'How many canceled holds to show in patron holds interfaces',
1932         'integer'
1933     );
1934
1935 INSERT INTO config.org_unit_setting_type ( name, label, description, datatype )
1936     VALUES (
1937         'circ.holds.canceled.display_age',
1938         'Holds: Canceled holds display age',
1939         'Show all canceled holds that were canceled within this amount of time',
1940         'interval'
1941     );
1942
1943 INSERT INTO config.org_unit_setting_type ( name, label, description, datatype )
1944     VALUES (
1945         'circ.holds.uncancel.reset_request_time',
1946         'Holds: Reset request time on un-cancel',
1947         'When a hold is uncanceled, reset the request time to push it to the end of the queue',
1948         'bool'
1949     );
1950
1951 INSERT INTO config.org_unit_setting_type (name, label, description, datatype)
1952     VALUES (
1953         'circ.holds.default_shelf_expire_interval',
1954         'Default hold shelf expire interval',
1955         '',
1956         'interval'
1957 );
1958
1959 INSERT INTO config.org_unit_setting_type (name, label, description, datatype, fm_class)
1960     VALUES (
1961         'circ.claim_return.copy_status', 
1962         'Claim Return Copy Status', 
1963         'Claims returned copies are put into this status.  Default is to leave the copy in the Checked Out status',
1964         'link', 
1965         'ccs' 
1966     );
1967
1968 INSERT INTO config.org_unit_setting_type ( name, label, description, datatype ) 
1969     VALUES ( 
1970         'circ.max_fine.cap_at_price',
1971         oils_i18n_gettext('circ.max_fine.cap_at_price', 'Circ: Cap Max Fine at Item Price', 'coust', 'label'),
1972         oils_i18n_gettext('circ.max_fine.cap_at_price', 'This prevents the system from charging more than the item price in overdue fines', 'coust', 'description'),
1973         'bool' 
1974     );
1975
1976 INSERT INTO config.org_unit_setting_type ( name, label, description, datatype, fm_class ) 
1977     VALUES ( 
1978         'circ.holds.clear_shelf.copy_status',
1979         oils_i18n_gettext('circ.holds.clear_shelf.copy_status', 'Holds: Clear shelf copy status', 'coust', 'label'),
1980         oils_i18n_gettext('circ.holds.clear_shelf.copy_status', 'Any copies that have not been put into reshelving, in-transit, or on-holds-shelf (for a new hold) during the clear shelf process will be put into this status.  This is basically a purgatory status for copies waiting to be pulled from the shelf and processed by hand', 'coust', 'description'),
1981         'link',
1982         'ccs'
1983     );
1984
1985 INSERT INTO config.org_unit_setting_type ( name, label, description, datatype )
1986     VALUES ( 
1987         'circ.selfcheck.workstation_required',
1988         oils_i18n_gettext('circ.selfcheck.workstation_required', 'Selfcheck: Workstation Required', 'coust', 'label'),
1989         oils_i18n_gettext('circ.selfcheck.workstation_required', 'All selfcheck stations must use a workstation', 'coust', 'description'),
1990         'bool'
1991     ), (
1992         'circ.selfcheck.patron_password_required',
1993         oils_i18n_gettext('circ.selfcheck.patron_password_required', 'Selfcheck: Require Patron Password', 'coust', 'label'),
1994         oils_i18n_gettext('circ.selfcheck.patron_password_required', 'Patron must log in with barcode and password at selfcheck station', 'coust', 'description'),
1995         'bool'
1996     );
1997
1998 INSERT INTO config.org_unit_setting_type ( name, label, description, datatype )
1999     VALUES ( 
2000         'circ.selfcheck.alert.sound',
2001         oils_i18n_gettext('circ.selfcheck.alert.sound', 'Selfcheck: Audio Alerts', 'coust', 'label'),
2002         oils_i18n_gettext('circ.selfcheck.alert.sound', 'Use audio alerts for selfcheck events', 'coust', 'description'),
2003         'bool'
2004     );
2005
2006 INSERT INTO
2007     config.org_unit_setting_type (name, label, description, datatype)
2008     VALUES (
2009         'notice.telephony.callfile_lines',
2010         'Telephony: Arbitrary line(s) to include in each notice callfile',
2011         $$
2012         This overrides lines from opensrf.xml.
2013         Line(s) must be valid for your target server and platform
2014         (e.g. Asterisk 1.4).
2015         $$,
2016         'string'
2017     );
2018
2019 INSERT INTO config.org_unit_setting_type ( name, label, description, datatype )
2020     VALUES ( 
2021         'circ.offline.username_allowed',
2022         oils_i18n_gettext('circ.offline.username_allowed', 'Offline: Patron Usernames Allowed', 'coust', 'label'),
2023         oils_i18n_gettext('circ.offline.username_allowed', 'During offline circulations, allow patrons to identify themselves with usernames in addition to barcode.  For this setting to work, a barcode format must also be defined', 'coust', 'description'),
2024         'bool'
2025     );
2026
2027 INSERT INTO config.org_unit_setting_type ( name, label, description, datatype )
2028 VALUES (
2029     'acq.fund.balance_limit.warn',
2030     oils_i18n_gettext('acq.fund.balance_limit.warn', 'Fund Spending Limit for Warning', 'coust', 'label'),
2031     oils_i18n_gettext('acq.fund.balance_limit.warn', 'When the amount remaining in the fund, including spent money and encumbrances, goes below this percentage, attempts to spend from the fund will result in a warning to the staff.', 'coust', 'descripton'),
2032     'integer'
2033 );
2034
2035 INSERT INTO config.org_unit_setting_type ( name, label, description, datatype )
2036 VALUES (
2037     'acq.fund.balance_limit.block',
2038     oils_i18n_gettext('acq.fund.balance_limit.block', 'Fund Spending Limit for Block', 'coust', 'label'),
2039     oils_i18n_gettext('acq.fund.balance_limit.block', 'When the amount remaining in the fund, including spent money and encumbrances, goes below this percentage, attempts to spend from the fund will be blocked.', 'coust', 'description'),
2040     'integer'
2041 );
2042
2043 INSERT INTO config.org_unit_setting_type ( name, label, description, datatype )
2044     VALUES (
2045         'circ.holds.hold_has_copy_at.alert',
2046         oils_i18n_gettext('circ.holds.hold_has_copy_at.alert', 'Holds: Has Local Copy Alert', 'coust', 'label'),
2047         oils_i18n_gettext('circ.holds.hold_has_copy_at.alert', 'If there is an available copy at the requesting library that could fulfill a hold during hold placement time, alert the patron', 'coust', 'description'),
2048         'bool'
2049     ),(
2050         'circ.holds.hold_has_copy_at.block',
2051         oils_i18n_gettext('circ.holds.hold_has_copy_at.block', 'Holds: Has Local Copy Block', 'coust', 'label'),
2052         oils_i18n_gettext('circ.holds.hold_has_copy_at.block', 'If there is an available copy at the requesting library that could fulfill a hold during hold placement time, do not allow the hold to be placed', 'coust', 'description'),
2053         'bool'
2054     );
2055
2056 INSERT INTO config.org_unit_setting_type ( name, label, description, datatype )
2057 VALUES (
2058     'auth.persistent_login_interval',
2059     oils_i18n_gettext('auth.persistent_login_interval', 'Persistent Login Duration', 'coust', 'label'),
2060     oils_i18n_gettext('auth.persistent_login_interval', 'How long a persistent login lasts.  E.g. ''2 weeks''', 'coust', 'description'),
2061     'interval'
2062 );
2063
2064 INSERT INTO config.org_unit_setting_type ( name, label, description, datatype ) VALUES (
2065         'cat.marc_control_number_identifier',
2066         oils_i18n_gettext(
2067             'cat.marc_control_number_identifier', 
2068             'Cat: Defines the control number identifier used in 003 and 035 fields.', 
2069             'coust', 
2070             'label'),
2071         oils_i18n_gettext(
2072             'cat.marc_control_number_identifier', 
2073             'Cat: Defines the control number identifier used in 003 and 035 fields.', 
2074             'coust', 
2075             'description'),
2076         'string'
2077 );
2078
2079 INSERT INTO config.org_unit_setting_type (name, label, description, datatype) 
2080     VALUES (
2081         'circ.selfcheck.block_checkout_on_copy_status',
2082         oils_i18n_gettext(
2083             'circ.selfcheck.block_checkout_on_copy_status',
2084             'Selfcheck: Block copy checkout status',
2085             'coust',
2086             'label'
2087         ),
2088         oils_i18n_gettext(
2089             'circ.selfcheck.block_checkout_on_copy_status',
2090             'List of copy status IDs that will block checkout even if the generic COPY_NOT_AVAILABLE event is overridden',
2091             'coust',
2092             'description'
2093         ),
2094         'array'
2095     );
2096
2097 INSERT INTO config.org_unit_setting_type ( name, label, description, datatype, fm_class )
2098 VALUES (
2099     'serial.prev_issuance_copy_location',
2100     oils_i18n_gettext(
2101         'serial.prev_issuance_copy_location',
2102         'Serials: Previous Issuance Copy Location',
2103         'coust',
2104         'label'
2105     ),
2106     oils_i18n_gettext(
2107         'serial.prev_issuance_copy_location',
2108         'When a serial issuance is received, copies (units) of the previous issuance will be automatically moved into the configured shelving location',
2109         'coust',
2110         'descripton'
2111         ),
2112     'link',
2113     'acpl'
2114 );
2115
2116 INSERT INTO config.org_unit_setting_type ( name, label, description, datatype, fm_class )
2117 VALUES (
2118     'cat.default_classification_scheme',
2119     oils_i18n_gettext(
2120         'cat.default_classification_scheme',
2121         'Cataloging: Default Classification Scheme',
2122         'coust',
2123         'label'
2124     ),
2125     oils_i18n_gettext(
2126         'cat.default_classification_scheme',
2127         'Defines the default classification scheme for new call numbers: 1 = Generic; 2 = Dewey; 3 = LC',
2128         'coust',
2129         'descripton'
2130         ),
2131     'link',
2132     'acnc'
2133 );
2134
2135 INSERT INTO config.org_unit_setting_type ( name, label, description, datatype ) VALUES (
2136         'opac.org_unit_hiding.depth',
2137         oils_i18n_gettext(
2138             'opac.org_unit_hiding.depth',
2139             'OPAC: Org Unit Hiding Depth', 
2140             'coust', 
2141             'label'),
2142         oils_i18n_gettext(
2143             'opac.org_unit_hiding.depth',
2144             'This will hide certain org units in the public OPAC if the Original Location (url param "ol") for the OPAC inherits this setting.  This setting specifies an org unit depth, that together with the OPAC Original Location determines which section of the Org Hierarchy should be visible in the OPAC.  For example, a stock Evergreen installation will have a 3-tier hierarchy (Consortium/System/Branch), where System has a depth of 1 and Branch has a depth of 2.  If this setting contains a depth of 1 in such an installation, then every library in the System in which the Original Location belongs will be visible, and everything else will be hidden.  A depth of 0 will effectively make every org visible.  The embedded OPAC in the staff client ignores this setting.', 
2145             'coust', 
2146             'description'),
2147         'integer'
2148 );
2149
2150 INSERT INTO config.org_unit_setting_type ( name, label, description, datatype )
2151     VALUES ( 
2152         'circ.holds.clear_shelf.no_capture_holds',
2153         oils_i18n_gettext('circ.holds.clear_shelf.no_capture_holds', 'Holds: Bypass hold capture during clear shelf process', 'coust', 'label'),
2154         oils_i18n_gettext('circ.holds.clear_shelf.no_capture_holds', 'During the clear shelf process, avoid capturing new holds on cleared items.', 'coust', 'description'),
2155         'bool'
2156     );
2157
2158 INSERT INTO config.org_unit_setting_type (name, label, description, datatype) VALUES (
2159     'circ.booking_reservation.stop_circ',
2160     'Disallow circulation of items when they are on booking reserve and that reserve overlaps with the checkout period',
2161     'When true, items on booking reserve during the proposed checkout period will not be allowed to circulate unless overridden with the COPY_RESERVED.override permission.',
2162     'bool'
2163 );
2164
2165 ---------------------------------
2166 -- Seed data for usr_setting_type
2167 ----------------------------------
2168
2169 INSERT INTO config.usr_setting_type (name,opac_visible,label,description,datatype)
2170     VALUES ('opac.default_font', TRUE, 'OPAC Font Size', 'OPAC Font Size', 'string');
2171
2172 INSERT INTO config.usr_setting_type (name,opac_visible,label,description,datatype)
2173     VALUES ('opac.default_search_depth', TRUE, 'OPAC Search Depth', 'OPAC Search Depth', 'integer');
2174
2175 INSERT INTO config.usr_setting_type (name,opac_visible,label,description,datatype)
2176     VALUES ('opac.default_search_location', TRUE, 'OPAC Search Location', 'OPAC Search Location', 'integer');
2177
2178 INSERT INTO config.usr_setting_type (name,opac_visible,label,description,datatype)
2179     VALUES ('opac.hits_per_page', TRUE, 'Hits per Page', 'Hits per Page', 'string');
2180
2181 INSERT INTO config.usr_setting_type (name,opac_visible,label,description,datatype)
2182     VALUES ('opac.hold_notify', TRUE, 'Hold Notification Format', 'Hold Notification Format', 'string');
2183
2184 INSERT INTO config.usr_setting_type (name,opac_visible,label,description,datatype)
2185     VALUES ('staff_client.catalog.record_view.default', TRUE, 'Default Record View', 'Default Record View', 'string');
2186
2187 INSERT INTO config.usr_setting_type (name,opac_visible,label,description,datatype)
2188     VALUES ('staff_client.copy_editor.templates', TRUE, 'Copy Editor Template', 'Copy Editor Template', 'object');
2189
2190 INSERT INTO config.usr_setting_type (name,opac_visible,label,description,datatype)
2191     VALUES ('circ.holds_behind_desk', FALSE, 'Hold is behind Circ Desk', 'Hold is behind Circ Desk', 'bool');
2192
2193 INSERT INTO config.usr_setting_type (name,opac_visible,label,description,datatype)
2194     VALUES (
2195         'history.circ.retention_age',
2196         TRUE,
2197         oils_i18n_gettext('history.circ.retention_age','Historical Circulation Retention Age','cust','label'),
2198         oils_i18n_gettext('history.circ.retention_age','Historical Circulation Retention Age','cust','description'),
2199         'interval'
2200     ),(
2201         'history.circ.retention_start',
2202         FALSE,
2203         oils_i18n_gettext('history.circ.retention_start','Historical Circulation Retention Start Date','cust','label'),
2204         oils_i18n_gettext('history.circ.retention_start','Historical Circulation Retention Start Date','cust','description'),
2205         'date'
2206     );
2207
2208 INSERT INTO config.usr_setting_type (name,opac_visible,label,description,datatype)
2209     VALUES (
2210         'history.hold.retention_age',
2211         TRUE,
2212         oils_i18n_gettext('history.hold.retention_age','Historical Hold Retention Age','cust','label'),
2213         oils_i18n_gettext('history.hold.retention_age','Historical Hold Retention Age','cust','description'),
2214         'interval'
2215     ),(
2216         'history.hold.retention_start',
2217         TRUE,
2218         oils_i18n_gettext('history.hold.retention_start','Historical Hold Retention Start Date','cust','label'),
2219         oils_i18n_gettext('history.hold.retention_start','Historical Hold Retention Start Date','cust','description'),
2220         'interval'
2221     ),(
2222         'history.hold.retention_count',
2223         TRUE,
2224         oils_i18n_gettext('history.hold.retention_count','Historical Hold Retention Count','cust','label'),
2225         oils_i18n_gettext('history.hold.retention_count','Historical Hold Retention Count','cust','description'),
2226         'integer'
2227     );
2228
2229 INSERT INTO config.usr_setting_type (name, opac_visible, label, description, datatype)
2230     VALUES (
2231         'opac.default_sort',
2232         TRUE,
2233         oils_i18n_gettext(
2234             'opac.default_sort',
2235             'OPAC Default Search Sort',
2236             'cust',
2237             'label'
2238         ),
2239         oils_i18n_gettext(
2240             'opac.default_sort',
2241             'OPAC Default Search Sort',
2242             'cust',
2243             'description'
2244         ),
2245         'string'
2246     );
2247
2248 INSERT INTO config.org_unit_setting_type ( name, label, description, datatype, fm_class ) VALUES (
2249         'circ.missing_pieces.copy_status',
2250         oils_i18n_gettext(
2251             'circ.missing_pieces.copy_status',
2252             'Circulation: Item Status for Missing Pieces',
2253             'coust',
2254             'label'),
2255         oils_i18n_gettext(
2256             'circ.missing_pieces.copy_status',
2257             'This is the Item Status to use for items that have been marked or scanned as having Missing Pieces.  In the absence of this setting, the Damaged status is used.',
2258             'coust',
2259             'description'),
2260         'link',
2261         'ccs'
2262 );
2263
2264 INSERT INTO config.org_unit_setting_type ( name, label, description, datatype ) VALUES (
2265         'circ.do_not_tally_claims_returned',
2266         oils_i18n_gettext(
2267             'circ.do_not_tally_claims_returned',
2268             'Circulation: Do not include outstanding Claims Returned circulations in lump sum tallies in Patron Display.',
2269             'coust',
2270             'label'),
2271         oils_i18n_gettext(
2272             'circ.do_not_tally_claims_returned',
2273             'In the Patron Display interface, the number of total active circulations for a given patron is presented in the Summary sidebar and underneath the Items Out navigation button.  This setting will prevent Claims Returned circulations from counting toward these tallies.',
2274             'coust',
2275             'description'),
2276         'bool'
2277 );
2278
2279 INSERT INTO config.org_unit_setting_type (name, label, description, datatype)
2280     VALUES
2281         ('cat.label.font.size',
2282             oils_i18n_gettext('cat.label.font.size',
2283                 'Cataloging: Spine and pocket label font size', 'coust', 'label'),
2284             oils_i18n_gettext('cat.label.font.size',
2285                 'Set the default font size for spine and pocket labels', 'coust', 'description'),
2286             'integer'
2287         )
2288         ,('cat.label.font.family',
2289             oils_i18n_gettext('cat.label.font.family',
2290                 'Cataloging: Spine and pocket label font family', 'coust', 'label'),
2291             oils_i18n_gettext('cat.label.font.family',
2292                 'Set the preferred font family for spine and pocket labels. You can specify a list of fonts, separated by commas, in order of preference; the system will use the first font it finds with a matching name. For example, "Arial, Helvetica, serif".',
2293                 'coust', 'description'),
2294             'string'
2295         )
2296         ,('cat.spine.line.width',
2297             oils_i18n_gettext('cat.spine.line.width',
2298                 'Cataloging: Spine label line width', 'coust', 'label'),
2299             oils_i18n_gettext('cat.spine.line.width',
2300                 'Set the default line width for spine labels in number of characters. This specifies the boundary at which lines must be wrapped.',
2301                 'coust', 'description'),
2302             'integer'
2303         )
2304         ,('cat.spine.line.height',
2305             oils_i18n_gettext('cat.spine.line.height',
2306                 'Cataloging: Spine label maximum lines', 'coust', 'label'),
2307             oils_i18n_gettext('cat.spine.line.height',
2308                 'Set the default maximum number of lines for spine labels.',
2309                 'coust', 'description'),
2310             'integer'
2311         )
2312         ,('cat.spine.line.margin',
2313             oils_i18n_gettext('cat.spine.line.margin',
2314                 'Cataloging: Spine label left margin', 'coust', 'label'),
2315             oils_i18n_gettext('cat.spine.line.margin',
2316                 'Set the left margin for spine labels in number of characters.',
2317                 'coust', 'description'),
2318             'integer'
2319         )
2320 ;
2321
2322 INSERT INTO config.org_unit_setting_type (name, label, description, datatype)
2323     VALUES
2324         ('cat.label.font.weight',
2325             oils_i18n_gettext('cat.label.font.weight',
2326                 'Cataloging: Spine and pocket label font weight', 'coust', 'label'),
2327             oils_i18n_gettext('cat.label.font.weight',
2328                 'Set the preferred font weight for spine and pocket labels. You can specify "normal", "bold", "bolder", or "lighter".',
2329                 'coust', 'description'),
2330             'string'
2331         )
2332 ;
2333
2334 INSERT INTO config.org_unit_setting_type ( name, label, description, datatype ) VALUES (
2335         'circ.patron_edit.clone.copy_address',
2336         oils_i18n_gettext(
2337             'circ.patron_edit.clone.copy_address',
2338             'Patron Registration: Cloned patrons get address copy',
2339             'coust',
2340             'label'
2341         ),
2342         oils_i18n_gettext(
2343             'circ.patron_edit.clone.copy_address',
2344             'In the Patron editor, copy addresses from the cloned user instead of linking directly to the address',
2345             'coust',
2346             'description'
2347         ),
2348         'bool'
2349 );
2350
2351 INSERT INTO config.org_unit_setting_type ( name, label, description, datatype, fm_class ) VALUES (
2352         'ui.patron.default_ident_type',
2353         oils_i18n_gettext(
2354             'ui.patron.default_ident_type',
2355             'GUI: Default Ident Type for Patron Registration',
2356             'coust',
2357             'label'),
2358         oils_i18n_gettext(
2359             'ui.patron.default_ident_type',
2360             'This is the default Ident Type for new users in the patron editor.',
2361             'coust',
2362             'description'),
2363         'link',
2364         'cit'
2365 );
2366
2367 INSERT INTO config.org_unit_setting_type ( name, label, description, datatype ) VALUES (
2368         'ui.patron.default_country',
2369         oils_i18n_gettext(
2370             'ui.patron.default_country',
2371             'GUI: Default Country for New Addresses in Patron Editor',
2372             'coust',
2373             'label'),
2374         oils_i18n_gettext(
2375             'ui.patron.default_country',
2376             'This is the default Country for new addresses in the patron editor.',
2377             'coust',
2378             'description'),
2379         'string'
2380 );
2381
2382 INSERT INTO config.org_unit_setting_type ( name, label, description, datatype ) VALUES (
2383         'ui.patron.registration.require_address',
2384         oils_i18n_gettext(
2385             'ui.patron.registration.require_address',
2386             'GUI: Require at least one address for Patron Registration',
2387             'coust',
2388             'label'),
2389         oils_i18n_gettext(
2390             'ui.patron.registration.require_address',
2391             'Enforces a requirement for having at least one address for a patron during registration.',
2392             'coust',
2393             'description'),
2394         'bool'
2395 );
2396
2397 INSERT INTO config.org_unit_setting_type (
2398     name, label, description, datatype
2399 ) VALUES
2400     ('credit.processor.payflowpro.enabled',
2401         'Credit card processing: Enable PayflowPro payments',
2402         'This is NOT the same thing as the settings labeled with just "PayPal."',
2403         'bool'
2404     ),
2405     ('credit.processor.payflowpro.login',
2406         'Credit card processing: PayflowPro login/merchant ID',
2407         'Often the same thing as the PayPal manager login',
2408         'string'
2409     ),
2410     ('credit.processor.payflowpro.password',
2411         'Credit card processing: PayflowPro password',
2412         'PayflowPro password',
2413         'string'
2414     ),
2415     ('credit.processor.payflowpro.testmode',
2416         'Credit card processing: PayflowPro test mode',
2417         'Do not really process transactions, but stay in test mode - uses pilot-payflowpro.paypal.com instead of the usual host',
2418         'bool'
2419     ),
2420     ('credit.processor.payflowpro.vendor',
2421         'Credit card processing: PayflowPro vendor',
2422         'Often the same thing as the login',
2423         'string'
2424     ),
2425     ('credit.processor.payflowpro.partner',
2426         'Credit card processing: PayflowPro partner',
2427         'Often "PayPal" or "VeriSign", sometimes others',
2428         'string'
2429     );
2430
2431 -- Patch the name of an old selfcheck setting
2432 UPDATE actor.org_unit_setting
2433     SET name = 'circ.selfcheck.alert.popup'
2434     WHERE name = 'circ.selfcheck.alert_on_checkout_event';
2435
2436 -- Rename certain existing org_unit settings, if present,
2437 -- and make sure their values are JSON
2438 UPDATE actor.org_unit_setting SET
2439     name = 'circ.holds.default_estimated_wait_interval',
2440     --
2441     -- The value column should be JSON.  The old value should be a number,
2442     -- but it may or may not be quoted.  The following CASE behaves
2443     -- differently depending on whether value is quoted.  It is simplistic,
2444     -- and will be defeated by leading or trailing white space, or various
2445     -- malformations.
2446     --
2447     value = CASE WHEN SUBSTR( value, 1, 1 ) = '"'
2448                 THEN '"' || SUBSTR( value, 2, LENGTH(value) - 2 ) || ' days"'
2449                 ELSE '"' || value || ' days"'
2450             END
2451 WHERE name = 'circ.hold_estimate_wait_interval';
2452
2453 -- Create types for existing org unit settings
2454 -- not otherwise accounted for
2455
2456 INSERT INTO config.org_unit_setting_type(
2457  name,
2458  label,
2459  description
2460 )
2461 SELECT DISTINCT
2462         name,
2463         name,
2464         'FIXME'
2465 FROM
2466         actor.org_unit_setting
2467 WHERE
2468         name NOT IN (
2469                 SELECT name
2470                 FROM config.org_unit_setting_type
2471         );
2472
2473 -- Add foreign key to org_unit_setting
2474
2475 ALTER TABLE actor.org_unit_setting
2476         ADD FOREIGN KEY (name) REFERENCES config.org_unit_setting_type (name)
2477                 DEFERRABLE INITIALLY DEFERRED;
2478
2479 -- Create types for existing user settings
2480 -- not otherwise accounted for
2481
2482 INSERT INTO config.usr_setting_type (
2483         name,
2484         label,
2485         description
2486 )
2487 SELECT DISTINCT
2488         name,
2489         name,
2490         'FIXME'
2491 FROM
2492         actor.usr_setting
2493 WHERE
2494         name NOT IN (
2495                 SELECT name
2496                 FROM config.usr_setting_type
2497         );
2498
2499 -- Add foreign key to user_setting_type
2500
2501 ALTER TABLE actor.usr_setting
2502         ADD FOREIGN KEY (name) REFERENCES config.usr_setting_type (name)
2503                 ON DELETE CASCADE ON UPDATE CASCADE
2504                 DEFERRABLE INITIALLY DEFERRED;
2505
2506 INSERT INTO actor.org_unit_setting (org_unit, name, value) VALUES
2507     (1, 'cat.spine.line.margin', 0)
2508     ,(1, 'cat.spine.line.height', 9)
2509     ,(1, 'cat.spine.line.width', 8)
2510     ,(1, 'cat.label.font.family', '"monospace"')
2511     ,(1, 'cat.label.font.size', 10)
2512     ,(1, 'cat.label.font.weight', '"normal"')
2513 ;
2514
2515 ALTER TABLE action_trigger.event_definition ADD COLUMN granularity TEXT;
2516 ALTER TABLE action_trigger.event ADD COLUMN async_output BIGINT REFERENCES action_trigger.event_output (id);
2517 ALTER TABLE action_trigger.event_definition ADD COLUMN usr_field TEXT;
2518 ALTER TABLE action_trigger.event_definition ADD COLUMN opt_in_setting TEXT REFERENCES config.usr_setting_type (name) DEFERRABLE INITIALLY DEFERRED;
2519
2520 CREATE OR REPLACE FUNCTION is_json( TEXT ) RETURNS BOOL AS $f$
2521     use JSON::XS;
2522     my $json = shift();
2523     eval { JSON::XS->new->allow_nonref->decode( $json ) };
2524     return $@ ? 0 : 1;
2525 $f$ LANGUAGE PLPERLU;
2526
2527 ALTER TABLE action_trigger.event ADD COLUMN user_data TEXT CHECK (user_data IS NULL OR is_json( user_data ));
2528
2529 INSERT INTO action_trigger.hook (key,core_type,description) VALUES (
2530     'hold_request.cancel.expire_no_target',
2531     'ahr',
2532     'A hold is cancelled because no copies were found'
2533 );
2534
2535 INSERT INTO action_trigger.hook (key,core_type,description) VALUES (
2536     'hold_request.cancel.expire_holds_shelf',
2537     'ahr',
2538     'A hold is cancelled because it was on the holds shelf too long'
2539 );
2540
2541 INSERT INTO action_trigger.hook (key,core_type,description) VALUES (
2542     'hold_request.cancel.staff',
2543     'ahr',
2544     'A hold is cancelled because it was cancelled by staff'
2545 );
2546
2547 INSERT INTO action_trigger.hook (key,core_type,description) VALUES (
2548     'hold_request.cancel.patron',
2549     'ahr',
2550     'A hold is cancelled by the patron'
2551 );
2552
2553 -- Fix typos in descriptions
2554 UPDATE action_trigger.hook SET description = 'A hold is successfully placed' WHERE key = 'hold_request.success';
2555 UPDATE action_trigger.hook SET description = 'A hold is attempted but not successfully placed' WHERE key = 'hold_request.failure';
2556
2557 -- Add a hook for renewals
2558 INSERT INTO action_trigger.hook (key,core_type,description) VALUES ('renewal','circ','Item renewed to user');
2559
2560 INSERT INTO action_trigger.validator (module,description) VALUES ('MaxPassiveDelayAge','Check that the event is not too far past the delay_field time -- requires a max_delay_age interval parameter');
2561
2562 -- Sample Pre-due Notice --
2563
2564 INSERT INTO action_trigger.event_definition (id, active, owner, name, hook, validator, reactor, delay, delay_field, group_field, template) 
2565     VALUES (6, 'f', 1, '3 Day Courtesy Notice', 'checkout.due', 'CircIsOpen', 'SendEmail', '-3 days', 'due_date', 'usr', 
2566 $$
2567 [%- USE date -%]
2568 [%- user = target.0.usr -%]
2569 To: [%- params.recipient_email || user.email %]
2570 From: [%- params.sender_email || default_sender %]
2571 Subject: Courtesy Notice
2572
2573 Dear [% user.family_name %], [% user.first_given_name %]
2574 As a reminder, the following items are due in 3 days.
2575
2576 [% FOR circ IN target %]
2577     Title: [% circ.target_copy.call_number.record.simple_record.title %] 
2578     Barcode: [% circ.target_copy.barcode %] 
2579     Due: [% date.format(helpers.format_date(circ.due_date), '%Y-%m-%d') %]
2580     Item Cost: [% helpers.get_copy_price(circ.target_copy) %]
2581     Library: [% circ.circ_lib.name %]
2582     Library Phone: [% circ.circ_lib.phone %]
2583 [% END %]
2584
2585 $$);
2586
2587 INSERT INTO action_trigger.environment (event_def, path) VALUES 
2588     (6, 'target_copy.call_number.record.simple_record'),
2589     (6, 'usr'),
2590     (6, 'circ_lib.billing_address');
2591
2592 INSERT INTO action_trigger.event_params (event_def, param, value) VALUES
2593     (6, 'max_delay_age', '"1 day"');
2594
2595 -- also add the max delay age to the default overdue notice event def
2596 INSERT INTO action_trigger.event_params (event_def, param, value) VALUES
2597     (1, 'max_delay_age', '"1 day"');
2598   
2599 INSERT INTO action_trigger.validator (module,description) VALUES ('MinPassiveTargetAge','Check that the target is old enough to be used by this event -- requires a min_target_age interval parameter, and accepts an optional target_age_field to specify what time to use for offsetting');
2600
2601 INSERT INTO action_trigger.reactor (module,description) VALUES ('ApplyPatronPenalty','Applies the configured penalty to a patron.  Required named environment variables are "user", which refers to the user object, and "context_org", which refers to the org_unit object that acts as the focus for the penalty.');
2602
2603 INSERT INTO action_trigger.hook (
2604         key,
2605         core_type,
2606         description,
2607         passive
2608     ) VALUES (
2609         'hold_request.shelf_expires_soon',
2610         'ahr',
2611         'A hold on the shelf will expire there soon.',
2612         TRUE
2613     );
2614
2615 INSERT INTO action_trigger.event_definition (
2616         id,
2617         active,
2618         owner,
2619         name,
2620         hook,
2621         validator,
2622         reactor,
2623         delay,
2624         delay_field,
2625         group_field,
2626         template
2627     ) VALUES (
2628         7,
2629         FALSE,
2630         1,
2631         'Hold Expires from Shelf Soon',
2632         'hold_request.shelf_expires_soon',
2633         'HoldIsAvailable',
2634         'SendEmail',
2635         '- 1 DAY',
2636         'shelf_expire_time',
2637         'usr',
2638 $$
2639 [%- USE date -%]
2640 [%- user = target.0.usr -%]
2641 To: [%- params.recipient_email || user.email %]
2642 From: [%- params.sender_email || default_sender %]
2643 Subject: Hold Available Notification
2644
2645 Dear [% user.family_name %], [% user.first_given_name %]
2646 You requested holds on the following item(s), which are available for
2647 pickup, but these holds will soon expire.
2648
2649 [% FOR hold IN target %]
2650     [%- data = helpers.get_copy_bib_basics(hold.current_copy.id) -%]
2651     Title: [% data.title %]
2652     Author: [% data.author %]
2653     Library: [% hold.pickup_lib.name %]
2654 [% END %]
2655 $$
2656     );
2657
2658 INSERT INTO action_trigger.environment (
2659         event_def,
2660         path
2661     ) VALUES
2662     ( 7, 'current_copy'),
2663     ( 7, 'pickup_lib.billing_address'),
2664     ( 7, 'usr');
2665
2666 INSERT INTO action_trigger.hook (
2667         key,
2668         core_type,
2669         description,
2670         passive
2671     ) VALUES (
2672         'hold_request.long_wait',
2673         'ahr',
2674         'A patron has been waiting on a hold to be fulfilled for a long time.',
2675         TRUE
2676     );
2677
2678 INSERT INTO action_trigger.event_definition (
2679         id,
2680         active,
2681         owner,
2682         name,
2683         hook,
2684         validator,
2685         reactor,
2686         delay,
2687         delay_field,
2688         group_field,
2689         template
2690     ) VALUES (
2691         9,
2692         FALSE,
2693         1,
2694         'Hold waiting for pickup for long time',
2695         'hold_request.long_wait',
2696         'NOOP_True',
2697         'SendEmail',
2698         '6 MONTHS',
2699         'request_time',
2700         'usr',
2701 $$
2702 [%- USE date -%]
2703 [%- user = target.0.usr -%]
2704 To: [%- params.recipient_email || user.email %]
2705 From: [%- params.sender_email || default_sender %]
2706 Subject: Long Wait Hold Notification
2707
2708 Dear [% user.family_name %], [% user.first_given_name %]
2709
2710 You requested hold(s) on the following item(s), but unfortunately
2711 we have not been able to fulfill your request after a considerable
2712 length of time.  If you would still like to receive these items,
2713 no action is required.
2714
2715 [% FOR hold IN target %]
2716     Title: [% hold.bib_rec.bib_record.simple_record.title %]
2717     Author: [% hold.bib_rec.bib_record.simple_record.author %]
2718 [% END %]
2719 $$
2720 );
2721
2722 INSERT INTO action_trigger.environment (
2723         event_def,
2724         path
2725     ) VALUES
2726     (9, 'pickup_lib'),
2727     (9, 'usr'),
2728     (9, 'bib_rec.bib_record.simple_record');
2729
2730 INSERT INTO action_trigger.hook (key, core_type, description, passive) 
2731     VALUES (
2732         'format.selfcheck.checkout',
2733         'circ',
2734         'Formats circ objects for self-checkout receipt',
2735         TRUE
2736     );
2737
2738 INSERT INTO action_trigger.event_definition (id, active, owner, name, hook, validator, reactor, group_field, granularity, template )
2739     VALUES (
2740         10,
2741         TRUE,
2742         1,
2743         'Self-Checkout Receipt',
2744         'format.selfcheck.checkout',
2745         'NOOP_True',
2746         'ProcessTemplate',
2747         'usr',
2748         'print-on-demand',
2749 $$
2750 [%- USE date -%]
2751 [%- SET user = target.0.usr -%]
2752 [%- SET lib = target.0.circ_lib -%]
2753 [%- SET lib_addr = target.0.circ_lib.billing_address -%]
2754 [%- SET hours = lib.hours_of_operation -%]
2755 <div>
2756     <style> li { padding: 8px; margin 5px; }</style>
2757     <div>[% date.format %]</div>
2758     <div>[% lib.name %]</div>
2759     <div>[% lib_addr.street1 %] [% lib_addr.street2 %]</div>
2760     <div>[% lib_addr.city %], [% lib_addr.state %] [% lb_addr.post_code %]</div>
2761     <div>[% lib.phone %]</div>
2762     <br/>
2763
2764     [% user.family_name %], [% user.first_given_name %]
2765     <ol>
2766     [% FOR circ IN target %]
2767         [%-
2768             SET idx = loop.count - 1;
2769             SET udata =  user_data.$idx
2770         -%]
2771         <li>
2772             <div>[% helpers.get_copy_bib_basics(circ.target_copy.id).title %]</div>
2773             <div>Barcode: [% circ.target_copy.barcode %]</div>
2774             [% IF udata.renewal_failure %]
2775                 <div style='color:red;'>Renewal Failed</div>
2776             [% ELSE %]
2777                 <div>Due Date: [% date.format(helpers.format_date(circ.due_date), '%Y-%m-%d') %]</div>
2778             [% END %]
2779         </li>
2780     [% END %]
2781     </ol>
2782     
2783     <div>
2784         Library Hours
2785         [%- BLOCK format_time; date.format(time _ ' 1/1/1000', format='%I:%M %p'); END -%]
2786         <div>
2787             Monday 
2788             [% PROCESS format_time time = hours.dow_0_open %] 
2789             [% PROCESS format_time time = hours.dow_0_close %] 
2790         </div>
2791         <div>
2792             Tuesday 
2793             [% PROCESS format_time time = hours.dow_1_open %] 
2794             [% PROCESS format_time time = hours.dow_1_close %] 
2795         </div>
2796         <div>
2797             Wednesday 
2798             [% PROCESS format_time time = hours.dow_2_open %] 
2799             [% PROCESS format_time time = hours.dow_2_close %] 
2800         </div>
2801         <div>
2802             Thursday
2803             [% PROCESS format_time time = hours.dow_3_open %] 
2804             [% PROCESS format_time time = hours.dow_3_close %] 
2805         </div>
2806         <div>
2807             Friday
2808             [% PROCESS format_time time = hours.dow_4_open %] 
2809             [% PROCESS format_time time = hours.dow_4_close %] 
2810         </div>
2811         <div>
2812             Saturday
2813             [% PROCESS format_time time = hours.dow_5_open %] 
2814             [% PROCESS format_time time = hours.dow_5_close %] 
2815         </div>
2816         <div>
2817             Sunday 
2818             [% PROCESS format_time time = hours.dow_6_open %] 
2819             [% PROCESS format_time time = hours.dow_6_close %] 
2820         </div>
2821     </div>
2822 </div>
2823 $$
2824 );
2825
2826 INSERT INTO action_trigger.environment ( event_def, path) VALUES
2827     ( 10, 'target_copy'),
2828     ( 10, 'circ_lib.billing_address'),
2829     ( 10, 'circ_lib.hours_of_operation'),
2830     ( 10, 'usr');
2831
2832 INSERT INTO action_trigger.hook (key, core_type, description, passive) 
2833     VALUES (
2834         'format.selfcheck.items_out',
2835         'circ',
2836         'Formats items out for self-checkout receipt',
2837         TRUE
2838     );
2839
2840 INSERT INTO action_trigger.event_definition (id, active, owner, name, hook, validator, reactor, group_field, granularity, template )
2841     VALUES (
2842         11,
2843         TRUE,
2844         1,
2845         'Self-Checkout Items Out Receipt',
2846         'format.selfcheck.items_out',
2847         'NOOP_True',
2848         'ProcessTemplate',
2849         'usr',
2850         'print-on-demand',
2851 $$
2852 [%- USE date -%]
2853 [%- SET user = target.0.usr -%]
2854 <div>
2855     <style> li { padding: 8px; margin 5px; }</style>
2856     <div>[% date.format %]</div>
2857     <br/>
2858
2859     [% user.family_name %], [% user.first_given_name %]
2860     <ol>
2861     [% FOR circ IN target %]
2862         <li>
2863             <div>[% helpers.get_copy_bib_basics(circ.target_copy.id).title %]</div>
2864             <div>Barcode: [% circ.target_copy.barcode %]</div>
2865             <div>Due Date: [% date.format(helpers.format_date(circ.due_date), '%Y-%m-%d') %]</div>
2866         </li>
2867     [% END %]
2868     </ol>
2869 </div>
2870 $$
2871 );
2872
2873
2874 INSERT INTO action_trigger.environment ( event_def, path) VALUES
2875     ( 11, 'target_copy'),
2876     ( 11, 'circ_lib.billing_address'),
2877     ( 11, 'circ_lib.hours_of_operation'),
2878     ( 11, 'usr');
2879
2880 INSERT INTO action_trigger.hook (key, core_type, description, passive) 
2881     VALUES (
2882         'format.selfcheck.holds',
2883         'ahr',
2884         'Formats holds for self-checkout receipt',
2885         TRUE
2886     );
2887
2888 INSERT INTO action_trigger.event_definition (id, active, owner, name, hook, validator, reactor, group_field, granularity, template )
2889     VALUES (
2890         12,
2891         TRUE,
2892         1,
2893         'Self-Checkout Holds Receipt',
2894         'format.selfcheck.holds',
2895         'NOOP_True',
2896         'ProcessTemplate',
2897         'usr',
2898         'print-on-demand',
2899 $$
2900 [%- USE date -%]
2901 [%- SET user = target.0.usr -%]
2902 <div>
2903     <style> li { padding: 8px; margin 5px; }</style>
2904     <div>[% date.format %]</div>
2905     <br/>
2906
2907     [% user.family_name %], [% user.first_given_name %]
2908     <ol>
2909     [% FOR hold IN target %]
2910         [%-
2911             SET idx = loop.count - 1;
2912             SET udata =  user_data.$idx
2913         -%]
2914         <li>
2915             <div>Title: [% hold.bib_rec.bib_record.simple_record.title %]</div>
2916             <div>Author: [% hold.bib_rec.bib_record.simple_record.author %]</div>
2917             <div>Pickup Location: [% hold.pickup_lib.name %]</div>
2918             <div>Status: 
2919                 [%- IF udata.ready -%]
2920                     Ready for pickup
2921                 [% ELSE %]
2922                     #[% udata.queue_position %] of [% udata.potential_copies %] copies.
2923                 [% END %]
2924             </div>
2925         </li>
2926     [% END %]
2927     </ol>
2928 </div>
2929 $$
2930 );
2931
2932
2933 INSERT INTO action_trigger.environment ( event_def, path) VALUES
2934     ( 12, 'bib_rec.bib_record.simple_record'),
2935     ( 12, 'pickup_lib'),
2936     ( 12, 'usr');
2937
2938 INSERT INTO action_trigger.hook (key, core_type, description, passive) 
2939     VALUES (
2940         'format.selfcheck.fines',
2941         'au',
2942         'Formats fines for self-checkout receipt',
2943         TRUE
2944     );
2945
2946 INSERT INTO action_trigger.event_definition (id, active, owner, name, hook, validator, reactor, granularity, template )
2947     VALUES (
2948         13,
2949         TRUE,
2950         1,
2951         'Self-Checkout Fines Receipt',
2952         'format.selfcheck.fines',
2953         'NOOP_True',
2954         'ProcessTemplate',
2955         'print-on-demand',
2956 $$
2957 [%- USE date -%]
2958 [%- SET user = target -%]
2959 <div>
2960     <style> li { padding: 8px; margin 5px; }</style>
2961     <div>[% date.format %]</div>
2962     <br/>
2963
2964     [% user.family_name %], [% user.first_given_name %]
2965     <ol>
2966     [% FOR xact IN user.open_billable_transactions_summary %]
2967         <li>
2968             <div>Details: 
2969                 [% IF xact.xact_type == 'circulation' %]
2970                     [%- helpers.get_copy_bib_basics(xact.circulation.target_copy).title -%]
2971                 [% ELSE %]
2972                     [%- xact.last_billing_type -%]
2973                 [% END %]
2974             </div>
2975             <div>Total Billed: [% xact.total_owed %]</div>
2976             <div>Total Paid: [% xact.total_paid %]</div>
2977             <div>Balance Owed : [% xact.balance_owed %]</div>
2978         </li>
2979     [% END %]
2980     </ol>
2981 </div>
2982 $$
2983 );
2984
2985 INSERT INTO action_trigger.environment ( event_def, path) VALUES
2986     ( 13, 'open_billable_transactions_summary.circulation' );
2987
2988 INSERT INTO action_trigger.reactor (module,description) VALUES
2989 (   'SendFile',
2990     oils_i18n_gettext(
2991         'SendFile',
2992         'Build and transfer a file to a remote server.  Required parameter "remote_host" specifying target server.  Optional parameters: remote_user, remote_password, remote_account, port, type (FTP, SFTP or SCP), and debug.',
2993         'atreact',
2994         'description'
2995     )
2996 );
2997
2998 INSERT INTO action_trigger.hook (key, core_type, description, passive) 
2999     VALUES (
3000         'format.acqli.html',
3001         'jub',
3002         'Formats lineitem worksheet for titles received',
3003         TRUE
3004     );
3005
3006 INSERT INTO action_trigger.event_definition (id, active, owner, name, hook, validator, reactor, granularity, template)
3007     VALUES (
3008         14,
3009         TRUE,
3010         1,
3011         'Lineitem Worksheet',
3012         'format.acqli.html',
3013         'NOOP_True',
3014         'ProcessTemplate',
3015         'print-on-demand',
3016 $$
3017 [%- USE date -%]
3018 [%- SET li = target; -%]
3019 <div class="wrapper">
3020     <div class="summary" style='font-size:110%; font-weight:bold;'>
3021
3022         <div>Title: [% helpers.get_li_attr("title", "", li.attributes) %]</div>
3023         <div>Author: [% helpers.get_li_attr("author", "", li.attributes) %]</div>
3024         <div class="count">Item Count: [% li.lineitem_details.size %]</div>
3025         <div class="lineid">Lineitem ID: [% li.id %]</div>
3026
3027         [% IF li.distribution_formulas.size > 0 %]
3028             [% SET forms = [] %]
3029             [% FOREACH form IN li.distribution_formulas; forms.push(form.formula.name); END %]
3030             <div>Distribution Formulas: [% forms.join(',') %]</div>
3031         [% END %]
3032
3033         [% IF li.lineitem_notes.size > 0 %]
3034             Lineitem Notes:
3035             <ul>
3036                 [%- FOR note IN li.lineitem_notes -%]
3037                     <li>
3038                     [% IF note.alert_text %]
3039                         [% note.alert_text.code -%] 
3040                         [% IF note.value -%]
3041                             : [% note.value %]
3042                         [% END %]
3043                     [% ELSE %]
3044                         [% note.value -%] 
3045                     [% END %]
3046                     </li>
3047                 [% END %]
3048             </ul>
3049         [% END %]
3050     </div>
3051     <br/>
3052     <table>
3053         <thead>
3054             <tr>
3055                 <th>Branch</th>
3056                 <th>Barcode</th>
3057                 <th>Call Number</th>
3058                 <th>Fund</th>
3059                 <th>Recd.</th>
3060                 <th>Notes</th>
3061             </tr>
3062         </thead>
3063         <tbody>
3064         [% FOREACH detail IN li.lineitem_details.sort('owning_lib') %]
3065             [% 
3066                 IF copy.eg_copy_id;
3067                     SET copy = copy.eg_copy_id;
3068                     SET cn_label = copy.call_number.label;
3069                 ELSE; 
3070                     SET copy = detail; 
3071                     SET cn_label = detail.cn_label;
3072                 END 
3073             %]
3074             <tr>
3075                 <!-- acq.lineitem_detail.id = [%- detail.id -%] -->
3076                 <td style='padding:5px;'>[% detail.owning_lib.shortname %]</td>
3077                 <td style='padding:5px;'>[% IF copy.barcode   %]<span class="barcode"  >[% detail.barcode   %]</span>[% END %]</td>
3078                 <td style='padding:5px;'>[% IF cn_label %]<span class="cn_label" >[% cn_label  %]</span>[% END %]</td>
3079                 <td style='padding:5px;'>[% IF detail.fund %]<span class="fund">[% detail.fund.code %] ([% detail.fund.year %])</span>[% END %]</td>
3080                 <td style='padding:5px;'>[% IF detail.recv_time %]<span class="recv_time">[% detail.recv_time %]</span>[% END %]</td>
3081                 <td style='padding:5px;'>[% detail.note %]</td>
3082             </tr>
3083         [% END %]
3084         </tbody>
3085     </table>
3086 </div>
3087 $$
3088 );
3089
3090
3091 INSERT INTO action_trigger.environment (event_def, path) VALUES
3092     ( 14, 'attributes' ),
3093     ( 14, 'lineitem_details' ),
3094     ( 14, 'lineitem_details.owning_lib' ),
3095     ( 14, 'lineitem_notes' )
3096 ;
3097
3098 INSERT INTO action_trigger.hook (key,core_type,description,passive) VALUES (
3099         'aur.ordered',
3100         'aur', 
3101         oils_i18n_gettext(
3102             'aur.ordered',
3103             'A patron acquisition request has been marked On-Order.',
3104             'ath',
3105             'description'
3106         ), 
3107         TRUE
3108     ), (
3109         'aur.received', 
3110         'aur', 
3111         oils_i18n_gettext(
3112             'aur.received', 
3113             'A patron acquisition request has been marked Received.',
3114             'ath',
3115             'description'
3116         ),
3117         TRUE
3118     ), (
3119         'aur.cancelled',
3120         'aur',
3121         oils_i18n_gettext(
3122             'aur.cancelled',
3123             'A patron acquisition request has been marked Cancelled.',
3124             'ath',
3125             'description'
3126         ),
3127         TRUE
3128     )
3129 ;
3130
3131 INSERT INTO action_trigger.validator (module,description) VALUES (
3132         'Acq::UserRequestOrdered',
3133         oils_i18n_gettext(
3134             'Acq::UserRequestOrdered',
3135             'Tests to see if the corresponding Line Item has a state of "on-order".',
3136             'atval',
3137             'description'
3138         )
3139     ), (
3140         'Acq::UserRequestReceived',
3141         oils_i18n_gettext(
3142             'Acq::UserRequestReceived',
3143             'Tests to see if the corresponding Line Item has a state of "received".',
3144             'atval',
3145             'description'
3146         )
3147     ), (
3148         'Acq::UserRequestCancelled',
3149         oils_i18n_gettext(
3150             'Acq::UserRequestCancelled',
3151             'Tests to see if the corresponding Line Item has a state of "cancelled".',
3152             'atval',
3153             'description'
3154         )
3155     )
3156 ;
3157
3158 -- What was event_definition #15 in v1.6.1 will be recreated as #20.  This
3159 -- renumbering requires some juggling:
3160 --
3161 -- 1. Update any child rows to point to #20.  These updates will temporarily
3162 -- violate foreign key constraints, but that's okay as long as we create
3163 -- #20 before committing.
3164 --
3165 -- 2. Delete the old #15.
3166 --
3167 -- 3. Insert the new #15.
3168 --
3169 -- 4. Insert #20.
3170 --
3171 -- We could combine steps 2 and 3 into a single update, but that would create
3172 -- additional opportunities for typos, since we already have the insert from
3173 -- an upgrade script.
3174
3175 UPDATE action_trigger.environment
3176 SET event_def = 20
3177 WHERE event_def = 15;
3178
3179 UPDATE action_trigger.event
3180 SET event_def = 20
3181 WHERE event_def = 15;
3182
3183 UPDATE action_trigger.event_params
3184 SET event_def = 20
3185 WHERE event_def = 15;
3186
3187 DELETE FROM action_trigger.event_definition
3188 WHERE id = 15;
3189
3190 INSERT INTO action_trigger.event_definition (
3191         id,
3192         active,
3193         owner,
3194         name,
3195         hook,
3196         validator,
3197         reactor,
3198         template
3199     ) VALUES (
3200         15,
3201         FALSE,
3202         1,
3203         'Email Notice: Patron Acquisition Request marked On-Order.',
3204         'aur.ordered',
3205         'Acq::UserRequestOrdered',
3206         'SendEmail',
3207 $$
3208 [%- USE date -%]
3209 [%- SET li = target.lineitem; -%]
3210 [%- SET user = target.usr -%]
3211 [%- SET title = helpers.get_li_attr("title", "", li.attributes) -%]
3212 [%- SET author = helpers.get_li_attr("author", "", li.attributes) -%]
3213 [%- SET edition = helpers.get_li_attr("edition", "", li.attributes) -%]
3214 [%- SET isbn = helpers.get_li_attr("isbn", "", li.attributes) -%]
3215 [%- SET publisher = helpers.get_li_attr("publisher", "", li.attributes) -%]
3216 [%- SET pubdate = helpers.get_li_attr("pubdate", "", li.attributes) -%]
3217
3218 To: [%- params.recipient_email || user.email %]
3219 From: [%- params.sender_email || default_sender %]
3220 Subject: Acquisition Request Notification
3221
3222 Dear [% user.family_name %], [% user.first_given_name %]
3223 Our records indicate the following acquisition request has been placed on order.
3224
3225 Title: [% title %]
3226 [% IF author %]Author: [% author %][% END %]
3227 [% IF edition %]Edition: [% edition %][% END %]
3228 [% IF isbn %]ISBN: [% isbn %][% END %]
3229 [% IF publisher %]Publisher: [% publisher %][% END %]
3230 [% IF pubdate %]Publication Date: [% pubdate %][% END %]
3231 Lineitem ID: [% li.id %]
3232 $$
3233     ), (
3234         16,
3235         FALSE,
3236         1,
3237         'Email Notice: Patron Acquisition Request marked Received.',
3238         'aur.received',
3239         'Acq::UserRequestReceived',
3240         'SendEmail',
3241 $$
3242 [%- USE date -%]
3243 [%- SET li = target.lineitem; -%]
3244 [%- SET user = target.usr -%]
3245 [%- SET title = helpers.get_li_attr("title", "", li.attributes) %]
3246 [%- SET author = helpers.get_li_attr("author", "", li.attributes) %]
3247 [%- SET edition = helpers.get_li_attr("edition", "", li.attributes) %]
3248 [%- SET isbn = helpers.get_li_attr("isbn", "", li.attributes) %]
3249 [%- SET publisher = helpers.get_li_attr("publisher", "", li.attributes) -%]
3250 [%- SET pubdate = helpers.get_li_attr("pubdate", "", li.attributes) -%]
3251
3252 To: [%- params.recipient_email || user.email %]
3253 From: [%- params.sender_email || default_sender %]
3254 Subject: Acquisition Request Notification
3255
3256 Dear [% user.family_name %], [% user.first_given_name %]
3257 Our records indicate the materials for the following acquisition request have been received.
3258
3259 Title: [% title %]
3260 [% IF author %]Author: [% author %][% END %]
3261 [% IF edition %]Edition: [% edition %][% END %]
3262 [% IF isbn %]ISBN: [% isbn %][% END %]
3263 [% IF publisher %]Publisher: [% publisher %][% END %]
3264 [% IF pubdate %]Publication Date: [% pubdate %][% END %]
3265 Lineitem ID: [% li.id %]
3266 $$
3267     ), (
3268         17,
3269         FALSE,
3270         1,
3271         'Email Notice: Patron Acquisition Request marked Cancelled.',
3272         'aur.cancelled',
3273         'Acq::UserRequestCancelled',
3274         'SendEmail',
3275 $$
3276 [%- USE date -%]
3277 [%- SET li = target.lineitem; -%]
3278 [%- SET user = target.usr -%]
3279 [%- SET title = helpers.get_li_attr("title", "", li.attributes) %]
3280 [%- SET author = helpers.get_li_attr("author", "", li.attributes) %]
3281 [%- SET edition = helpers.get_li_attr("edition", "", li.attributes) %]
3282 [%- SET isbn = helpers.get_li_attr("isbn", "", li.attributes) %]
3283 [%- SET publisher = helpers.get_li_attr("publisher", "", li.attributes) -%]
3284 [%- SET pubdate = helpers.get_li_attr("pubdate", "", li.attributes) -%]
3285
3286 To: [%- params.recipient_email || user.email %]
3287 From: [%- params.sender_email || default_sender %]
3288 Subject: Acquisition Request Notification
3289
3290 Dear [% user.family_name %], [% user.first_given_name %]
3291 Our records indicate the following acquisition request has been cancelled.
3292
3293 Title: [% title %]
3294 [% IF author %]Author: [% author %][% END %]
3295 [% IF edition %]Edition: [% edition %][% END %]
3296 [% IF isbn %]ISBN: [% isbn %][% END %]
3297 [% IF publisher %]Publisher: [% publisher %][% END %]
3298 [% IF pubdate %]Publication Date: [% pubdate %][% END %]
3299 Lineitem ID: [% li.id %]
3300 $$
3301     );
3302
3303 INSERT INTO action_trigger.environment (
3304         event_def,
3305         path
3306     ) VALUES 
3307         ( 15, 'lineitem' ),
3308         ( 15, 'lineitem.attributes' ),
3309         ( 15, 'usr' ),
3310
3311         ( 16, 'lineitem' ),
3312         ( 16, 'lineitem.attributes' ),
3313         ( 16, 'usr' ),
3314
3315         ( 17, 'lineitem' ),
3316         ( 17, 'lineitem.attributes' ),
3317         ( 17, 'usr' )
3318     ;
3319
3320 INSERT INTO action_trigger.event_definition
3321 (id, active, owner, name, hook, validator, reactor, cleanup_success, cleanup_failure, delay, delay_field, group_field, template) VALUES
3322 (23, true, 1, 'PO JEDI', 'acqpo.activated', 'Acq::PurchaseOrderEDIRequired', 'GeneratePurchaseOrderJEDI', NULL, NULL, '00:05:00', NULL, NULL,
3323 $$
3324 [%- USE date -%]
3325 [%# start JEDI document 
3326   # Vendor specific kludges:
3327   # BT      - vendcode goes to NAD/BY *suffix*  w/ 91 qualifier
3328   # INGRAM  - vendcode goes to NAD/BY *segment* w/ 91 qualifier (separately)
3329   # BRODART - vendcode goes to FTX segment (lineitem level)
3330 -%]
3331 [%- 
3332 IF target.provider.edi_default.vendcode && target.provider.code == 'BRODART';
3333     xtra_ftx = target.provider.edi_default.vendcode;
3334 END;
3335 -%]
3336 [%- BLOCK big_block -%]
3337 {
3338    "recipient":"[% target.provider.san %]",
3339    "sender":"[% target.ordering_agency.mailing_address.san %]",
3340    "body": [{
3341      "ORDERS":[ "order", {
3342         "po_number":[% target.id %],
3343         "date":"[% date.format(date.now, '%Y%m%d') %]",
3344         "buyer":[
3345             [%   IF   target.provider.edi_default.vendcode && (target.provider.code == 'BT' || target.provider.name.match('(?i)^BAKER & TAYLOR'))  -%]
3346                 {"id-qualifier": 91, "id":"[% target.ordering_agency.mailing_address.san _ ' ' _ target.provider.edi_default.vendcode %]"}
3347             [%- ELSIF target.provider.edi_default.vendcode && target.provider.code == 'INGRAM' -%]
3348                 {"id":"[% target.ordering_agency.mailing_address.san %]"},
3349                 {"id-qualifier": 91, "id":"[% target.provider.edi_default.vendcode %]"}
3350             [%- ELSE -%]
3351                 {"id":"[% target.ordering_agency.mailing_address.san %]"}
3352             [%- END -%]
3353         ],
3354         "vendor":[
3355             [%- # target.provider.name (target.provider.id) -%]
3356             "[% target.provider.san %]",
3357             {"id-qualifier": 92, "id":"[% target.provider.id %]"}
3358         ],
3359         "currency":"[% target.provider.currency_type %]",
3360                 
3361         "items":[
3362         [%- FOR li IN target.lineitems %]
3363         {
3364             "line_index":"[% li.id %]",
3365             "identifiers":[   [%-# li.isbns = helpers.get_li_isbns(li.attributes) %]
3366             [% FOR isbn IN helpers.get_li_isbns(li.attributes) -%]
3367                 [% IF isbn.length == 13 -%]
3368                 {"id-qualifier":"EN","id":"[% isbn %]"},
3369                 [% ELSE -%]
3370                 {"id-qualifier":"IB","id":"[% isbn %]"},
3371                 [%- END %]
3372             [% END %]
3373                 {"id-qualifier":"IN","id":"[% li.id %]"}
3374             ],
3375             "price":[% li.estimated_unit_price || '0.00' %],
3376             "desc":[
3377                 {"BTI":"[% helpers.get_li_attr('title',     '', li.attributes) %]"},
3378                 {"BPU":"[% helpers.get_li_attr('publisher', '', li.attributes) %]"},
3379                 {"BPD":"[% helpers.get_li_attr('pubdate',   '', li.attributes) %]"},
3380                 {"BPH":"[% helpers.get_li_attr('pagination','', li.attributes) %]"}
3381             ],
3382             [%- ftx_vals = []; 
3383                 FOR note IN li.lineitem_notes; 
3384                     NEXT UNLESS note.vendor_public == 't'; 
3385                     ftx_vals.push(note.value); 
3386                 END; 
3387                 IF xtra_ftx;           ftx_vals.unshift(xtra_ftx); END; 
3388                 IF ftx_vals.size == 0; ftx_vals.unshift('');       END;  # BT needs FTX+LIN for every LI, even if it is an empty one
3389             -%]
3390
3391             "free-text":[ 
3392                 [% FOR note IN ftx_vals -%] "[% note %]"[% UNLESS loop.last %], [% END %][% END %] 
3393             ],            
3394             "quantity":[% li.lineitem_details.size %]
3395         }[% UNLESS loop.last %],[% END %]
3396         [%-# TODO: lineitem details (later) -%]
3397         [% END %]
3398         ],
3399         "line_items":[% target.lineitems.size %]
3400      }]  [%# close ORDERS array %]
3401    }]    [%# close  body  array %]
3402 }
3403 [% END %]
3404 [% tempo = PROCESS big_block; helpers.escape_json(tempo) %]
3405
3406 $$);
3407
3408 INSERT INTO action_trigger.environment (event_def, path) VALUES 
3409   (23, 'lineitems.attributes'), 
3410   (23, 'lineitems.lineitem_details'), 
3411   (23, 'lineitems.lineitem_notes'), 
3412   (23, 'ordering_agency.mailing_address'), 
3413   (23, 'provider');
3414
3415 UPDATE action_trigger.event_definition SET template = 
3416 $$
3417 [%- USE date -%]
3418 [%-
3419     # find a lineitem attribute by name and optional type
3420     BLOCK get_li_attr;
3421         FOR attr IN li.attributes;
3422             IF attr.attr_name == attr_name;
3423                 IF !attr_type OR attr_type == attr.attr_type;
3424                     attr.attr_value;
3425                     LAST;
3426                 END;
3427             END;
3428         END;
3429     END
3430 -%]
3431
3432 <h2>Purchase Order [% target.id %]</h2>
3433 <br/>
3434 date <b>[% date.format(date.now, '%Y%m%d') %]</b>
3435 <br/>
3436
3437 <style>
3438     table td { padding:5px; border:1px solid #aaa;}
3439     table { width:95%; border-collapse:collapse; }
3440     #vendor-notes { padding:5px; border:1px solid #aaa; }
3441 </style>
3442 <table id='vendor-table'>
3443   <tr>
3444     <td valign='top'>Vendor</td>
3445     <td>
3446       <div>[% target.provider.name %]</div>
3447       <div>[% target.provider.addresses.0.street1 %]</div>
3448       <div>[% target.provider.addresses.0.street2 %]</div>
3449       <div>[% target.provider.addresses.0.city %]</div>
3450       <div>[% target.provider.addresses.0.state %]</div>
3451       <div>[% target.provider.addresses.0.country %]</div>
3452       <div>[% target.provider.addresses.0.post_code %]</div>
3453     </td>
3454     <td valign='top'>Ship to / Bill to</td>
3455     <td>
3456       <div>[% target.ordering_agency.name %]</div>
3457       <div>[% target.ordering_agency.billing_address.street1 %]</div>
3458       <div>[% target.ordering_agency.billing_address.street2 %]</div>
3459       <div>[% target.ordering_agency.billing_address.city %]</div>
3460       <div>[% target.ordering_agency.billing_address.state %]</div>
3461       <div>[% target.ordering_agency.billing_address.country %]</div>
3462       <div>[% target.ordering_agency.billing_address.post_code %]</div>
3463     </td>
3464   </tr>
3465 </table>
3466
3467 <br/><br/>
3468 <fieldset id='vendor-notes'>
3469     <legend>Notes to the Vendor</legend>
3470     <ul>
3471     [% FOR note IN target.notes %]
3472         [% IF note.vendor_public == 't' %]
3473             <li>[% note.value %]</li>
3474         [% END %]
3475     [% END %]
3476     </ul>
3477 </fieldset>
3478 <br/><br/>
3479
3480 <table>
3481   <thead>
3482     <tr>
3483       <th>PO#</th>
3484       <th>ISBN or Item #</th>
3485       <th>Title</th>
3486       <th>Quantity</th>
3487       <th>Unit Price</th>
3488       <th>Line Total</th>
3489       <th>Notes</th>
3490     </tr>
3491   </thead>
3492   <tbody>
3493
3494   [% subtotal = 0 %]
3495   [% FOR li IN target.lineitems %]
3496
3497   <tr>
3498     [% count = li.lineitem_details.size %]
3499     [% price = li.estimated_unit_price %]
3500     [% litotal = (price * count) %]
3501     [% subtotal = subtotal + litotal %]
3502     [% isbn = PROCESS get_li_attr attr_name = 'isbn' %]
3503     [% ident = PROCESS get_li_attr attr_name = 'identifier' %]
3504
3505     <td>[% target.id %]</td>
3506     <td>[% isbn || ident %]</td>
3507     <td>[% PROCESS get_li_attr attr_name = 'title' %]</td>
3508     <td>[% count %]</td>
3509     <td>[% price %]</td>
3510     <td>[% litotal %]</td>
3511     <td>
3512         <ul>
3513         [% FOR note IN li.lineitem_notes %]
3514             [% IF note.vendor_public == 't' %]
3515                 <li>[% note.value %]</li>
3516             [% END %]
3517         [% END %]
3518         </ul>
3519     </td>
3520   </tr>
3521   [% END %]
3522   <tr>
3523     <td/><td/><td/><td/>
3524     <td>Subtotal</td>
3525     <td>[% subtotal %]</td>
3526   </tr>
3527   </tbody>
3528 </table>
3529
3530 <br/>
3531
3532 Total Line Item Count: [% target.lineitems.size %]
3533 $$
3534 WHERE id = 4;
3535
3536 INSERT INTO action_trigger.environment (event_def, path) VALUES 
3537     (4, 'lineitems.lineitem_notes'),
3538     (4, 'notes');
3539
3540 INSERT INTO action_trigger.environment (event_def, path) VALUES
3541     ( 14, 'lineitem_notes.alert_text' ),
3542     ( 14, 'distribution_formulas.formula' ),
3543     ( 14, 'lineitem_details.fund' ),
3544     ( 14, 'lineitem_details.eg_copy_id' ),
3545     ( 14, 'lineitem_details.eg_copy_id.call_number' )
3546 ;
3547
3548 INSERT INTO action_trigger.hook (key,core_type,description,passive) VALUES (
3549         'aur.created',
3550         'aur',
3551         oils_i18n_gettext(
3552             'aur.created',
3553             'A patron has made an acquisitions request.',
3554             'ath',
3555             'description'
3556         ),
3557         TRUE
3558     ), (
3559         'aur.rejected',
3560         'aur',
3561         oils_i18n_gettext(
3562             'aur.rejected',
3563             'A patron acquisition request has been rejected.',
3564             'ath',
3565             'description'
3566         ),
3567         TRUE
3568     )
3569 ;
3570
3571 INSERT INTO action_trigger.event_definition (
3572         id,
3573         active,
3574         owner,
3575         name,
3576         hook,
3577         validator,
3578         reactor,
3579         template
3580     ) VALUES (
3581         18,
3582         FALSE,
3583         1,
3584         'Email Notice: Acquisition Request created.',
3585         'aur.created',
3586         'NOOP_True',
3587         'SendEmail',
3588 $$
3589 [%- USE date -%]
3590 [%- SET user = target.usr -%]
3591 [%- SET title = target.title -%]
3592 [%- SET author = target.author -%]
3593 [%- SET isxn = target.isxn -%]
3594 [%- SET publisher = target.publisher -%]
3595 [%- SET pubdate = target.pubdate -%]
3596
3597 To: [%- params.recipient_email || user.email %]
3598 From: [%- params.sender_email || default_sender %]
3599 Subject: Acquisition Request Notification
3600
3601 Dear [% user.family_name %], [% user.first_given_name %]
3602 Our records indicate that you have made the following acquisition request:
3603
3604 Title: [% title %]
3605 [% IF author %]Author: [% author %][% END %]
3606 [% IF edition %]Edition: [% edition %][% END %]
3607 [% IF isbn %]ISXN: [% isxn %][% END %]
3608 [% IF publisher %]Publisher: [% publisher %][% END %]
3609 [% IF pubdate %]Publication Date: [% pubdate %][% END %]
3610 $$
3611     ), (
3612         19,
3613         FALSE,
3614         1,
3615         'Email Notice: Acquisition Request Rejected.',
3616         'aur.rejected',
3617         'NOOP_True',
3618         'SendEmail',
3619 $$
3620 [%- USE date -%]
3621 [%- SET user = target.usr -%]
3622 [%- SET title = target.title -%]
3623 [%- SET author = target.author -%]
3624 [%- SET isxn = target.isxn -%]
3625 [%- SET publisher = target.publisher -%]
3626 [%- SET pubdate = target.pubdate -%]
3627 [%- SET cancel_reason = target.cancel_reason.description -%]
3628
3629 To: [%- params.recipient_email || user.email %]
3630 From: [%- params.sender_email || default_sender %]
3631 Subject: Acquisition Request Notification
3632
3633 Dear [% user.family_name %], [% user.first_given_name %]
3634 Our records indicate the following acquisition request has been rejected for this reason: [% cancel_reason %]
3635
3636 Title: [% title %]
3637 [% IF author %]Author: [% author %][% END %]
3638 [% IF edition %]Edition: [% edition %][% END %]
3639 [% IF isbn %]ISBN: [% isbn %][% END %]
3640 [% IF publisher %]Publisher: [% publisher %][% END %]
3641 [% IF pubdate %]Publication Date: [% pubdate %][% END %]
3642 $$
3643     );
3644
3645 INSERT INTO action_trigger.environment (
3646         event_def,
3647         path
3648     ) VALUES 
3649         ( 18, 'usr' ),
3650         ( 19, 'usr' ),
3651         ( 19, 'cancel_reason' )
3652     ;
3653
3654 INSERT INTO action_trigger.hook (key, core_type, description, passive)
3655     VALUES (
3656         'format.acqcle.html',
3657         'acqcle',
3658         'Formats claim events into a voucher',
3659         TRUE
3660     );
3661
3662 INSERT INTO action_trigger.event_definition (
3663         id, active, owner, name, hook, group_field,
3664         validator, reactor, granularity, template
3665     ) VALUES (
3666         21,
3667         TRUE,
3668         1,
3669         'Claim Voucher',
3670         'format.acqcle.html',
3671         'claim',
3672         'NOOP_True',
3673         'ProcessTemplate',
3674         'print-on-demand',
3675 $$
3676 [%- USE date -%]
3677 [%- SET claim = target.0.claim -%]
3678 <!-- This will need refined/prettified. -->
3679 <div class="acq-claim-voucher">
3680     <h2>Claim: [% claim.id %] ([% claim.type.code %])</h2>
3681     <h3>Against: [%- helpers.get_li_attr("title", "", claim.lineitem_detail.lineitem.attributes) -%]</h3>
3682     <ul>
3683         [% FOR event IN target %]
3684         <li>
3685             Event type: [% event.type.code %]
3686             [% IF event.type.library_initiated %](Library initiated)[% END %]
3687             <br />
3688             Event date: [% event.event_date %]<br />
3689             Order date: [% event.claim.lineitem_detail.lineitem.purchase_order.order_date %]<br />
3690             Expected receive date: [% event.claim.lineitem_detail.lineitem.expected_recv_time %]<br />
3691             Initiated by: [% event.creator.family_name %], [% event.creator.first_given_name %] [% event.creator.second_given_name %]<br />
3692             Barcode: [% event.claim.lineitem_detail.barcode %]; Fund:
3693             [% event.claim.lineitem_detail.fund.code %]
3694             ([% event.claim.lineitem_detail.fund.year %])
3695         </li>
3696         [% END %]
3697     </ul>
3698 </div>
3699 $$
3700 );
3701
3702 INSERT INTO action_trigger.environment (event_def, path) VALUES
3703     (21, 'claim'),
3704     (21, 'claim.type'),
3705     (21, 'claim.lineitem_detail'),
3706     (21, 'claim.lineitem_detail.fund'),
3707     (21, 'claim.lineitem_detail.lineitem.attributes'),
3708     (21, 'claim.lineitem_detail.lineitem.purchase_order'),
3709     (21, 'creator'),
3710     (21, 'type')
3711 ;
3712
3713 INSERT INTO action_trigger.hook (key, core_type, description, passive)
3714     VALUES (
3715         'format.acqinv.html',
3716         'acqinv',
3717         'Formats invoices into a voucher',
3718         TRUE
3719     );
3720
3721 INSERT INTO action_trigger.event_definition (
3722         id, active, owner, name, hook,
3723         validator, reactor, granularity, template
3724     ) VALUES (
3725         22,
3726         TRUE,
3727         1,
3728         'Invoice',
3729         'format.acqinv.html',
3730         'NOOP_True',
3731         'ProcessTemplate',
3732         'print-on-demand',
3733 $$
3734 [% FILTER collapse %]
3735 [%- SET invoice = target -%]
3736 <!-- This lacks totals, info about funds (for invoice entries,
3737     funds are per-LID!), and general refinement -->
3738 <div class="acq-invoice-voucher">
3739     <h1>Invoice</h1>
3740     <div>
3741         <strong>No.</strong> [% invoice.inv_ident %]
3742         [% IF invoice.inv_type %]
3743             / <strong>Type:</strong>[% invoice.inv_type %]
3744         [% END %]
3745     </div>
3746     <div>
3747         <dl>
3748             [% BLOCK ent_with_address %]
3749             <dt>[% ent_label %]: [% ent.name %] ([% ent.code %])</dt>
3750             <dd>
3751                 [% IF ent.addresses.0 %]
3752                     [% SET addr = ent.addresses.0 %]
3753                     [% addr.street1 %]<br />
3754                     [% IF addr.street2 %][% addr.street2 %]<br />[% END %]
3755                     [% addr.city %],
3756                     [% IF addr.county %] [% addr.county %], [% END %]
3757                     [% IF addr.state %] [% addr.state %] [% END %]
3758                     [% IF addr.post_code %][% addr.post_code %][% END %]<br />
3759                     [% IF addr.country %] [% addr.country %] [% END %]
3760                 [% END %]
3761                 <p>
3762                     [% IF ent.phone %] Phone: [% ent.phone %]<br />[% END %]
3763                     [% IF ent.fax_phone %] Fax: [% ent.fax_phone %]<br />[% END %]
3764                     [% IF ent.url %] URL: [% ent.url %]<br />[% END %]
3765                     [% IF ent.email %] E-mail: [% ent.email %] [% END %]
3766                 </p>
3767             </dd>
3768             [% END %]
3769             [% INCLUDE ent_with_address
3770                 ent = invoice.provider
3771                 ent_label = "Provider" %]
3772             [% INCLUDE ent_with_address
3773                 ent = invoice.shipper
3774                 ent_label = "Shipper" %]
3775             <dt>Receiver</dt>
3776             <dd>
3777                 [% invoice.receiver.name %] ([% invoice.receiver.shortname %])
3778             </dd>
3779             <dt>Received</dt>
3780             <dd>
3781                 [% helpers.format_date(invoice.recv_date) %] by
3782                 [% invoice.recv_method %]
3783             </dd>
3784             [% IF invoice.note %]
3785                 <dt>Note</dt>
3786                 <dd>
3787                     [% invoice.note %]
3788                 </dd>
3789             [% END %]
3790         </dl>
3791     </div>
3792     <ul>
3793         [% FOR entry IN invoice.entries %]
3794             <li>
3795                 [% IF entry.lineitem %]
3796                     Title: [% helpers.get_li_attr(
3797                         "title", "", entry.lineitem.attributes
3798                     ) %]<br />
3799                     Author: [% helpers.get_li_attr(
3800                         "author", "", entry.lineitem.attributes
3801                     ) %]
3802                 [% END %]
3803                 [% IF entry.purchase_order %]
3804                     (PO: [% entry.purchase_order.name %])
3805                 [% END %]<br />
3806                 Invoice item count: [% entry.inv_item_count %]
3807                 [% IF entry.phys_item_count %]
3808                     / Physical item count: [% entry.phys_item_count %]
3809                 [% END %]
3810                 <br />
3811                 [% IF entry.cost_billed %]
3812                     Cost billed: [% entry.cost_billed %]
3813                     [% IF entry.billed_per_item %](per item)[% END %]
3814                     <br />
3815                 [% END %]
3816                 [% IF entry.actual_cost %]
3817                     Actual cost: [% entry.actual_cost %]<br />
3818                 [% END %]
3819                 [% IF entry.amount_paid %]
3820                     Amount paid: [% entry.amount_paid %]<br />
3821                 [% END %]
3822                 [% IF entry.note %]Note: [% entry.note %][% END %]
3823             </li>
3824         [% END %]
3825         [% FOR item IN invoice.items %]
3826             <li>
3827                 [% IF item.inv_item_type %]
3828                     Item Type: [% item.inv_item_type %]<br />
3829                 [% END %]
3830                 [% IF item.title %]Title/Description:
3831                     [% item.title %]<br />
3832                 [% END %]
3833                 [% IF item.author %]Author: [% item.author %]<br />[% END %]
3834                 [% IF item.purchase_order %]PO: [% item.purchase_order %]<br />[% END %]
3835                 [% IF item.note %]Note: [% item.note %]<br />[% END %]
3836                 [% IF item.cost_billed %]
3837                     Cost billed: [% item.cost_billed %]<br />
3838                 [% END %]
3839                 [% IF item.actual_cost %]
3840                     Actual cost: [% item.actual_cost %]<br />
3841                 [% END %]
3842                 [% IF item.amount_paid %]
3843                     Amount paid: [% item.amount_paid %]<br />
3844                 [% END %]
3845             </li>
3846         [% END %]
3847     </ul>
3848 </div>
3849 [% END %]
3850 $$
3851 );
3852
3853 UPDATE action_trigger.event_definition SET template = $$[% FILTER collapse %]
3854 [%- SET invoice = target -%]
3855 <!-- This lacks general refinement -->
3856 <div class="acq-invoice-voucher">
3857     <h1>Invoice</h1>
3858     <div>
3859         <strong>No.</strong> [% invoice.inv_ident %]
3860         [% IF invoice.inv_type %]
3861             / <strong>Type:</strong>[% invoice.inv_type %]
3862         [% END %]
3863     </div>
3864     <div>
3865         <dl>
3866             [% BLOCK ent_with_address %]
3867             <dt>[% ent_label %]: [% ent.name %] ([% ent.code %])</dt>
3868             <dd>
3869                 [% IF ent.addresses.0 %]
3870                     [% SET addr = ent.addresses.0 %]
3871                     [% addr.street1 %]<br />
3872                     [% IF addr.street2 %][% addr.street2 %]<br />[% END %]
3873                     [% addr.city %],
3874                     [% IF addr.county %] [% addr.county %], [% END %]
3875                     [% IF addr.state %] [% addr.state %] [% END %]
3876                     [% IF addr.post_code %][% addr.post_code %][% END %]<br />
3877                     [% IF addr.country %] [% addr.country %] [% END %]
3878                 [% END %]
3879                 <p>
3880                     [% IF ent.phone %] Phone: [% ent.phone %]<br />[% END %]
3881                     [% IF ent.fax_phone %] Fax: [% ent.fax_phone %]<br />[% END %]
3882                     [% IF ent.url %] URL: [% ent.url %]<br />[% END %]
3883                     [% IF ent.email %] E-mail: [% ent.email %] [% END %]
3884                 </p>
3885             </dd>
3886             [% END %]
3887             [% INCLUDE ent_with_address
3888                 ent = invoice.provider
3889                 ent_label = "Provider" %]
3890             [% INCLUDE ent_with_address
3891                 ent = invoice.shipper
3892                 ent_label = "Shipper" %]
3893             <dt>Receiver</dt>
3894             <dd>
3895                 [% invoice.receiver.name %] ([% invoice.receiver.shortname %])
3896             </dd>
3897             <dt>Received</dt>
3898             <dd>
3899                 [% helpers.format_date(invoice.recv_date) %] by
3900                 [% invoice.recv_method %]
3901             </dd>
3902             [% IF invoice.note %]
3903                 <dt>Note</dt>
3904                 <dd>
3905                     [% invoice.note %]
3906                 </dd>
3907             [% END %]
3908         </dl>
3909     </div>
3910     <ul>
3911         [% FOR entry IN invoice.entries %]
3912             <li>
3913                 [% IF entry.lineitem %]
3914                     Title: [% helpers.get_li_attr(
3915                         "title", "", entry.lineitem.attributes
3916                     ) %]<br />
3917                     Author: [% helpers.get_li_attr(
3918                         "author", "", entry.lineitem.attributes
3919                     ) %]
3920                 [% END %]
3921                 [% IF entry.purchase_order %]
3922                     (PO: [% entry.purchase_order.name %])
3923                 [% END %]<br />
3924                 Invoice item count: [% entry.inv_item_count %]
3925                 [% IF entry.phys_item_count %]
3926                     / Physical item count: [% entry.phys_item_count %]
3927                 [% END %]
3928                 <br />
3929                 [% IF entry.cost_billed %]
3930                     Cost billed: [% entry.cost_billed %]
3931                     [% IF entry.billed_per_item %](per item)[% END %]
3932                     <br />
3933                 [% END %]
3934                 [% IF entry.actual_cost %]
3935                     Actual cost: [% entry.actual_cost %]<br />
3936                 [% END %]
3937                 [% IF entry.amount_paid %]
3938                     Amount paid: [% entry.amount_paid %]<br />
3939                 [% END %]
3940                 [% IF entry.note %]Note: [% entry.note %][% END %]
3941             </li>
3942         [% END %]
3943         [% FOR item IN invoice.items %]
3944             <li>
3945                 [% IF item.inv_item_type %]
3946                     Item Type: [% item.inv_item_type %]<br />
3947                 [% END %]
3948                 [% IF item.title %]Title/Description:
3949                     [% item.title %]<br />
3950                 [% END %]
3951                 [% IF item.author %]Author: [% item.author %]<br />[% END %]
3952                 [% IF item.purchase_order %]PO: [% item.purchase_order %]<br />[% END %]
3953                 [% IF item.note %]Note: [% item.note %]<br />[% END %]
3954                 [% IF item.cost_billed %]
3955                     Cost billed: [% item.cost_billed %]<br />
3956                 [% END %]
3957                 [% IF item.actual_cost %]
3958                     Actual cost: [% item.actual_cost %]<br />
3959                 [% END %]
3960                 [% IF item.amount_paid %]
3961                     Amount paid: [% item.amount_paid %]<br />
3962                 [% END %]
3963             </li>
3964         [% END %]
3965     </ul>
3966     <div>
3967         Amounts spent per fund:
3968         <table>
3969         [% FOR blob IN user_data %]
3970             <tr>
3971                 <th style="text-align: left;">[% blob.fund.code %] ([% blob.fund.year %]):</th>
3972                 <td>$[% blob.total %]</td>
3973             </tr>
3974         [% END %]
3975         </table>
3976     </div>
3977 </div>
3978 [% END %]$$ WHERE id = 22;
3979 INSERT INTO action_trigger.environment (event_def, path) VALUES
3980     (22, 'provider'),
3981     (22, 'provider.addresses'),
3982     (22, 'shipper'),
3983     (22, 'shipper.addresses'),
3984     (22, 'receiver'),
3985     (22, 'entries'),
3986     (22, 'entries.purchase_order'),
3987     (22, 'entries.lineitem'),
3988     (22, 'entries.lineitem.attributes'),
3989     (22, 'items')
3990 ;
3991
3992 INSERT INTO action_trigger.environment (event_def, path) VALUES 
3993   (23, 'provider.edi_default');
3994
3995 INSERT INTO action_trigger.validator (module, description) 
3996     VALUES (
3997         'Acq::PurchaseOrderEDIRequired',
3998         oils_i18n_gettext(
3999             'Acq::PurchaseOrderEDIRequired',
4000             'Purchase order is delivered via EDI',
4001             'atval',
4002             'description'
4003         )
4004     );
4005
4006 INSERT INTO action_trigger.reactor (module, description)
4007     VALUES (
4008         'GeneratePurchaseOrderJEDI',
4009         oils_i18n_gettext(
4010             'GeneratePurchaseOrderJEDI',
4011             'Creates purchase order JEDI (JSON EDI) for subsequent EDI processing',
4012             'atreact',
4013             'description'
4014         )
4015     );
4016
4017 UPDATE action_trigger.hook 
4018     SET 
4019         key = 'acqpo.activated', 
4020         passive = FALSE,
4021         description = oils_i18n_gettext(
4022             'acqpo.activated',
4023             'Purchase order was activated',
4024             'ath',
4025             'description'
4026         )
4027     WHERE key = 'format.po.jedi';
4028
4029 -- We just changed a key in action_trigger.hook.  Now redirect any
4030 -- child rows to point to the new key.  (There probably aren't any;
4031 -- this is just a precaution against possible local modifications.)
4032
4033 UPDATE action_trigger.event_definition
4034 SET hook = 'acqpo.activated'
4035 WHERE hook = 'format.po.jedi';
4036
4037 INSERT INTO action_trigger.reactor (module, description) VALUES (
4038     'AstCall', 'Possibly place a phone call with Asterisk'
4039 );
4040
4041 INSERT INTO
4042     action_trigger.event_definition (
4043         id, active, owner, name, hook, validator, reactor,
4044         cleanup_success, cleanup_failure, delay, delay_field, group_field,
4045         max_delay, granularity, usr_field, opt_in_setting, template
4046     ) VALUES (
4047         24,
4048         FALSE,
4049         1,
4050         'Telephone Overdue Notice',
4051         'checkout.due', 'NOOP_True', 'AstCall',
4052         DEFAULT, DEFAULT, '5 seconds', 'due_date', 'usr',
4053         DEFAULT, DEFAULT, DEFAULT, DEFAULT,
4054         $$
4055 [% phone = target.0.usr.day_phone | replace('[\s\-\(\)]', '') -%]
4056 [% IF phone.match('^[2-9]') %][% country = 1 %][% ELSE %][% country = '' %][% END -%]
4057 Channel: [% channel_prefix %]/[% country %][% phone %]
4058 Context: overdue-test
4059 MaxRetries: 1
4060 RetryTime: 60
4061 WaitTime: 30
4062 Extension: 10
4063 Archive: 1
4064 Set: eg_user_id=[% target.0.usr.id %]
4065 Set: items=[% target.size %]
4066 Set: titlestring=[% titles = [] %][% FOR circ IN target %][% titles.push(circ.target_copy.call_number.record.simple_record.title) %][% END %][% titles.join(". ") %]
4067 $$
4068     );
4069
4070 INSERT INTO
4071     action_trigger.environment (id, event_def, path)
4072     VALUES
4073         (DEFAULT, 24, 'target_copy.call_number.record.simple_record'),
4074         (DEFAULT, 24, 'usr')
4075     ;
4076
4077 INSERT INTO action_trigger.hook (key,core_type,description,passive) VALUES (
4078         'circ.format.history.email',
4079         'circ', 
4080         oils_i18n_gettext(
4081             'circ.format.history.email',
4082             'An email has been requested for a circ history.',
4083             'ath',
4084             'description'
4085         ), 
4086         FALSE
4087     )
4088     ,(
4089         'circ.format.history.print',
4090         'circ', 
4091         oils_i18n_gettext(
4092             'circ.format.history.print',
4093             'A circ history needs to be formatted for printing.',
4094             'ath',
4095             'description'
4096         ), 
4097         FALSE
4098     )
4099     ,(
4100         'ahr.format.history.email',
4101         'ahr', 
4102         oils_i18n_gettext(
4103             'ahr.format.history.email',
4104             'An email has been requested for a hold request history.',
4105             'ath',
4106             'description'
4107         ), 
4108         FALSE
4109     )
4110     ,(
4111         'ahr.format.history.print',
4112         'ahr', 
4113         oils_i18n_gettext(
4114             'ahr.format.history.print',
4115             'A hold request history needs to be formatted for printing.',
4116             'ath',
4117             'description'
4118         ), 
4119         FALSE
4120     )
4121
4122 ;
4123
4124 INSERT INTO action_trigger.event_definition (
4125         id,
4126         active,
4127         owner,
4128         name,
4129         hook,
4130         validator,
4131         reactor,
4132         group_field,
4133         granularity,
4134         template
4135     ) VALUES (
4136         25,
4137         TRUE,
4138         1,
4139         'circ.history.email',
4140         'circ.format.history.email',
4141         'NOOP_True',
4142         'SendEmail',
4143         'usr',
4144         NULL,
4145 $$
4146 [%- USE date -%]
4147 [%- SET user = target.0.usr -%]
4148 To: [%- params.recipient_email || user.email %]
4149 From: [%- params.sender_email || default_sender %]
4150 Subject: Circulation History
4151
4152     [% FOR circ IN target %]
4153             [% helpers.get_copy_bib_basics(circ.target_copy.id).title %]
4154             Barcode: [% circ.target_copy.barcode %]
4155             Checked Out: [% date.format(helpers.format_date(circ.xact_start), '%Y-%m-%d') %]
4156             Due Date: [% date.format(helpers.format_date(circ.due_date), '%Y-%m-%d') %]
4157             Returned: [% date.format(helpers.format_date(circ.checkin_time), '%Y-%m-%d') %]
4158     [% END %]
4159 $$
4160     )
4161     ,(
4162         26,
4163         TRUE,
4164         1,
4165         'circ.history.print',
4166         'circ.format.history.print',
4167         'NOOP_True',
4168         'ProcessTemplate',
4169         'usr',
4170         'print-on-demand',
4171 $$
4172 [%- USE date -%]
4173 <div>
4174     <style> li { padding: 8px; margin 5px; }</style>
4175     <div>[% date.format %]</div>
4176     <br/>
4177
4178     [% user.family_name %], [% user.first_given_name %]
4179     <ol>
4180     [% FOR circ IN target %]
4181         <li>
4182             <div>[% helpers.get_copy_bib_basics(circ.target_copy.id).title %]</div>
4183             <div>Barcode: [% circ.target_copy.barcode %]</div>
4184             <div>Checked Out: [% date.format(helpers.format_date(circ.xact_start), '%Y-%m-%d') %]</div>
4185             <div>Due Date: [% date.format(helpers.format_date(circ.due_date), '%Y-%m-%d') %]</div>
4186             <div>Returned: [% date.format(helpers.format_date(circ.checkin_time), '%Y-%m-%d') %]</div>
4187         </li>
4188     [% END %]
4189     </ol>
4190 </div>
4191 $$
4192     )
4193     ,(
4194         27,
4195         TRUE,
4196         1,
4197         'ahr.history.email',
4198         'ahr.format.history.email',
4199         'NOOP_True',
4200         'SendEmail',
4201         'usr',
4202         NULL,
4203 $$
4204 [%- USE date -%]
4205 [%- SET user = target.0.usr -%]
4206 To: [%- params.recipient_email || user.email %]
4207 From: [%- params.sender_email || default_sender %]
4208 Subject: Hold Request History
4209
4210     [% FOR hold IN target %]
4211             [% helpers.get_copy_bib_basics(hold.current_copy.id).title %]
4212             Requested: [% date.format(helpers.format_date(hold.request_time), '%Y-%m-%d') %]
4213             [% IF hold.fulfillment_time %]Fulfilled: [% date.format(helpers.format_date(hold.fulfillment_time), '%Y-%m-%d') %][% END %]
4214     [% END %]
4215 $$
4216     )
4217     ,(
4218         28,
4219         TRUE,
4220         1,
4221         'ahr.history.print',
4222         'ahr.format.history.print',
4223         'NOOP_True',
4224         'ProcessTemplate',
4225         'usr',
4226         'print-on-demand',
4227 $$
4228 [%- USE date -%]
4229 <div>
4230     <style> li { padding: 8px; margin 5px; }</style>
4231     <div>[% date.format %]</div>
4232     <br/>
4233
4234     [% user.family_name %], [% user.first_given_name %]
4235     <ol>
4236     [% FOR hold IN target %]
4237         <li>
4238             <div>[% helpers.get_copy_bib_basics(hold.current_copy.id).title %]</div>
4239             <div>Requested: [% date.format(helpers.format_date(hold.request_time), '%Y-%m-%d') %]</div>
4240             [% IF hold.fulfillment_time %]<div>Fulfilled: [% date.format(helpers.format_date(hold.fulfillment_time), '%Y-%m-%d') %]</div>[% END %]
4241         </li>
4242     [% END %]
4243     </ol>
4244 </div>
4245 $$
4246     )
4247
4248 ;
4249
4250 INSERT INTO action_trigger.environment (
4251         event_def,
4252         path
4253     ) VALUES 
4254          ( 25, 'target_copy')
4255         ,( 25, 'usr' )
4256         ,( 26, 'target_copy' )
4257         ,( 26, 'usr' )
4258         ,( 27, 'current_copy' )
4259         ,( 27, 'usr' )
4260         ,( 28, 'current_copy' )
4261         ,( 28, 'usr' )
4262 ;
4263
4264 INSERT INTO action_trigger.hook (key,core_type,description,passive) VALUES (
4265         'money.format.payment_receipt.email',
4266         'mp', 
4267         oils_i18n_gettext(
4268             'money.format.payment_receipt.email',
4269             'An email has been requested for a payment receipt.',
4270             'ath',
4271             'description'
4272         ), 
4273         FALSE
4274     )
4275     ,(
4276         'money.format.payment_receipt.print',
4277         'mp', 
4278         oils_i18n_gettext(
4279             'money.format.payment_receipt.print',
4280             'A payment receipt needs to be formatted for printing.',
4281             'ath',
4282             'description'
4283         ), 
4284         FALSE
4285     )
4286 ;
4287
4288 INSERT INTO action_trigger.event_definition (
4289         id,
4290         active,
4291         owner,
4292         name,
4293         hook,
4294         validator,
4295         reactor,
4296         group_field,
4297         granularity,
4298         template
4299     ) VALUES (
4300         29,
4301         TRUE,
4302         1,
4303         'money.payment_receipt.email',
4304         'money.format.payment_receipt.email',
4305         'NOOP_True',
4306         'SendEmail',
4307         'xact.usr',
4308         NULL,
4309 $$
4310 [%- USE date -%]
4311 [%- SET user = target.0.xact.usr -%]
4312 To: [%- params.recipient_email || user.email %]
4313 From: [%- params.sender_email || default_sender %]
4314 Subject: Payment Receipt
4315
4316 [% date.format -%]
4317 [%- SET xact_mp_hash = {} -%]
4318 [%- FOR mp IN target %][%# Template is hooked around payments, but let us make the receipt focused on transactions -%]
4319     [%- SET xact_id = mp.xact.id -%]
4320     [%- IF ! xact_mp_hash.defined( xact_id ) -%][%- xact_mp_hash.$xact_id = { 'xact' => mp.xact, 'payments' => [] } -%][%- END -%]
4321     [%- xact_mp_hash.$xact_id.payments.push(mp) -%]
4322 [%- END -%]
4323 [%- FOR xact_id IN xact_mp_hash.keys.sort -%]
4324     [%- SET xact = xact_mp_hash.$xact_id.xact %]
4325 Transaction ID: [% xact_id %]
4326     [% IF xact.circulation %][% helpers.get_copy_bib_basics(xact.circulation.target_copy).title %]
4327     [% ELSE %]Miscellaneous
4328     [% END %]
4329     Line item billings:
4330         [%- SET mb_type_hash = {} -%]
4331         [%- FOR mb IN xact.billings %][%# Group billings by their btype -%]
4332             [%- IF mb.voided == 'f' -%]
4333                 [%- SET mb_type = mb.btype.id -%]
4334                 [%- IF ! mb_type_hash.defined( mb_type ) -%][%- mb_type_hash.$mb_type = { 'sum' => 0.00, 'billings' => [] } -%][%- END -%]
4335                 [%- IF ! mb_type_hash.$mb_type.defined( 'first_ts' ) -%][%- mb_type_hash.$mb_type.first_ts = mb.billing_ts -%][%- END -%]
4336                 [%- mb_type_hash.$mb_type.last_ts = mb.billing_ts -%]
4337                 [%- mb_type_hash.$mb_type.sum = mb_type_hash.$mb_type.sum + mb.amount -%]
4338                 [%- mb_type_hash.$mb_type.billings.push( mb ) -%]
4339             [%- END -%]
4340         [%- END -%]
4341         [%- FOR mb_type IN mb_type_hash.keys.sort -%]
4342             [%- IF mb_type == 1 %][%-# Consolidated view of overdue billings -%]
4343                 $[% mb_type_hash.$mb_type.sum %] for [% mb_type_hash.$mb_type.billings.0.btype.name %] 
4344                     on [% mb_type_hash.$mb_type.first_ts %] through [% mb_type_hash.$mb_type.last_ts %]
4345             [%- ELSE -%][%# all other billings show individually %]
4346                 [% FOR mb IN mb_type_hash.$mb_type.billings %]
4347                     $[% mb.amount %] for [% mb.btype.name %] on [% mb.billing_ts %] [% mb.note %]
4348                 [% END %]
4349             [% END %]
4350         [% END %]
4351     Line item payments:
4352         [% FOR mp IN xact_mp_hash.$xact_id.payments %]
4353             Payment ID: [% mp.id %]
4354                 Paid [% mp.amount %] via [% SWITCH mp.payment_type -%]
4355                     [% CASE "cash_payment" %]cash
4356                     [% CASE "check_payment" %]check
4357                     [% CASE "credit_card_payment" %]credit card (
4358                         [%- SET cc_chunks = mp.credit_card_payment.cc_number.replace(' ','').chunk(4); -%]
4359                         [%- cc_chunks.slice(0, -1+cc_chunks.max).join.replace('\S','X') -%] 
4360                         [% cc_chunks.last -%]
4361                         exp [% mp.credit_card_payment.expire_month %]/[% mp.credit_card_payment.expire_year -%]
4362                     )
4363                     [% CASE "credit_payment" %]credit
4364                     [% CASE "forgive_payment" %]forgiveness
4365                     [% CASE "goods_payment" %]goods
4366                     [% CASE "work_payment" %]work
4367                 [%- END %] on [% mp.payment_ts %] [% mp.note %]
4368         [% END %]
4369 [% END %]
4370 $$
4371     )
4372     ,(
4373         30,
4374         TRUE,
4375         1,
4376         'money.payment_receipt.print',
4377         'money.format.payment_receipt.print',
4378         'NOOP_True',
4379         'ProcessTemplate',
4380         'xact.usr',
4381         'print-on-demand',
4382 $$
4383 [%- USE date -%][%- SET user = target.0.xact.usr -%]
4384 <div style="li { padding: 8px; margin 5px; }">
4385     <div>[% date.format %]</div><br/>
4386     <ol>
4387     [% SET xact_mp_hash = {} %]
4388     [% FOR mp IN target %][%# Template is hooked around payments, but let us make the receipt focused on transactions %]
4389         [% SET xact_id = mp.xact.id %]
4390         [% IF ! xact_mp_hash.defined( xact_id ) %][% xact_mp_hash.$xact_id = { 'xact' => mp.xact, 'payments' => [] } %][% END %]
4391         [% xact_mp_hash.$xact_id.payments.push(mp) %]
4392     [% END %]
4393     [% FOR xact_id IN xact_mp_hash.keys.sort %]
4394         [% SET xact = xact_mp_hash.$xact_id.xact %]
4395         <li>Transaction ID: [% xact_id %]
4396             [% IF xact.circulation %][% helpers.get_copy_bib_basics(xact.circulation.target_copy).title %]
4397             [% ELSE %]Miscellaneous
4398             [% END %]
4399             Line item billings:<ol>
4400                 [% SET mb_type_hash = {} %]
4401                 [% FOR mb IN xact.billings %][%# Group billings by their btype %]
4402                     [% IF mb.voided == 'f' %]
4403                         [% SET mb_type = mb.btype.id %]
4404                         [% IF ! mb_type_hash.defined( mb_type ) %][% mb_type_hash.$mb_type = { 'sum' => 0.00, 'billings' => [] } %][% END %]
4405                         [% IF ! mb_type_hash.$mb_type.defined( 'first_ts' ) %][% mb_type_hash.$mb_type.first_ts = mb.billing_ts %][% END %]
4406                         [% mb_type_hash.$mb_type.last_ts = mb.billing_ts %]
4407                         [% mb_type_hash.$mb_type.sum = mb_type_hash.$mb_type.sum + mb.amount %]
4408                         [% mb_type_hash.$mb_type.billings.push( mb ) %]
4409                     [% END %]
4410                 [% END %]
4411                 [% FOR mb_type IN mb_type_hash.keys.sort %]
4412                     <li>[% IF mb_type == 1 %][%# Consolidated view of overdue billings %]
4413                         $[% mb_type_hash.$mb_type.sum %] for [% mb_type_hash.$mb_type.billings.0.btype.name %] 
4414                             on [% mb_type_hash.$mb_type.first_ts %] through [% mb_type_hash.$mb_type.last_ts %]
4415                     [% ELSE %][%# all other billings show individually %]
4416                         [% FOR mb IN mb_type_hash.$mb_type.billings %]
4417                             $[% mb.amount %] for [% mb.btype.name %] on [% mb.billing_ts %] [% mb.note %]
4418                         [% END %]
4419                     [% END %]</li>
4420                 [% END %]
4421             </ol>
4422             Line item payments:<ol>
4423                 [% FOR mp IN xact_mp_hash.$xact_id.payments %]
4424                     <li>Payment ID: [% mp.id %]
4425                         Paid [% mp.amount %] via [% SWITCH mp.payment_type -%]
4426                             [% CASE "cash_payment" %]cash
4427                             [% CASE "check_payment" %]check
4428                             [% CASE "credit_card_payment" %]credit card (
4429                                 [%- SET cc_chunks = mp.credit_card_payment.cc_number.replace(' ','').chunk(4); -%]
4430                                 [%- cc_chunks.slice(0, -1+cc_chunks.max).join.replace('\S','X') -%] 
4431                                 [% cc_chunks.last -%]
4432                                 exp [% mp.credit_card_payment.expire_month %]/[% mp.credit_card_payment.expire_year -%]
4433                             )
4434                             [% CASE "credit_payment" %]credit
4435                             [% CASE "forgive_payment" %]forgiveness
4436                             [% CASE "goods_payment" %]goods
4437                             [% CASE "work_payment" %]work
4438                         [%- END %] on [% mp.payment_ts %] [% mp.note %]
4439                     </li>
4440                 [% END %]
4441             </ol>
4442         </li>
4443     [% END %]
4444     </ol>
4445 </div>
4446 $$
4447     )
4448 ;
4449
4450 INSERT INTO action_trigger.environment (
4451         event_def,
4452         path
4453     ) VALUES -- for fleshing mp objects
4454          ( 29, 'xact')
4455         ,( 29, 'xact.usr')
4456         ,( 29, 'xact.grocery' )
4457         ,( 29, 'xact.circulation' )
4458         ,( 29, 'xact.summary' )
4459         ,( 30, 'xact')
4460         ,( 30, 'xact.usr')
4461         ,( 30, 'xact.grocery' )
4462         ,( 30, 'xact.circulation' )
4463         ,( 30, 'xact.summary' )
4464 ;
4465
4466 INSERT INTO action_trigger.cleanup ( module, description ) VALUES (
4467     'DeleteTempBiblioBucket',
4468     oils_i18n_gettext(
4469         'DeleteTempBiblioBucket',
4470         'Deletes a cbreb object used as a target if it has a btype of "temp"',
4471         'atclean',
4472         'description'
4473     )
4474 );
4475
4476 INSERT INTO action_trigger.hook (key,core_type,description,passive) VALUES (
4477         'biblio.format.record_entry.email',
4478         'cbreb', 
4479         oils_i18n_gettext(
4480             'biblio.format.record_entry.email',
4481             'An email has been requested for one or more biblio record entries.',
4482             'ath',
4483             'description'
4484         ), 
4485         FALSE
4486     )
4487     ,(
4488         'biblio.format.record_entry.print',
4489         'cbreb', 
4490         oils_i18n_gettext(
4491             'biblio.format.record_entry.print',
4492             'One or more biblio record entries need to be formatted for printing.',
4493             'ath',
4494             'description'
4495         ), 
4496         FALSE
4497     )
4498 ;
4499
4500 INSERT INTO action_trigger.event_definition (
4501         id,
4502         active,
4503         owner,
4504         name,
4505         hook,
4506         validator,
4507         reactor,
4508         cleanup_success,
4509         cleanup_failure,
4510         group_field,
4511         granularity,
4512         template
4513     ) VALUES (
4514         31,
4515         TRUE,
4516         1,
4517         'biblio.record_entry.email',
4518         'biblio.format.record_entry.email',
4519         'NOOP_True',
4520         'SendEmail',
4521         'DeleteTempBiblioBucket',
4522         'DeleteTempBiblioBucket',
4523         'owner',
4524         NULL,
4525 $$
4526 [%- USE date -%]
4527 [%- SET user = target.0.owner -%]
4528 To: [%- params.recipient_email || user.email %]
4529 From: [%- params.sender_email || default_sender %]
4530 Subject: Bibliographic Records
4531
4532     [% FOR cbreb IN target %]
4533     [% FOR cbrebi IN cbreb.items %]
4534         Bib ID# [% cbrebi.target_biblio_record_entry.id %] ISBN: [% crebi.target_biblio_record_entry.simple_record.isbn %]
4535         Title: [% cbrebi.target_biblio_record_entry.simple_record.title %]
4536         Author: [% cbrebi.target_biblio_record_entry.simple_record.author %]
4537         Publication Year: [% cbrebi.target_biblio_record_entry.simple_record.pubdate %]
4538
4539     [% END %]
4540     [% END %]
4541 $$
4542     )
4543     ,(
4544         32,
4545         TRUE,
4546         1,
4547         'biblio.record_entry.print',
4548         'biblio.format.record_entry.print',
4549         'NOOP_True',
4550         'ProcessTemplate',
4551         'DeleteTempBiblioBucket',
4552         'DeleteTempBiblioBucket',
4553         'owner',
4554         'print-on-demand',
4555 $$
4556 [%- USE date -%]
4557 <div>
4558     <style> li { padding: 8px; margin 5px; }</style>
4559     <ol>
4560     [% FOR cbreb IN target %]
4561     [% FOR cbrebi IN cbreb.items %]
4562         <li>Bib ID# [% cbrebi.target_biblio_record_entry.id %] ISBN: [% crebi.target_biblio_record_entry.simple_record.isbn %]<br />
4563             Title: [% cbrebi.target_biblio_record_entry.simple_record.title %]<br />
4564             Author: [% cbrebi.target_biblio_record_entry.simple_record.author %]<br />
4565             Publication Year: [% cbrebi.target_biblio_record_entry.simple_record.pubdate %]
4566         </li>
4567     [% END %]
4568     [% END %]
4569     </ol>
4570 </div>
4571 $$
4572     )
4573 ;
4574
4575 INSERT INTO action_trigger.environment (
4576         event_def,
4577         path
4578     ) VALUES -- for fleshing cbreb objects
4579          ( 31, 'owner' )
4580         ,( 31, 'items' )
4581         ,( 31, 'items.target_biblio_record_entry' )
4582         ,( 31, 'items.target_biblio_record_entry.simple_record' )
4583         ,( 31, 'items.target_biblio_record_entry.call_numbers' )
4584         ,( 31, 'items.target_biblio_record_entry.fixed_fields' )
4585         ,( 31, 'items.target_biblio_record_entry.notes' )
4586         ,( 31, 'items.target_biblio_record_entry.full_record_entries' )
4587         ,( 32, 'owner' )
4588         ,( 32, 'items' )
4589         ,( 32, 'items.target_biblio_record_entry' )
4590         ,( 32, 'items.target_biblio_record_entry.simple_record' )
4591         ,( 32, 'items.target_biblio_record_entry.call_numbers' )
4592         ,( 32, 'items.target_biblio_record_entry.fixed_fields' )
4593         ,( 32, 'items.target_biblio_record_entry.notes' )
4594         ,( 32, 'items.target_biblio_record_entry.full_record_entries' )
4595 ;
4596
4597 INSERT INTO action_trigger.environment (
4598         event_def,
4599         path
4600     ) VALUES -- for fleshing mp objects
4601          ( 29, 'credit_card_payment')
4602         ,( 29, 'xact.billings')
4603         ,( 29, 'xact.billings.btype')
4604         ,( 30, 'credit_card_payment')
4605         ,( 30, 'xact.billings')
4606         ,( 30, 'xact.billings.btype')
4607 ;
4608
4609 INSERT INTO action_trigger.hook (key,core_type,description,passive) VALUES 
4610     (   'circ.format.missing_pieces.slip.print',
4611         'circ', 
4612         oils_i18n_gettext(
4613             'circ.format.missing_pieces.slip.print',
4614             'A missing pieces slip needs to be formatted for printing.',
4615             'ath',
4616             'description'
4617         ), 
4618         FALSE
4619     )
4620     ,(  'circ.format.missing_pieces.letter.print',
4621         'circ', 
4622         oils_i18n_gettext(
4623             'circ.format.missing_pieces.letter.print',
4624             'A missing pieces patron letter needs to be formatted for printing.',
4625             'ath',
4626             'description'
4627         ), 
4628         FALSE
4629     )
4630 ;
4631
4632 INSERT INTO action_trigger.event_definition (
4633         id,
4634         active,
4635         owner,
4636         name,
4637         hook,
4638         validator,
4639         reactor,
4640         group_field,
4641         granularity,
4642         template
4643     ) VALUES (
4644         33,
4645         TRUE,
4646         1,
4647         'circ.missing_pieces.slip.print',
4648         'circ.format.missing_pieces.slip.print',
4649         'NOOP_True',
4650         'ProcessTemplate',
4651         'usr',
4652         'print-on-demand',
4653 $$
4654 [%- USE date -%]
4655 [%- SET user = target.0.usr -%]
4656 <div style="li { padding: 8px; margin 5px; }">
4657     <div>[% date.format %]</div><br/>
4658     Missing pieces for:
4659     <ol>
4660     [% FOR circ IN target %]
4661         <li>Barcode: [% circ.target_copy.barcode %] Transaction ID: [% circ.id %] Due: [% circ.due_date.format %]<br />
4662             [% helpers.get_copy_bib_basics(circ.target_copy.id).title %]
4663         </li>
4664     [% END %]
4665     </ol>
4666 </div>
4667 $$
4668     )
4669     ,(
4670         34,
4671         TRUE,
4672         1,
4673         'circ.missing_pieces.letter.print',
4674         'circ.format.missing_pieces.letter.print',
4675         'NOOP_True',
4676         'ProcessTemplate',
4677         'usr',
4678         'print-on-demand',
4679 $$
4680 [%- USE date -%]
4681 [%- SET user = target.0.usr -%]
4682 [% date.format %]
4683 Dear [% user.prefix %] [% user.first_given_name %] [% user.family_name %],
4684
4685 We are missing pieces for the following returned items:
4686 [% FOR circ IN target %]
4687 Barcode: [% circ.target_copy.barcode %] Transaction ID: [% circ.id %] Due: [% circ.due_date.format %]
4688 [% helpers.get_copy_bib_basics(circ.target_copy.id).title %]
4689 [% END %]
4690
4691 Please return these pieces as soon as possible.
4692
4693 Thanks!
4694
4695 Library Staff
4696 $$
4697     )
4698 ;
4699
4700 INSERT INTO action_trigger.environment (
4701         event_def,
4702         path
4703     ) VALUES -- for fleshing circ objects
4704          ( 33, 'usr')
4705         ,( 33, 'target_copy')
4706         ,( 33, 'target_copy.circ_lib')
4707         ,( 33, 'target_copy.circ_lib.mailing_address')
4708         ,( 33, 'target_copy.circ_lib.billing_address')
4709         ,( 33, 'target_copy.call_number')
4710         ,( 33, 'target_copy.call_number.owning_lib')
4711         ,( 33, 'target_copy.call_number.owning_lib.mailing_address')
4712         ,( 33, 'target_copy.call_number.owning_lib.billing_address')
4713         ,( 33, 'circ_lib')
4714         ,( 33, 'circ_lib.mailing_address')
4715         ,( 33, 'circ_lib.billing_address')
4716         ,( 34, 'usr')
4717         ,( 34, 'target_copy')
4718         ,( 34, 'target_copy.circ_lib')
4719         ,( 34, 'target_copy.circ_lib.mailing_address')
4720         ,( 34, 'target_copy.circ_lib.billing_address')
4721         ,( 34, 'target_copy.call_number')
4722         ,( 34, 'target_copy.call_number.owning_lib')
4723         ,( 34, 'target_copy.call_number.owning_lib.mailing_address')
4724         ,( 34, 'target_copy.call_number.owning_lib.billing_address')
4725         ,( 34, 'circ_lib')
4726         ,( 34, 'circ_lib.mailing_address')
4727         ,( 34, 'circ_lib.billing_address')
4728 ;
4729
4730 INSERT INTO action_trigger.hook (key,core_type,description,passive) 
4731     VALUES (   
4732         'ahr.format.pull_list',
4733         'ahr', 
4734         oils_i18n_gettext(
4735             'ahr.format.pull_list',
4736             'Format holds pull list for printing',
4737             'ath',
4738             'description'
4739         ), 
4740         FALSE
4741     );
4742
4743 INSERT INTO action_trigger.event_definition (
4744         id,
4745         active,
4746         owner,
4747         name,
4748         hook,
4749         validator,
4750         reactor,
4751         group_field,
4752         granularity,
4753         template
4754     ) VALUES (
4755         35,
4756         TRUE,
4757         1,
4758         'Holds Pull List',
4759         'ahr.format.pull_list',
4760         'NOOP_True',
4761         'ProcessTemplate',
4762         'pickup_lib',
4763         'print-on-demand',
4764 $$
4765 [%- USE date -%]
4766 <style>
4767     table { border-collapse: collapse; } 
4768     td { padding: 5px; border-bottom: 1px solid #888; } 
4769     th { font-weight: bold; }
4770 </style>
4771 [% 
4772     # Sort the holds into copy-location buckets
4773     # In the main print loop, sort each bucket by callnumber before printing
4774     SET holds_list = [];
4775     SET loc_data = [];
4776     SET current_location = target.0.current_copy.location.id;
4777     FOR hold IN target;
4778         IF current_location != hold.current_copy.location.id;
4779             SET current_location = hold.current_copy.location.id;
4780             holds_list.push(loc_data);
4781             SET loc_data = [];
4782         END;
4783         SET hold_data = {
4784             'hold' => hold,
4785             'callnumber' => hold.current_copy.call_number.label
4786         };
4787         loc_data.push(hold_data);
4788     END;
4789     holds_list.push(loc_data)
4790 %]
4791 <table>
4792     <thead>
4793         <tr>
4794             <th>Title</th>
4795             <th>Author</th>
4796             <th>Shelving Location</th>
4797             <th>Call Number</th>
4798             <th>Barcode</th>
4799             <th>Patron</th>
4800         </tr>
4801     </thead>
4802     <tbody>
4803     [% FOR loc_data IN holds_list  %]
4804         [% FOR hold_data IN loc_data.sort('callnumber') %]
4805             [% 
4806                 SET hold = hold_data.hold;
4807                 SET copy_data = helpers.get_copy_bib_basics(hold.current_copy.id);
4808             %]
4809             <tr>
4810                 <td>[% copy_data.title | truncate %]</td>
4811                 <td>[% copy_data.author | truncate %]</td>
4812                 <td>[% hold.current_copy.location.name %]</td>
4813                 <td>[% hold.current_copy.call_number.label %]</td>
4814                 <td>[% hold.current_copy.barcode %]</td>
4815                 <td>[% hold.usr.card.barcode %]</td>
4816             </tr>
4817         [% END %]
4818     [% END %]
4819     <tbody>
4820 </table>
4821 $$
4822 );
4823
4824 INSERT INTO action_trigger.environment (
4825         event_def,
4826         path
4827     ) VALUES
4828         (35, 'current_copy.location'),
4829         (35, 'current_copy.call_number'),
4830         (35, 'usr.card'),
4831         (35, 'pickup_lib')
4832 ;
4833
4834 INSERT INTO action_trigger.validator (module, description) VALUES ( 
4835     'HoldIsCancelled', 
4836     oils_i18n_gettext( 
4837         'HoldIsCancelled', 
4838         'Check whether a hold request is cancelled.', 
4839         'atval', 
4840         'description' 
4841     ) 
4842 );
4843
4844 -- Create the query schema, and the tables and views therein
4845
4846 DROP SCHEMA IF EXISTS sql CASCADE;
4847 DROP SCHEMA IF EXISTS query CASCADE;
4848
4849 CREATE SCHEMA query;
4850
4851 CREATE TABLE query.datatype (
4852         id              SERIAL            PRIMARY KEY,
4853         datatype_name   TEXT              NOT NULL UNIQUE,
4854         is_numeric      BOOL              NOT NULL DEFAULT FALSE,
4855         is_composite    BOOL              NOT NULL DEFAULT FALSE,
4856         CONSTRAINT qdt_comp_not_num CHECK
4857         ( is_numeric IS FALSE OR is_composite IS FALSE )
4858 );
4859
4860 -- Define the most common datatypes in query.datatype.  Note that none of
4861 -- these stock datatypes specifies a width or precision.
4862
4863 -- Also: set the sequence for query.datatype to 1000, leaving plenty of
4864 -- room for more stock datatypes if we ever want to add them.
4865
4866 SELECT setval( 'query.datatype_id_seq', 1000 );
4867
4868 INSERT INTO query.datatype (id, datatype_name, is_numeric )
4869   VALUES (1, 'SMALLINT', true);
4870  
4871 INSERT INTO query.datatype (id, datatype_name, is_numeric )
4872   VALUES (2, 'INTEGER', true);
4873  
4874 INSERT INTO query.datatype (id, datatype_name, is_numeric )
4875   VALUES (3, 'BIGINT', true);
4876  
4877 INSERT INTO query.datatype (id, datatype_name, is_numeric )
4878   VALUES (4, 'DECIMAL', true);
4879  
4880 INSERT INTO query.datatype (id, datatype_name, is_numeric )
4881   VALUES (5, 'NUMERIC', true);
4882  
4883 INSERT INTO query.datatype (id, datatype_name, is_numeric )
4884   VALUES (6, 'REAL', true);
4885  
4886 INSERT INTO query.datatype (id, datatype_name, is_numeric )
4887   VALUES (7, 'DOUBLE PRECISION', true);
4888  
4889 INSERT INTO query.datatype (id, datatype_name, is_numeric )
4890   VALUES (8, 'SERIAL', true);
4891  
4892 INSERT INTO query.datatype (id, datatype_name, is_numeric )
4893   VALUES (9, 'BIGSERIAL', true);
4894  
4895 INSERT INTO query.datatype (id, datatype_name, is_numeric )
4896   VALUES (10, 'MONEY', false);
4897  
4898 INSERT INTO query.datatype (id, datatype_name, is_numeric )
4899   VALUES (11, 'VARCHAR', false);
4900  
4901 INSERT INTO query.datatype (id, datatype_name, is_numeric )
4902   VALUES (12, 'CHAR', false);
4903  
4904 INSERT INTO query.datatype (id, datatype_name, is_numeric )
4905   VALUES (13, 'TEXT', false);
4906  
4907 INSERT INTO query.datatype (id, datatype_name, is_numeric )
4908   VALUES (14, '"char"', false);
4909  
4910 INSERT INTO query.datatype (id, datatype_name, is_numeric )
4911   VALUES (15, 'NAME', false);
4912  
4913 INSERT INTO query.datatype (id, datatype_name, is_numeric )
4914   VALUES (16, 'BYTEA', false);
4915  
4916 INSERT INTO query.datatype (id, datatype_name, is_numeric )
4917   VALUES (17, 'TIMESTAMP WITHOUT TIME ZONE', false);
4918  
4919 INSERT INTO query.datatype (id, datatype_name, is_numeric )
4920   VALUES (18, 'TIMESTAMP WITH TIME ZONE', false);
4921  
4922 INSERT INTO query.datatype (id, datatype_name, is_numeric )
4923   VALUES (19, 'DATE', false);
4924  
4925 INSERT INTO query.datatype (id, datatype_name, is_numeric )
4926   VALUES (20, 'TIME WITHOUT TIME ZONE', false);
4927  
4928 INSERT INTO query.datatype (id, datatype_name, is_numeric )
4929   VALUES (21, 'TIME WITH TIME ZONE', false);
4930  
4931 INSERT INTO query.datatype (id, datatype_name, is_numeric )
4932   VALUES (22, 'INTERVAL', false);
4933  
4934 INSERT INTO query.datatype (id, datatype_name, is_numeric )
4935   VALUES (23, 'BOOLEAN', false);
4936  
4937 CREATE TABLE query.subfield (
4938         id              SERIAL            PRIMARY KEY,
4939         composite_type  INT               NOT NULL
4940                                           REFERENCES query.datatype(id)
4941                                           ON DELETE CASCADE
4942                                           DEFERRABLE INITIALLY DEFERRED,
4943         seq_no          INT               NOT NULL
4944                                           CONSTRAINT qsf_pos_seq_no
4945                                           CHECK( seq_no > 0 ),
4946         subfield_type   INT               NOT NULL
4947                                           REFERENCES query.datatype(id)
4948                                           DEFERRABLE INITIALLY DEFERRED,
4949         CONSTRAINT qsf_datatype_seq_no UNIQUE (composite_type, seq_no)
4950 );
4951
4952 CREATE TABLE query.function_sig (
4953         id              SERIAL            PRIMARY KEY,
4954         function_name   TEXT              NOT NULL,
4955         return_type     INT               REFERENCES query.datatype(id)
4956                                           DEFERRABLE INITIALLY DEFERRED,
4957         is_aggregate    BOOL              NOT NULL DEFAULT FALSE,
4958         CONSTRAINT qfd_rtn_or_aggr CHECK
4959         ( return_type IS NULL OR is_aggregate = FALSE )
4960 );
4961
4962 CREATE INDEX query_function_sig_name_idx 
4963         ON query.function_sig (function_name);
4964
4965 CREATE TABLE query.function_param_def (
4966         id              SERIAL            PRIMARY KEY,
4967         function_id     INT               NOT NULL
4968                                           REFERENCES query.function_sig( id )
4969                                           ON DELETE CASCADE
4970                                           DEFERRABLE INITIALLY DEFERRED,
4971         seq_no          INT               NOT NULL
4972                                           CONSTRAINT qfpd_pos_seq_no CHECK
4973                                           ( seq_no > 0 ),
4974         datatype        INT               NOT NULL
4975                                           REFERENCES query.datatype( id )
4976                                           DEFERRABLE INITIALLY DEFERRED,
4977         CONSTRAINT qfpd_function_param_seq UNIQUE (function_id, seq_no)
4978 );
4979
4980 CREATE TABLE  query.stored_query (
4981         id            SERIAL         PRIMARY KEY,
4982         type          TEXT           NOT NULL CONSTRAINT query_type CHECK
4983                                      ( type IN ( 'SELECT', 'UNION', 'INTERSECT', 'EXCEPT' ) ),
4984         use_all       BOOLEAN        NOT NULL DEFAULT FALSE,
4985         use_distinct  BOOLEAN        NOT NULL DEFAULT FALSE,
4986         from_clause   INT            , --REFERENCES query.from_clause
4987                                      --DEFERRABLE INITIALLY DEFERRED,
4988         where_clause  INT            , --REFERENCES query.expression
4989                                      --DEFERRABLE INITIALLY DEFERRED,
4990         having_clause INT            , --REFERENCES query.expression
4991                                      --DEFERRABLE INITIALLY DEFERRED
4992         limit_count   INT            , --REFERENCES query.expression( id )
4993                                      --DEFERRABLE INITIALLY DEFERRED,
4994         offset_count  INT            --REFERENCES query.expression( id )
4995                                      --DEFERRABLE INITIALLY DEFERRED
4996 );
4997
4998 -- (Foreign keys to be defined later after other tables are created)
4999
5000 CREATE TABLE query.query_sequence (
5001         id              SERIAL            PRIMARY KEY,
5002         parent_query    INT               NOT NULL
5003                                           REFERENCES query.stored_query
5004                                                                           ON DELETE CASCADE
5005                                                                           DEFERRABLE INITIALLY DEFERRED,
5006         seq_no          INT               NOT NULL,
5007         child_query     INT               NOT NULL
5008                                           REFERENCES query.stored_query
5009                                                                           ON DELETE CASCADE
5010                                                                           DEFERRABLE INITIALLY DEFERRED,
5011         CONSTRAINT query_query_seq UNIQUE( parent_query, seq_no )
5012 );
5013
5014 CREATE TABLE query.bind_variable (
5015         name          TEXT             PRIMARY KEY,
5016         type          TEXT             NOT NULL
5017                                            CONSTRAINT bind_variable_type CHECK
5018                                            ( type in ( 'string', 'number', 'string_list', 'number_list' )),
5019         description   TEXT             NOT NULL,
5020         default_value TEXT,            -- to be encoded in JSON
5021         label         TEXT             NOT NULL
5022 );
5023
5024 CREATE TABLE query.expression (
5025         id            SERIAL        PRIMARY KEY,
5026         type          TEXT          NOT NULL CONSTRAINT expression_type CHECK
5027                                     ( type IN (
5028                                     'xbet',    -- between
5029                                     'xbind',   -- bind variable
5030                                     'xbool',   -- boolean
5031                                     'xcase',   -- case
5032                                     'xcast',   -- cast
5033                                     'xcol',    -- column
5034                                     'xex',     -- exists
5035                                     'xfunc',   -- function
5036                                     'xin',     -- in
5037                                     'xisnull', -- is null
5038                                     'xnull',   -- null
5039                                     'xnum',    -- number
5040                                     'xop',     -- operator
5041                                     'xser',    -- series
5042                                     'xstr',    -- string
5043                                     'xsubq'    -- subquery
5044                                                                 ) ),
5045         parenthesize  BOOL          NOT NULL DEFAULT FALSE,
5046         parent_expr   INT           REFERENCES query.expression
5047                                     ON DELETE CASCADE
5048                                     DEFERRABLE INITIALLY DEFERRED,
5049         seq_no        INT           NOT NULL DEFAULT 1,
5050         literal       TEXT,
5051         table_alias   TEXT,
5052         column_name   TEXT,
5053         left_operand  INT           REFERENCES query.expression
5054                                     DEFERRABLE INITIALLY DEFERRED,
5055         operator      TEXT,
5056         right_operand INT           REFERENCES query.expression
5057                                     DEFERRABLE INITIALLY DEFERRED,
5058         function_id   INT           REFERENCES query.function_sig
5059                                     DEFERRABLE INITIALLY DEFERRED,
5060         subquery      INT           REFERENCES query.stored_query
5061                                     DEFERRABLE INITIALLY DEFERRED,
5062         cast_type     INT           REFERENCES query.datatype
5063                                     DEFERRABLE INITIALLY DEFERRED,
5064         negate        BOOL          NOT NULL DEFAULT FALSE,
5065         bind_variable TEXT          REFERENCES query.bind_variable
5066                                         DEFERRABLE INITIALLY DEFERRED
5067 );
5068
5069 CREATE UNIQUE INDEX query_expr_parent_seq
5070         ON query.expression( parent_expr, seq_no )
5071         WHERE parent_expr IS NOT NULL;
5072
5073 -- Due to some circular references, the following foreign key definitions
5074 -- had to be deferred until query.expression existed:
5075
5076 ALTER TABLE query.stored_query
5077         ADD FOREIGN KEY ( where_clause )
5078         REFERENCES query.expression( id )
5079         DEFERRABLE INITIALLY DEFERRED;
5080
5081 ALTER TABLE query.stored_query
5082         ADD FOREIGN KEY ( having_clause )
5083         REFERENCES query.expression( id )
5084         DEFERRABLE INITIALLY DEFERRED;
5085
5086 ALTER TABLE query.stored_query
5087     ADD FOREIGN KEY ( limit_count )
5088     REFERENCES query.expression( id )
5089     DEFERRABLE INITIALLY DEFERRED;
5090
5091 ALTER TABLE query.stored_query
5092     ADD FOREIGN KEY ( offset_count )
5093     REFERENCES query.expression( id )
5094     DEFERRABLE INITIALLY DEFERRED;
5095
5096 CREATE TABLE query.case_branch (
5097         id            SERIAL        PRIMARY KEY,
5098         parent_expr   INT           NOT NULL REFERENCES query.expression
5099                                     ON DELETE CASCADE
5100                                     DEFERRABLE INITIALLY DEFERRED,
5101         seq_no        INT           NOT NULL,
5102         condition     INT           REFERENCES query.expression
5103                                     DEFERRABLE INITIALLY DEFERRED,
5104         result        INT           NOT NULL REFERENCES query.expression
5105                                     DEFERRABLE INITIALLY DEFERRED,
5106         CONSTRAINT case_branch_parent_seq UNIQUE (parent_expr, seq_no)
5107 );
5108
5109 CREATE TABLE query.from_relation (
5110         id               SERIAL        PRIMARY KEY,
5111         type             TEXT          NOT NULL CONSTRAINT relation_type CHECK (
5112                                            type IN ( 'RELATION', 'SUBQUERY', 'FUNCTION' ) ),
5113         table_name       TEXT,
5114         class_name       TEXT,
5115         subquery         INT           REFERENCES query.stored_query,
5116         function_call    INT           REFERENCES query.expression,
5117         table_alias      TEXT,
5118         parent_relation  INT           REFERENCES query.from_relation
5119                                        ON DELETE CASCADE
5120                                        DEFERRABLE INITIALLY DEFERRED,
5121         seq_no           INT           NOT NULL DEFAULT 1,
5122         join_type        TEXT          CONSTRAINT good_join_type CHECK (
5123                                            join_type IS NULL OR join_type IN
5124                                            ( 'INNER', 'LEFT', 'RIGHT', 'FULL' )
5125                                        ),
5126         on_clause        INT           REFERENCES query.expression
5127                                        DEFERRABLE INITIALLY DEFERRED,
5128         CONSTRAINT join_or_core CHECK (
5129         ( parent_relation IS NULL AND join_type IS NULL
5130           AND on_clause IS NULL )
5131         OR
5132         ( parent_relation IS NOT NULL AND join_type IS NOT NULL
5133           AND on_clause IS NOT NULL )
5134         )
5135 );
5136
5137 CREATE UNIQUE INDEX from_parent_seq
5138         ON query.from_relation( parent_relation, seq_no )
5139         WHERE parent_relation IS NOT NULL;
5140
5141 -- The following foreign key had to be deferred until
5142 -- query.from_relation existed
5143
5144 ALTER TABLE query.stored_query
5145         ADD FOREIGN KEY (from_clause)
5146         REFERENCES query.from_relation
5147         DEFERRABLE INITIALLY DEFERRED;
5148
5149 CREATE TABLE query.record_column (
5150         id            SERIAL            PRIMARY KEY,
5151         from_relation INT               NOT NULL REFERENCES query.from_relation
5152                                         ON DELETE CASCADE
5153                                         DEFERRABLE INITIALLY DEFERRED,
5154         seq_no        INT               NOT NULL,
5155         column_name   TEXT              NOT NULL,
5156         column_type   INT               NOT NULL REFERENCES query.datatype
5157                                         ON DELETE CASCADE
5158                                                                         DEFERRABLE INITIALLY DEFERRED,
5159         CONSTRAINT column_sequence UNIQUE (from_relation, seq_no)
5160 );
5161
5162 CREATE TABLE query.select_item (
5163         id               SERIAL         PRIMARY KEY,
5164         stored_query     INT            NOT NULL REFERENCES query.stored_query
5165                                         ON DELETE CASCADE
5166                                         DEFERRABLE INITIALLY DEFERRED,
5167         seq_no           INT            NOT NULL,
5168         expression       INT            NOT NULL REFERENCES query.expression
5169                                         DEFERRABLE INITIALLY DEFERRED,
5170         column_alias     TEXT,
5171         grouped_by       BOOL           NOT NULL DEFAULT FALSE,
5172         CONSTRAINT select_sequence UNIQUE( stored_query, seq_no )
5173 );
5174
5175 CREATE TABLE query.order_by_item (
5176         id               SERIAL         PRIMARY KEY,
5177         stored_query     INT            NOT NULL REFERENCES query.stored_query
5178                                         ON DELETE CASCADE
5179                                         DEFERRABLE INITIALLY DEFERRED,
5180         seq_no           INT            NOT NULL,
5181         expression       INT            NOT NULL REFERENCES query.expression
5182                                         ON DELETE CASCADE
5183                                         DEFERRABLE INITIALLY DEFERRED,
5184         CONSTRAINT order_by_sequence UNIQUE( stored_query, seq_no )
5185 );
5186
5187 ------------------------------------------------------------
5188 -- Create updatable views for different kinds of expressions
5189 ------------------------------------------------------------
5190
5191 -- Create updatable view for BETWEEN expressions
5192
5193 CREATE OR REPLACE VIEW query.expr_xbet AS
5194     SELECT
5195                 id,
5196                 parenthesize,
5197                 parent_expr,
5198                 seq_no,
5199                 left_operand,
5200                 negate
5201     FROM
5202         query.expression
5203     WHERE
5204         type = 'xbet';
5205
5206 CREATE OR REPLACE RULE query_expr_xbet_insert_rule AS
5207     ON INSERT TO query.expr_xbet
5208     DO INSTEAD
5209     INSERT INTO query.expression (
5210                 id,
5211                 type,
5212                 parenthesize,
5213                 parent_expr,
5214                 seq_no,
5215                 left_operand,
5216                 negate
5217     ) VALUES (
5218         COALESCE(NEW.id, NEXTVAL('query.expression_id_seq'::REGCLASS)),
5219         'xbet',
5220         COALESCE(NEW.parenthesize, FALSE),
5221         NEW.parent_expr,
5222         COALESCE(NEW.seq_no, 1),
5223                 NEW.left_operand,
5224                 COALESCE(NEW.negate, false)
5225     );
5226
5227 CREATE OR REPLACE RULE query_expr_xbet_update_rule AS
5228     ON UPDATE TO query.expr_xbet
5229     DO INSTEAD
5230     UPDATE query.expression SET
5231         id = NEW.id,
5232         parenthesize = NEW.parenthesize,
5233         parent_expr = NEW.parent_expr,
5234         seq_no = NEW.seq_no,
5235                 left_operand = NEW.left_operand,
5236                 negate = NEW.negate
5237     WHERE
5238         id = OLD.id;
5239
5240 CREATE OR REPLACE RULE query_expr_xbet_delete_rule AS
5241     ON DELETE TO query.expr_xbet
5242     DO INSTEAD
5243     DELETE FROM query.expression WHERE id = OLD.id;
5244
5245 -- Create updatable view for bind variable expressions
5246
5247 CREATE OR REPLACE VIEW query.expr_xbind AS
5248     SELECT
5249                 id,
5250                 parenthesize,
5251                 parent_expr,
5252                 seq_no,
5253                 bind_variable
5254     FROM
5255         query.expression
5256     WHERE
5257         type = 'xbind';
5258
5259 CREATE OR REPLACE RULE query_expr_xbind_insert_rule AS
5260     ON INSERT TO query.expr_xbind
5261     DO INSTEAD
5262     INSERT INTO query.expression (
5263                 id,
5264                 type,
5265                 parenthesize,
5266                 parent_expr,
5267                 seq_no,
5268                 bind_variable
5269     ) VALUES (
5270         COALESCE(NEW.id, NEXTVAL('query.expression_id_seq'::REGCLASS)),
5271         'xbind',
5272         COALESCE(NEW.parenthesize, FALSE),
5273         NEW.parent_expr,
5274         COALESCE(NEW.seq_no, 1),
5275                 NEW.bind_variable
5276     );
5277
5278 CREATE OR REPLACE RULE query_expr_xbind_update_rule AS
5279     ON UPDATE TO query.expr_xbind
5280     DO INSTEAD
5281     UPDATE query.expression SET
5282         id = NEW.id,
5283         parenthesize = NEW.parenthesize,
5284         parent_expr = NEW.parent_expr,
5285         seq_no = NEW.seq_no,
5286                 bind_variable = NEW.bind_variable
5287     WHERE
5288         id = OLD.id;
5289
5290 CREATE OR REPLACE RULE query_expr_xbind_delete_rule AS
5291     ON DELETE TO query.expr_xbind
5292     DO INSTEAD
5293     DELETE FROM query.expression WHERE id = OLD.id;
5294
5295 -- Create updatable view for boolean expressions
5296
5297 CREATE OR REPLACE VIEW query.expr_xbool AS
5298     SELECT
5299                 id,
5300                 parenthesize,
5301                 parent_expr,
5302                 seq_no,
5303                 literal,
5304                 negate
5305     FROM
5306         query.expression
5307     WHERE
5308         type = 'xbool';
5309
5310 CREATE OR REPLACE RULE query_expr_xbool_insert_rule AS
5311     ON INSERT TO query.expr_xbool
5312     DO INSTEAD
5313     INSERT INTO query.expression (
5314                 id,
5315                 type,
5316                 parenthesize,
5317                 parent_expr,
5318                 seq_no,
5319                 literal,
5320                 negate
5321     ) VALUES (
5322         COALESCE(NEW.id, NEXTVAL('query.expression_id_seq'::REGCLASS)),
5323         'xbool',
5324         COALESCE(NEW.parenthesize, FALSE),
5325         NEW.parent_expr,
5326         COALESCE(NEW.seq_no, 1),
5327         NEW.literal,
5328                 COALESCE(NEW.negate, false)
5329     );
5330
5331 CREATE OR REPLACE RULE query_expr_xbool_update_rule AS
5332     ON UPDATE TO query.expr_xbool
5333     DO INSTEAD
5334     UPDATE query.expression SET
5335         id = NEW.id,
5336         parenthesize = NEW.parenthesize,
5337         parent_expr = NEW.parent_expr,
5338         seq_no = NEW.seq_no,
5339         literal = NEW.literal,
5340                 negate = NEW.negate
5341     WHERE
5342         id = OLD.id;
5343
5344 CREATE OR REPLACE RULE query_expr_xbool_delete_rule AS
5345     ON DELETE TO query.expr_xbool
5346     DO INSTEAD
5347     DELETE FROM query.expression WHERE id = OLD.id;
5348
5349 -- Create updatable view for CASE expressions
5350
5351 CREATE OR REPLACE VIEW query.expr_xcase AS
5352     SELECT
5353                 id,
5354                 parenthesize,
5355                 parent_expr,
5356                 seq_no,
5357                 left_operand,
5358                 negate
5359     FROM
5360         query.expression
5361     WHERE
5362         type = 'xcase';
5363
5364 CREATE OR REPLACE RULE query_expr_xcase_insert_rule AS
5365     ON INSERT TO query.expr_xcase
5366     DO INSTEAD
5367     INSERT INTO query.expression (
5368                 id,
5369                 type,
5370                 parenthesize,
5371                 parent_expr,
5372                 seq_no,
5373                 left_operand,
5374                 negate
5375     ) VALUES (
5376         COALESCE(NEW.id, NEXTVAL('query.expression_id_seq'::REGCLASS)),
5377         'xcase',
5378         COALESCE(NEW.parenthesize, FALSE),
5379         NEW.parent_expr,
5380         COALESCE(NEW.seq_no, 1),
5381                 NEW.left_operand,
5382                 COALESCE(NEW.negate, false)
5383     );
5384
5385 CREATE OR REPLACE RULE query_expr_xcase_update_rule AS
5386     ON UPDATE TO query.expr_xcase
5387     DO INSTEAD
5388     UPDATE query.expression SET
5389         id = NEW.id,
5390         parenthesize = NEW.parenthesize,
5391         parent_expr = NEW.parent_expr,
5392         seq_no = NEW.seq_no,
5393                 left_operand = NEW.left_operand,
5394                 negate = NEW.negate
5395     WHERE
5396         id = OLD.id;
5397
5398 CREATE OR REPLACE RULE query_expr_xcase_delete_rule AS
5399     ON DELETE TO query.expr_xcase
5400     DO INSTEAD
5401     DELETE FROM query.expression WHERE id = OLD.id;
5402
5403 -- Create updatable view for cast expressions
5404
5405 CREATE OR REPLACE VIEW query.expr_xcast AS
5406     SELECT
5407                 id,
5408                 parenthesize,
5409                 parent_expr,
5410                 seq_no,
5411                 left_operand,
5412                 cast_type,
5413                 negate
5414     FROM
5415         query.expression
5416     WHERE
5417         type = 'xcast';
5418
5419 CREATE OR REPLACE RULE query_expr_xcast_insert_rule AS
5420     ON INSERT TO query.expr_xcast
5421     DO INSTEAD
5422     INSERT INTO query.expression (
5423         id,
5424         type,
5425         parenthesize,
5426         parent_expr,
5427         seq_no,
5428         left_operand,
5429         cast_type,
5430         negate
5431     ) VALUES (
5432         COALESCE(NEW.id, NEXTVAL('query.expression_id_seq'::REGCLASS)),
5433         'xcast',
5434         COALESCE(NEW.parenthesize, FALSE),
5435         NEW.parent_expr,
5436         COALESCE(NEW.seq_no, 1),
5437         NEW.left_operand,
5438         NEW.cast_type,
5439         COALESCE(NEW.negate, false)
5440     );
5441
5442 CREATE OR REPLACE RULE query_expr_xcast_update_rule AS
5443     ON UPDATE TO query.expr_xcast
5444     DO INSTEAD
5445     UPDATE query.expression SET
5446         id = NEW.id,
5447         parenthesize = NEW.parenthesize,
5448         parent_expr = NEW.parent_expr,
5449         seq_no = NEW.seq_no,
5450                 left_operand = NEW.left_operand,
5451                 cast_type = NEW.cast_type,
5452                 negate = NEW.negate
5453     WHERE
5454         id = OLD.id;
5455
5456 CREATE OR REPLACE RULE query_expr_xcast_delete_rule AS
5457     ON DELETE TO query.expr_xcast
5458     DO INSTEAD
5459     DELETE FROM query.expression WHERE id = OLD.id;
5460
5461 -- Create updatable view for column expressions
5462
5463 CREATE OR REPLACE VIEW query.expr_xcol AS
5464     SELECT
5465                 id,
5466                 parenthesize,
5467                 parent_expr,
5468                 seq_no,
5469                 table_alias,
5470                 column_name,
5471                 negate
5472     FROM
5473         query.expression
5474     WHERE
5475         type = 'xcol';
5476
5477 CREATE OR REPLACE RULE query_expr_xcol_insert_rule AS
5478     ON INSERT TO query.expr_xcol
5479     DO INSTEAD
5480     INSERT INTO query.expression (
5481                 id,
5482                 type,
5483                 parenthesize,
5484                 parent_expr,
5485                 seq_no,
5486                 table_alias,
5487                 column_name,
5488                 negate
5489     ) VALUES (
5490         COALESCE(NEW.id, NEXTVAL('query.expression_id_seq'::REGCLASS)),
5491         'xcol',
5492         COALESCE(NEW.parenthesize, FALSE),
5493         NEW.parent_expr,
5494         COALESCE(NEW.seq_no, 1),
5495                 NEW.table_alias,
5496                 NEW.column_name,
5497                 COALESCE(NEW.negate, false)
5498     );
5499
5500 CREATE OR REPLACE RULE query_expr_xcol_update_rule AS
5501     ON UPDATE TO query.expr_xcol
5502     DO INSTEAD
5503     UPDATE query.expression SET
5504         id = NEW.id,
5505         parenthesize = NEW.parenthesize,
5506         parent_expr = NEW.parent_expr,
5507         seq_no = NEW.seq_no,
5508                 table_alias = NEW.table_alias,
5509                 column_name = NEW.column_name,
5510                 negate = NEW.negate
5511     WHERE
5512         id = OLD.id;
5513
5514 CREATE OR REPLACE RULE query_expr_xcol_delete_rule AS
5515     ON DELETE TO query.expr_xcol
5516     DO INSTEAD
5517     DELETE FROM query.expression WHERE id = OLD.id;
5518
5519 -- Create updatable view for EXISTS expressions
5520
5521 CREATE OR REPLACE VIEW query.expr_xex AS
5522     SELECT
5523                 id,
5524                 parenthesize,
5525                 parent_expr,
5526                 seq_no,
5527                 subquery,
5528                 negate
5529     FROM
5530         query.expression
5531     WHERE
5532         type = 'xex';
5533
5534 CREATE OR REPLACE RULE query_expr_xex_insert_rule AS
5535     ON INSERT TO query.expr_xex
5536     DO INSTEAD
5537     INSERT INTO query.expression (
5538                 id,
5539                 type,
5540                 parenthesize,
5541                 parent_expr,
5542                 seq_no,
5543                 subquery,
5544                 negate
5545     ) VALUES (
5546         COALESCE(NEW.id, NEXTVAL('query.expression_id_seq'::REGCLASS)),
5547         'xex',
5548         COALESCE(NEW.parenthesize, FALSE),
5549         NEW.parent_expr,
5550         COALESCE(NEW.seq_no, 1),
5551                 NEW.subquery,
5552                 COALESCE(NEW.negate, false)
5553     );
5554
5555 CREATE OR REPLACE RULE query_expr_xex_update_rule AS
5556     ON UPDATE TO query.expr_xex
5557     DO INSTEAD
5558     UPDATE query.expression SET
5559         id = NEW.id,
5560         parenthesize = NEW.parenthesize,
5561         parent_expr = NEW.parent_expr,
5562         seq_no = NEW.seq_no,
5563                 subquery = NEW.subquery,
5564                 negate = NEW.negate
5565     WHERE
5566         id = OLD.id;
5567
5568 CREATE OR REPLACE RULE query_expr_xex_delete_rule AS
5569     ON DELETE TO query.expr_xex
5570     DO INSTEAD
5571     DELETE FROM query.expression WHERE id = OLD.id;
5572
5573 -- Create updatable view for function call expressions
5574
5575 CREATE OR REPLACE VIEW query.expr_xfunc AS
5576     SELECT
5577         id,
5578         parenthesize,
5579         parent_expr,
5580         seq_no,
5581         column_name,
5582         function_id,
5583         negate
5584     FROM
5585         query.expression
5586     WHERE
5587         type = 'xfunc';
5588
5589 CREATE OR REPLACE RULE query_expr_xfunc_insert_rule AS
5590     ON INSERT TO query.expr_xfunc
5591     DO INSTEAD
5592     INSERT INTO query.expression (
5593         id,
5594         type,
5595         parenthesize,
5596         parent_expr,
5597         seq_no,
5598         column_name,
5599         function_id,
5600         negate
5601     ) VALUES (
5602         COALESCE(NEW.id, NEXTVAL('query.expression_id_seq'::REGCLASS)),
5603         'xfunc',
5604         COALESCE(NEW.parenthesize, FALSE),
5605         NEW.parent_expr,
5606         COALESCE(NEW.seq_no, 1),
5607         NEW.column_name,
5608         NEW.function_id,
5609         COALESCE(NEW.negate, false)
5610     );
5611
5612 CREATE OR REPLACE RULE query_expr_xfunc_update_rule AS
5613     ON UPDATE TO query.expr_xfunc
5614     DO INSTEAD
5615     UPDATE query.expression SET
5616         id = NEW.id,
5617         parenthesize = NEW.parenthesize,
5618         parent_expr = NEW.parent_expr,
5619         seq_no = NEW.seq_no,
5620         column_name = NEW.column_name,
5621         function_id = NEW.function_id,
5622         negate = NEW.negate
5623     WHERE
5624         id = OLD.id;
5625
5626 CREATE OR REPLACE RULE query_expr_xfunc_delete_rule AS
5627     ON DELETE TO query.expr_xfunc
5628     DO INSTEAD
5629     DELETE FROM query.expression WHERE id = OLD.id;
5630
5631 -- Create updatable view for IN expressions
5632
5633 CREATE OR REPLACE VIEW query.expr_xin AS
5634     SELECT
5635                 id,
5636                 parenthesize,
5637                 parent_expr,
5638                 seq_no,
5639                 left_operand,
5640                 subquery,
5641                 negate
5642     FROM
5643         query.expression
5644     WHERE
5645         type = 'xin';
5646
5647 CREATE OR REPLACE RULE query_expr_xin_insert_rule AS
5648     ON INSERT TO query.expr_xin
5649     DO INSTEAD
5650     INSERT INTO query.expression (
5651                 id,
5652                 type,
5653                 parenthesize,
5654                 parent_expr,
5655                 seq_no,
5656                 left_operand,
5657                 subquery,
5658                 negate
5659     ) VALUES (
5660         COALESCE(NEW.id, NEXTVAL('query.expression_id_seq'::REGCLASS)),
5661         'xin',
5662         COALESCE(NEW.parenthesize, FALSE),
5663         NEW.parent_expr,
5664         COALESCE(NEW.seq_no, 1),
5665                 NEW.left_operand,
5666                 NEW.subquery,
5667                 COALESCE(NEW.negate, false)
5668     );
5669
5670 CREATE OR REPLACE RULE query_expr_xin_update_rule AS
5671     ON UPDATE TO query.expr_xin
5672     DO INSTEAD
5673     UPDATE query.expression SET
5674         id = NEW.id,
5675         parenthesize = NEW.parenthesize,
5676         parent_expr = NEW.parent_expr,
5677         seq_no = NEW.seq_no,
5678                 left_operand = NEW.left_operand,
5679                 subquery = NEW.subquery,
5680                 negate = NEW.negate
5681     WHERE
5682         id = OLD.id;
5683
5684 CREATE OR REPLACE RULE query_expr_xin_delete_rule AS
5685     ON DELETE TO query.expr_xin
5686     DO INSTEAD
5687     DELETE FROM query.expression WHERE id = OLD.id;
5688
5689 -- Create updatable view for IS NULL expressions
5690
5691 CREATE OR REPLACE VIEW query.expr_xisnull AS
5692     SELECT
5693                 id,
5694                 parenthesize,
5695                 parent_expr,
5696                 seq_no,
5697                 left_operand,
5698                 negate
5699     FROM
5700         query.expression
5701     WHERE
5702         type = 'xisnull';
5703
5704 CREATE OR REPLACE RULE query_expr_xisnull_insert_rule AS
5705     ON INSERT TO query.expr_xisnull
5706     DO INSTEAD
5707     INSERT INTO query.expression (
5708                 id,
5709                 type,
5710                 parenthesize,
5711                 parent_expr,
5712                 seq_no,
5713                 left_operand,
5714                 negate
5715     ) VALUES (
5716         COALESCE(NEW.id, NEXTVAL('query.expression_id_seq'::REGCLASS)),
5717         'xisnull',
5718         COALESCE(NEW.parenthesize, FALSE),
5719         NEW.parent_expr,
5720         COALESCE(NEW.seq_no, 1),
5721                 NEW.left_operand,
5722                 COALESCE(NEW.negate, false)
5723     );
5724
5725 CREATE OR REPLACE RULE query_expr_xisnull_update_rule AS
5726     ON UPDATE TO query.expr_xisnull
5727     DO INSTEAD
5728     UPDATE query.expression SET
5729         id = NEW.id,
5730         parenthesize = NEW.parenthesize,
5731         parent_expr = NEW.parent_expr,
5732         seq_no = NEW.seq_no,
5733                 left_operand = NEW.left_operand,
5734                 negate = NEW.negate
5735     WHERE
5736         id = OLD.id;
5737
5738 CREATE OR REPLACE RULE query_expr_xisnull_delete_rule AS
5739     ON DELETE TO query.expr_xisnull
5740     DO INSTEAD
5741     DELETE FROM query.expression WHERE id = OLD.id;
5742
5743 -- Create updatable view for NULL expressions
5744
5745 CREATE OR REPLACE VIEW query.expr_xnull AS
5746     SELECT
5747                 id,
5748                 parenthesize,
5749                 parent_expr,
5750                 seq_no,
5751                 negate
5752     FROM
5753         query.expression
5754     WHERE
5755         type = 'xnull';
5756
5757 CREATE OR REPLACE RULE query_expr_xnull_insert_rule AS
5758     ON INSERT TO query.expr_xnull
5759     DO INSTEAD
5760     INSERT INTO query.expression (
5761                 id,
5762                 type,
5763                 parenthesize,
5764                 parent_expr,
5765                 seq_no,
5766                 negate
5767     ) VALUES (
5768         COALESCE(NEW.id, NEXTVAL('query.expression_id_seq'::REGCLASS)),
5769         'xnull',
5770         COALESCE(NEW.parenthesize, FALSE),
5771         NEW.parent_expr,
5772         COALESCE(NEW.seq_no, 1),
5773                 COALESCE(NEW.negate, false)
5774     );
5775
5776 CREATE OR REPLACE RULE query_expr_xnull_update_rule AS
5777     ON UPDATE TO query.expr_xnull
5778     DO INSTEAD
5779     UPDATE query.expression SET
5780         id = NEW.id,
5781         parenthesize = NEW.parenthesize,
5782         parent_expr = NEW.parent_expr,
5783         seq_no = NEW.seq_no,
5784                 negate = NEW.negate
5785     WHERE
5786         id = OLD.id;
5787
5788 CREATE OR REPLACE RULE query_expr_xnull_delete_rule AS
5789     ON DELETE TO query.expr_xnull
5790     DO INSTEAD
5791     DELETE FROM query.expression WHERE id = OLD.id;
5792
5793 -- Create updatable view for numeric literal expressions
5794
5795 CREATE OR REPLACE VIEW query.expr_xnum AS
5796     SELECT
5797                 id,
5798                 parenthesize,
5799                 parent_expr,
5800                 seq_no,
5801                 literal
5802     FROM
5803         query.expression
5804     WHERE
5805         type = 'xnum';
5806
5807 CREATE OR REPLACE RULE query_expr_xnum_insert_rule AS
5808     ON INSERT TO query.expr_xnum
5809     DO INSTEAD
5810     INSERT INTO query.expression (
5811                 id,
5812                 type,
5813                 parenthesize,
5814                 parent_expr,
5815                 seq_no,
5816                 literal
5817     ) VALUES (
5818         COALESCE(NEW.id, NEXTVAL('query.expression_id_seq'::REGCLASS)),
5819         'xnum',
5820         COALESCE(NEW.parenthesize, FALSE),
5821         NEW.parent_expr,
5822         COALESCE(NEW.seq_no, 1),
5823         NEW.literal
5824     );
5825
5826 CREATE OR REPLACE RULE query_expr_xnum_update_rule AS
5827     ON UPDATE TO query.expr_xnum
5828     DO INSTEAD
5829     UPDATE query.expression SET
5830         id = NEW.id,
5831         parenthesize = NEW.parenthesize,
5832         parent_expr = NEW.parent_expr,
5833         seq_no = NEW.seq_no,
5834         literal = NEW.literal
5835     WHERE
5836         id = OLD.id;
5837
5838 CREATE OR REPLACE RULE query_expr_xnum_delete_rule AS
5839     ON DELETE TO query.expr_xnum
5840     DO INSTEAD
5841     DELETE FROM query.expression WHERE id = OLD.id;
5842
5843 -- Create updatable view for operator expressions
5844
5845 CREATE OR REPLACE VIEW query.expr_xop AS
5846     SELECT
5847                 id,
5848                 parenthesize,
5849                 parent_expr,
5850                 seq_no,
5851                 left_operand,
5852                 operator,
5853                 right_operand,
5854                 negate
5855     FROM
5856         query.expression
5857     WHERE
5858         type = 'xop';
5859
5860 CREATE OR REPLACE RULE query_expr_xop_insert_rule AS
5861     ON INSERT TO query.expr_xop
5862     DO INSTEAD
5863     INSERT INTO query.expression (
5864                 id,
5865                 type,
5866                 parenthesize,
5867                 parent_expr,
5868                 seq_no,
5869                 left_operand,
5870                 operator,
5871                 right_operand,
5872                 negate
5873     ) VALUES (
5874         COALESCE(NEW.id, NEXTVAL('query.expression_id_seq'::REGCLASS)),
5875         'xop',
5876         COALESCE(NEW.parenthesize, FALSE),
5877         NEW.parent_expr,
5878         COALESCE(NEW.seq_no, 1),
5879                 NEW.left_operand,
5880                 NEW.operator,
5881                 NEW.right_operand,
5882                 COALESCE(NEW.negate, false)
5883     );
5884
5885 CREATE OR REPLACE RULE query_expr_xop_update_rule AS
5886     ON UPDATE TO query.expr_xop
5887     DO INSTEAD
5888     UPDATE query.expression SET
5889         id = NEW.id,
5890         parenthesize = NEW.parenthesize,
5891         parent_expr = NEW.parent_expr,
5892         seq_no = NEW.seq_no,
5893                 left_operand = NEW.left_operand,
5894                 operator = NEW.operator,
5895                 right_operand = NEW.right_operand,
5896                 negate = NEW.negate
5897     WHERE
5898         id = OLD.id;
5899
5900 CREATE OR REPLACE RULE query_expr_xop_delete_rule AS
5901     ON DELETE TO query.expr_xop
5902     DO INSTEAD
5903     DELETE FROM query.expression WHERE id = OLD.id;
5904
5905 -- Create updatable view for series expressions
5906 -- i.e. series of expressions separated by operators
5907
5908 CREATE OR REPLACE VIEW query.expr_xser AS
5909     SELECT
5910                 id,
5911                 parenthesize,
5912                 parent_expr,
5913                 seq_no,
5914                 operator,
5915                 negate
5916     FROM
5917         query.expression
5918     WHERE
5919         type = 'xser';
5920
5921 CREATE OR REPLACE RULE query_expr_xser_insert_rule AS
5922     ON INSERT TO query.expr_xser
5923     DO INSTEAD
5924     INSERT INTO query.expression (
5925                 id,
5926                 type,
5927                 parenthesize,
5928                 parent_expr,
5929                 seq_no,
5930                 operator,
5931                 negate
5932     ) VALUES (
5933         COALESCE(NEW.id, NEXTVAL('query.expression_id_seq'::REGCLASS)),
5934         'xser',
5935         COALESCE(NEW.parenthesize, FALSE),
5936         NEW.parent_expr,
5937         COALESCE(NEW.seq_no, 1),
5938                 NEW.operator,
5939                 COALESCE(NEW.negate, false)
5940     );
5941
5942 CREATE OR REPLACE RULE query_expr_xser_update_rule AS
5943     ON UPDATE TO query.expr_xser
5944     DO INSTEAD
5945     UPDATE query.expression SET
5946         id = NEW.id,
5947         parenthesize = NEW.parenthesize,
5948         parent_expr = NEW.parent_expr,
5949         seq_no = NEW.seq_no,
5950                 operator = NEW.operator,
5951                 negate = NEW.negate
5952     WHERE
5953         id = OLD.id;
5954
5955 CREATE OR REPLACE RULE query_expr_xser_delete_rule AS
5956     ON DELETE TO query.expr_xser
5957     DO INSTEAD
5958     DELETE FROM query.expression WHERE id = OLD.id;
5959
5960 -- Create updatable view for string literal expressions
5961
5962 CREATE OR REPLACE VIEW query.expr_xstr AS
5963     SELECT
5964         id,
5965         parenthesize,
5966         parent_expr,
5967         seq_no,
5968         literal
5969     FROM
5970         query.expression
5971     WHERE
5972         type = 'xstr';
5973
5974 CREATE OR REPLACE RULE query_expr_string_insert_rule AS
5975     ON INSERT TO query.expr_xstr
5976     DO INSTEAD
5977     INSERT INTO query.expression (
5978         id,
5979         type,
5980         parenthesize,
5981         parent_expr,
5982         seq_no,
5983         literal
5984     ) VALUES (
5985         COALESCE(NEW.id, NEXTVAL('query.expression_id_seq'::REGCLASS)),
5986         'xstr',
5987         COALESCE(NEW.parenthesize, FALSE),
5988         NEW.parent_expr,
5989         COALESCE(NEW.seq_no, 1),
5990         NEW.literal
5991     );
5992
5993 CREATE OR REPLACE RULE query_expr_string_update_rule AS
5994     ON UPDATE TO query.expr_xstr
5995     DO INSTEAD
5996     UPDATE query.expression SET
5997         id = NEW.id,
5998         parenthesize = NEW.parenthesize,
5999         parent_expr = NEW.parent_expr,
6000         seq_no = NEW.seq_no,
6001         literal = NEW.literal
6002     WHERE
6003         id = OLD.id;
6004
6005 CREATE OR REPLACE RULE query_expr_string_delete_rule AS
6006     ON DELETE TO query.expr_xstr
6007     DO INSTEAD
6008     DELETE FROM query.expression WHERE id = OLD.id;
6009
6010 -- Create updatable view for subquery expressions
6011
6012 CREATE OR REPLACE VIEW query.expr_xsubq AS
6013     SELECT
6014                 id,
6015                 parenthesize,
6016                 parent_expr,
6017                 seq_no,
6018                 subquery,
6019                 negate
6020     FROM
6021         query.expression
6022     WHERE
6023         type = 'xsubq';
6024
6025 CREATE OR REPLACE RULE query_expr_xsubq_insert_rule AS
6026     ON INSERT TO query.expr_xsubq
6027     DO INSTEAD
6028     INSERT INTO query.expression (
6029                 id,
6030                 type,
6031                 parenthesize,
6032                 parent_expr,
6033                 seq_no,
6034                 subquery,
6035                 negate
6036     ) VALUES (
6037         COALESCE(NEW.id, NEXTVAL('query.expression_id_seq'::REGCLASS)),
6038         'xsubq',
6039         COALESCE(NEW.parenthesize, FALSE),
6040         NEW.parent_expr,
6041         COALESCE(NEW.seq_no, 1),
6042                 NEW.subquery,
6043                 COALESCE(NEW.negate, false)
6044     );
6045
6046 CREATE OR REPLACE RULE query_expr_xsubq_update_rule AS
6047     ON UPDATE TO query.expr_xsubq
6048     DO INSTEAD
6049     UPDATE query.expression SET
6050         id = NEW.id,
6051         parenthesize = NEW.parenthesize,
6052         parent_expr = NEW.parent_expr,
6053         seq_no = NEW.seq_no,
6054                 subquery = NEW.subquery,
6055                 negate = NEW.negate
6056     WHERE
6057         id = OLD.id;
6058
6059 CREATE OR REPLACE RULE query_expr_xsubq_delete_rule AS
6060     ON DELETE TO query.expr_xsubq
6061     DO INSTEAD
6062     DELETE FROM query.expression WHERE id = OLD.id;
6063
6064 CREATE TABLE action.fieldset (
6065     id              SERIAL          PRIMARY KEY,
6066     owner           INT             NOT NULL REFERENCES actor.usr (id)
6067                                     DEFERRABLE INITIALLY DEFERRED,
6068     owning_lib      INT             NOT NULL REFERENCES actor.org_unit (id)
6069                                     DEFERRABLE INITIALLY DEFERRED,
6070     status          TEXT            NOT NULL
6071                                     CONSTRAINT valid_status CHECK ( status in
6072                                     ( 'PENDING', 'APPLIED', 'ERROR' )),
6073     creation_time   TIMESTAMPTZ     NOT NULL DEFAULT NOW(),
6074     scheduled_time  TIMESTAMPTZ,
6075     applied_time    TIMESTAMPTZ,
6076     classname       TEXT            NOT NULL, -- an IDL class name
6077     name            TEXT            NOT NULL,
6078     stored_query    INT             REFERENCES query.stored_query (id)
6079                                     DEFERRABLE INITIALLY DEFERRED,
6080     pkey_value      TEXT,
6081     CONSTRAINT lib_name_unique UNIQUE (owning_lib, name),
6082     CONSTRAINT fieldset_one_or_the_other CHECK (
6083         (stored_query IS NOT NULL AND pkey_value IS NULL) OR
6084         (pkey_value IS NOT NULL AND stored_query IS NULL)
6085     )
6086     -- the CHECK constraint means we can update the fields for a single
6087     -- row without all the extra overhead involved in a query
6088 );
6089
6090 CREATE INDEX action_fieldset_sched_time_idx ON action.fieldset( scheduled_time );
6091 CREATE INDEX action_owner_idx               ON action.fieldset( owner );
6092
6093 CREATE TABLE action.fieldset_col_val (
6094     id              SERIAL  PRIMARY KEY,
6095     fieldset        INT     NOT NULL REFERENCES action.fieldset
6096                                          ON DELETE CASCADE
6097                                          DEFERRABLE INITIALLY DEFERRED,
6098     col             TEXT    NOT NULL,  -- "field" from the idl ... the column on the table
6099     val             TEXT,              -- value for the column ... NULL means, well, NULL
6100     CONSTRAINT fieldset_col_once_per_set UNIQUE (fieldset, col)
6101 );
6102
6103 CREATE OR REPLACE FUNCTION action.apply_fieldset(
6104         fieldset_id IN INT,        -- id from action.fieldset
6105         table_name  IN TEXT,       -- table to be updated
6106         pkey_name   IN TEXT,       -- name of primary key column in that table
6107         query       IN TEXT        -- query constructed by qstore (for query-based
6108                                    --    fieldsets only; otherwise null
6109 )
6110 RETURNS TEXT AS $$
6111 DECLARE
6112         statement TEXT;
6113         fs_status TEXT;
6114         fs_pkey_value TEXT;
6115         fs_query TEXT;
6116         sep CHAR;
6117         status_code TEXT;
6118         msg TEXT;
6119         update_count INT;
6120         cv RECORD;
6121 BEGIN
6122         -- Sanity checks
6123         IF fieldset_id IS NULL THEN
6124                 RETURN 'Fieldset ID parameter is NULL';
6125         END IF;
6126         IF table_name IS NULL THEN
6127                 RETURN 'Table name parameter is NULL';
6128         END IF;
6129         IF pkey_name IS NULL THEN
6130                 RETURN 'Primary key name parameter is NULL';
6131         END IF;
6132         --
6133         statement := 'UPDATE ' || table_name || ' SET';
6134         --
6135         SELECT
6136                 status,
6137                 quote_literal( pkey_value )
6138         INTO
6139                 fs_status,
6140                 fs_pkey_value
6141         FROM
6142                 action.fieldset
6143         WHERE
6144                 id = fieldset_id;
6145         --
6146         IF fs_status IS NULL THEN
6147                 RETURN 'No fieldset found for id = ' || fieldset_id;
6148         ELSIF fs_status = 'APPLIED' THEN
6149                 RETURN 'Fieldset ' || fieldset_id || ' has already been applied';
6150         END IF;
6151         --
6152         sep := '';
6153         FOR cv IN
6154                 SELECT  col,
6155                                 val
6156                 FROM    action.fieldset_col_val
6157                 WHERE   fieldset = fieldset_id
6158         LOOP
6159                 statement := statement || sep || ' ' || cv.col
6160                                          || ' = ' || coalesce( quote_literal( cv.val ), 'NULL' );
6161                 sep := ',';
6162         END LOOP;
6163         --
6164         IF sep = '' THEN
6165                 RETURN 'Fieldset ' || fieldset_id || ' has no column values defined';
6166         END IF;
6167         --
6168         -- Add the WHERE clause.  This differs according to whether it's a
6169         -- single-row fieldset or a query-based fieldset.
6170         --
6171         IF query IS NULL        AND fs_pkey_value IS NULL THEN
6172                 RETURN 'Incomplete fieldset: neither a primary key nor a query available';
6173         ELSIF query IS NOT NULL AND fs_pkey_value IS NULL THEN
6174             fs_query := rtrim( query, ';' );
6175             statement := statement || ' WHERE ' || pkey_name || ' IN ( '
6176                          || fs_query || ' );';
6177         ELSIF query IS NULL     AND fs_pkey_value IS NOT NULL THEN
6178                 statement := statement || ' WHERE ' || pkey_name || ' = '
6179                                      || fs_pkey_value || ';';
6180         ELSE  -- both are not null
6181                 RETURN 'Ambiguous fieldset: both a primary key and a query provided';
6182         END IF;
6183         --
6184         -- Execute the update
6185         --
6186         BEGIN
6187                 EXECUTE statement;
6188                 GET DIAGNOSTICS update_count = ROW_COUNT;
6189                 --
6190                 IF UPDATE_COUNT > 0 THEN
6191                         status_code := 'APPLIED';
6192                         msg := NULL;
6193                 ELSE
6194                         status_code := 'ERROR';
6195                         msg := 'No eligible rows found for fieldset ' || fieldset_id;
6196         END IF;
6197         EXCEPTION WHEN OTHERS THEN
6198                 status_code := 'ERROR';
6199                 msg := 'Unable to apply fieldset ' || fieldset_id
6200                            || ': ' || sqlerrm;
6201         END;
6202         --
6203         -- Update fieldset status
6204         --
6205         UPDATE action.fieldset
6206         SET status       = status_code,
6207             applied_time = now()
6208         WHERE id = fieldset_id;
6209         --
6210         RETURN msg;
6211 END;
6212 $$ LANGUAGE plpgsql;
6213
6214 COMMENT ON FUNCTION action.apply_fieldset( INT, TEXT, TEXT, TEXT ) IS $$
6215 /**
6216  * Applies a specified fieldset, using a supplied table name and primary
6217  * key name.  The query parameter should be non-null only for
6218  * query-based fieldsets.
6219  *
6220  * Returns NULL if successful, or an error message if not.
6221  */
6222 $$;
6223
6224 CREATE INDEX uhr_hold_idx ON action.unfulfilled_hold_list (hold);
6225
6226 CREATE OR REPLACE VIEW action.unfulfilled_hold_loops AS
6227     SELECT  u.hold,
6228             c.circ_lib,
6229             count(*)
6230       FROM  action.unfulfilled_hold_list u
6231             JOIN asset.copy c ON (c.id = u.current_copy)
6232       GROUP BY 1,2;
6233
6234 CREATE OR REPLACE VIEW action.unfulfilled_hold_min_loop AS
6235     SELECT  hold,
6236             min(count)
6237       FROM  action.unfulfilled_hold_loops
6238       GROUP BY 1;
6239
6240 CREATE OR REPLACE VIEW action.unfulfilled_hold_innermost_loop AS
6241     SELECT  DISTINCT l.*
6242       FROM  action.unfulfilled_hold_loops l
6243             JOIN action.unfulfilled_hold_min_loop m USING (hold)
6244       WHERE l.count = m.min;
6245
6246 ALTER TABLE asset.copy
6247 ADD COLUMN dummy_isbn TEXT;
6248
6249 ALTER TABLE auditor.asset_copy_history
6250 ADD COLUMN dummy_isbn TEXT;
6251
6252 -- Add new column status_changed_date to asset.copy, with trigger to maintain it
6253 -- Add corresponding new column to auditor.asset_copy_history
6254
6255 ALTER TABLE asset.copy
6256         ADD COLUMN status_changed_time TIMESTAMPTZ;
6257
6258 ALTER TABLE auditor.asset_copy_history
6259         ADD COLUMN status_changed_time TIMESTAMPTZ;
6260
6261 CREATE OR REPLACE FUNCTION asset.acp_status_changed()
6262 RETURNS TRIGGER AS $$
6263 BEGIN
6264     IF NEW.status <> OLD.status THEN
6265         NEW.status_changed_time := now();
6266     END IF;
6267     RETURN NEW;
6268 END;
6269 $$ LANGUAGE plpgsql;
6270
6271 CREATE TRIGGER acp_status_changed_trig
6272         BEFORE UPDATE ON asset.copy
6273         FOR EACH ROW EXECUTE PROCEDURE asset.acp_status_changed();
6274
6275 ALTER TABLE asset.copy
6276 ADD COLUMN mint_condition boolean NOT NULL DEFAULT TRUE;
6277
6278 ALTER TABLE auditor.asset_copy_history
6279 ADD COLUMN mint_condition boolean NOT NULL DEFAULT TRUE;
6280
6281 ALTER TABLE asset.copy ADD COLUMN floating BOOL NOT NULL DEFAULT FALSE;
6282 ALTER TABLE auditor.asset_copy_history ADD COLUMN floating BOOL;
6283
6284 DROP INDEX IF EXISTS asset.copy_barcode_key;
6285 CREATE UNIQUE INDEX copy_barcode_key ON asset.copy (barcode) WHERE deleted = FALSE OR deleted IS FALSE;
6286
6287 -- Note: later we create a trigger a_opac_vis_mat_view_tgr
6288 -- AFTER INSERT OR UPDATE ON asset.copy
6289
6290 ALTER TABLE asset.copy ADD COLUMN cost NUMERIC(8,2);
6291 ALTER TABLE auditor.asset_copy_history ADD COLUMN cost NUMERIC(8,2);
6292
6293 -- Moke mostly parallel changes to action.circulation
6294 -- and action.aged_circulation
6295
6296 ALTER TABLE action.circulation
6297 ADD COLUMN workstation INT
6298     REFERENCES actor.workstation
6299         ON DELETE SET NULL
6300         DEFERRABLE INITIALLY DEFERRED;
6301
6302 ALTER TABLE action.aged_circulation
6303 ADD COLUMN workstation INT;
6304
6305 ALTER TABLE action.circulation
6306 ADD COLUMN parent_circ BIGINT
6307         REFERENCES action.circulation(id)
6308         DEFERRABLE INITIALLY DEFERRED;
6309
6310 CREATE UNIQUE INDEX circ_parent_idx
6311 ON action.circulation( parent_circ )
6312 WHERE parent_circ IS NOT NULL;
6313
6314 ALTER TABLE action.aged_circulation
6315 ADD COLUMN parent_circ BIGINT;
6316
6317 ALTER TABLE action.circulation
6318 ADD COLUMN checkin_workstation INT
6319         REFERENCES actor.workstation(id)
6320         ON DELETE SET NULL
6321         DEFERRABLE INITIALLY DEFERRED;
6322
6323 ALTER TABLE action.aged_circulation
6324 ADD COLUMN checkin_workstation INT;
6325
6326 ALTER TABLE action.circulation
6327 ADD COLUMN checkin_scan_time TIMESTAMPTZ;
6328
6329 ALTER TABLE action.aged_circulation
6330 ADD COLUMN checkin_scan_time TIMESTAMPTZ;
6331
6332 CREATE INDEX action_circulation_target_copy_idx
6333 ON action.circulation (target_copy);
6334
6335 CREATE INDEX action_aged_circulation_target_copy_idx
6336 ON action.aged_circulation (target_copy);
6337
6338 ALTER TABLE action.circulation
6339 DROP CONSTRAINT circulation_stop_fines_check;
6340
6341 ALTER TABLE action.circulation
6342         ADD CONSTRAINT circulation_stop_fines_check
6343         CHECK (stop_fines IN (
6344         'CHECKIN','CLAIMSRETURNED','LOST','MAXFINES','RENEW','LONGOVERDUE','CLAIMSNEVERCHECKEDOUT'));
6345
6346 -- Hard due-date functionality
6347 CREATE TABLE config.hard_due_date (
6348         id          SERIAL      PRIMARY KEY,
6349         name        TEXT        NOT NULL UNIQUE CHECK ( name ~ E'^\\w+$' ),
6350         ceiling_date    TIMESTAMPTZ NOT NULL,
6351         forceto     BOOL        NOT NULL,
6352         owner       INT         NOT NULL
6353 );
6354
6355 CREATE TABLE config.hard_due_date_values (
6356     id                  SERIAL      PRIMARY KEY,
6357     hard_due_date       INT         NOT NULL REFERENCES config.hard_due_date (id)
6358                                     DEFERRABLE INITIALLY DEFERRED,
6359     ceiling_date        TIMESTAMPTZ NOT NULL,
6360     active_date         TIMESTAMPTZ NOT NULL
6361 );
6362
6363 ALTER TABLE config.circ_matrix_matchpoint ADD COLUMN hard_due_date INT REFERENCES config.hard_due_date (id);
6364
6365 CREATE OR REPLACE FUNCTION config.update_hard_due_dates () RETURNS INT AS $func$
6366 DECLARE
6367     temp_value  config.hard_due_date_values%ROWTYPE;
6368     updated     INT := 0;
6369 BEGIN
6370     FOR temp_value IN
6371       SELECT  DISTINCT ON (hard_due_date) *
6372         FROM  config.hard_due_date_values
6373         WHERE active_date <= NOW() -- We've passed (or are at) the rollover time
6374         ORDER BY active_date DESC -- Latest (nearest to us) active time
6375    LOOP
6376         UPDATE  config.hard_due_date
6377           SET   ceiling_date = temp_value.ceiling_date
6378           WHERE id = temp_value.hard_due_date
6379                 AND ceiling_date <> temp_value.ceiling_date; -- Time is equal if we've already updated the chdd
6380
6381         IF FOUND THEN
6382             updated := updated + 1;
6383         END IF;
6384     END LOOP;
6385
6386     RETURN updated;
6387 END;
6388 $func$ LANGUAGE plpgsql;
6389
6390 -- Correct some long-standing misspellings involving variations of "recur"
6391
6392 ALTER TABLE action.circulation RENAME COLUMN recuring_fine TO recurring_fine;
6393 ALTER TABLE action.circulation RENAME COLUMN recuring_fine_rule TO recurring_fine_rule;
6394
6395 ALTER TABLE action.aged_circulation RENAME COLUMN recuring_fine TO recurring_fine;
6396 ALTER TABLE action.aged_circulation RENAME COLUMN recuring_fine_rule TO recurring_fine_rule;
6397
6398 ALTER TABLE config.rule_recuring_fine RENAME TO rule_recurring_fine;
6399 ALTER TABLE config.rule_recuring_fine_id_seq RENAME TO rule_recurring_fine_id_seq;
6400
6401 ALTER TABLE config.rule_recurring_fine RENAME COLUMN recurance_interval TO recurrence_interval;
6402
6403 -- Might as well keep the comment in sync as well
6404 COMMENT ON TABLE config.rule_recurring_fine IS $$
6405 /*
6406  * Copyright (C) 2005  Georgia Public Library Service 
6407  * Mike Rylander <mrylander@gmail.com>
6408  *
6409  * Circulation Recurring Fine rules
6410  *
6411  * Each circulation is given a recurring fine amount based on one of
6412  * these rules.  The recurrence_interval should not be any shorter
6413  * than the interval between runs of the fine_processor.pl script
6414  * (which is run from CRON), or you could miss fines.
6415  * 
6416  *
6417  * ****
6418  *
6419  * This program is free software; you can redistribute it and/or
6420  * modify it under the terms of the GNU General Public License
6421  * as published by the Free Software Foundation; either version 2
6422  * of the License, or (at your option) any later version.
6423  *
6424  * This program is distributed in the hope that it will be useful,
6425  * but WITHOUT ANY WARRANTY; without even the implied warranty of
6426  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
6427  * GNU General Public License for more details.
6428  */
6429 $$;
6430
6431 -- Extend the name change to some related views:
6432
6433 DROP VIEW IF EXISTS reporter.overdue_circs;
6434
6435 CREATE OR REPLACE VIEW reporter.overdue_circs AS
6436 SELECT  *
6437   FROM  action.circulation
6438     WHERE checkin_time is null
6439                 AND (stop_fines NOT IN ('LOST','CLAIMSRETURNED') OR stop_fines IS NULL)
6440                                 AND due_date < now();
6441
6442 DROP VIEW IF EXISTS stats.fleshed_circulation;
6443
6444 DROP VIEW IF EXISTS stats.fleshed_copy;
6445
6446 CREATE VIEW stats.fleshed_copy AS
6447         SELECT  cp.*,
6448         CAST(cp.create_date AS DATE) AS create_date_day,
6449         CAST(cp.edit_date AS DATE) AS edit_date_day,
6450         DATE_TRUNC('hour', cp.create_date) AS create_date_hour,
6451         DATE_TRUNC('hour', cp.edit_date) AS edit_date_hour,
6452                 cn.label AS call_number_label,
6453                 cn.owning_lib,
6454                 rd.item_lang,
6455                 rd.item_type,
6456                 rd.item_form
6457         FROM    asset.copy cp
6458                 JOIN asset.call_number cn ON (cp.call_number = cn.id)
6459                 JOIN metabib.rec_descriptor rd ON (rd.record = cn.record);
6460
6461 CREATE VIEW stats.fleshed_circulation AS
6462         SELECT  c.*,
6463                 CAST(c.xact_start AS DATE) AS start_date_day,
6464                 CAST(c.xact_finish AS DATE) AS finish_date_day,
6465                 DATE_TRUNC('hour', c.xact_start) AS start_date_hour,
6466                 DATE_TRUNC('hour', c.xact_finish) AS finish_date_hour,
6467                 cp.call_number_label,
6468                 cp.owning_lib,
6469                 cp.item_lang,
6470                 cp.item_type,
6471                 cp.item_form
6472         FROM    action.circulation c
6473                 JOIN stats.fleshed_copy cp ON (cp.id = c.target_copy);
6474
6475 -- Drop a view temporarily in order to alter action.all_circulation, upon
6476 -- which it is dependent.  We will recreate the view later.
6477
6478 DROP VIEW IF EXISTS extend_reporter.full_circ_count;
6479
6480 -- You would think that CREATE OR REPLACE would be enough, but in testing
6481 -- PostgreSQL complained about renaming the columns in the view. So we
6482 -- drop the view first.
6483 DROP VIEW IF EXISTS action.all_circulation;
6484
6485 CREATE OR REPLACE VIEW action.all_circulation AS
6486     SELECT  id,usr_post_code, usr_home_ou, usr_profile, usr_birth_year, copy_call_number, copy_location,
6487         copy_owning_lib, copy_circ_lib, copy_bib_record, xact_start, xact_finish, target_copy,
6488         circ_lib, circ_staff, checkin_staff, checkin_lib, renewal_remaining, due_date,
6489         stop_fines_time, checkin_time, create_time, duration, fine_interval, recurring_fine,
6490         max_fine, phone_renewal, desk_renewal, opac_renewal, duration_rule, recurring_fine_rule,
6491         max_fine_rule, stop_fines, workstation, checkin_workstation, checkin_scan_time, parent_circ
6492       FROM  action.aged_circulation
6493             UNION ALL
6494     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,
6495         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,
6496         cn.record AS copy_bib_record, circ.xact_start, circ.xact_finish, circ.target_copy, circ.circ_lib, circ.circ_staff, circ.checkin_staff,
6497         circ.checkin_lib, circ.renewal_remaining, circ.due_date, circ.stop_fines_time, circ.checkin_time, circ.create_time, circ.duration,
6498         circ.fine_interval, circ.recurring_fine, circ.max_fine, circ.phone_renewal, circ.desk_renewal, circ.opac_renewal, circ.duration_rule,
6499         circ.recurring_fine_rule, circ.max_fine_rule, circ.stop_fines, circ.workstation, circ.checkin_workstation, circ.checkin_scan_time,
6500         circ.parent_circ
6501       FROM  action.circulation circ
6502         JOIN asset.copy cp ON (circ.target_copy = cp.id)
6503         JOIN asset.call_number cn ON (cp.call_number = cn.id)
6504         JOIN actor.usr p ON (circ.usr = p.id)
6505         LEFT JOIN actor.usr_address a ON (p.mailing_address = a.id)
6506         LEFT JOIN actor.usr_address b ON (p.billing_address = a.id);
6507
6508 -- Recreate the temporarily dropped view, having altered the action.all_circulation view:
6509
6510 CREATE OR REPLACE VIEW extend_reporter.full_circ_count AS
6511  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
6512    FROM asset."copy" cp
6513    LEFT JOIN extend_reporter.legacy_circ_count c USING (id)
6514    LEFT JOIN "action".circulation circ ON circ.target_copy = cp.id
6515    LEFT JOIN "action".aged_circulation acirc ON acirc.target_copy = cp.id
6516   GROUP BY cp.id;
6517
6518 CREATE UNIQUE INDEX only_one_concurrent_checkout_per_copy ON action.circulation(target_copy) WHERE checkin_time IS NULL;
6519
6520 ALTER TABLE action.circulation DROP CONSTRAINT action_circulation_target_copy_fkey;
6521
6522 -- Rebuild dependent views
6523
6524 DROP VIEW IF EXISTS action.billable_circulations;
6525
6526 CREATE OR REPLACE VIEW action.billable_circulations AS
6527     SELECT  *
6528       FROM  action.circulation
6529       WHERE xact_finish IS NULL;
6530
6531 DROP VIEW IF EXISTS action.open_circulation;
6532
6533 CREATE OR REPLACE VIEW action.open_circulation AS
6534     SELECT  *
6535       FROM  action.circulation
6536       WHERE checkin_time IS NULL
6537       ORDER BY due_date;
6538
6539 CREATE OR REPLACE FUNCTION action.age_circ_on_delete () RETURNS TRIGGER AS $$
6540 DECLARE
6541 found char := 'N';
6542 BEGIN
6543
6544     -- If there are any renewals for this circulation, don't archive or delete
6545     -- it yet.   We'll do so later, when we archive and delete the renewals.
6546
6547     SELECT 'Y' INTO found
6548     FROM action.circulation
6549     WHERE parent_circ = OLD.id
6550     LIMIT 1;
6551
6552     IF found = 'Y' THEN
6553         RETURN NULL;  -- don't delete
6554         END IF;
6555
6556     -- Archive a copy of the old row to action.aged_circulation
6557
6558     INSERT INTO action.aged_circulation
6559         (id,usr_post_code, usr_home_ou, usr_profile, usr_birth_year, copy_call_number, copy_location,
6560         copy_owning_lib, copy_circ_lib, copy_bib_record, xact_start, xact_finish, target_copy,
6561         circ_lib, circ_staff, checkin_staff, checkin_lib, renewal_remaining, due_date,
6562         stop_fines_time, checkin_time, create_time, duration, fine_interval, recurring_fine,
6563         max_fine, phone_renewal, desk_renewal, opac_renewal, duration_rule, recurring_fine_rule,
6564         max_fine_rule, stop_fines, workstation, checkin_workstation, checkin_scan_time, parent_circ)
6565       SELECT
6566         id,usr_post_code, usr_home_ou, usr_profile, usr_birth_year, copy_call_number, copy_location,
6567         copy_owning_lib, copy_circ_lib, copy_bib_record, xact_start, xact_finish, target_copy,
6568         circ_lib, circ_staff, checkin_staff, checkin_lib, renewal_remaining, due_date,
6569         stop_fines_time, checkin_time, create_time, duration, fine_interval, recurring_fine,
6570         max_fine, phone_renewal, desk_renewal, opac_renewal, duration_rule, recurring_fine_rule,
6571         max_fine_rule, stop_fines, workstation, checkin_workstation, checkin_scan_time, parent_circ
6572         FROM action.all_circulation WHERE id = OLD.id;
6573
6574     RETURN OLD;
6575 END;
6576 $$ LANGUAGE 'plpgsql';
6577
6578 UPDATE config.z3950_attr SET truncation = 1 WHERE source = 'biblios' AND name = 'title';
6579
6580 UPDATE config.z3950_attr SET truncation = 1 WHERE source = 'biblios' AND truncation = 0;
6581
6582 -- Adding circ.holds.target_skip_me OU setting logic to the pre-matchpoint tests
6583
6584 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$
6585 DECLARE
6586     current_requestor_group    permission.grp_tree%ROWTYPE;
6587     requestor_object    actor.usr%ROWTYPE;
6588     user_object        actor.usr%ROWTYPE;
6589     item_object        asset.copy%ROWTYPE;
6590     item_cn_object        asset.call_number%ROWTYPE;
6591     rec_descriptor        metabib.rec_descriptor%ROWTYPE;
6592     current_mp_weight    FLOAT;
6593     matchpoint_weight    FLOAT;
6594     tmp_weight        FLOAT;
6595     current_mp        config.hold_matrix_matchpoint%ROWTYPE;
6596     matchpoint        config.hold_matrix_matchpoint%ROWTYPE;
6597 BEGIN
6598     SELECT INTO user_object * FROM actor.usr WHERE id = match_user;
6599     SELECT INTO requestor_object * FROM actor.usr WHERE id = match_requestor;
6600     SELECT INTO item_object * FROM asset.copy WHERE id = match_item;
6601     SELECT INTO item_cn_object * FROM asset.call_number WHERE id = item_object.call_number;
6602     SELECT INTO rec_descriptor r.* FROM metabib.rec_descriptor r WHERE r.record = item_cn_object.record;
6603
6604     PERFORM * FROM config.internal_flag WHERE name = 'circ.holds.usr_not_requestor' AND enabled;
6605
6606     IF NOT FOUND THEN
6607         SELECT INTO current_requestor_group * FROM permission.grp_tree WHERE id = requestor_object.profile;
6608     ELSE
6609         SELECT INTO current_requestor_group * FROM permission.grp_tree WHERE id = user_object.profile;
6610     END IF;
6611
6612     LOOP 
6613         -- for each potential matchpoint for this ou and group ...
6614         FOR current_mp IN
6615             SELECT    m.*
6616               FROM    config.hold_matrix_matchpoint m
6617               WHERE    m.requestor_grp = current_requestor_group.id AND m.active
6618               ORDER BY    CASE WHEN m.circ_modifier    IS NOT NULL THEN 16 ELSE 0 END +
6619                     CASE WHEN m.juvenile_flag    IS NOT NULL THEN 16 ELSE 0 END +
6620                     CASE WHEN m.marc_type        IS NOT NULL THEN 8 ELSE 0 END +
6621                     CASE WHEN m.marc_form        IS NOT NULL THEN 4 ELSE 0 END +
6622                     CASE WHEN m.marc_vr_format    IS NOT NULL THEN 2 ELSE 0 END +
6623                     CASE WHEN m.ref_flag        IS NOT NULL THEN 1 ELSE 0 END DESC LOOP
6624
6625             current_mp_weight := 5.0;
6626
6627             IF current_mp.circ_modifier IS NOT NULL THEN
6628                 CONTINUE WHEN current_mp.circ_modifier <> item_object.circ_modifier OR item_object.circ_modifier IS NULL;
6629             END IF;
6630
6631             IF current_mp.marc_type IS NOT NULL THEN
6632                 IF item_object.circ_as_type IS NOT NULL THEN
6633                     CONTINUE WHEN current_mp.marc_type <> item_object.circ_as_type;
6634                 ELSE
6635                     CONTINUE WHEN current_mp.marc_type <> rec_descriptor.item_type;
6636                 END IF;
6637             END IF;
6638
6639             IF current_mp.marc_form IS NOT NULL THEN
6640                 CONTINUE WHEN current_mp.marc_form <> rec_descriptor.item_form;
6641             END IF;
6642
6643             IF current_mp.marc_vr_format IS NOT NULL THEN
6644                 CONTINUE WHEN current_mp.marc_vr_format <> rec_descriptor.vr_format;
6645             END IF;
6646
6647             IF current_mp.juvenile_flag IS NOT NULL THEN
6648                 CONTINUE WHEN current_mp.juvenile_flag <> user_object.juvenile;
6649             END IF;
6650
6651             IF current_mp.ref_flag IS NOT NULL THEN
6652                 CONTINUE WHEN current_mp.ref_flag <> item_object.ref;
6653             END IF;
6654
6655
6656             -- caclulate the rule match weight
6657             IF current_mp.item_owning_ou IS NOT NULL THEN
6658                 CONTINUE WHEN current_mp.item_owning_ou NOT IN (SELECT (actor.org_unit_ancestors(item_cn_object.owning_lib)).id);
6659                 SELECT INTO tmp_weight 1.0 / (actor.org_unit_proximity(current_mp.item_owning_ou, item_cn_object.owning_lib)::FLOAT + 1.0)::FLOAT;
6660                 current_mp_weight := current_mp_weight - tmp_weight;
6661             END IF; 
6662
6663             IF current_mp.item_circ_ou IS NOT NULL THEN
6664                 CONTINUE WHEN current_mp.item_circ_ou NOT IN (SELECT (actor.org_unit_ancestors(item_object.circ_lib)).id);
6665                 SELECT INTO tmp_weight 1.0 / (actor.org_unit_proximity(current_mp.item_circ_ou, item_object.circ_lib)::FLOAT + 1.0)::FLOAT;
6666                 current_mp_weight := current_mp_weight - tmp_weight;
6667             END IF; 
6668
6669             IF current_mp.pickup_ou IS NOT NULL THEN
6670                 CONTINUE WHEN current_mp.pickup_ou NOT IN (SELECT (actor.org_unit_ancestors(pickup_ou)).id);
6671                 SELECT INTO tmp_weight 1.0 / (actor.org_unit_proximity(current_mp.pickup_ou, pickup_ou)::FLOAT + 1.0)::FLOAT;
6672                 current_mp_weight := current_mp_weight - tmp_weight;
6673             END IF; 
6674
6675             IF current_mp.request_ou IS NOT NULL THEN
6676                 CONTINUE WHEN current_mp.request_ou NOT IN (SELECT (actor.org_unit_ancestors(request_ou)).id);
6677                 SELECT INTO tmp_weight 1.0 / (actor.org_unit_proximity(current_mp.request_ou, request_ou)::FLOAT + 1.0)::FLOAT;
6678                 current_mp_weight := current_mp_weight - tmp_weight;
6679             END IF; 
6680
6681             IF current_mp.user_home_ou IS NOT NULL THEN
6682                 CONTINUE WHEN current_mp.user_home_ou NOT IN (SELECT (actor.org_unit_ancestors(user_object.home_ou)).id);
6683                 SELECT INTO tmp_weight 1.0 / (actor.org_unit_proximity(current_mp.user_home_ou, user_object.home_ou)::FLOAT + 1.0)::FLOAT;
6684                 current_mp_weight := current_mp_weight - tmp_weight;
6685             END IF; 
6686
6687             -- set the matchpoint if we found the best one
6688             IF matchpoint_weight IS NULL OR matchpoint_weight > current_mp_weight THEN
6689                 matchpoint = current_mp;
6690                 matchpoint_weight = current_mp_weight;
6691             END IF;
6692
6693         END LOOP;
6694
6695         EXIT WHEN current_requestor_group.parent IS NULL OR matchpoint.id IS NOT NULL;
6696
6697         SELECT INTO current_requestor_group * FROM permission.grp_tree WHERE id = current_requestor_group.parent;
6698     END LOOP;
6699
6700     RETURN matchpoint.id;
6701 END;
6702 $func$ LANGUAGE plpgsql;
6703
6704 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$
6705 DECLARE
6706     matchpoint_id        INT;
6707     user_object        actor.usr%ROWTYPE;
6708     age_protect_object    config.rule_age_hold_protect%ROWTYPE;
6709     standing_penalty    config.standing_penalty%ROWTYPE;
6710     transit_range_ou_type    actor.org_unit_type%ROWTYPE;
6711     transit_source        actor.org_unit%ROWTYPE;
6712     item_object        asset.copy%ROWTYPE;
6713     ou_skip              actor.org_unit_setting%ROWTYPE;
6714     result            action.matrix_test_result;
6715     hold_test        config.hold_matrix_matchpoint%ROWTYPE;
6716     hold_count        INT;
6717     hold_transit_prox    INT;
6718     frozen_hold_count    INT;
6719     context_org_list    INT[];
6720     done            BOOL := FALSE;
6721 BEGIN
6722     SELECT INTO user_object * FROM actor.usr WHERE id = match_user;
6723     SELECT INTO context_org_list ARRAY_ACCUM(id) FROM actor.org_unit_full_path( pickup_ou );
6724
6725     result.success := TRUE;
6726
6727     -- Fail if we couldn't find a user
6728     IF user_object.id IS NULL THEN
6729         result.fail_part := 'no_user';
6730         result.success := FALSE;
6731         done := TRUE;
6732         RETURN NEXT result;
6733         RETURN;
6734     END IF;
6735
6736     SELECT INTO item_object * FROM asset.copy WHERE id = match_item;
6737
6738     -- Fail if we couldn't find a copy
6739     IF item_object.id IS NULL THEN
6740         result.fail_part := 'no_item';
6741         result.success := FALSE;
6742         done := TRUE;
6743         RETURN NEXT result;
6744         RETURN;
6745     END IF;
6746
6747     SELECT INTO matchpoint_id action.find_hold_matrix_matchpoint(pickup_ou, request_ou, match_item, match_user, match_requestor);
6748     result.matchpoint := matchpoint_id;
6749
6750     SELECT INTO ou_skip * FROM actor.org_unit_setting WHERE name = 'circ.holds.target_skip_me' AND org_unit = item_object.circ_lib;
6751
6752     -- Fail if the circ_lib for the item has circ.holds.target_skip_me set to true
6753     IF ou_skip.id IS NOT NULL AND ou_skip.value = 'true' THEN
6754         result.fail_part := 'circ.holds.target_skip_me';
6755         result.success := FALSE;
6756         done := TRUE;
6757         RETURN NEXT result;
6758         RETURN;
6759     END IF;
6760
6761     -- Fail if user is barred
6762     IF user_object.barred IS TRUE THEN
6763         result.fail_part := 'actor.usr.barred';
6764         result.success := FALSE;
6765         done := TRUE;
6766         RETURN NEXT result;
6767         RETURN;
6768     END IF;
6769
6770     -- Fail if we couldn't find any matchpoint (requires a default)
6771     IF matchpoint_id IS NULL THEN
6772         result.fail_part := 'no_matchpoint';
6773         result.success := FALSE;
6774         done := TRUE;
6775         RETURN NEXT result;
6776         RETURN;
6777     END IF;
6778
6779     SELECT INTO hold_test * FROM config.hold_matrix_matchpoint WHERE id = matchpoint_id;
6780
6781     IF hold_test.holdable IS FALSE THEN
6782         result.fail_part := 'config.hold_matrix_test.holdable';
6783         result.success := FALSE;
6784         done := TRUE;
6785         RETURN NEXT result;
6786     END IF;
6787
6788     IF hold_test.transit_range IS NOT NULL THEN
6789         SELECT INTO transit_range_ou_type * FROM actor.org_unit_type WHERE id = hold_test.transit_range;
6790         IF hold_test.distance_is_from_owner THEN
6791             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;
6792         ELSE
6793             SELECT INTO transit_source * FROM actor.org_unit WHERE id = item_object.circ_lib;
6794         END IF;
6795
6796         PERFORM * FROM actor.org_unit_descendants( transit_source.id, transit_range_ou_type.depth ) WHERE id = pickup_ou;
6797
6798         IF NOT FOUND THEN
6799             result.fail_part := 'transit_range';
6800             result.success := FALSE;
6801             done := TRUE;
6802             RETURN NEXT result;
6803         END IF;
6804     END IF;
6805  
6806     IF NOT retargetting THEN
6807         FOR standing_penalty IN
6808             SELECT  DISTINCT csp.*
6809               FROM  actor.usr_standing_penalty usp
6810                     JOIN config.standing_penalty csp ON (csp.id = usp.standing_penalty)
6811               WHERE usr = match_user
6812                     AND usp.org_unit IN ( SELECT * FROM explode_array(context_org_list) )
6813                     AND (usp.stop_date IS NULL or usp.stop_date > NOW())
6814                     AND csp.block_list LIKE '%HOLD%' LOOP
6815     
6816             result.fail_part := standing_penalty.name;
6817             result.success := FALSE;
6818             done := TRUE;
6819             RETURN NEXT result;
6820         END LOOP;
6821     
6822         IF hold_test.stop_blocked_user IS TRUE THEN
6823             FOR standing_penalty IN
6824                 SELECT  DISTINCT csp.*
6825                   FROM  actor.usr_standing_penalty usp
6826                         JOIN config.standing_penalty csp ON (csp.id = usp.standing_penalty)
6827                   WHERE usr = match_user
6828                         AND usp.org_unit IN ( SELECT * FROM explode_array(context_org_list) )
6829                         AND (usp.stop_date IS NULL or usp.stop_date > NOW())
6830                         AND csp.block_list LIKE '%CIRC%' LOOP
6831         
6832                 result.fail_part := standing_penalty.name;
6833                 result.success := FALSE;
6834                 done := TRUE;
6835                 RETURN NEXT result;
6836             END LOOP;
6837         END IF;
6838     
6839         IF hold_test.max_holds IS NOT NULL THEN
6840             SELECT    INTO hold_count COUNT(*)
6841               FROM    action.hold_request
6842               WHERE    usr = match_user
6843                 AND fulfillment_time IS NULL
6844                 AND cancel_time IS NULL
6845                 AND CASE WHEN hold_test.include_frozen_holds THEN TRUE ELSE frozen IS FALSE END;
6846     
6847             IF hold_count >= hold_test.max_holds THEN
6848                 result.fail_part := 'config.hold_matrix_test.max_holds';
6849                 result.success := FALSE;
6850                 done := TRUE;
6851                 RETURN NEXT result;
6852             END IF;
6853         END IF;
6854     END IF;
6855
6856     IF item_object.age_protect IS NOT NULL THEN
6857         SELECT INTO age_protect_object * FROM config.rule_age_hold_protect WHERE id = item_object.age_protect;
6858
6859         IF item_object.create_date + age_protect_object.age > NOW() THEN
6860             IF hold_test.distance_is_from_owner THEN
6861                 SELECT INTO hold_transit_prox prox FROM actor.org_unit_proximity WHERE from_org = item_cn_object.owning_lib AND to_org = pickup_ou;
6862             ELSE
6863                 SELECT INTO hold_transit_prox prox FROM actor.org_unit_proximity WHERE from_org = item_object.circ_lib AND to_org = pickup_ou;
6864             END IF;
6865
6866             IF hold_transit_prox > age_protect_object.prox THEN
6867                 result.fail_part := 'config.rule_age_hold_protect.prox';
6868                 result.success := FALSE;
6869                 done := TRUE;
6870                 RETURN NEXT result;
6871             END IF;
6872         END IF;
6873     END IF;
6874
6875     IF NOT done THEN
6876         RETURN NEXT result;
6877     END IF;
6878
6879     RETURN;
6880 END;
6881 $func$ LANGUAGE plpgsql;
6882
6883 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$
6884     SELECT * FROM action.hold_request_permit_test( $1, $2, $3, $4, $5, FALSE);
6885 $func$ LANGUAGE SQL;
6886
6887 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$
6888     SELECT * FROM action.hold_request_permit_test( $1, $2, $3, $4, $5, TRUE );
6889 $func$ LANGUAGE SQL;
6890
6891 -- New post-delete trigger to propagate deletions to parent(s)
6892
6893 CREATE OR REPLACE FUNCTION action.age_parent_circ_on_delete () RETURNS TRIGGER AS $$
6894 BEGIN
6895
6896     -- Having deleted a renewal, we can delete the original circulation (or a previous
6897     -- renewal, if that's what parent_circ is pointing to).  That deletion will trigger
6898     -- deletion of any prior parents, etc. recursively.
6899
6900     IF OLD.parent_circ IS NOT NULL THEN
6901         DELETE FROM action.circulation
6902         WHERE id = OLD.parent_circ;
6903     END IF;
6904
6905     RETURN OLD;
6906 END;
6907 $$ LANGUAGE 'plpgsql';
6908
6909 CREATE TRIGGER age_parent_circ AFTER DELETE ON action.circulation
6910 FOR EACH ROW EXECUTE PROCEDURE action.age_parent_circ_on_delete ();
6911
6912 -- This only gets inserted if there are no other id > 100 billing types
6913 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;
6914 SELECT SETVAL('config.billing_type_id_seq'::TEXT, 101) FROM config.billing_type_id_seq WHERE last_value < 101;
6915
6916 -- Populate xact_type column in the materialized version of billable_xact_summary
6917
6918 CREATE OR REPLACE FUNCTION money.mat_summary_create () RETURNS TRIGGER AS $$
6919 BEGIN
6920         INSERT INTO money.materialized_billable_xact_summary (id, usr, xact_start, xact_finish, total_paid, total_owed, balance_owed, xact_type)
6921                 VALUES ( NEW.id, NEW.usr, NEW.xact_start, NEW.xact_finish, 0.0, 0.0, 0.0, TG_ARGV[0]);
6922         RETURN NEW;
6923 END;
6924 $$ LANGUAGE PLPGSQL;
6925  
6926 DROP TRIGGER IF EXISTS mat_summary_create_tgr ON action.circulation;
6927 CREATE TRIGGER mat_summary_create_tgr AFTER INSERT ON action.circulation FOR EACH ROW EXECUTE PROCEDURE money.mat_summary_create ('circulation');
6928  
6929 DROP TRIGGER IF EXISTS mat_summary_create_tgr ON money.grocery;
6930 CREATE TRIGGER mat_summary_create_tgr AFTER INSERT ON money.grocery FOR EACH ROW EXECUTE PROCEDURE money.mat_summary_create ('grocery');
6931
6932 CREATE RULE money_payment_view_update AS ON UPDATE TO money.payment_view DO INSTEAD 
6933     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;
6934
6935 -- Generate the equivalent of compound subject entries from the existing rows
6936 -- so that we don't have to laboriously reindex them
6937
6938 --INSERT INTO config.metabib_field (field_class, name, format, xpath ) VALUES
6939 --    ( 'subject', 'complete', 'mods32', $$//mods32:mods/mods32:subject//text()$$ );
6940 --
6941 --CREATE INDEX metabib_subject_field_entry_source_idx ON metabib.subject_field_entry (source);
6942 --
6943 --INSERT INTO metabib.subject_field_entry (source, field, value)
6944 --    SELECT source, (
6945 --            SELECT id 
6946 --            FROM config.metabib_field
6947 --            WHERE field_class = 'subject' AND name = 'complete'
6948 --        ), 
6949 --        ARRAY_TO_STRING ( 
6950 --            ARRAY (
6951 --                SELECT value 
6952 --                FROM metabib.subject_field_entry msfe
6953 --                WHERE msfe.source = groupee.source
6954 --                ORDER BY source 
6955 --            ), ' ' 
6956 --        ) AS grouped
6957 --    FROM ( 
6958 --        SELECT source
6959 --        FROM metabib.subject_field_entry
6960 --        GROUP BY source
6961 --    ) AS groupee;
6962
6963 CREATE OR REPLACE FUNCTION money.materialized_summary_billing_del () RETURNS TRIGGER AS $$
6964 DECLARE
6965         prev_billing    money.billing%ROWTYPE;
6966         old_billing     money.billing%ROWTYPE;
6967 BEGIN
6968         SELECT * INTO prev_billing FROM money.billing WHERE xact = OLD.xact AND NOT voided ORDER BY billing_ts DESC LIMIT 1 OFFSET 1;
6969         SELECT * INTO old_billing FROM money.billing WHERE xact = OLD.xact AND NOT voided ORDER BY billing_ts DESC LIMIT 1;
6970
6971         IF OLD.id = old_billing.id THEN
6972                 UPDATE  money.materialized_billable_xact_summary
6973                   SET   last_billing_ts = prev_billing.billing_ts,
6974                         last_billing_note = prev_billing.note,
6975                         last_billing_type = prev_billing.billing_type
6976                   WHERE id = OLD.xact;
6977         END IF;
6978
6979         IF NOT OLD.voided THEN
6980                 UPDATE  money.materialized_billable_xact_summary
6981                   SET   total_owed = total_owed - OLD.amount,
6982                         balance_owed = balance_owed + OLD.amount
6983                   WHERE id = OLD.xact;
6984         END IF;
6985
6986         RETURN OLD;
6987 END;
6988 $$ LANGUAGE PLPGSQL;
6989
6990 -- ARG! need to rid ourselves of the broken table definition ... this mechanism is not ideal, sorry.
6991 DROP TABLE IF EXISTS config.index_normalizer CASCADE;
6992
6993 CREATE OR REPLACE FUNCTION public.naco_normalize( TEXT, TEXT ) RETURNS TEXT AS $func$
6994     use Unicode::Normalize;
6995     use Encode;
6996
6997     # When working with Unicode data, the first step is to decode it to
6998     # a byte string; after that, lowercasing is safe
6999     my $txt = lc(decode_utf8(shift));
7000     my $sf = shift;
7001
7002     $txt = NFD($txt);
7003     $txt =~ s/\pM+//go; # Remove diacritics
7004
7005     # remove non-combining diacritics
7006     # this list of characters follows the NACO normalization spec,
7007     # but a looser but more comprehensive version might be
7008     # $txt =~ s/\pLm+//go;
7009     $txt =~ tr/\x{02B9}\x{02BA}\x{02BB}\x{02BC}//d;
7010
7011     $txt =~ s/\xE6/AE/go;   # Convert ae digraph
7012     $txt =~ s/\x{153}/OE/go;# Convert oe digraph
7013     $txt =~ s/\xFE/TH/go;   # Convert Icelandic thorn
7014
7015     $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
7016     $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
7017
7018     $txt =~ tr/\x{0251}\x{03B1}\x{03B2}\x{0262}\x{03B3}/AABGG/;     # Convert Latin and Greek
7019     $txt =~ tr/\x{2113}\xF0\x{111}\!\"\(\)\-\{\}\<\>\;\:\.\?\xA1\xBF\/\\\@\*\%\=\xB1\+\xAE\xA9\x{2117}\$\xA3\x{FFE1}\xB0\^\_\~\`/LDD /; # Convert Misc
7020     $txt =~ tr/\'\[\]\|//d;                         # Remove Misc
7021
7022     if ($sf && $sf =~ /^a/o) {
7023         my $commapos = index($txt,',');
7024         if ($commapos > -1) {
7025             if ($commapos != length($txt) - 1) {
7026                 my @list = split /,/, $txt;
7027                 my $first = shift @list;
7028                 $txt = $first . ',' . join(' ', @list);
7029             } else {
7030                 $txt =~ s/,/ /go;
7031             }
7032         }
7033     } else {
7034         $txt =~ s/,/ /go;
7035     }
7036
7037     $txt =~ s/\s+/ /go; # Compress multiple spaces
7038     $txt =~ s/^\s+//o;  # Remove leading space
7039     $txt =~ s/\s+$//o;  # Remove trailing space
7040
7041     # Encoding the outgoing string is good practice, but not strictly
7042     # necessary in this case because we've stripped everything from it
7043     return encode_utf8($txt);
7044 $func$ LANGUAGE 'plperlu' STRICT IMMUTABLE;
7045
7046 -- Some handy functions, based on existing ones, to provide optional ingest normalization
7047
7048 CREATE OR REPLACE FUNCTION public.left_trunc( TEXT, INT ) RETURNS TEXT AS $func$
7049         SELECT SUBSTRING($1,$2);
7050 $func$ LANGUAGE SQL STRICT IMMUTABLE;
7051
7052 CREATE OR REPLACE FUNCTION public.right_trunc( TEXT, INT ) RETURNS TEXT AS $func$
7053         SELECT SUBSTRING($1,1,$2);
7054 $func$ LANGUAGE SQL STRICT IMMUTABLE;
7055
7056 CREATE OR REPLACE FUNCTION public.naco_normalize_keep_comma( TEXT ) RETURNS TEXT AS $func$
7057         SELECT public.naco_normalize($1,'a');
7058 $func$ LANGUAGE SQL STRICT IMMUTABLE;
7059
7060 CREATE OR REPLACE FUNCTION public.split_date_range( TEXT ) RETURNS TEXT AS $func$
7061         SELECT REGEXP_REPLACE( $1, E'(\\d{4})-(\\d{4})', E'\\1 \\2', 'g' );
7062 $func$ LANGUAGE SQL STRICT IMMUTABLE;
7063
7064 -- And ... a table in which to register them
7065
7066 CREATE TABLE config.index_normalizer (
7067         id              SERIAL  PRIMARY KEY,
7068         name            TEXT    UNIQUE NOT NULL,
7069         description     TEXT,
7070         func            TEXT    NOT NULL,
7071         param_count     INT     NOT NULL DEFAULT 0
7072 );
7073
7074 CREATE TABLE config.metabib_field_index_norm_map (
7075         id      SERIAL  PRIMARY KEY,
7076         field   INT     NOT NULL REFERENCES config.metabib_field (id) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED,
7077         norm    INT     NOT NULL REFERENCES config.index_normalizer (id) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED,
7078         params  TEXT,
7079         pos     INT     NOT NULL DEFAULT 0
7080 );
7081
7082 INSERT INTO config.index_normalizer (name, description, func, param_count) VALUES (
7083         'NACO Normalize',
7084         'Apply NACO normalization rules to the extracted text.  See http://www.loc.gov/catdir/pcc/naco/normrule-2.html for details.',
7085         'naco_normalize',
7086         0
7087 );
7088
7089 INSERT INTO config.index_normalizer (name, description, func, param_count) VALUES (
7090         'Normalize date range',
7091         'Split date ranges in the form of "XXXX-YYYY" into "XXXX YYYY" for proper index.',
7092         'split_date_range',
7093         1
7094 );
7095
7096 INSERT INTO config.index_normalizer (name, description, func, param_count) VALUES (
7097         'NACO Normalize -- retain first comma',
7098         '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.',
7099         'naco_normalize_keep_comma',
7100         0
7101 );
7102
7103 INSERT INTO config.index_normalizer (name, description, func, param_count) VALUES (
7104         'Strip Diacritics',
7105         'Convert text to NFD form and remove non-spacing combining marks.',
7106         'remove_diacritics',
7107         0
7108 );
7109
7110 INSERT INTO config.index_normalizer (name, description, func, param_count) VALUES (
7111         'Up-case',
7112         'Convert text upper case.',
7113         'uppercase',
7114         0
7115 );
7116
7117 INSERT INTO config.index_normalizer (name, description, func, param_count) VALUES (
7118         'Down-case',
7119         'Convert text lower case.',
7120         'lowercase',
7121         0
7122 );
7123
7124 INSERT INTO config.index_normalizer (name, description, func, param_count) VALUES (
7125         'Extract Dewey-like number',
7126         'Extract a string of numeric characters ther resembles a DDC number.',
7127         'call_number_dewey',
7128         0
7129 );
7130
7131 INSERT INTO config.index_normalizer (name, description, func, param_count) VALUES (
7132         'Left truncation',
7133         'Discard the specified number of characters from the left side of the string.',
7134         'left_trunc',
7135         1
7136 );
7137
7138 INSERT INTO config.index_normalizer (name, description, func, param_count) VALUES (
7139         'Right truncation',
7140         'Include only the specified number of characters from the left side of the string.',
7141         'right_trunc',
7142         1
7143 );
7144
7145 INSERT INTO config.index_normalizer (name, description, func, param_count) VALUES (
7146         'First word',
7147         'Include only the first space-separated word of a string.',
7148         'first_word',
7149         0
7150 );
7151
7152 INSERT INTO config.metabib_field_index_norm_map (field,norm)
7153         SELECT  m.id,
7154                 i.id
7155           FROM  config.metabib_field m,
7156                 config.index_normalizer i
7157           WHERE i.func IN ('naco_normalize','split_date_range');
7158
7159 CREATE OR REPLACE FUNCTION oils_tsearch2 () RETURNS TRIGGER AS $$
7160 DECLARE
7161     normalizer      RECORD;
7162     value           TEXT := '';
7163 BEGIN
7164
7165     value := NEW.value;
7166
7167     IF TG_TABLE_NAME::TEXT ~ 'field_entry$' THEN
7168         FOR normalizer IN
7169             SELECT  n.func AS func,
7170                     n.param_count AS param_count,
7171                     m.params AS params
7172               FROM  config.index_normalizer n
7173                     JOIN config.metabib_field_index_norm_map m ON (m.norm = n.id)
7174               WHERE field = NEW.field AND m.pos < 0
7175               ORDER BY m.pos LOOP
7176                 EXECUTE 'SELECT ' || normalizer.func || '(' ||
7177                     quote_literal( value ) ||
7178                     CASE
7179                         WHEN normalizer.param_count > 0
7180                             THEN ',' || REPLACE(REPLACE(BTRIM(normalizer.params,'[]'),E'\'',E'\\\''),E'"',E'\'')
7181                             ELSE ''
7182                         END ||
7183                     ')' INTO value;
7184
7185         END LOOP;
7186
7187         NEW.value := value;
7188     END IF;
7189
7190     IF NEW.index_vector = ''::tsvector THEN
7191         RETURN NEW;
7192     END IF;
7193
7194     IF TG_TABLE_NAME::TEXT ~ 'field_entry$' THEN
7195         FOR normalizer IN
7196             SELECT  n.func AS func,
7197                     n.param_count AS param_count,
7198                     m.params AS params
7199               FROM  config.index_normalizer n
7200                     JOIN config.metabib_field_index_norm_map m ON (m.norm = n.id)
7201               WHERE field = NEW.field AND m.pos >= 0
7202               ORDER BY m.pos LOOP
7203                 EXECUTE 'SELECT ' || normalizer.func || '(' ||
7204                     quote_literal( value ) ||
7205                     CASE
7206                         WHEN normalizer.param_count > 0
7207                             THEN ',' || REPLACE(REPLACE(BTRIM(normalizer.params,'[]'),E'\'',E'\\\''),E'"',E'\'')
7208                             ELSE ''
7209                         END ||
7210                     ')' INTO value;
7211
7212         END LOOP;
7213     END IF;
7214
7215     IF REGEXP_REPLACE(VERSION(),E'^.+?(\\d+\\.\\d+).*?$',E'\\1')::FLOAT > 8.2 THEN
7216         NEW.index_vector = to_tsvector((TG_ARGV[0])::regconfig, value);
7217     ELSE
7218         NEW.index_vector = to_tsvector(TG_ARGV[0], value);
7219     END IF;
7220
7221     RETURN NEW;
7222 END;
7223 $$ LANGUAGE PLPGSQL;
7224
7225 CREATE OR REPLACE FUNCTION oils_xpath ( TEXT, TEXT, ANYARRAY ) RETURNS TEXT[] AS 'SELECT XPATH( $1, $2::XML, $3 )::TEXT[];' LANGUAGE SQL IMMUTABLE;
7226
7227 CREATE OR REPLACE FUNCTION oils_xpath ( TEXT, TEXT ) RETURNS TEXT[] AS 'SELECT XPATH( $1, $2::XML )::TEXT[];' LANGUAGE SQL IMMUTABLE;
7228
7229 CREATE OR REPLACE FUNCTION oils_xpath_string ( TEXT, TEXT, TEXT, ANYARRAY ) RETURNS TEXT AS $func$
7230     SELECT  ARRAY_TO_STRING(
7231                 oils_xpath(
7232                     $1 ||
7233                         CASE WHEN $1 ~ $re$/[^/[]*@[^]]+$$re$ OR $1 ~ $re$text\(\)$$re$ THEN '' ELSE '//text()' END,
7234                     $2,
7235                     $4
7236                 ),
7237                 $3
7238             );
7239 $func$ LANGUAGE SQL IMMUTABLE;
7240
7241 CREATE OR REPLACE FUNCTION oils_xpath_string ( TEXT, TEXT, TEXT ) RETURNS TEXT AS $func$
7242     SELECT oils_xpath_string( $1, $2, $3, '{}'::TEXT[] );
7243 $func$ LANGUAGE SQL IMMUTABLE;
7244
7245 CREATE OR REPLACE FUNCTION oils_xpath_string ( TEXT, TEXT, ANYARRAY ) RETURNS TEXT AS $func$
7246     SELECT oils_xpath_string( $1, $2, '', $3 );
7247 $func$ LANGUAGE SQL IMMUTABLE;
7248
7249 CREATE OR REPLACE FUNCTION oils_xpath_string ( TEXT, TEXT ) RETURNS TEXT AS $func$
7250     SELECT oils_xpath_string( $1, $2, '{}'::TEXT[] );
7251 $func$ LANGUAGE SQL IMMUTABLE;
7252
7253 CREATE TYPE metabib.field_entry_template AS (
7254         field_class     TEXT,
7255         field           INT,
7256         source          BIGINT,
7257         value           TEXT
7258 );
7259
7260 CREATE OR REPLACE FUNCTION oils_xslt_process(TEXT, TEXT) RETURNS TEXT AS $func$
7261   use strict;
7262
7263   use XML::LibXSLT;
7264   use XML::LibXML;
7265
7266   my $doc = shift;
7267   my $xslt = shift;
7268
7269   # The following approach uses the older XML::LibXML 1.69 / XML::LibXSLT 1.68
7270   # methods of parsing XML documents and stylesheets, in the hopes of broader
7271   # compatibility with distributions
7272   my $parser = $_SHARED{'_xslt_process'}{parsers}{xml} || XML::LibXML->new();
7273
7274   # Cache the XML parser, if we do not already have one
7275   $_SHARED{'_xslt_process'}{parsers}{xml} = $parser
7276     unless ($_SHARED{'_xslt_process'}{parsers}{xml});
7277
7278   my $xslt_parser = $_SHARED{'_xslt_process'}{parsers}{xslt} || XML::LibXSLT->new();
7279
7280   # Cache the XSLT processor, if we do not already have one
7281   $_SHARED{'_xslt_process'}{parsers}{xslt} = $xslt_parser
7282     unless ($_SHARED{'_xslt_process'}{parsers}{xslt});
7283
7284   my $stylesheet = $_SHARED{'_xslt_process'}{stylesheets}{$xslt} ||
7285     $xslt_parser->parse_stylesheet( $parser->parse_string($xslt) );
7286
7287   $_SHARED{'_xslt_process'}{stylesheets}{$xslt} = $stylesheet
7288     unless ($_SHARED{'_xslt_process'}{stylesheets}{$xslt});
7289
7290   return $stylesheet->output_string(
7291     $stylesheet->transform(
7292       $parser->parse_string($doc)
7293     )
7294   );
7295
7296 $func$ LANGUAGE 'plperlu' STRICT IMMUTABLE;
7297
7298 -- Add two columns so that the following function will compile.
7299 -- Eventually the label column will be NOT NULL, but not yet.
7300 ALTER TABLE config.metabib_field ADD COLUMN label TEXT;
7301 ALTER TABLE config.metabib_field ADD COLUMN facet_xpath TEXT;
7302
7303 CREATE OR REPLACE FUNCTION biblio.extract_metabib_field_entry ( rid BIGINT, default_joiner TEXT ) RETURNS SETOF metabib.field_entry_template AS $func$
7304 DECLARE
7305     bib     biblio.record_entry%ROWTYPE;
7306     idx     config.metabib_field%ROWTYPE;
7307     xfrm        config.xml_transform%ROWTYPE;
7308     prev_xfrm   TEXT;
7309     transformed_xml TEXT;
7310     xml_node    TEXT;
7311     xml_node_list   TEXT[];
7312     facet_text  TEXT;
7313     raw_text    TEXT;
7314     curr_text   TEXT;
7315     joiner      TEXT := default_joiner; -- XXX will index defs supply a joiner?
7316     output_row  metabib.field_entry_template%ROWTYPE;
7317 BEGIN
7318
7319     -- Get the record
7320     SELECT INTO bib * FROM biblio.record_entry WHERE id = rid;
7321
7322     -- Loop over the indexing entries
7323     FOR idx IN SELECT * FROM config.metabib_field ORDER BY format LOOP
7324
7325         SELECT INTO xfrm * from config.xml_transform WHERE name = idx.format;
7326
7327         -- See if we can skip the XSLT ... it's expensive
7328         IF prev_xfrm IS NULL OR prev_xfrm <> xfrm.name THEN
7329             -- Can't skip the transform
7330             IF xfrm.xslt <> '---' THEN
7331                 transformed_xml := oils_xslt_process(bib.marc,xfrm.xslt);
7332             ELSE
7333                 transformed_xml := bib.marc;
7334             END IF;
7335
7336             prev_xfrm := xfrm.name;
7337         END IF;
7338
7339         xml_node_list := oils_xpath( idx.xpath, transformed_xml, ARRAY[ARRAY[xfrm.prefix, xfrm.namespace_uri]] );
7340
7341         raw_text := NULL;
7342         FOR xml_node IN SELECT x FROM explode_array(xml_node_list) AS x LOOP
7343             CONTINUE WHEN xml_node !~ E'^\\s*<';
7344
7345             curr_text := ARRAY_TO_STRING(
7346                 oils_xpath( '//text()',
7347                     REGEXP_REPLACE( -- This escapes all &s not followed by "amp;".  Data ise returned from oils_xpath (above) in UTF-8, not entity encoded
7348                         REGEXP_REPLACE( -- This escapes embeded <s
7349                             xml_node,
7350                             $re$(>[^<]+)(<)([^>]+<)$re$,
7351                             E'\\1&lt;\\3',
7352                             'g'
7353                         ),
7354                         '&(?!amp;)',
7355                         '&amp;',
7356                         'g'
7357                     )
7358                 ),
7359                 ' '
7360             );
7361
7362             CONTINUE WHEN curr_text IS NULL OR curr_text = '';
7363
7364             IF raw_text IS NOT NULL THEN
7365                 raw_text := raw_text || joiner;
7366             END IF;
7367
7368             raw_text := COALESCE(raw_text,'') || curr_text;
7369
7370             -- insert raw node text for faceting
7371             IF idx.facet_field THEN
7372
7373                 IF idx.facet_xpath IS NOT NULL AND idx.facet_xpath <> '' THEN
7374                     facet_text := oils_xpath_string( idx.facet_xpath, xml_node, joiner, ARRAY[ARRAY[xfrm.prefix, xfrm.namespace_uri]] );
7375                 ELSE
7376                     facet_text := curr_text;
7377                 END IF;
7378
7379                 output_row.field_class = idx.field_class;
7380                 output_row.field = -1 * idx.id;
7381                 output_row.source = rid;
7382                 output_row.value = BTRIM(REGEXP_REPLACE(facet_text, E'\\s+', ' ', 'g'));
7383
7384                 RETURN NEXT output_row;
7385             END IF;
7386
7387         END LOOP;
7388
7389         CONTINUE WHEN raw_text IS NULL OR raw_text = '';
7390
7391         -- insert combined node text for searching
7392         IF idx.search_field THEN
7393             output_row.field_class = idx.field_class;
7394             output_row.field = idx.id;
7395             output_row.source = rid;
7396             output_row.value = BTRIM(REGEXP_REPLACE(raw_text, E'\\s+', ' ', 'g'));
7397
7398             RETURN NEXT output_row;
7399         END IF;
7400
7401     END LOOP;
7402
7403 END;
7404 $func$ LANGUAGE PLPGSQL;
7405
7406 -- default to a space joiner
7407 CREATE OR REPLACE FUNCTION biblio.extract_metabib_field_entry ( BIGINT ) RETURNS SETOF metabib.field_entry_template AS $func$
7408         SELECT * FROM biblio.extract_metabib_field_entry($1, ' ');
7409 $func$ LANGUAGE SQL;
7410
7411 CREATE OR REPLACE FUNCTION biblio.flatten_marc ( TEXT ) RETURNS SETOF metabib.full_rec AS $func$
7412
7413 use MARC::Record;
7414 use MARC::File::XML (BinaryEncoding => 'UTF-8');
7415
7416 my $xml = shift;
7417 my $r = MARC::Record->new_from_xml( $xml );
7418
7419 return_next( { tag => 'LDR', value => $r->leader } );
7420
7421 for my $f ( $r->fields ) {
7422     if ($f->is_control_field) {
7423         return_next({ tag => $f->tag, value => $f->data });
7424     } else {
7425         for my $s ($f->subfields) {
7426             return_next({
7427                 tag      => $f->tag,
7428                 ind1     => $f->indicator(1),
7429                 ind2     => $f->indicator(2),
7430                 subfield => $s->[0],
7431                 value    => $s->[1]
7432             });
7433
7434             if ( $f->tag eq '245' and $s->[0] eq 'a' ) {
7435                 my $trim = $f->indicator(2) || 0;
7436                 return_next({
7437                     tag      => 'tnf',
7438                     ind1     => $f->indicator(1),
7439                     ind2     => $f->indicator(2),
7440                     subfield => 'a',
7441                     value    => substr( $s->[1], $trim )
7442                 });
7443             }
7444         }
7445     }
7446 }
7447
7448 return undef;
7449
7450 $func$ LANGUAGE PLPERLU;
7451
7452 CREATE OR REPLACE FUNCTION biblio.flatten_marc ( rid BIGINT ) RETURNS SETOF metabib.full_rec AS $func$
7453 DECLARE
7454     bib biblio.record_entry%ROWTYPE;
7455     output  metabib.full_rec%ROWTYPE;
7456     field   RECORD;
7457 BEGIN
7458     SELECT INTO bib * FROM biblio.record_entry WHERE id = rid;
7459
7460     FOR field IN SELECT * FROM biblio.flatten_marc( bib.marc ) LOOP
7461         output.record := rid;
7462         output.ind1 := field.ind1;
7463         output.ind2 := field.ind2;
7464         output.tag := field.tag;
7465         output.subfield := field.subfield;
7466         IF field.subfield IS NOT NULL AND field.tag NOT IN ('020','022','024') THEN -- exclude standard numbers and control fields
7467             output.value := naco_normalize(field.value, field.subfield);
7468         ELSE
7469             output.value := field.value;
7470         END IF;
7471
7472         CONTINUE WHEN output.value IS NULL;
7473
7474         RETURN NEXT output;
7475     END LOOP;
7476 END;
7477 $func$ LANGUAGE PLPGSQL;
7478
7479 -- functions to create auditor objects
7480
7481 CREATE FUNCTION auditor.create_auditor_seq     ( sch TEXT, tbl TEXT ) RETURNS BOOL AS $creator$
7482 BEGIN
7483     EXECUTE $$
7484         CREATE SEQUENCE auditor.$$ || sch || $$_$$ || tbl || $$_pkey_seq;
7485     $$;
7486         RETURN TRUE;
7487 END;
7488 $creator$ LANGUAGE 'plpgsql';
7489
7490 CREATE FUNCTION auditor.create_auditor_history ( sch TEXT, tbl TEXT ) RETURNS BOOL AS $creator$
7491 BEGIN
7492     EXECUTE $$
7493         CREATE TABLE auditor.$$ || sch || $$_$$ || tbl || $$_history (
7494             audit_id    BIGINT                          PRIMARY KEY,
7495             audit_time  TIMESTAMP WITH TIME ZONE        NOT NULL,
7496             audit_action        TEXT                            NOT NULL,
7497             LIKE $$ || sch || $$.$$ || tbl || $$
7498         );
7499     $$;
7500         RETURN TRUE;
7501 END;
7502 $creator$ LANGUAGE 'plpgsql';
7503
7504 CREATE FUNCTION auditor.create_auditor_func    ( sch TEXT, tbl TEXT ) RETURNS BOOL AS $creator$
7505 BEGIN
7506     EXECUTE $$
7507         CREATE FUNCTION auditor.audit_$$ || sch || $$_$$ || tbl || $$_func ()
7508         RETURNS TRIGGER AS $func$
7509         BEGIN
7510             INSERT INTO auditor.$$ || sch || $$_$$ || tbl || $$_history
7511                 SELECT  nextval('auditor.$$ || sch || $$_$$ || tbl || $$_pkey_seq'),
7512                     now(),
7513                     SUBSTR(TG_OP,1,1),
7514                     OLD.*;
7515             RETURN NULL;
7516         END;
7517         $func$ LANGUAGE 'plpgsql';
7518     $$;
7519         RETURN TRUE;
7520 END;
7521 $creator$ LANGUAGE 'plpgsql';
7522
7523 CREATE FUNCTION auditor.create_auditor_update_trigger ( sch TEXT, tbl TEXT ) RETURNS BOOL AS $creator$
7524 BEGIN
7525     EXECUTE $$
7526         CREATE TRIGGER audit_$$ || sch || $$_$$ || tbl || $$_update_trigger
7527             AFTER UPDATE OR DELETE ON $$ || sch || $$.$$ || tbl || $$ FOR EACH ROW
7528             EXECUTE PROCEDURE auditor.audit_$$ || sch || $$_$$ || tbl || $$_func ();
7529     $$;
7530         RETURN TRUE;
7531 END;
7532 $creator$ LANGUAGE 'plpgsql';
7533
7534 CREATE FUNCTION auditor.create_auditor_lifecycle     ( sch TEXT, tbl TEXT ) RETURNS BOOL AS $creator$
7535 BEGIN
7536     EXECUTE $$
7537         CREATE VIEW auditor.$$ || sch || $$_$$ || tbl || $$_lifecycle AS
7538             SELECT      -1, now() as audit_time, '-' as audit_action, *
7539               FROM      $$ || sch || $$.$$ || tbl || $$
7540                 UNION ALL
7541             SELECT      *
7542               FROM      auditor.$$ || sch || $$_$$ || tbl || $$_history;
7543     $$;
7544         RETURN TRUE;
7545 END;
7546 $creator$ LANGUAGE 'plpgsql';
7547
7548 DROP FUNCTION IF EXISTS auditor.create_auditor (TEXT, TEXT);
7549
7550 -- The main event
7551
7552 CREATE FUNCTION auditor.create_auditor ( sch TEXT, tbl TEXT ) RETURNS BOOL AS $creator$
7553 BEGIN
7554     PERFORM auditor.create_auditor_seq(sch, tbl);
7555     PERFORM auditor.create_auditor_history(sch, tbl);
7556     PERFORM auditor.create_auditor_func(sch, tbl);
7557     PERFORM auditor.create_auditor_update_trigger(sch, tbl);
7558     PERFORM auditor.create_auditor_lifecycle(sch, tbl);
7559     RETURN TRUE;
7560 END;
7561 $creator$ LANGUAGE 'plpgsql';
7562
7563 ALTER TABLE action.hold_request ADD COLUMN cut_in_line BOOL;
7564
7565 ALTER TABLE action.hold_request
7566 ADD COLUMN mint_condition boolean NOT NULL DEFAULT TRUE;
7567
7568 ALTER TABLE action.hold_request
7569 ADD COLUMN shelf_expire_time TIMESTAMPTZ;
7570
7571 ALTER TABLE action.hold_request DROP CONSTRAINT hold_request_current_copy_fkey;
7572
7573 ALTER TABLE action.hold_request DROP CONSTRAINT hold_request_hold_type_check;
7574
7575 UPDATE config.index_normalizer SET param_count = 0 WHERE func = 'split_date_range';
7576
7577 CREATE INDEX actor_usr_usrgroup_idx ON actor.usr (usrgroup);
7578
7579 -- Add claims_never_checked_out_count to actor.usr, related history
7580
7581 ALTER TABLE actor.usr ADD COLUMN
7582         claims_never_checked_out_count  INT         NOT NULL DEFAULT 0;
7583
7584 ALTER TABLE AUDITOR.actor_usr_history ADD COLUMN 
7585         claims_never_checked_out_count INT;
7586
7587 DROP VIEW IF EXISTS auditor.actor_usr_lifecycle;
7588
7589 SELECT auditor.create_auditor_lifecycle( 'actor', 'usr' );
7590
7591 -----------
7592
7593 CREATE OR REPLACE FUNCTION action.circulation_claims_returned () RETURNS TRIGGER AS $$
7594 BEGIN
7595         IF OLD.stop_fines IS NULL OR OLD.stop_fines <> NEW.stop_fines THEN
7596                 IF NEW.stop_fines = 'CLAIMSRETURNED' THEN
7597                         UPDATE actor.usr SET claims_returned_count = claims_returned_count + 1 WHERE id = NEW.usr;
7598                 END IF;
7599                 IF NEW.stop_fines = 'CLAIMSNEVERCHECKEDOUT' THEN
7600                         UPDATE actor.usr SET claims_never_checked_out_count = claims_never_checked_out_count + 1 WHERE id = NEW.usr;
7601                 END IF;
7602                 IF NEW.stop_fines = 'LOST' THEN
7603                         UPDATE asset.copy SET status = 3 WHERE id = NEW.target_copy;
7604                 END IF;
7605         END IF;
7606         RETURN NEW;
7607 END;
7608 $$ LANGUAGE 'plpgsql';
7609
7610 -- Create new table acq.fund_allocation_percent
7611 -- Populate it from acq.fund_allocation
7612 -- Convert all percentages to amounts in acq.fund_allocation
7613
7614 CREATE TABLE acq.fund_allocation_percent
7615 (
7616     id                   SERIAL            PRIMARY KEY,
7617     funding_source       INT               NOT NULL REFERENCES acq.funding_source
7618                                                DEFERRABLE INITIALLY DEFERRED,
7619     org                  INT               NOT NULL REFERENCES actor.org_unit
7620                                                DEFERRABLE INITIALLY DEFERRED,
7621     fund_code            TEXT,
7622     percent              NUMERIC           NOT NULL,
7623     allocator            INTEGER           NOT NULL REFERENCES actor.usr
7624                                                DEFERRABLE INITIALLY DEFERRED,
7625     note                 TEXT,
7626     create_time          TIMESTAMPTZ       NOT NULL DEFAULT now(),
7627     CONSTRAINT logical_key UNIQUE( funding_source, org, fund_code ),
7628     CONSTRAINT percentage_range CHECK( percent >= 0 AND percent <= 100 )
7629 );
7630
7631 -- Trigger function to validate combination of org_unit and fund_code
7632
7633 CREATE OR REPLACE FUNCTION acq.fund_alloc_percent_val()
7634 RETURNS TRIGGER AS $$
7635 --
7636 DECLARE
7637 --
7638 dummy int := 0;
7639 --
7640 BEGIN
7641     SELECT
7642         1
7643     INTO
7644         dummy
7645     FROM
7646         acq.fund
7647     WHERE
7648         org = NEW.org
7649         AND code = NEW.fund_code
7650         LIMIT 1;
7651     --
7652     IF dummy = 1 then
7653         RETURN NEW;
7654     ELSE
7655         RAISE EXCEPTION 'No fund exists for org % and code %', NEW.org, NEW.fund_code;
7656     END IF;
7657 END;
7658 $$ LANGUAGE plpgsql;
7659
7660 CREATE TRIGGER acq_fund_alloc_percent_val_trig
7661     BEFORE INSERT OR UPDATE ON acq.fund_allocation_percent
7662     FOR EACH ROW EXECUTE PROCEDURE acq.fund_alloc_percent_val();
7663
7664 CREATE OR REPLACE FUNCTION acq.fap_limit_100()
7665 RETURNS TRIGGER AS $$
7666 DECLARE
7667 --
7668 total_percent numeric;
7669 --
7670 BEGIN
7671     SELECT
7672         sum( percent )
7673     INTO
7674         total_percent
7675     FROM
7676         acq.fund_allocation_percent AS fap
7677     WHERE
7678         fap.funding_source = NEW.funding_source;
7679     --
7680     IF total_percent > 100 THEN
7681         RAISE EXCEPTION 'Total percentages exceed 100 for funding_source %',
7682             NEW.funding_source;
7683     ELSE
7684         RETURN NEW;
7685     END IF;
7686 END;
7687 $$ LANGUAGE plpgsql;
7688
7689 CREATE TRIGGER acqfap_limit_100_trig
7690     AFTER INSERT OR UPDATE ON acq.fund_allocation_percent
7691     FOR EACH ROW EXECUTE PROCEDURE acq.fap_limit_100();
7692
7693 -- Populate new table from acq.fund_allocation
7694
7695 INSERT INTO acq.fund_allocation_percent
7696 (
7697     funding_source,
7698     org,
7699     fund_code,
7700     percent,
7701     allocator,
7702     note,
7703     create_time
7704 )
7705     SELECT
7706         fa.funding_source,
7707         fund.org,
7708         fund.code,
7709         fa.percent,
7710         fa.allocator,
7711         fa.note,
7712         fa.create_time
7713     FROM
7714         acq.fund_allocation AS fa
7715             INNER JOIN acq.fund AS fund
7716                 ON ( fa.fund = fund.id )
7717     WHERE
7718         fa.percent is not null
7719     ORDER BY
7720         fund.org;
7721
7722 -- Temporary function to convert percentages to amounts in acq.fund_allocation
7723
7724 -- Algorithm to apply to each funding source:
7725
7726 -- 1. Add up the credits.
7727 -- 2. Add up the percentages.
7728 -- 3. Multiply the sum of the percentages times the sum of the credits.  Drop any
7729 --    fractional cents from the result.  This is the total amount to be allocated.
7730 -- 4. For each allocation: multiply the percentage by the total allocation.  Drop any
7731 --    fractional cents to get a preliminary amount.
7732 -- 5. Add up the preliminary amounts for all the allocations.
7733 -- 6. Subtract the results of step 5 from the result of step 3.  The difference is the
7734 --    number of residual cents (resulting from having dropped fractional cents) that
7735 --    must be distributed across the funds in order to make the total of the amounts
7736 --    match the total allocation.
7737 -- 7. Make a second pass through the allocations, in decreasing order of the fractional
7738 --    cents that were dropped from their amounts in step 4.  Add one cent to the amount
7739 --    for each successive fund, until all the residual cents have been exhausted.
7740
7741 -- Result: the sum of the individual allocations now equals the total to be allocated,
7742 -- to the penny.  The individual amounts match the percentages as closely as possible,
7743 -- given the constraint that the total must match.
7744
7745 CREATE OR REPLACE FUNCTION acq.apply_percents()
7746 RETURNS VOID AS $$
7747 declare
7748 --
7749 tot              RECORD;
7750 fund             RECORD;
7751 tot_cents        INTEGER;
7752 src              INTEGER;
7753 id               INTEGER[];
7754 curr_id          INTEGER;
7755 pennies          NUMERIC[];
7756 curr_amount      NUMERIC;
7757 i                INTEGER;
7758 total_of_floors  INTEGER;
7759 total_percent    NUMERIC;
7760 total_allocation INTEGER;
7761 residue          INTEGER;
7762 --
7763 begin
7764         RAISE NOTICE 'Applying percents';
7765         FOR tot IN
7766                 SELECT
7767                         fsrc.funding_source,
7768                         sum( fsrc.amount ) AS total
7769                 FROM
7770                         acq.funding_source_credit AS fsrc
7771                 WHERE fsrc.funding_source IN
7772                         ( SELECT DISTINCT fa.funding_source
7773                           FROM acq.fund_allocation AS fa
7774                           WHERE fa.percent IS NOT NULL )
7775                 GROUP BY
7776                         fsrc.funding_source
7777         LOOP
7778                 tot_cents = floor( tot.total * 100 );
7779                 src = tot.funding_source;
7780                 RAISE NOTICE 'Funding source % total %',
7781                         src, tot_cents;
7782                 i := 0;
7783                 total_of_floors := 0;
7784                 total_percent := 0;
7785                 --
7786                 FOR fund in
7787                         SELECT
7788                                 fa.id,
7789                                 fa.percent,
7790                                 floor( fa.percent * tot_cents / 100 ) as floor_pennies
7791                         FROM
7792                                 acq.fund_allocation AS fa
7793                         WHERE
7794                                 fa.funding_source = src
7795                                 AND fa.percent IS NOT NULL
7796                         ORDER BY
7797                                 mod( fa.percent * tot_cents / 100, 1 ),
7798                                 fa.fund,
7799                                 fa.id
7800                 LOOP
7801                         RAISE NOTICE '   %: %',
7802                                 fund.id,
7803                                 fund.floor_pennies;
7804                         i := i + 1;
7805                         id[i] = fund.id;
7806                         pennies[i] = fund.floor_pennies;
7807                         total_percent := total_percent + fund.percent;
7808                         total_of_floors := total_of_floors + pennies[i];
7809                 END LOOP;
7810                 total_allocation := floor( total_percent * tot_cents /100 );
7811                 RAISE NOTICE 'Total before distributing residue: %', total_of_floors;
7812                 residue := total_allocation - total_of_floors;
7813                 RAISE NOTICE 'Residue: %', residue;
7814                 --
7815                 -- Post the calculated amounts, revising as needed to
7816                 -- distribute the rounding error
7817                 --
7818                 WHILE i > 0 LOOP
7819                         IF residue > 0 THEN
7820                                 pennies[i] = pennies[i] + 1;
7821                                 residue := residue - 1;
7822                         END IF;
7823                         --
7824                         -- Post amount
7825                         --
7826                         curr_id     := id[i];
7827                         curr_amount := trunc( pennies[i] / 100, 2 );
7828                         --
7829                         UPDATE
7830                                 acq.fund_allocation AS fa
7831                         SET
7832                                 amount = curr_amount,
7833                                 percent = NULL
7834                         WHERE
7835                                 fa.id = curr_id;
7836                         --
7837                         RAISE NOTICE '   ID % and amount %',
7838                                 curr_id,
7839                                 curr_amount;
7840                         i = i - 1;
7841                 END LOOP;
7842         END LOOP;
7843 end;
7844 $$ LANGUAGE 'plpgsql';
7845
7846 -- Run the temporary function
7847
7848 select * from acq.apply_percents();
7849
7850 -- Drop the temporary function now that we're done with it
7851
7852 DROP FUNCTION IF EXISTS acq.apply_percents();
7853
7854 -- Eliminate acq.fund_allocation.percent, which has been moved to the acq.fund_allocation_percent table.
7855
7856 -- If the following step fails, it's probably because there are still some non-null percent values in
7857 -- acq.fund_allocation.  They should have all been converted to amounts, and then set to null, by a
7858 -- previous upgrade step based on 0049.schema.acq_funding_allocation_percent.sql.  If there are any
7859 -- non-null values, then either that step didn't run, or it didn't work, or some non-null values
7860 -- slipped in afterwards.
7861
7862 -- To convert any remaining percents to amounts: create, run, and then drop the temporary stored
7863 -- procedure acq.apply_percents() as defined above.
7864
7865 ALTER TABLE acq.fund_allocation
7866 ALTER COLUMN amount SET NOT NULL;
7867
7868 CREATE OR REPLACE VIEW acq.fund_allocation_total AS
7869     SELECT  fund,
7870             SUM(a.amount * acq.exchange_ratio(s.currency_type, f.currency_type))::NUMERIC(100,2) AS amount
7871     FROM acq.fund_allocation a
7872          JOIN acq.fund f ON (a.fund = f.id)
7873          JOIN acq.funding_source s ON (a.funding_source = s.id)
7874     GROUP BY 1;
7875
7876 CREATE OR REPLACE VIEW acq.funding_source_allocation_total AS
7877     SELECT  funding_source,
7878             SUM(a.amount)::NUMERIC(100,2) AS amount
7879     FROM  acq.fund_allocation a
7880     GROUP BY 1;
7881
7882 ALTER TABLE acq.fund_allocation
7883 DROP COLUMN percent;
7884
7885 CREATE TABLE asset.copy_location_order
7886 (
7887         id              SERIAL           PRIMARY KEY,
7888         location        INT              NOT NULL
7889                                              REFERENCES asset.copy_location
7890                                              ON DELETE CASCADE
7891                                              DEFERRABLE INITIALLY DEFERRED,
7892         org             INT              NOT NULL
7893                                              REFERENCES actor.org_unit
7894                                              ON DELETE CASCADE
7895                                              DEFERRABLE INITIALLY DEFERRED,
7896         position        INT              NOT NULL DEFAULT 0,
7897         CONSTRAINT acplo_once_per_org UNIQUE ( location, org )
7898 );
7899
7900 ALTER TABLE money.credit_card_payment ADD COLUMN cc_processor TEXT;
7901
7902 -- If you ran this before its most recent incarnation:
7903 -- delete from config.upgrade_log where version = '0328';
7904 -- alter table money.credit_card_payment drop column cc_name;
7905
7906 ALTER TABLE money.credit_card_payment ADD COLUMN cc_first_name TEXT;
7907 ALTER TABLE money.credit_card_payment ADD COLUMN cc_last_name TEXT;
7908
7909 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$
7910 DECLARE
7911     current_group    permission.grp_tree%ROWTYPE;
7912     user_object    actor.usr%ROWTYPE;
7913     item_object    asset.copy%ROWTYPE;
7914     cn_object    asset.call_number%ROWTYPE;
7915     rec_descriptor    metabib.rec_descriptor%ROWTYPE;
7916     current_mp    config.circ_matrix_matchpoint%ROWTYPE;
7917     matchpoint    config.circ_matrix_matchpoint%ROWTYPE;
7918 BEGIN
7919     SELECT INTO user_object * FROM actor.usr WHERE id = match_user;
7920     SELECT INTO item_object * FROM asset.copy WHERE id = match_item;
7921     SELECT INTO cn_object * FROM asset.call_number WHERE id = item_object.call_number;
7922     SELECT INTO rec_descriptor r.* FROM metabib.rec_descriptor r JOIN asset.call_number c USING (record) WHERE c.id = item_object.call_number;
7923     SELECT INTO current_group * FROM permission.grp_tree WHERE id = user_object.profile;
7924
7925     LOOP 
7926         -- for each potential matchpoint for this ou and group ...
7927         FOR current_mp IN
7928             SELECT  m.*
7929               FROM  config.circ_matrix_matchpoint m
7930                     JOIN actor.org_unit_ancestors( context_ou ) d ON (m.org_unit = d.id)
7931                     LEFT JOIN actor.org_unit_proximity p ON (p.from_org = context_ou AND p.to_org = d.id)
7932               WHERE m.grp = current_group.id
7933                     AND m.active
7934                     AND (m.copy_owning_lib IS NULL OR cn_object.owning_lib IN ( SELECT id FROM actor.org_unit_descendants(m.copy_owning_lib) ))
7935                     AND (m.copy_circ_lib   IS NULL OR item_object.circ_lib IN ( SELECT id FROM actor.org_unit_descendants(m.copy_circ_lib)   ))
7936               ORDER BY    CASE WHEN p.prox        IS NULL THEN 999 ELSE p.prox END,
7937                     CASE WHEN m.copy_owning_lib IS NOT NULL
7938                         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 )
7939                         ELSE 0
7940                     END +
7941                     CASE WHEN m.copy_circ_lib IS NOT NULL
7942                         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 )
7943                         ELSE 0
7944                     END +
7945                     CASE WHEN m.is_renewal = renewal        THEN 128 ELSE 0 END +
7946                     CASE WHEN m.juvenile_flag    IS NOT NULL THEN 64 ELSE 0 END +
7947                     CASE WHEN m.circ_modifier    IS NOT NULL THEN 32 ELSE 0 END +
7948                     CASE WHEN m.marc_type        IS NOT NULL THEN 16 ELSE 0 END +
7949                     CASE WHEN m.marc_form        IS NOT NULL THEN 8 ELSE 0 END +
7950                     CASE WHEN m.marc_vr_format    IS NOT NULL THEN 4 ELSE 0 END +
7951                     CASE WHEN m.ref_flag        IS NOT NULL THEN 2 ELSE 0 END +
7952                     CASE WHEN m.usr_age_lower_bound    IS NOT NULL THEN 0.5 ELSE 0 END +
7953                     CASE WHEN m.usr_age_upper_bound    IS NOT NULL THEN 0.5 ELSE 0 END DESC LOOP
7954
7955             IF current_mp.is_renewal IS NOT NULL THEN
7956                 CONTINUE WHEN current_mp.is_renewal <> renewal;
7957             END IF;
7958
7959             IF current_mp.circ_modifier IS NOT NULL THEN
7960                 CONTINUE WHEN current_mp.circ_modifier <> item_object.circ_modifier OR item_object.circ_modifier IS NULL;
7961             END IF;
7962
7963             IF current_mp.marc_type IS NOT NULL THEN
7964                 IF item_object.circ_as_type IS NOT NULL THEN
7965                     CONTINUE WHEN current_mp.marc_type <> item_object.circ_as_type;
7966                 ELSE
7967                     CONTINUE WHEN current_mp.marc_type <> rec_descriptor.item_type;
7968                 END IF;
7969             END IF;
7970
7971             IF current_mp.marc_form IS NOT NULL THEN
7972                 CONTINUE WHEN current_mp.marc_form <> rec_descriptor.item_form;
7973             END IF;
7974
7975             IF current_mp.marc_vr_format IS NOT NULL THEN
7976                 CONTINUE WHEN current_mp.marc_vr_format <> rec_descriptor.vr_format;
7977             END IF;
7978
7979             IF current_mp.ref_flag IS NOT NULL THEN
7980                 CONTINUE WHEN current_mp.ref_flag <> item_object.ref;
7981             END IF;
7982
7983             IF current_mp.juvenile_flag IS NOT NULL THEN
7984                 CONTINUE WHEN current_mp.juvenile_flag <> user_object.juvenile;
7985             END IF;
7986
7987             IF current_mp.usr_age_lower_bound IS NOT NULL THEN
7988                 CONTINUE WHEN user_object.dob IS NULL OR current_mp.usr_age_lower_bound < age(user_object.dob);
7989             END IF;
7990
7991             IF current_mp.usr_age_upper_bound IS NOT NULL THEN
7992                 CONTINUE WHEN user_object.dob IS NULL OR current_mp.usr_age_upper_bound > age(user_object.dob);
7993             END IF;
7994
7995
7996             -- everything was undefined or matched
7997             matchpoint = current_mp;
7998
7999             EXIT WHEN matchpoint.id IS NOT NULL;
8000         END LOOP;
8001
8002         EXIT WHEN current_group.parent IS NULL OR matchpoint.id IS NOT NULL;
8003
8004         SELECT INTO current_group * FROM permission.grp_tree WHERE id = current_group.parent;
8005     END LOOP;
8006
8007     RETURN matchpoint;
8008 END;
8009 $func$ LANGUAGE plpgsql;
8010
8011 CREATE TYPE action.hold_stats AS (
8012     hold_count              INT,
8013     copy_count              INT,
8014     available_count         INT,
8015     total_copy_ratio        FLOAT,
8016     available_copy_ratio    FLOAT
8017 );
8018
8019 CREATE OR REPLACE FUNCTION action.copy_related_hold_stats (copy_id INT) RETURNS action.hold_stats AS $func$
8020 DECLARE
8021     output          action.hold_stats%ROWTYPE;
8022     hold_count      INT := 0;
8023     copy_count      INT := 0;
8024     available_count INT := 0;
8025     hold_map_data   RECORD;
8026 BEGIN
8027
8028     output.hold_count := 0;
8029     output.copy_count := 0;
8030     output.available_count := 0;
8031
8032     SELECT  COUNT( DISTINCT m.hold ) INTO hold_count
8033       FROM  action.hold_copy_map m
8034             JOIN action.hold_request h ON (m.hold = h.id)
8035       WHERE m.target_copy = copy_id
8036             AND NOT h.frozen;
8037
8038     output.hold_count := hold_count;
8039
8040     IF output.hold_count > 0 THEN
8041         FOR hold_map_data IN
8042             SELECT  DISTINCT m.target_copy,
8043                     acp.status
8044               FROM  action.hold_copy_map m
8045                     JOIN asset.copy acp ON (m.target_copy = acp.id)
8046                     JOIN action.hold_request h ON (m.hold = h.id)
8047               WHERE m.hold IN ( SELECT DISTINCT hold FROM action.hold_copy_map WHERE target_copy = copy_id ) AND NOT h.frozen
8048         LOOP
8049             output.copy_count := output.copy_count + 1;
8050             IF hold_map_data.status IN (0,7,12) THEN
8051                 output.available_count := output.available_count + 1;
8052             END IF;
8053         END LOOP;
8054         output.total_copy_ratio = output.copy_count::FLOAT / output.hold_count::FLOAT;
8055         output.available_copy_ratio = output.available_count::FLOAT / output.hold_count::FLOAT;
8056
8057     END IF;
8058
8059     RETURN output;
8060
8061 END;
8062 $func$ LANGUAGE PLPGSQL;
8063
8064 ALTER TABLE config.circ_matrix_matchpoint ADD COLUMN total_copy_hold_ratio FLOAT;
8065 ALTER TABLE config.circ_matrix_matchpoint ADD COLUMN available_copy_hold_ratio FLOAT;
8066
8067 ALTER TABLE config.circ_matrix_matchpoint DROP CONSTRAINT ep_once_per_grp_loc_mod_marc;
8068
8069 ALTER TABLE config.circ_matrix_matchpoint ADD COLUMN copy_circ_lib   INT REFERENCES actor.org_unit (id) DEFERRABLE INITIALLY DEFERRED;
8070 ALTER TABLE config.circ_matrix_matchpoint ADD COLUMN copy_owning_lib INT REFERENCES actor.org_unit (id) DEFERRABLE INITIALLY DEFERRED;
8071
8072 ALTER TABLE config.circ_matrix_matchpoint ADD CONSTRAINT ep_once_per_grp_loc_mod_marc UNIQUE (
8073     grp, org_unit, circ_modifier, marc_type, marc_form, marc_vr_format, ref_flag,
8074     juvenile_flag, usr_age_lower_bound, usr_age_upper_bound, is_renewal, copy_circ_lib,
8075     copy_owning_lib
8076 );
8077
8078 -- Return the correct fail_part when the item can't be found
8079 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$
8080 DECLARE
8081     user_object        actor.usr%ROWTYPE;
8082     standing_penalty    config.standing_penalty%ROWTYPE;
8083     item_object        asset.copy%ROWTYPE;
8084     item_status_object    config.copy_status%ROWTYPE;
8085     item_location_object    asset.copy_location%ROWTYPE;
8086     result            action.matrix_test_result;
8087     circ_test        config.circ_matrix_matchpoint%ROWTYPE;
8088     out_by_circ_mod        config.circ_matrix_circ_mod_test%ROWTYPE;
8089     circ_mod_map        config.circ_matrix_circ_mod_test_map%ROWTYPE;
8090     hold_ratio          action.hold_stats%ROWTYPE;
8091     penalty_type         TEXT;
8092     tmp_grp         INT;
8093     items_out        INT;
8094     context_org_list        INT[];
8095     done            BOOL := FALSE;
8096 BEGIN
8097     result.success := TRUE;
8098
8099     -- Fail if the user is BARRED
8100     SELECT INTO user_object * FROM actor.usr WHERE id = match_user;
8101
8102     -- Fail if we couldn't find the user 
8103     IF user_object.id IS NULL THEN
8104         result.fail_part := 'no_user';
8105         result.success := FALSE;
8106         done := TRUE;
8107         RETURN NEXT result;
8108         RETURN;
8109     END IF;
8110
8111     SELECT INTO item_object * FROM asset.copy WHERE id = match_item;
8112
8113     -- Fail if we couldn't find the item 
8114     IF item_object.id IS NULL THEN
8115         result.fail_part := 'no_item';
8116         result.success := FALSE;
8117         done := TRUE;
8118         RETURN NEXT result;
8119         RETURN;
8120     END IF;
8121
8122     SELECT INTO circ_test * FROM action.find_circ_matrix_matchpoint(circ_ou, match_item, match_user, renewal);
8123     result.matchpoint := circ_test.id;
8124
8125     -- Fail if we couldn't find a matchpoint
8126     IF result.matchpoint IS NULL THEN
8127         result.fail_part := 'no_matchpoint';
8128         result.success := FALSE;
8129         done := TRUE;
8130         RETURN NEXT result;
8131     END IF;
8132
8133     IF user_object.barred IS TRUE THEN
8134         result.fail_part := 'actor.usr.barred';
8135         result.success := FALSE;
8136         done := TRUE;
8137         RETURN NEXT result;
8138     END IF;
8139
8140     -- Fail if the item can't circulate
8141     IF item_object.circulate IS FALSE THEN
8142         result.fail_part := 'asset.copy.circulate';
8143         result.success := FALSE;
8144         done := TRUE;
8145         RETURN NEXT result;
8146     END IF;
8147
8148     -- Fail if the item isn't in a circulateable status on a non-renewal
8149     IF NOT renewal AND item_object.status NOT IN ( 0, 7, 8 ) THEN 
8150         result.fail_part := 'asset.copy.status';
8151         result.success := FALSE;
8152         done := TRUE;
8153         RETURN NEXT result;
8154     ELSIF renewal AND item_object.status <> 1 THEN
8155         result.fail_part := 'asset.copy.status';
8156         result.success := FALSE;
8157         done := TRUE;
8158         RETURN NEXT result;
8159     END IF;
8160
8161     -- Fail if the item can't circulate because of the shelving location
8162     SELECT INTO item_location_object * FROM asset.copy_location WHERE id = item_object.location;
8163     IF item_location_object.circulate IS FALSE THEN
8164         result.fail_part := 'asset.copy_location.circulate';
8165         result.success := FALSE;
8166         done := TRUE;
8167         RETURN NEXT result;
8168     END IF;
8169
8170     SELECT INTO context_org_list ARRAY_ACCUM(id) FROM actor.org_unit_full_path( circ_test.org_unit );
8171
8172     -- Fail if the test is set to hard non-circulating
8173     IF circ_test.circulate IS FALSE THEN
8174         result.fail_part := 'config.circ_matrix_test.circulate';
8175         result.success := FALSE;
8176         done := TRUE;
8177         RETURN NEXT result;
8178     END IF;
8179
8180     -- Fail if the total copy-hold ratio is too low
8181     IF circ_test.total_copy_hold_ratio IS NOT NULL THEN
8182         SELECT INTO hold_ratio * FROM action.copy_related_hold_stats(match_item);
8183         IF hold_ratio.total_copy_ratio IS NOT NULL AND hold_ratio.total_copy_ratio < circ_test.total_copy_hold_ratio THEN
8184             result.fail_part := 'config.circ_matrix_test.total_copy_hold_ratio';
8185             result.success := FALSE;
8186             done := TRUE;
8187             RETURN NEXT result;
8188         END IF;
8189     END IF;
8190
8191     -- Fail if the available copy-hold ratio is too low
8192     IF circ_test.available_copy_hold_ratio IS NOT NULL THEN
8193         SELECT INTO hold_ratio * FROM action.copy_related_hold_stats(match_item);
8194         IF hold_ratio.available_copy_ratio IS NOT NULL AND hold_ratio.available_copy_ratio < circ_test.available_copy_hold_ratio THEN
8195             result.fail_part := 'config.circ_matrix_test.available_copy_hold_ratio';
8196             result.success := FALSE;
8197             done := TRUE;
8198             RETURN NEXT result;
8199         END IF;
8200     END IF;
8201
8202     IF renewal THEN
8203         penalty_type = '%RENEW%';
8204     ELSE
8205         penalty_type = '%CIRC%';
8206     END IF;
8207
8208     FOR standing_penalty IN
8209         SELECT  DISTINCT csp.*
8210           FROM  actor.usr_standing_penalty usp
8211                 JOIN config.standing_penalty csp ON (csp.id = usp.standing_penalty)
8212           WHERE usr = match_user
8213                 AND usp.org_unit IN ( SELECT * FROM unnest(context_org_list) )
8214                 AND (usp.stop_date IS NULL or usp.stop_date > NOW())
8215                 AND csp.block_list LIKE penalty_type LOOP
8216
8217         result.fail_part := standing_penalty.name;
8218         result.success := FALSE;
8219         done := TRUE;
8220         RETURN NEXT result;
8221     END LOOP;
8222
8223     -- Fail if the user has too many items with specific circ_modifiers checked out
8224     FOR out_by_circ_mod IN SELECT * FROM config.circ_matrix_circ_mod_test WHERE matchpoint = circ_test.id LOOP
8225         SELECT  INTO items_out COUNT(*)
8226           FROM  action.circulation circ
8227             JOIN asset.copy cp ON (cp.id = circ.target_copy)
8228           WHERE circ.usr = match_user
8229                AND circ.circ_lib IN ( SELECT * FROM unnest(context_org_list) )
8230             AND circ.checkin_time IS NULL
8231             AND (circ.stop_fines IN ('MAXFINES','LONGOVERDUE') OR circ.stop_fines IS NULL)
8232             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);
8233         IF items_out >= out_by_circ_mod.items_out THEN
8234             result.fail_part := 'config.circ_matrix_circ_mod_test';
8235             result.success := FALSE;
8236             done := TRUE;
8237             RETURN NEXT result;
8238         END IF;
8239     END LOOP;
8240
8241     -- If we passed everything, return the successful matchpoint id
8242     IF NOT done THEN
8243         RETURN NEXT result;
8244     END IF;
8245
8246     RETURN;
8247 END;
8248 $func$ LANGUAGE plpgsql;
8249
8250 CREATE TABLE config.remote_account (
8251     id          SERIAL  PRIMARY KEY,
8252     label       TEXT    NOT NULL,
8253     host        TEXT    NOT NULL,   -- name or IP, :port optional
8254     username    TEXT,               -- optional, since we could default to $USER
8255     password    TEXT,               -- optional, since we could use SSH keys, or anonymous login.
8256     account     TEXT,               -- aka profile or FTP "account" command
8257     path        TEXT,               -- aka directory
8258     owner       INT     NOT NULL REFERENCES actor.org_unit (id) DEFERRABLE INITIALLY DEFERRED,
8259     last_activity TIMESTAMP WITH TIME ZONE
8260 );
8261
8262 CREATE TABLE acq.edi_account (      -- similar tables can extend remote_account for other parts of EG
8263     provider    INT     NOT NULL REFERENCES acq.provider          (id) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED,
8264     in_dir      TEXT,   -- incoming messages dir (probably different than config.remote_account.path, the outgoing dir)
8265         vendcode    TEXT,
8266         vendacct    TEXT
8267
8268 ) INHERITS (config.remote_account);
8269
8270 ALTER TABLE acq.edi_account ADD PRIMARY KEY (id);
8271
8272 CREATE TABLE acq.claim_type (
8273         id             SERIAL           PRIMARY KEY,
8274         org_unit       INT              NOT NULL REFERENCES actor.org_unit(id)
8275                                                  DEFERRABLE INITIALLY DEFERRED,
8276         code           TEXT             NOT NULL,
8277         description    TEXT             NOT NULL,
8278         CONSTRAINT claim_type_once_per_org UNIQUE ( org_unit, code )
8279 );
8280
8281 CREATE TABLE acq.claim (
8282         id             SERIAL           PRIMARY KEY,
8283         type           INT              NOT NULL REFERENCES acq.claim_type
8284                                                  DEFERRABLE INITIALLY DEFERRED,
8285         lineitem_detail BIGINT          NOT NULL REFERENCES acq.lineitem_detail
8286                                                  DEFERRABLE INITIALLY DEFERRED
8287 );
8288
8289 CREATE TABLE acq.claim_policy (
8290         id              SERIAL       PRIMARY KEY,
8291         org_unit        INT          NOT NULL REFERENCES actor.org_unit
8292                                      DEFERRABLE INITIALLY DEFERRED,
8293         name            TEXT         NOT NULL,
8294         description     TEXT         NOT NULL,
8295         CONSTRAINT name_once_per_org UNIQUE (org_unit, name)
8296 );
8297
8298 -- Add a san column for EDI. 
8299 -- See: http://isbn.org/standards/home/isbn/us/san/san-qa.asp
8300
8301 ALTER TABLE acq.provider ADD COLUMN san INT;
8302
8303 ALTER TABLE acq.provider ALTER COLUMN san TYPE TEXT USING lpad(text(san), 7, '0');
8304
8305 -- null edi_default is OK... it has to be, since we have no values in acq.edi_account yet
8306 ALTER TABLE acq.provider ADD COLUMN edi_default INT REFERENCES acq.edi_account (id) DEFERRABLE INITIALLY DEFERRED;
8307
8308 ALTER TABLE acq.provider
8309         ADD COLUMN active BOOL NOT NULL DEFAULT TRUE;
8310
8311 ALTER TABLE acq.provider
8312         ADD COLUMN prepayment_required BOOLEAN NOT NULL DEFAULT FALSE;
8313
8314 ALTER TABLE acq.provider
8315         ADD COLUMN url TEXT;
8316
8317 ALTER TABLE acq.provider
8318         ADD COLUMN email TEXT;
8319
8320 ALTER TABLE acq.provider
8321         ADD COLUMN phone TEXT;
8322
8323 ALTER TABLE acq.provider
8324         ADD COLUMN fax_phone TEXT;
8325
8326 ALTER TABLE acq.provider
8327         ADD COLUMN default_claim_policy INT
8328                 REFERENCES acq.claim_policy
8329                 DEFERRABLE INITIALLY DEFERRED;
8330
8331 ALTER TABLE action.transit_copy
8332 ADD COLUMN prev_dest INTEGER REFERENCES actor.org_unit( id )
8333                                                          DEFERRABLE INITIALLY DEFERRED;
8334
8335 DROP SCHEMA IF EXISTS booking CASCADE;
8336
8337 CREATE SCHEMA booking;
8338
8339 CREATE TABLE booking.resource_type (
8340         id             SERIAL          PRIMARY KEY,
8341         name           TEXT            NOT NULL,
8342         fine_interval  INTERVAL,
8343         fine_amount    DECIMAL(8,2)    NOT NULL DEFAULT 0,
8344         owner          INT             NOT NULL
8345                                        REFERENCES actor.org_unit( id )
8346                                        DEFERRABLE INITIALLY DEFERRED,
8347         catalog_item   BOOLEAN         NOT NULL DEFAULT FALSE,
8348         transferable   BOOLEAN         NOT NULL DEFAULT FALSE,
8349     record         BIGINT          REFERENCES biblio.record_entry (id)
8350                                        DEFERRABLE INITIALLY DEFERRED,
8351     max_fine       NUMERIC(8,2),
8352     elbow_room     INTERVAL,
8353     CONSTRAINT brt_name_and_record_once_per_owner UNIQUE(owner, name, record)
8354 );
8355
8356 CREATE TABLE booking.resource (
8357         id             SERIAL           PRIMARY KEY,
8358         owner          INT              NOT NULL
8359                                         REFERENCES actor.org_unit(id)
8360                                         DEFERRABLE INITIALLY DEFERRED,
8361         type           INT              NOT NULL
8362                                         REFERENCES booking.resource_type(id)
8363                                         DEFERRABLE INITIALLY DEFERRED,
8364         overbook       BOOLEAN          NOT NULL DEFAULT FALSE,
8365         barcode        TEXT             NOT NULL,
8366         deposit        BOOLEAN          NOT NULL DEFAULT FALSE,
8367         deposit_amount DECIMAL(8,2)     NOT NULL DEFAULT 0.00,
8368         user_fee       DECIMAL(8,2)     NOT NULL DEFAULT 0.00,
8369         CONSTRAINT br_unique UNIQUE (owner, barcode)
8370 );
8371
8372 -- For non-catalog items: hijack barcode for name/description
8373
8374 CREATE TABLE booking.resource_attr (
8375         id              SERIAL          PRIMARY KEY,
8376         owner           INT             NOT NULL
8377                                         REFERENCES actor.org_unit(id)
8378                                         DEFERRABLE INITIALLY DEFERRED,
8379         name            TEXT            NOT NULL,
8380         resource_type   INT             NOT NULL
8381                                         REFERENCES booking.resource_type(id)
8382                                         ON DELETE CASCADE
8383                                         DEFERRABLE INITIALLY DEFERRED,
8384         required        BOOLEAN         NOT NULL DEFAULT FALSE,
8385         CONSTRAINT bra_name_once_per_type UNIQUE(resource_type, name)
8386 );
8387
8388 CREATE TABLE booking.resource_attr_value (
8389         id               SERIAL         PRIMARY KEY,
8390         owner            INT            NOT NULL
8391                                         REFERENCES actor.org_unit(id)
8392                                         DEFERRABLE INITIALLY DEFERRED,
8393         attr             INT            NOT NULL
8394                                         REFERENCES booking.resource_attr(id)
8395                                         DEFERRABLE INITIALLY DEFERRED,
8396         valid_value      TEXT           NOT NULL,
8397         CONSTRAINT brav_logical_key UNIQUE(owner, attr, valid_value)
8398 );
8399
8400 CREATE TABLE booking.resource_attr_map (
8401         id               SERIAL         PRIMARY KEY,
8402         resource         INT            NOT NULL
8403                                         REFERENCES booking.resource(id)
8404                                         ON DELETE CASCADE
8405                                         DEFERRABLE INITIALLY DEFERRED,
8406         resource_attr    INT            NOT NULL
8407                                         REFERENCES booking.resource_attr(id)
8408                                         ON DELETE CASCADE
8409                                         DEFERRABLE INITIALLY DEFERRED,
8410         value            INT            NOT NULL
8411                                         REFERENCES booking.resource_attr_value(id)
8412                                         DEFERRABLE INITIALLY DEFERRED,
8413         CONSTRAINT bram_one_value_per_attr UNIQUE(resource, resource_attr)
8414 );
8415
8416 CREATE TABLE booking.reservation (
8417         request_time     TIMESTAMPTZ   NOT NULL DEFAULT now(),
8418         start_time       TIMESTAMPTZ,
8419         end_time         TIMESTAMPTZ,
8420         capture_time     TIMESTAMPTZ,
8421         cancel_time      TIMESTAMPTZ,
8422         pickup_time      TIMESTAMPTZ,
8423         return_time      TIMESTAMPTZ,
8424         booking_interval INTERVAL,
8425         fine_interval    INTERVAL,
8426         fine_amount      DECIMAL(8,2),
8427         target_resource_type  INT       NOT NULL
8428                                         REFERENCES booking.resource_type(id)
8429                                         ON DELETE CASCADE
8430                                         DEFERRABLE INITIALLY DEFERRED,
8431         target_resource  INT            REFERENCES booking.resource(id)
8432                                         ON DELETE CASCADE
8433                                         DEFERRABLE INITIALLY DEFERRED,
8434         current_resource INT            REFERENCES booking.resource(id)
8435                                         ON DELETE CASCADE
8436                                         DEFERRABLE INITIALLY DEFERRED,
8437         request_lib      INT            NOT NULL
8438                                         REFERENCES actor.org_unit(id)
8439                                         DEFERRABLE INITIALLY DEFERRED,
8440         pickup_lib       INT            REFERENCES actor.org_unit(id)
8441                                         DEFERRABLE INITIALLY DEFERRED,
8442         capture_staff    INT            REFERENCES actor.usr(id)
8443                                         DEFERRABLE INITIALLY DEFERRED,
8444     max_fine         NUMERIC(8,2)
8445 ) INHERITS (money.billable_xact);
8446
8447 ALTER TABLE booking.reservation ADD PRIMARY KEY (id);
8448
8449 ALTER TABLE booking.reservation
8450         ADD CONSTRAINT booking_reservation_usr_fkey
8451         FOREIGN KEY (usr) REFERENCES actor.usr (id)
8452         DEFERRABLE INITIALLY DEFERRED;
8453
8454 CREATE TABLE booking.reservation_attr_value_map (
8455         id               SERIAL         PRIMARY KEY,
8456         reservation      INT            NOT NULL
8457                                         REFERENCES booking.reservation(id)
8458                                         ON DELETE CASCADE
8459                                         DEFERRABLE INITIALLY DEFERRED,
8460         attr_value       INT            NOT NULL
8461                                         REFERENCES booking.resource_attr_value(id)
8462                                         ON DELETE CASCADE
8463                                         DEFERRABLE INITIALLY DEFERRED,
8464         CONSTRAINT bravm_logical_key UNIQUE(reservation, attr_value)
8465 );
8466
8467 -- represents a circ chain summary
8468 CREATE TYPE action.circ_chain_summary AS (
8469     num_circs INTEGER,
8470     start_time TIMESTAMP WITH TIME ZONE,
8471     checkout_workstation TEXT,
8472     last_renewal_time TIMESTAMP WITH TIME ZONE, -- NULL if no renewals
8473     last_stop_fines TEXT,
8474     last_stop_fines_time TIMESTAMP WITH TIME ZONE,
8475     last_renewal_workstation TEXT, -- NULL if no renewals
8476     last_checkin_workstation TEXT,
8477     last_checkin_time TIMESTAMP WITH TIME ZONE,
8478     last_checkin_scan_time TIMESTAMP WITH TIME ZONE
8479 );
8480
8481 CREATE OR REPLACE FUNCTION action.circ_chain ( ctx_circ_id INTEGER ) RETURNS SETOF action.circulation AS $$
8482 DECLARE
8483     tmp_circ action.circulation%ROWTYPE;
8484     circ_0 action.circulation%ROWTYPE;
8485 BEGIN
8486
8487     SELECT INTO tmp_circ * FROM action.circulation WHERE id = ctx_circ_id;
8488
8489     IF tmp_circ IS NULL THEN
8490         RETURN NEXT tmp_circ;
8491     END IF;
8492     circ_0 := tmp_circ;
8493
8494     -- find the front of the chain
8495     WHILE TRUE LOOP
8496         SELECT INTO tmp_circ * FROM action.circulation WHERE id = tmp_circ.parent_circ;
8497         IF tmp_circ IS NULL THEN
8498             EXIT;
8499         END IF;
8500         circ_0 := tmp_circ;
8501     END LOOP;
8502
8503     -- now send the circs to the caller, oldest to newest
8504     tmp_circ := circ_0;
8505     WHILE TRUE LOOP
8506         IF tmp_circ IS NULL THEN
8507             EXIT;
8508         END IF;
8509         RETURN NEXT tmp_circ;
8510         SELECT INTO tmp_circ * FROM action.circulation WHERE parent_circ = tmp_circ.id;
8511     END LOOP;
8512
8513 END;
8514 $$ LANGUAGE 'plpgsql';
8515
8516 CREATE OR REPLACE FUNCTION action.summarize_circ_chain ( ctx_circ_id INTEGER ) RETURNS action.circ_chain_summary AS $$
8517
8518 DECLARE
8519
8520     -- first circ in the chain
8521     circ_0 action.circulation%ROWTYPE;
8522
8523     -- last circ in the chain
8524     circ_n action.circulation%ROWTYPE;
8525
8526     -- circ chain under construction
8527     chain action.circ_chain_summary;
8528     tmp_circ action.circulation%ROWTYPE;
8529
8530 BEGIN
8531     
8532     chain.num_circs := 0;
8533     FOR tmp_circ IN SELECT * FROM action.circ_chain(ctx_circ_id) LOOP
8534
8535         IF chain.num_circs = 0 THEN
8536             circ_0 := tmp_circ;
8537         END IF;
8538
8539         chain.num_circs := chain.num_circs + 1;
8540         circ_n := tmp_circ;
8541     END LOOP;
8542
8543     chain.start_time := circ_0.xact_start;
8544     chain.last_stop_fines := circ_n.stop_fines;
8545     chain.last_stop_fines_time := circ_n.stop_fines_time;
8546     chain.last_checkin_time := circ_n.checkin_time;
8547     chain.last_checkin_scan_time := circ_n.checkin_scan_time;
8548     SELECT INTO chain.checkout_workstation name FROM actor.workstation WHERE id = circ_0.workstation;
8549     SELECT INTO chain.last_checkin_workstation name FROM actor.workstation WHERE id = circ_n.checkin_workstation;
8550
8551     IF chain.num_circs > 1 THEN
8552         chain.last_renewal_time := circ_n.xact_start;
8553         SELECT INTO chain.last_renewal_workstation name FROM actor.workstation WHERE id = circ_n.workstation;
8554     END IF;
8555
8556     RETURN chain;
8557
8558 END;
8559 $$ LANGUAGE 'plpgsql';
8560
8561 CREATE TRIGGER mat_summary_create_tgr AFTER INSERT ON booking.reservation FOR EACH ROW EXECUTE PROCEDURE money.mat_summary_create ('reservation');
8562 CREATE TRIGGER mat_summary_change_tgr AFTER UPDATE ON booking.reservation FOR EACH ROW EXECUTE PROCEDURE money.mat_summary_update ();
8563 CREATE TRIGGER mat_summary_remove_tgr AFTER DELETE ON booking.reservation FOR EACH ROW EXECUTE PROCEDURE money.mat_summary_delete ();
8564
8565 ALTER TABLE config.standing_penalty
8566         ADD COLUMN org_depth   INTEGER;
8567
8568 CREATE OR REPLACE FUNCTION actor.calculate_system_penalties( match_user INT, context_org INT ) RETURNS SETOF actor.usr_standing_penalty AS $func$
8569 DECLARE
8570     user_object         actor.usr%ROWTYPE;
8571     new_sp_row          actor.usr_standing_penalty%ROWTYPE;
8572     existing_sp_row     actor.usr_standing_penalty%ROWTYPE;
8573     collections_fines   permission.grp_penalty_threshold%ROWTYPE;
8574     max_fines           permission.grp_penalty_threshold%ROWTYPE;
8575     max_overdue         permission.grp_penalty_threshold%ROWTYPE;
8576     max_items_out       permission.grp_penalty_threshold%ROWTYPE;
8577     tmp_grp             INT;
8578     items_overdue       INT;
8579     items_out           INT;
8580     context_org_list    INT[];
8581     current_fines        NUMERIC(8,2) := 0.0;
8582     tmp_fines            NUMERIC(8,2);
8583     tmp_groc            RECORD;
8584     tmp_circ            RECORD;
8585     tmp_org             actor.org_unit%ROWTYPE;
8586     tmp_penalty         config.standing_penalty%ROWTYPE;
8587     tmp_depth           INTEGER;
8588 BEGIN
8589     SELECT INTO user_object * FROM actor.usr WHERE id = match_user;
8590
8591     -- Max fines
8592     SELECT INTO tmp_org * FROM actor.org_unit WHERE id = context_org;
8593
8594     -- Fail if the user has a high fine balance
8595     LOOP
8596         tmp_grp := user_object.profile;
8597         LOOP
8598             SELECT * INTO max_fines FROM permission.grp_penalty_threshold WHERE grp = tmp_grp AND penalty = 1 AND org_unit = tmp_org.id;
8599
8600             IF max_fines.threshold IS NULL THEN
8601                 SELECT parent INTO tmp_grp FROM permission.grp_tree WHERE id = tmp_grp;
8602             ELSE
8603                 EXIT;
8604             END IF;
8605
8606             IF tmp_grp IS NULL THEN
8607                 EXIT;
8608             END IF;
8609         END LOOP;
8610
8611         IF max_fines.threshold IS NOT NULL OR tmp_org.parent_ou IS NULL THEN
8612             EXIT;
8613         END IF;
8614
8615         SELECT * INTO tmp_org FROM actor.org_unit WHERE id = tmp_org.parent_ou;
8616
8617     END LOOP;
8618
8619     IF max_fines.threshold IS NOT NULL THEN
8620
8621         RETURN QUERY
8622             SELECT  *
8623               FROM  actor.usr_standing_penalty
8624               WHERE usr = match_user
8625                     AND org_unit = max_fines.org_unit
8626                     AND (stop_date IS NULL or stop_date > NOW())
8627                     AND standing_penalty = 1;
8628
8629         SELECT INTO context_org_list ARRAY_ACCUM(id) FROM actor.org_unit_full_path( max_fines.org_unit );
8630
8631         SELECT  SUM(f.balance_owed) INTO current_fines
8632           FROM  money.materialized_billable_xact_summary f
8633                 JOIN (
8634                     SELECT  r.id
8635                       FROM  booking.reservation r
8636                       WHERE r.usr = match_user
8637                             AND r.pickup_lib IN (SELECT * FROM unnest(context_org_list))
8638                             AND xact_finish IS NULL
8639                                 UNION ALL
8640                     SELECT  g.id
8641                       FROM  money.grocery g
8642                       WHERE g.usr = match_user
8643                             AND g.billing_location IN (SELECT * FROM unnest(context_org_list))
8644                             AND xact_finish IS NULL
8645                                 UNION ALL
8646                     SELECT  circ.id
8647                       FROM  action.circulation circ
8648                       WHERE circ.usr = match_user
8649                             AND circ.circ_lib IN (SELECT * FROM unnest(context_org_list))
8650                             AND xact_finish IS NULL ) l USING (id);
8651
8652         IF current_fines >= max_fines.threshold THEN
8653             new_sp_row.usr := match_user;
8654             new_sp_row.org_unit := max_fines.org_unit;
8655             new_sp_row.standing_penalty := 1;
8656             RETURN NEXT new_sp_row;
8657         END IF;
8658     END IF;
8659
8660     -- Start over for max overdue
8661     SELECT INTO tmp_org * FROM actor.org_unit WHERE id = context_org;
8662
8663     -- Fail if the user has too many overdue items
8664     LOOP
8665         tmp_grp := user_object.profile;
8666         LOOP
8667
8668             SELECT * INTO max_overdue FROM permission.grp_penalty_threshold WHERE grp = tmp_grp AND penalty = 2 AND org_unit = tmp_org.id;
8669
8670             IF max_overdue.threshold IS NULL THEN
8671                 SELECT parent INTO tmp_grp FROM permission.grp_tree WHERE id = tmp_grp;
8672             ELSE
8673                 EXIT;
8674             END IF;
8675
8676             IF tmp_grp IS NULL THEN
8677                 EXIT;
8678             END IF;
8679         END LOOP;
8680
8681         IF max_overdue.threshold IS NOT NULL OR tmp_org.parent_ou IS NULL THEN
8682             EXIT;
8683         END IF;
8684
8685         SELECT INTO tmp_org * FROM actor.org_unit WHERE id = tmp_org.parent_ou;
8686
8687     END LOOP;
8688
8689     IF max_overdue.threshold IS NOT NULL THEN
8690
8691         RETURN QUERY
8692             SELECT  *
8693               FROM  actor.usr_standing_penalty
8694               WHERE usr = match_user
8695                     AND org_unit = max_overdue.org_unit
8696                     AND (stop_date IS NULL or stop_date > NOW())
8697                     AND standing_penalty = 2;
8698
8699         SELECT  INTO items_overdue COUNT(*)
8700           FROM  action.circulation circ
8701                 JOIN  actor.org_unit_full_path( max_overdue.org_unit ) fp ON (circ.circ_lib = fp.id)
8702           WHERE circ.usr = match_user
8703             AND circ.checkin_time IS NULL
8704             AND circ.due_date < NOW()
8705             AND (circ.stop_fines = 'MAXFINES' OR circ.stop_fines IS NULL);
8706
8707         IF items_overdue >= max_overdue.threshold::INT THEN
8708             new_sp_row.usr := match_user;
8709             new_sp_row.org_unit := max_overdue.org_unit;
8710             new_sp_row.standing_penalty := 2;
8711             RETURN NEXT new_sp_row;
8712         END IF;
8713     END IF;
8714
8715     -- Start over for max out
8716     SELECT INTO tmp_org * FROM actor.org_unit WHERE id = context_org;
8717
8718     -- Fail if the user has too many checked out items
8719     LOOP
8720         tmp_grp := user_object.profile;
8721         LOOP
8722             SELECT * INTO max_items_out FROM permission.grp_penalty_threshold WHERE grp = tmp_grp AND penalty = 3 AND org_unit = tmp_org.id;
8723
8724             IF max_items_out.threshold IS NULL THEN
8725                 SELECT parent INTO tmp_grp FROM permission.grp_tree WHERE id = tmp_grp;
8726             ELSE
8727                 EXIT;
8728             END IF;
8729
8730             IF tmp_grp IS NULL THEN
8731                 EXIT;
8732             END IF;
8733         END LOOP;
8734
8735         IF max_items_out.threshold IS NOT NULL OR tmp_org.parent_ou IS NULL THEN
8736             EXIT;
8737         END IF;
8738
8739         SELECT INTO tmp_org * FROM actor.org_unit WHERE id = tmp_org.parent_ou;
8740
8741     END LOOP;
8742
8743
8744     -- Fail if the user has too many items checked out
8745     IF max_items_out.threshold IS NOT NULL THEN
8746
8747         RETURN QUERY
8748             SELECT  *
8749               FROM  actor.usr_standing_penalty
8750               WHERE usr = match_user
8751                     AND org_unit = max_items_out.org_unit
8752                     AND (stop_date IS NULL or stop_date > NOW())
8753                     AND standing_penalty = 3;
8754
8755         SELECT  INTO items_out COUNT(*)
8756           FROM  action.circulation circ
8757                 JOIN  actor.org_unit_full_path( max_items_out.org_unit ) fp ON (circ.circ_lib = fp.id)
8758           WHERE circ.usr = match_user
8759                 AND circ.checkin_time IS NULL
8760                 AND (circ.stop_fines IN ('MAXFINES','LONGOVERDUE') OR circ.stop_fines IS NULL);
8761
8762            IF items_out >= max_items_out.threshold::INT THEN
8763             new_sp_row.usr := match_user;
8764             new_sp_row.org_unit := max_items_out.org_unit;
8765             new_sp_row.standing_penalty := 3;
8766             RETURN NEXT new_sp_row;
8767            END IF;
8768     END IF;
8769
8770     -- Start over for collections warning
8771     SELECT INTO tmp_org * FROM actor.org_unit WHERE id = context_org;
8772
8773     -- Fail if the user has a collections-level fine balance
8774     LOOP
8775         tmp_grp := user_object.profile;
8776         LOOP
8777             SELECT * INTO max_fines FROM permission.grp_penalty_threshold WHERE grp = tmp_grp AND penalty = 4 AND org_unit = tmp_org.id;
8778
8779             IF max_fines.threshold IS NULL THEN
8780                 SELECT parent INTO tmp_grp FROM permission.grp_tree WHERE id = tmp_grp;
8781             ELSE
8782                 EXIT;
8783             END IF;
8784
8785             IF tmp_grp IS NULL THEN
8786                 EXIT;
8787             END IF;
8788         END LOOP;
8789
8790         IF max_fines.threshold IS NOT NULL OR tmp_org.parent_ou IS NULL THEN
8791             EXIT;
8792         END IF;
8793
8794         SELECT * INTO tmp_org FROM actor.org_unit WHERE id = tmp_org.parent_ou;
8795
8796     END LOOP;
8797
8798     IF max_fines.threshold IS NOT NULL THEN
8799
8800         RETURN QUERY
8801             SELECT  *
8802               FROM  actor.usr_standing_penalty
8803               WHERE usr = match_user
8804                     AND org_unit = max_fines.org_unit
8805                     AND (stop_date IS NULL or stop_date > NOW())
8806                     AND standing_penalty = 4;
8807
8808         SELECT INTO context_org_list ARRAY_ACCUM(id) FROM actor.org_unit_full_path( max_fines.org_unit );
8809
8810         SELECT  SUM(f.balance_owed) INTO current_fines
8811           FROM  money.materialized_billable_xact_summary f
8812                 JOIN (
8813                     SELECT  r.id
8814                       FROM  booking.reservation r
8815                       WHERE r.usr = match_user
8816                             AND r.pickup_lib IN (SELECT * FROM unnest(context_org_list))
8817                             AND r.xact_finish IS NULL
8818                                 UNION ALL
8819                     SELECT  g.id
8820                       FROM  money.grocery g
8821                       WHERE g.usr = match_user
8822                             AND g.billing_location IN (SELECT * FROM unnest(context_org_list))
8823                             AND g.xact_finish IS NULL
8824                                 UNION ALL
8825                     SELECT  circ.id
8826                       FROM  action.circulation circ
8827                       WHERE circ.usr = match_user
8828                             AND circ.circ_lib IN (SELECT * FROM unnest(context_org_list))
8829                             AND circ.xact_finish IS NULL ) l USING (id);
8830
8831         IF current_fines >= max_fines.threshold THEN
8832             new_sp_row.usr := match_user;
8833             new_sp_row.org_unit := max_fines.org_unit;
8834             new_sp_row.standing_penalty := 4;
8835             RETURN NEXT new_sp_row;
8836         END IF;
8837     END IF;
8838
8839     -- Start over for in collections
8840     SELECT INTO tmp_org * FROM actor.org_unit WHERE id = context_org;
8841
8842     -- Remove the in-collections penalty if the user has paid down enough
8843     -- This penalty is different, because this code is not responsible for creating 
8844     -- new in-collections penalties, only for removing them
8845     LOOP
8846         tmp_grp := user_object.profile;
8847         LOOP
8848             SELECT * INTO max_fines FROM permission.grp_penalty_threshold WHERE grp = tmp_grp AND penalty = 30 AND org_unit = tmp_org.id;
8849
8850             IF max_fines.threshold IS NULL THEN
8851                 SELECT parent INTO tmp_grp FROM permission.grp_tree WHERE id = tmp_grp;
8852             ELSE
8853                 EXIT;
8854             END IF;
8855
8856             IF tmp_grp IS NULL THEN
8857                 EXIT;
8858             END IF;
8859         END LOOP;
8860
8861         IF max_fines.threshold IS NOT NULL OR tmp_org.parent_ou IS NULL THEN
8862             EXIT;
8863         END IF;
8864
8865         SELECT * INTO tmp_org FROM actor.org_unit WHERE id = tmp_org.parent_ou;
8866
8867     END LOOP;
8868
8869     IF max_fines.threshold IS NOT NULL THEN
8870
8871         SELECT INTO context_org_list ARRAY_ACCUM(id) FROM actor.org_unit_full_path( max_fines.org_unit );
8872
8873         -- first, see if the user had paid down to the threshold
8874         SELECT  SUM(f.balance_owed) INTO current_fines
8875           FROM  money.materialized_billable_xact_summary f
8876                 JOIN (
8877                     SELECT  r.id
8878                       FROM  booking.reservation r
8879                       WHERE r.usr = match_user
8880                             AND r.pickup_lib IN (SELECT * FROM unnest(context_org_list))
8881                             AND r.xact_finish IS NULL
8882                                 UNION ALL
8883                     SELECT  g.id
8884                       FROM  money.grocery g
8885                       WHERE g.usr = match_user
8886                             AND g.billing_location IN (SELECT * FROM unnest(context_org_list))
8887                             AND g.xact_finish IS NULL
8888                                 UNION ALL
8889                     SELECT  circ.id
8890                       FROM  action.circulation circ
8891                       WHERE circ.usr = match_user
8892                             AND circ.circ_lib IN (SELECT * FROM unnest(context_org_list))
8893                             AND circ.xact_finish IS NULL ) l USING (id);
8894
8895         IF current_fines IS NULL OR current_fines <= max_fines.threshold THEN
8896             -- patron has paid down enough
8897
8898             SELECT INTO tmp_penalty * FROM config.standing_penalty WHERE id = 30;
8899
8900             IF tmp_penalty.org_depth IS NOT NULL THEN
8901
8902                 -- since this code is not responsible for applying the penalty, it can't 
8903                 -- guarantee the current context org will match the org at which the penalty 
8904                 --- was applied.  search up the org tree until we hit the configured penalty depth
8905                 SELECT INTO tmp_org * FROM actor.org_unit WHERE id = context_org;
8906                 SELECT INTO tmp_depth depth FROM actor.org_unit_type WHERE id = tmp_org.ou_type;
8907
8908                 WHILE tmp_depth >= tmp_penalty.org_depth LOOP
8909
8910                     RETURN QUERY
8911                         SELECT  *
8912                           FROM  actor.usr_standing_penalty
8913                           WHERE usr = match_user
8914                                 AND org_unit = tmp_org.id
8915                                 AND (stop_date IS NULL or stop_date > NOW())
8916                                 AND standing_penalty = 30;
8917
8918                     IF tmp_org.parent_ou IS NULL THEN
8919                         EXIT;
8920                     END IF;
8921
8922                     SELECT * INTO tmp_org FROM actor.org_unit WHERE id = tmp_org.parent_ou;
8923                     SELECT INTO tmp_depth depth FROM actor.org_unit_type WHERE id = tmp_org.ou_type;
8924                 END LOOP;
8925
8926             ELSE
8927
8928                 -- no penalty depth is defined, look for exact matches
8929
8930                 RETURN QUERY
8931                     SELECT  *
8932                       FROM  actor.usr_standing_penalty
8933                       WHERE usr = match_user
8934                             AND org_unit = max_fines.org_unit
8935                             AND (stop_date IS NULL or stop_date > NOW())
8936                             AND standing_penalty = 30;
8937             END IF;
8938     
8939         END IF;
8940
8941     END IF;
8942
8943     RETURN;
8944 END;
8945 $func$ LANGUAGE plpgsql;
8946
8947 -- Create a default row in acq.fiscal_calendar
8948 -- Add a column in actor.org_unit to point to it
8949
8950 INSERT INTO acq.fiscal_calendar ( id, name ) VALUES ( 1, 'Default' );
8951
8952 ALTER TABLE actor.org_unit
8953 ADD COLUMN fiscal_calendar INT NOT NULL
8954         REFERENCES acq.fiscal_calendar( id )
8955         DEFERRABLE INITIALLY DEFERRED
8956         DEFAULT 1;
8957
8958 ALTER TABLE auditor.actor_org_unit_history
8959         ADD COLUMN fiscal_calendar INT;
8960
8961 DROP VIEW IF EXISTS auditor.actor_org_unit_lifecycle;
8962
8963 SELECT auditor.create_auditor_lifecycle( 'actor', 'org_unit' );
8964
8965 ALTER TABLE acq.funding_source_credit
8966 ADD COLUMN deadline_date TIMESTAMPTZ;
8967
8968 ALTER TABLE acq.funding_source_credit
8969 ADD COLUMN effective_date TIMESTAMPTZ NOT NULL DEFAULT now();
8970
8971 INSERT INTO config.standing_penalty (id,name,label) VALUES (30,'PATRON_IN_COLLECTIONS','Patron has been referred to a collections agency');
8972
8973 CREATE TABLE acq.fund_transfer (
8974         id               SERIAL         PRIMARY KEY,
8975         src_fund         INT            NOT NULL REFERENCES acq.fund( id )
8976                                         DEFERRABLE INITIALLY DEFERRED,
8977         src_amount       NUMERIC        NOT NULL,
8978         dest_fund        INT            REFERENCES acq.fund( id )
8979                                         DEFERRABLE INITIALLY DEFERRED,
8980         dest_amount      NUMERIC,
8981         transfer_time    TIMESTAMPTZ    NOT NULL DEFAULT now(),
8982         transfer_user    INT            NOT NULL REFERENCES actor.usr( id )
8983                                         DEFERRABLE INITIALLY DEFERRED,
8984         note             TEXT,
8985     funding_source_credit INTEGER   NOT NULL
8986                                         REFERENCES acq.funding_source_credit(id)
8987                                         DEFERRABLE INITIALLY DEFERRED
8988 );
8989
8990 CREATE INDEX acqftr_usr_idx
8991 ON acq.fund_transfer( transfer_user );
8992
8993 COMMENT ON TABLE acq.fund_transfer IS $$
8994 /*
8995  * Copyright (C) 2009  Georgia Public Library Service
8996  * Scott McKellar <scott@esilibrary.com>
8997  *
8998  * Fund Transfer
8999  *
9000  * Each row represents the transfer of money from a source fund
9001  * to a destination fund.  There should be corresponding entries
9002  * in acq.fund_allocation.  The purpose of acq.fund_transfer is
9003  * to record how much money moved from which fund to which other
9004  * fund.
9005  * 
9006  * The presence of two amount fields, rather than one, reflects
9007  * the possibility that the two funds are denominated in different
9008  * currencies.  If they use the same currency type, the two
9009  * amounts should be the same.
9010  *
9011  * ****
9012  *
9013  * This program is free software; you can redistribute it and/or
9014  * modify it under the terms of the GNU General Public License
9015  * as published by the Free Software Foundation; either version 2
9016  * of the License, or (at your option) any later version.
9017  *
9018  * This program is distributed in the hope that it will be useful,
9019  * but WITHOUT ANY WARRANTY; without even the implied warranty of
9020  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
9021  * GNU General Public License for more details.
9022  */
9023 $$;
9024
9025 CREATE TABLE acq.claim_event_type (
9026         id             SERIAL           PRIMARY KEY,
9027         org_unit       INT              NOT NULL REFERENCES actor.org_unit(id)
9028                                                  DEFERRABLE INITIALLY DEFERRED,
9029         code           TEXT             NOT NULL,
9030         description    TEXT             NOT NULL,
9031         library_initiated BOOL          NOT NULL DEFAULT FALSE,
9032         CONSTRAINT event_type_once_per_org UNIQUE ( org_unit, code )
9033 );
9034
9035 CREATE TABLE acq.claim_event (
9036         id             BIGSERIAL        PRIMARY KEY,
9037         type           INT              NOT NULL REFERENCES acq.claim_event_type
9038                                                  DEFERRABLE INITIALLY DEFERRED,
9039         claim          SERIAL           NOT NULL REFERENCES acq.claim
9040                                                  DEFERRABLE INITIALLY DEFERRED,
9041         event_date     TIMESTAMPTZ      NOT NULL DEFAULT now(),
9042         creator        INT              NOT NULL REFERENCES actor.usr
9043                                                  DEFERRABLE INITIALLY DEFERRED,
9044         note           TEXT
9045 );
9046
9047 CREATE INDEX claim_event_claim_date_idx ON acq.claim_event( claim, event_date );
9048
9049 CREATE OR REPLACE FUNCTION actor.usr_purge_data(
9050         src_usr  IN INTEGER,
9051         dest_usr IN INTEGER
9052 ) RETURNS VOID AS $$
9053 DECLARE
9054         suffix TEXT;
9055         renamable_row RECORD;
9056 BEGIN
9057
9058         UPDATE actor.usr SET
9059                 active = FALSE,
9060                 card = NULL,
9061                 mailing_address = NULL,
9062                 billing_address = NULL
9063         WHERE id = src_usr;
9064
9065         -- acq.*
9066         UPDATE acq.fund_allocation SET allocator = dest_usr WHERE allocator = src_usr;
9067         UPDATE acq.lineitem SET creator = dest_usr WHERE creator = src_usr;
9068         UPDATE acq.lineitem SET editor = dest_usr WHERE editor = src_usr;
9069         UPDATE acq.lineitem SET selector = dest_usr WHERE selector = src_usr;
9070         UPDATE acq.lineitem_note SET creator = dest_usr WHERE creator = src_usr;
9071         UPDATE acq.lineitem_note SET editor = dest_usr WHERE editor = src_usr;
9072         DELETE FROM acq.lineitem_usr_attr_definition WHERE usr = src_usr;
9073
9074         -- Update with a rename to avoid collisions
9075         FOR renamable_row in
9076                 SELECT id, name
9077                 FROM   acq.picklist
9078                 WHERE  owner = src_usr
9079         LOOP
9080                 suffix := ' (' || src_usr || ')';
9081                 LOOP
9082                         BEGIN
9083                                 UPDATE  acq.picklist
9084                                 SET     owner = dest_usr, name = name || suffix
9085                                 WHERE   id = renamable_row.id;
9086                         EXCEPTION WHEN unique_violation THEN
9087                                 suffix := suffix || ' ';
9088                                 CONTINUE;
9089                         END;
9090                         EXIT;
9091                 END LOOP;
9092         END LOOP;
9093
9094         UPDATE acq.picklist SET creator = dest_usr WHERE creator = src_usr;
9095         UPDATE acq.picklist SET editor = dest_usr WHERE editor = src_usr;
9096         UPDATE acq.po_note SET creator = dest_usr WHERE creator = src_usr;
9097         UPDATE acq.po_note SET editor = dest_usr WHERE editor = src_usr;
9098         UPDATE acq.purchase_order SET owner = dest_usr WHERE owner = src_usr;
9099         UPDATE acq.purchase_order SET creator = dest_usr WHERE creator = src_usr;
9100         UPDATE acq.purchase_order SET editor = dest_usr WHERE editor = src_usr;
9101         UPDATE acq.claim_event SET creator = dest_usr WHERE creator = src_usr;
9102
9103         -- action.*
9104         DELETE FROM action.circulation WHERE usr = src_usr;
9105         UPDATE action.circulation SET circ_staff = dest_usr WHERE circ_staff = src_usr;
9106         UPDATE action.circulation SET checkin_staff = dest_usr WHERE checkin_staff = src_usr;
9107         UPDATE action.hold_notification SET notify_staff = dest_usr WHERE notify_staff = src_usr;
9108         UPDATE action.hold_request SET fulfillment_staff = dest_usr WHERE fulfillment_staff = src_usr;
9109         UPDATE action.hold_request SET requestor = dest_usr WHERE requestor = src_usr;
9110         DELETE FROM action.hold_request WHERE usr = src_usr;
9111         UPDATE action.in_house_use SET staff = dest_usr WHERE staff = src_usr;
9112         UPDATE action.non_cat_in_house_use SET staff = dest_usr WHERE staff = src_usr;
9113         DELETE FROM action.non_cataloged_circulation WHERE patron = src_usr;
9114         UPDATE action.non_cataloged_circulation SET staff = dest_usr WHERE staff = src_usr;
9115         DELETE FROM action.survey_response WHERE usr = src_usr;
9116         UPDATE action.fieldset SET owner = dest_usr WHERE owner = src_usr;
9117
9118         -- actor.*
9119         DELETE FROM actor.card WHERE usr = src_usr;
9120         DELETE FROM actor.stat_cat_entry_usr_map WHERE target_usr = src_usr;
9121
9122         -- The following update is intended to avoid transient violations of a foreign
9123         -- key constraint, whereby actor.usr_address references itself.  It may not be
9124         -- necessary, but it does no harm.
9125         UPDATE actor.usr_address SET replaces = NULL
9126                 WHERE usr = src_usr AND replaces IS NOT NULL;
9127         DELETE FROM actor.usr_address WHERE usr = src_usr;
9128         DELETE FROM actor.usr_note WHERE usr = src_usr;
9129         UPDATE actor.usr_note SET creator = dest_usr WHERE creator = src_usr;
9130         DELETE FROM actor.usr_org_unit_opt_in WHERE usr = src_usr;
9131         UPDATE actor.usr_org_unit_opt_in SET staff = dest_usr WHERE staff = src_usr;
9132         DELETE FROM actor.usr_setting WHERE usr = src_usr;
9133         DELETE FROM actor.usr_standing_penalty WHERE usr = src_usr;
9134         UPDATE actor.usr_standing_penalty SET staff = dest_usr WHERE staff = src_usr;
9135
9136         -- asset.*
9137         UPDATE asset.call_number SET creator = dest_usr WHERE creator = src_usr;
9138         UPDATE asset.call_number SET editor = dest_usr WHERE editor = src_usr;
9139         UPDATE asset.call_number_note SET creator = dest_usr WHERE creator = src_usr;
9140         UPDATE asset.copy SET creator = dest_usr WHERE creator = src_usr;
9141         UPDATE asset.copy SET editor = dest_usr WHERE editor = src_usr;
9142         UPDATE asset.copy_note SET creator = dest_usr WHERE creator = src_usr;
9143
9144         -- auditor.*
9145         DELETE FROM auditor.actor_usr_address_history WHERE id = src_usr;
9146         DELETE FROM auditor.actor_usr_history WHERE id = src_usr;
9147         UPDATE auditor.asset_call_number_history SET creator = dest_usr WHERE creator = src_usr;
9148         UPDATE auditor.asset_call_number_history SET editor  = dest_usr WHERE editor  = src_usr;
9149         UPDATE auditor.asset_copy_history SET creator = dest_usr WHERE creator = src_usr;
9150         UPDATE auditor.asset_copy_history SET editor  = dest_usr WHERE editor  = src_usr;
9151         UPDATE auditor.biblio_record_entry_history SET creator = dest_usr WHERE creator = src_usr;
9152         UPDATE auditor.biblio_record_entry_history SET editor  = dest_usr WHERE editor  = src_usr;
9153
9154         -- biblio.*
9155         UPDATE biblio.record_entry SET creator = dest_usr WHERE creator = src_usr;
9156         UPDATE biblio.record_entry SET editor = dest_usr WHERE editor = src_usr;
9157         UPDATE biblio.record_note SET creator = dest_usr WHERE creator = src_usr;
9158         UPDATE biblio.record_note SET editor = dest_usr WHERE editor = src_usr;
9159
9160         -- container.*
9161         -- Update buckets with a rename to avoid collisions
9162         FOR renamable_row in
9163                 SELECT id, name
9164                 FROM   container.biblio_record_entry_bucket
9165                 WHERE  owner = src_usr
9166         LOOP
9167                 suffix := ' (' || src_usr || ')';
9168                 LOOP
9169                         BEGIN
9170                                 UPDATE  container.biblio_record_entry_bucket
9171                                 SET     owner = dest_usr, name = name || suffix
9172                                 WHERE   id = renamable_row.id;
9173                         EXCEPTION WHEN unique_violation THEN
9174                                 suffix := suffix || ' ';
9175                                 CONTINUE;
9176                         END;
9177                         EXIT;
9178                 END LOOP;
9179         END LOOP;
9180
9181         FOR renamable_row in
9182                 SELECT id, name
9183                 FROM   container.call_number_bucket
9184                 WHERE  owner = src_usr
9185         LOOP
9186                 suffix := ' (' || src_usr || ')';
9187                 LOOP
9188                         BEGIN
9189                                 UPDATE  container.call_number_bucket
9190                                 SET     owner = dest_usr, name = name || suffix
9191                                 WHERE   id = renamable_row.id;
9192                         EXCEPTION WHEN unique_violation THEN
9193                                 suffix := suffix || ' ';
9194                                 CONTINUE;
9195                         END;
9196                         EXIT;
9197                 END LOOP;
9198         END LOOP;
9199
9200         FOR renamable_row in
9201                 SELECT id, name
9202                 FROM   container.copy_bucket
9203                 WHERE  owner = src_usr
9204         LOOP
9205                 suffix := ' (' || src_usr || ')';
9206                 LOOP
9207                         BEGIN
9208                                 UPDATE  container.copy_bucket
9209                                 SET     owner = dest_usr, name = name || suffix
9210                                 WHERE   id = renamable_row.id;
9211                         EXCEPTION WHEN unique_violation THEN
9212                                 suffix := suffix || ' ';
9213                                 CONTINUE;
9214                         END;
9215                         EXIT;
9216                 END LOOP;
9217         END LOOP;
9218
9219         FOR renamable_row in
9220                 SELECT id, name
9221                 FROM   container.user_bucket
9222                 WHERE  owner = src_usr
9223         LOOP
9224                 suffix := ' (' || src_usr || ')';
9225                 LOOP
9226                         BEGIN
9227                                 UPDATE  container.user_bucket
9228                                 SET     owner = dest_usr, name = name || suffix
9229                                 WHERE   id = renamable_row.id;
9230                         EXCEPTION WHEN unique_violation THEN
9231                                 suffix := suffix || ' ';
9232                                 CONTINUE;
9233                         END;
9234                         EXIT;
9235                 END LOOP;
9236         END LOOP;
9237
9238         DELETE FROM container.user_bucket_item WHERE target_user = src_usr;
9239
9240         -- money.*
9241         DELETE FROM money.billable_xact WHERE usr = src_usr;
9242         DELETE FROM money.collections_tracker WHERE usr = src_usr;
9243         UPDATE money.collections_tracker SET collector = dest_usr WHERE collector = src_usr;
9244
9245         -- permission.*
9246         DELETE FROM permission.usr_grp_map WHERE usr = src_usr;
9247         DELETE FROM permission.usr_object_perm_map WHERE usr = src_usr;
9248         DELETE FROM permission.usr_perm_map WHERE usr = src_usr;
9249         DELETE FROM permission.usr_work_ou_map WHERE usr = src_usr;
9250
9251         -- reporter.*
9252         -- Update with a rename to avoid collisions
9253         BEGIN
9254                 FOR renamable_row in
9255                         SELECT id, name
9256                         FROM   reporter.output_folder
9257                         WHERE  owner = src_usr
9258                 LOOP
9259                         suffix := ' (' || src_usr || ')';
9260                         LOOP
9261                                 BEGIN
9262                                         UPDATE  reporter.output_folder
9263                                         SET     owner = dest_usr, name = name || suffix
9264                                         WHERE   id = renamable_row.id;
9265                                 EXCEPTION WHEN unique_violation THEN
9266                                         suffix := suffix || ' ';
9267                                         CONTINUE;
9268                                 END;
9269                                 EXIT;
9270                         END LOOP;
9271                 END LOOP;
9272         EXCEPTION WHEN undefined_table THEN
9273                 -- do nothing
9274         END;
9275
9276         BEGIN
9277                 UPDATE reporter.report SET owner = dest_usr WHERE owner = src_usr;
9278         EXCEPTION WHEN undefined_table THEN
9279                 -- do nothing
9280         END;
9281
9282         -- Update with a rename to avoid collisions
9283         BEGIN
9284                 FOR renamable_row in
9285                         SELECT id, name
9286                         FROM   reporter.report_folder
9287                         WHERE  owner = src_usr
9288                 LOOP
9289                         suffix := ' (' || src_usr || ')';
9290                         LOOP
9291                                 BEGIN
9292                                         UPDATE  reporter.report_folder
9293                                         SET     owner = dest_usr, name = name || suffix
9294                                         WHERE   id = renamable_row.id;
9295                                 EXCEPTION WHEN unique_violation THEN
9296                                         suffix := suffix || ' ';
9297                                         CONTINUE;
9298                                 END;
9299                                 EXIT;
9300                         END LOOP;
9301                 END LOOP;
9302         EXCEPTION WHEN undefined_table THEN
9303                 -- do nothing
9304         END;
9305
9306         BEGIN
9307                 UPDATE reporter.schedule SET runner = dest_usr WHERE runner = src_usr;
9308         EXCEPTION WHEN undefined_table THEN
9309                 -- do nothing
9310         END;
9311
9312         BEGIN
9313                 UPDATE reporter.template SET owner = dest_usr WHERE owner = src_usr;
9314         EXCEPTION WHEN undefined_table THEN
9315                 -- do nothing
9316         END;
9317
9318         -- Update with a rename to avoid collisions
9319         BEGIN
9320                 FOR renamable_row in
9321                         SELECT id, name
9322                         FROM   reporter.template_folder
9323                         WHERE  owner = src_usr
9324                 LOOP
9325                         suffix := ' (' || src_usr || ')';
9326                         LOOP
9327                                 BEGIN
9328                                         UPDATE  reporter.template_folder
9329                                         SET     owner = dest_usr, name = name || suffix
9330                                         WHERE   id = renamable_row.id;
9331                                 EXCEPTION WHEN unique_violation THEN
9332                                         suffix := suffix || ' ';
9333                                         CONTINUE;
9334                                 END;
9335                                 EXIT;
9336                         END LOOP;
9337                 END LOOP;
9338         EXCEPTION WHEN undefined_table THEN
9339         -- do nothing
9340         END;
9341
9342         -- vandelay.*
9343         -- Update with a rename to avoid collisions
9344         FOR renamable_row in
9345                 SELECT id, name
9346                 FROM   vandelay.queue
9347                 WHERE  owner = src_usr
9348         LOOP
9349                 suffix := ' (' || src_usr || ')';
9350                 LOOP
9351                         BEGIN
9352                                 UPDATE  vandelay.queue
9353                                 SET     owner = dest_usr, name = name || suffix
9354                                 WHERE   id = renamable_row.id;
9355                         EXCEPTION WHEN unique_violation THEN
9356                                 suffix := suffix || ' ';
9357                                 CONTINUE;
9358                         END;
9359                         EXIT;
9360                 END LOOP;
9361         END LOOP;
9362
9363 END;
9364 $$ LANGUAGE plpgsql;
9365
9366 COMMENT ON FUNCTION actor.usr_purge_data(INT, INT) IS $$
9367 /**
9368  * Finds rows dependent on a given row in actor.usr and either deletes them
9369  * or reassigns them to a different user.
9370  */
9371 $$;
9372
9373 CREATE OR REPLACE FUNCTION actor.usr_delete(
9374         src_usr  IN INTEGER,
9375         dest_usr IN INTEGER
9376 ) RETURNS VOID AS $$
9377 DECLARE
9378         old_profile actor.usr.profile%type;
9379         old_home_ou actor.usr.home_ou%type;
9380         new_profile actor.usr.profile%type;
9381         new_home_ou actor.usr.home_ou%type;
9382         new_name    text;
9383         new_dob     actor.usr.dob%type;
9384 BEGIN
9385         SELECT
9386                 id || '-PURGED-' || now(),
9387                 profile,
9388                 home_ou,
9389                 dob
9390         INTO
9391                 new_name,
9392                 old_profile,
9393                 old_home_ou,
9394                 new_dob
9395         FROM
9396                 actor.usr
9397         WHERE
9398                 id = src_usr;
9399         --
9400         -- Quit if no such user
9401         --
9402         IF old_profile IS NULL THEN
9403                 RETURN;
9404         END IF;
9405         --
9406         perform actor.usr_purge_data( src_usr, dest_usr );
9407         --
9408         -- Find the root grp_tree and the root org_unit.  This would be simpler if we 
9409         -- could assume that there is only one root.  Theoretically, someday, maybe,
9410         -- there could be multiple roots, so we take extra trouble to get the right ones.
9411         --
9412         SELECT
9413                 id
9414         INTO
9415                 new_profile
9416         FROM
9417                 permission.grp_ancestors( old_profile )
9418         WHERE
9419                 parent is null;
9420         --
9421         SELECT
9422                 id
9423         INTO
9424                 new_home_ou
9425         FROM
9426                 actor.org_unit_ancestors( old_home_ou )
9427         WHERE
9428                 parent_ou is null;
9429         --
9430         -- Truncate date of birth
9431         --
9432         IF new_dob IS NOT NULL THEN
9433                 new_dob := date_trunc( 'year', new_dob );
9434         END IF;
9435         --
9436         UPDATE
9437                 actor.usr
9438                 SET
9439                         card = NULL,
9440                         profile = new_profile,
9441                         usrname = new_name,
9442                         email = NULL,
9443                         passwd = random()::text,
9444                         standing = DEFAULT,
9445                         ident_type = 
9446                         (
9447                                 SELECT MIN( id )
9448                                 FROM config.identification_type
9449                         ),
9450                         ident_value = NULL,
9451                         ident_type2 = NULL,
9452                         ident_value2 = NULL,
9453                         net_access_level = DEFAULT,
9454                         photo_url = NULL,
9455                         prefix = NULL,
9456                         first_given_name = new_name,
9457                         second_given_name = NULL,
9458                         family_name = new_name,
9459                         suffix = NULL,
9460                         alias = NULL,
9461                         day_phone = NULL,
9462                         evening_phone = NULL,
9463                         other_phone = NULL,
9464                         mailing_address = NULL,
9465                         billing_address = NULL,
9466                         home_ou = new_home_ou,
9467                         dob = new_dob,
9468                         active = FALSE,
9469                         master_account = DEFAULT, 
9470                         super_user = DEFAULT,
9471                         barred = FALSE,
9472                         deleted = TRUE,
9473                         juvenile = DEFAULT,
9474                         usrgroup = 0,
9475                         claims_returned_count = DEFAULT,
9476                         credit_forward_balance = DEFAULT,
9477                         last_xact_id = DEFAULT,
9478                         alert_message = NULL,
9479                         create_date = now(),
9480                         expire_date = now()
9481         WHERE
9482                 id = src_usr;
9483 END;
9484 $$ LANGUAGE plpgsql;
9485
9486 COMMENT ON FUNCTION actor.usr_delete(INT, INT) IS $$
9487 /**
9488  * Logically deletes a user.  Removes personally identifiable information,
9489  * and purges associated data in other tables.
9490  */
9491 $$;
9492
9493 -- INSERT INTO config.copy_status (id,name) VALUES (15,oils_i18n_gettext(15, 'On reservation shelf', 'ccs', 'name'));
9494
9495 ALTER TABLE acq.fund
9496 ADD COLUMN rollover BOOL NOT NULL DEFAULT FALSE;
9497
9498 ALTER TABLE acq.fund
9499         ADD COLUMN propagate BOOLEAN NOT NULL DEFAULT TRUE;
9500
9501 -- A fund can't roll over if it doesn't propagate from one year to the next
9502
9503 ALTER TABLE acq.fund
9504         ADD CONSTRAINT acq_fund_rollover_implies_propagate CHECK
9505         ( propagate OR NOT rollover );
9506
9507 ALTER TABLE acq.fund
9508         ADD COLUMN active BOOL NOT NULL DEFAULT TRUE;
9509
9510 ALTER TABLE acq.fund
9511     ADD COLUMN balance_warning_percent INT;
9512
9513 ALTER TABLE acq.fund
9514     ADD COLUMN balance_stop_percent INT;
9515
9516 CREATE VIEW acq.ordered_funding_source_credit AS
9517         SELECT
9518                 CASE WHEN deadline_date IS NULL THEN
9519                         2
9520                 ELSE
9521                         1
9522                 END AS sort_priority,
9523                 CASE WHEN deadline_date IS NULL THEN
9524                         effective_date
9525                 ELSE
9526                         deadline_date
9527                 END AS sort_date,
9528                 id,
9529                 funding_source,
9530                 amount,
9531                 note
9532         FROM
9533                 acq.funding_source_credit;
9534
9535 COMMENT ON VIEW acq.ordered_funding_source_credit IS $$
9536 /*
9537  * Copyright (C) 2009  Georgia Public Library Service
9538  * Scott McKellar <scott@gmail.com>
9539  *
9540  * The acq.ordered_funding_source_credit view is a prioritized
9541  * ordering of funding source credits.  When ordered by the first
9542  * three columns, this view defines the order in which the various
9543  * credits are to be tapped for spending, subject to the allocations
9544  * in the acq.fund_allocation table.
9545  *
9546  * The first column reflects the principle that we should spend
9547  * money with deadlines before spending money without deadlines.
9548  *
9549  * The second column reflects the principle that we should spend the
9550  * oldest money first.  For money with deadlines, that means that we
9551  * spend first from the credit with the earliest deadline.  For
9552  * money without deadlines, we spend first from the credit with the
9553  * earliest effective date.  
9554  *
9555  * The third column is a tie breaker to ensure a consistent
9556  * ordering.
9557  *
9558  * ****
9559  *
9560  * This program is free software; you can redistribute it and/or
9561  * modify it under the terms of the GNU General Public License
9562  * as published by the Free Software Foundation; either version 2
9563  * of the License, or (at your option) any later version.
9564  *
9565  * This program is distributed in the hope that it will be useful,
9566  * but WITHOUT ANY WARRANTY; without even the implied warranty of
9567  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
9568  * GNU General Public License for more details.
9569  */
9570 $$;
9571
9572 CREATE OR REPLACE VIEW money.billable_xact_summary_location_view AS
9573     SELECT  m.*, COALESCE(c.circ_lib, g.billing_location, r.pickup_lib) AS billing_location
9574       FROM  money.materialized_billable_xact_summary m
9575             LEFT JOIN action.circulation c ON (c.id = m.id)
9576             LEFT JOIN money.grocery g ON (g.id = m.id)
9577             LEFT JOIN booking.reservation r ON (r.id = m.id);
9578
9579 CREATE TABLE config.marc21_rec_type_map (
9580     code        TEXT    PRIMARY KEY,
9581     type_val    TEXT    NOT NULL,
9582     blvl_val    TEXT    NOT NULL
9583 );
9584
9585 CREATE TABLE config.marc21_ff_pos_map (
9586     id          SERIAL  PRIMARY KEY,
9587     fixed_field TEXT    NOT NULL,
9588     tag         TEXT    NOT NULL,
9589     rec_type    TEXT    NOT NULL,
9590     start_pos   INT     NOT NULL,
9591     length      INT     NOT NULL,
9592     default_val TEXT    NOT NULL DEFAULT ' '
9593 );
9594
9595 CREATE TABLE config.marc21_physical_characteristic_type_map (
9596     ptype_key   TEXT    PRIMARY KEY,
9597     label       TEXT    NOT NULL -- I18N
9598 );
9599
9600 CREATE TABLE config.marc21_physical_characteristic_subfield_map (
9601     id          SERIAL  PRIMARY KEY,
9602     ptype_key   TEXT    NOT NULL REFERENCES config.marc21_physical_characteristic_type_map (ptype_key) ON DELETE CASCADE ON UPDATE CASCADE,
9603     subfield    TEXT    NOT NULL,
9604     start_pos   INT     NOT NULL,
9605     length      INT     NOT NULL,
9606     label       TEXT    NOT NULL -- I18N
9607 );
9608
9609 CREATE TABLE config.marc21_physical_characteristic_value_map (
9610     id              SERIAL  PRIMARY KEY,
9611     value           TEXT    NOT NULL,
9612     ptype_subfield  INT     NOT NULL REFERENCES config.marc21_physical_characteristic_subfield_map (id),
9613     label           TEXT    NOT NULL -- I18N
9614 );
9615
9616 ----------------------------------
9617 -- MARC21 record structure data --
9618 ----------------------------------
9619
9620 -- Record type map
9621 INSERT INTO config.marc21_rec_type_map (code, type_val, blvl_val) VALUES ('BKS','at','acdm');
9622 INSERT INTO config.marc21_rec_type_map (code, type_val, blvl_val) VALUES ('SER','a','bsi');
9623 INSERT INTO config.marc21_rec_type_map (code, type_val, blvl_val) VALUES ('VIS','gkro','abcdmsi');
9624 INSERT INTO config.marc21_rec_type_map (code, type_val, blvl_val) VALUES ('MIX','p','cdi');
9625 INSERT INTO config.marc21_rec_type_map (code, type_val, blvl_val) VALUES ('MAP','ef','abcdmsi');
9626 INSERT INTO config.marc21_rec_type_map (code, type_val, blvl_val) VALUES ('SCO','cd','abcdmsi');
9627 INSERT INTO config.marc21_rec_type_map (code, type_val, blvl_val) VALUES ('REC','ij','abcdmsi');
9628 INSERT INTO config.marc21_rec_type_map (code, type_val, blvl_val) VALUES ('COM','m','abcdmsi');
9629
9630 ------ Physical Characteristics
9631
9632 -- Map
9633 INSERT INTO config.marc21_physical_characteristic_type_map (ptype_key, label) VALUES ('a','Map');
9634 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('a','b','1','1','SMD');
9635 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('d',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Atlas');
9636 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('g',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Diagram');
9637 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('j',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Map');
9638 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('k',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Profile');
9639 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('q',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Model');
9640 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');
9641 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('s',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Section');
9642 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unspecified');
9643 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('y',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'View');
9644 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
9645 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('a','d','3','1','Color');
9646 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');
9647 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('c',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Multicolored');
9648 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('a','e','4','1','Physical medium');
9649 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('a',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Paper');
9650 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('b',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Wood');
9651 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('c',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Stone');
9652 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('d',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Metal');
9653 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('e',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Synthetics');
9654 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('f',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Skins');
9655 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('g',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Textile');
9656 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('p',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Plaster');
9657 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');
9658 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');
9659 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');
9660 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');
9661 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
9662 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');
9663 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
9664 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('a','f','5','1','Type of reproduction');
9665 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('f',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Facsimile');
9666 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');
9667 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
9668 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
9669 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('a','g','6','1','Production/reproduction details');
9670 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');
9671 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('b',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Photocopy');
9672 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');
9673 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('d',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Film');
9674 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
9675 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
9676 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('a','h','7','1','Positive/negative');
9677 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('a',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Positive');
9678 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('b',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Negative');
9679 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('m',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Mixed');
9680 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('n',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Not applicable');
9681
9682 -- Electronic Resource
9683 INSERT INTO config.marc21_physical_characteristic_type_map (ptype_key, label) VALUES ('c','Electronic Resource');
9684 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('c','b','1','1','SMD');
9685 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');
9686 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');
9687 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');
9688 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');
9689 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');
9690 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');
9691 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');
9692 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');
9693 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('r',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Remote');
9694 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unspecified');
9695 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
9696 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('c','d','3','1','Color');
9697 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');
9698 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');
9699 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('c',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Multicolored');
9700 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');
9701 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('m',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Mixed');
9702 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('n',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Not applicable');
9703 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
9704 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
9705 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('c','e','4','1','Dimensions');
9706 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.');
9707 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.');
9708 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.');
9709 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.');
9710 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.');
9711 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');
9712 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.');
9713 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
9714 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('v',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'8 in.');
9715 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
9716 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('c','f','5','1','Sound');
9717 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)');
9718 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('a',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Sound');
9719 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
9720 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('c','g','6','3','Image bit depth');
9721 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('---',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
9722 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('mmm',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Multiple');
9723 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');
9724 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('c','h','9','1','File formats');
9725 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');
9726 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');
9727 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
9728 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('c','i','10','1','Quality assurance target(s)');
9729 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('a',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Absent');
9730 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');
9731 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('p',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Present');
9732 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
9733 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('c','j','11','1','Antecedent/Source');
9734 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');
9735 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');
9736 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');
9737 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)');
9738 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('m',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Mixed');
9739 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');
9740 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
9741 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('c','k','12','1','Level of compression');
9742 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('a',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Uncompressed');
9743 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('b',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Lossless');
9744 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('d',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Lossy');
9745 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('m',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Mixed');
9746 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
9747 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('c','l','13','1','Reformatting quality');
9748 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('a',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Access');
9749 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');
9750 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('p',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Preservation');
9751 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('r',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Replacement');
9752 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
9753
9754 -- Globe
9755 INSERT INTO config.marc21_physical_characteristic_type_map (ptype_key, label) VALUES ('d','Globe');
9756 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('d','b','1','1','SMD');
9757 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');
9758 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');
9759 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');
9760 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');
9761 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unspecified');
9762 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
9763 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('d','d','3','1','Color');
9764 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');
9765 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('c',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Multicolored');
9766 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('d','e','4','1','Physical medium');
9767 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('a',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Paper');
9768 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('b',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Wood');
9769 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('c',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Stone');
9770 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('d',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Metal');
9771 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('e',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Synthetics');
9772 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('f',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Skins');
9773 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('g',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Textile');
9774 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('p',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Plaster');
9775 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
9776 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
9777 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('d','f','5','1','Type of reproduction');
9778 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('f',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Facsimile');
9779 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');
9780 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
9781 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
9782
9783 -- Tactile Material
9784 INSERT INTO config.marc21_physical_characteristic_type_map (ptype_key, label) VALUES ('f','Tactile Material');
9785 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('f','b','1','1','SMD');
9786 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('a',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Moon');
9787 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('b',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Braille');
9788 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('c',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Combination');
9789 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');
9790 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unspecified');
9791 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
9792 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('f','d','3','2','Class of braille writing');
9793 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');
9794 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');
9795 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');
9796 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');
9797 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');
9798 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');
9799 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');
9800 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
9801 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
9802 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('f','e','4','1','Level of contraction');
9803 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('a',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Uncontracted');
9804 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('b',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Contracted');
9805 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('m',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Combination');
9806 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');
9807 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
9808 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
9809 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('f','f','6','3','Braille music format');
9810 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');
9811 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');
9812 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');
9813 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('d',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Paragraph');
9814 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');
9815 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');
9816 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');
9817 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');
9818 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');
9819 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');
9820 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('k',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Outline');
9821 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');
9822 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');
9823 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
9824 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
9825 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('f','g','9','1','Special physical characteristics');
9826 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');
9827 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');
9828 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');
9829 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
9830 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
9831
9832 -- Projected Graphic
9833 INSERT INTO config.marc21_physical_characteristic_type_map (ptype_key, label) VALUES ('g','Projected Graphic');
9834 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('g','b','1','1','SMD');
9835 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');
9836 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('d',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Filmstrip');
9837 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');
9838 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');
9839 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('s',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Slide');
9840 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('t',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Transparency');
9841 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
9842 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('g','d','3','1','Color');
9843 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('b',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Black-and-white');
9844 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('c',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Multicolored');
9845 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('h',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Hand-colored');
9846 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('m',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Mixed');
9847 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');
9848 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
9849 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
9850 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('g','e','4','1','Base of emulsion');
9851 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('d',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Glass');
9852 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('e',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Synthetics');
9853 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');
9854 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');
9855 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');
9856 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('o',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Paper');
9857 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
9858 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
9859 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');
9860 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');
9861 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');
9862 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
9863 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('g','g','6','1','Medium for sound');
9864 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');
9865 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');
9866 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');
9867 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');
9868 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');
9869 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');
9870 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');
9871 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('h',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Videotape');
9872 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('i',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Videodisc');
9873 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
9874 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
9875 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('g','h','7','1','Dimensions');
9876 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.');
9877 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.');
9878 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.');
9879 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.');
9880 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.');
9881 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.');
9882 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.');
9883 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.)');
9884 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.)');
9885 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.)');
9886 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.)');
9887 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
9888 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.)');
9889 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.)');
9890 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.)');
9891 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.)');
9892 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
9893 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('g','i','8','1','Secondary support material');
9894 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('c',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Cardboard');
9895 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('d',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Glass');
9896 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('e',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Synthetics');
9897 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('h',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'metal');
9898 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');
9899 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');
9900 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('m',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Mixed collection');
9901 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
9902 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
9903
9904 -- Microform
9905 INSERT INTO config.marc21_physical_characteristic_type_map (ptype_key, label) VALUES ('h','Microform');
9906 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('h','b','1','1','SMD');
9907 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');
9908 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');
9909 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');
9910 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');
9911 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('e',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Microfiche');
9912 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');
9913 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('g',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Microopaque');
9914 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unspecified');
9915 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
9916 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('h','d','3','1','Positive/negative');
9917 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('a',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Positive');
9918 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('b',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Negative');
9919 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('m',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Mixed');
9920 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
9921 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('h','e','4','1','Dimensions');
9922 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.');
9923 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.');
9924 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.');
9925 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('g',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'70mm.');
9926 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.');
9927 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.)');
9928 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.)');
9929 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.)');
9930 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.)');
9931 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
9932 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
9933 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('h','f','5','4','Reduction ratio range/Reduction ratio');
9934 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)');
9935 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)');
9936 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)');
9937 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)');
9938 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-)');
9939 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
9940 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');
9941 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('h','g','9','1','Color');
9942 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');
9943 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('c',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Multicolored');
9944 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('m',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Mixed');
9945 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
9946 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
9947 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('h','h','10','1','Emulsion on film');
9948 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');
9949 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('b',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Diazo');
9950 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('c',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Vesicular');
9951 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('m',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Mixed');
9952 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');
9953 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
9954 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
9955 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('h','i','11','1','Quality assurance target(s)');
9956 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');
9957 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');
9958 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');
9959 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');
9960 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
9961 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('h','j','12','1','Base of film');
9962 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');
9963 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');
9964 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');
9965 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');
9966 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');
9967 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');
9968 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');
9969 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');
9970 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');
9971 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
9972 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
9973
9974 -- Non-projected Graphic
9975 INSERT INTO config.marc21_physical_characteristic_type_map (ptype_key, label) VALUES ('k','Non-projected Graphic');
9976 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('k','b','1','1','SMD');
9977 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('c',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Collage');
9978 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('d',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Drawing');
9979 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('e',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Painting');
9980 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');
9981 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('g',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Photonegative');
9982 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('h',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Photoprint');
9983 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('i',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Picture');
9984 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('j',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Print');
9985 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');
9986 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('n',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Chart');
9987 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');
9988 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unspecified');
9989 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
9990 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('k','d','3','1','Color');
9991 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');
9992 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');
9993 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('c',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Multicolored');
9994 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');
9995 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('m',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Mixed');
9996 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
9997 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
9998 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('k','e','4','1','Primary support material');
9999 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('a',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Canvas');
10000 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');
10001 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');
10002 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('d',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Glass');
10003 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('e',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Synthetics');
10004 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('f',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Skins');
10005 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('g',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Textile');
10006 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('h',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Metal');
10007 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');
10008 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('o',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Paper');
10009 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('p',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Plaster');
10010 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('q',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Hardboard');
10011 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('r',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Porcelain');
10012 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('s',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Stone');
10013 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('t',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Wood');
10014 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
10015 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
10016 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('k','f','5','1','Secondary support material');
10017 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('a',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Canvas');
10018 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');
10019 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');
10020 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('d',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Glass');
10021 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('e',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Synthetics');
10022 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('f',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Skins');
10023 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('g',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Textile');
10024 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('h',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Metal');
10025 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('m',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Mixed collection');
10026 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('o',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Paper');
10027 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('p',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Plaster');
10028 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('q',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Hardboard');
10029 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('r',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Porcelain');
10030 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('s',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Stone');
10031 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('t',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Wood');
10032 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
10033 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
10034
10035 -- Motion Picture
10036 INSERT INTO config.marc21_physical_characteristic_type_map (ptype_key, label) VALUES ('m','Motion Picture');
10037 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('m','b','1','1','SMD');
10038 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');
10039 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');
10040 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');
10041 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unspecified');
10042 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
10043 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('m','d','3','1','Color');
10044 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');
10045 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('c',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Multicolored');
10046 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');
10047 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('m',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Mixed');
10048 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
10049 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
10050 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('m','e','4','1','Motion picture presentation format');
10051 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');
10052 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)');
10053 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('c',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'3D');
10054 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)');
10055 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');
10056 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');
10057 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
10058 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
10059 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');
10060 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');
10061 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');
10062 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
10063 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('m','g','6','1','Medium for sound');
10064 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');
10065 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');
10066 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');
10067 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');
10068 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');
10069 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');
10070 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');
10071 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('h',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Videotape');
10072 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('i',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Videodisc');
10073 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
10074 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
10075 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('m','h','7','1','Dimensions');
10076 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.');
10077 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.');
10078 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.');
10079 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.');
10080 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.');
10081 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.');
10082 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.');
10083 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
10084 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
10085 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('m','i','8','1','Configuration of playback channels');
10086 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('k',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Mixed');
10087 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('m',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Monaural');
10088 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('n',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Not applicable');
10089 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('q',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Multichannel, surround or quadraphonic');
10090 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('s',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Stereophonic');
10091 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
10092 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
10093 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('m','j','9','1','Production elements');
10094 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');
10095 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('b',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Trims');
10096 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('c',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Outtakes');
10097 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('d',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Rushes');
10098 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');
10099 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');
10100 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');
10101 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');
10102 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
10103
10104 -- Remote-sensing Image
10105 INSERT INTO config.marc21_physical_characteristic_type_map (ptype_key, label) VALUES ('r','Remote-sensing Image');
10106 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('r','b','1','1','SMD');
10107 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unspecified');
10108 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('r','d','3','1','Altitude of sensor');
10109 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('a',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Surface');
10110 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('b',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Airborne');
10111 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('c',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Spaceborne');
10112 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');
10113 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
10114 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
10115 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('r','e','4','1','Attitude of sensor');
10116 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');
10117 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');
10118 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('c',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Vertical');
10119 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');
10120 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
10121 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('r','f','5','1','Cloud cover');
10122 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%');
10123 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%');
10124 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%');
10125 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%');
10126 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%');
10127 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%');
10128 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%');
10129 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%');
10130 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%');
10131 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%');
10132 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');
10133 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
10134 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('r','g','6','1','Platform construction type');
10135 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('a',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Balloon');
10136 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');
10137 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');
10138 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');
10139 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');
10140 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');
10141 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');
10142 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');
10143 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');
10144 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');
10145 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
10146 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
10147 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('r','h','7','1','Platform use category');
10148 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('a',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Meteorological');
10149 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');
10150 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');
10151 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');
10152 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');
10153 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
10154 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
10155 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('r','i','8','1','Sensor type');
10156 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('a',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Active');
10157 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('b',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Passive');
10158 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
10159 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
10160 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('r','j','9','2','Data type');
10161 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');
10162 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');
10163 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');
10164 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');
10165 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');
10166 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)');
10167 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');
10168 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('dv',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Combinations');
10169 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');
10170 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)');
10171 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)');
10172 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)');
10173 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');
10174 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');
10175 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');
10176 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');
10177 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');
10178 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');
10179 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');
10180 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');
10181 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');
10182 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');
10183 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');
10184 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');
10185 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');
10186 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');
10187 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');
10188 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');
10189 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');
10190 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');
10191 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');
10192 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');
10193 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');
10194 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)');
10195 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');
10196 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('rc',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Bouger');
10197 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('rd',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Isostatic');
10198 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');
10199 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');
10200 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('uu',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
10201 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('zz',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
10202
10203 -- Sound Recording
10204 INSERT INTO config.marc21_physical_characteristic_type_map (ptype_key, label) VALUES ('s','Sound Recording');
10205 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('s','b','1','1','SMD');
10206 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');
10207 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('e',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Cylinder');
10208 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');
10209 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');
10210 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('q',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Roll');
10211 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');
10212 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');
10213 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unspecified');
10214 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');
10215 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
10216 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('s','d','3','1','Speed');
10217 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');
10218 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');
10219 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');
10220 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');
10221 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');
10222 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');
10223 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');
10224 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');
10225 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');
10226 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');
10227 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');
10228 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');
10229 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');
10230 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');
10231 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
10232 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
10233 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('s','e','4','1','Configuration of playback channels');
10234 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('m',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Monaural');
10235 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('q',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Quadraphonic');
10236 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('s',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Stereophonic');
10237 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
10238 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
10239 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('s','f','5','1','Groove width or pitch');
10240 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');
10241 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');
10242 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');
10243 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
10244 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
10245 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('s','g','6','1','Dimensions');
10246 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.');
10247 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.');
10248 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.');
10249 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.');
10250 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.');
10251 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.');
10252 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.)');
10253 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.');
10254 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');
10255 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.');
10256 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.');
10257 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
10258 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
10259 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('s','h','7','1','Tape width');
10260 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.');
10261 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.');
10262 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');
10263 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.');
10264 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.');
10265 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
10266 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
10267 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('s','i','8','1','Tape configuration ');
10268 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');
10269 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');
10270 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');
10271 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');
10272 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');
10273 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');
10274 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');
10275 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
10276 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
10277 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('s','m','12','1','Special playback');
10278 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');
10279 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');
10280 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');
10281 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');
10282 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');
10283 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');
10284 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');
10285 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');
10286 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');
10287 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
10288 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
10289 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('s','n','13','1','Capture and storage');
10290 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');
10291 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');
10292 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');
10293 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');
10294 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
10295 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
10296
10297 -- Videorecording
10298 INSERT INTO config.marc21_physical_characteristic_type_map (ptype_key, label) VALUES ('v','Videorecording');
10299 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('v','b','1','1','SMD');
10300 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('c',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Videocartridge');
10301 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('d',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Videodisc');
10302 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('f',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Videocassette');
10303 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('r',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Videoreel');
10304 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unspecified');
10305 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
10306 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('v','d','3','1','Color');
10307 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');
10308 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('c',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Multicolored');
10309 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('m',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Mixed');
10310 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');
10311 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
10312 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
10313 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('v','e','4','1','Videorecording format');
10314 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('a',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Beta');
10315 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('b',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'VHS');
10316 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');
10317 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('d',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'EIAJ');
10318 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');
10319 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('f',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Quadruplex');
10320 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('g',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Laserdisc');
10321 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('h',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'CED');
10322 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('i',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Betacam');
10323 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');
10324 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');
10325 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');
10326 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');
10327 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.');
10328 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.');
10329 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
10330 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('v',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'DVD');
10331 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
10332 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');
10333 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');
10334 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');
10335 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
10336 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('v','g','6','1','Medium for sound');
10337 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');
10338 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');
10339 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');
10340 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');
10341 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');
10342 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');
10343 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');
10344 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('h',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Videotape');
10345 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('i',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Videodisc');
10346 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
10347 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
10348 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('v','h','7','1','Dimensions');
10349 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.');
10350 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.');
10351 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.');
10352 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.');
10353 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.');
10354 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.');
10355 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
10356 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
10357 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('v','i','8','1','Configuration of playback channel');
10358 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('k',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Mixed');
10359 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('m',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Monaural');
10360 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');
10361 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');
10362 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('s',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Stereophonic');
10363 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
10364 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
10365
10366 -- Fixed Field position data -- 0-based!
10367 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Alph', '006', 'SER', 16, 1, ' ');
10368 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Alph', '008', 'SER', 33, 1, ' ');
10369 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Audn', '006', 'BKS', 5, 1, ' ');
10370 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Audn', '006', 'COM', 5, 1, ' ');
10371 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Audn', '006', 'REC', 5, 1, ' ');
10372 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Audn', '006', 'SCO', 5, 1, ' ');
10373 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Audn', '006', 'SER', 5, 1, ' ');
10374 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Audn', '006', 'VIS', 5, 1, ' ');
10375 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Audn', '008', 'BKS', 22, 1, ' ');
10376 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Audn', '008', 'COM', 22, 1, ' ');
10377 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Audn', '008', 'REC', 22, 1, ' ');
10378 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Audn', '008', 'SCO', 22, 1, ' ');
10379 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Audn', '008', 'SER', 22, 1, ' ');
10380 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Audn', '008', 'VIS', 22, 1, ' ');
10381 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('BLvl', 'ldr', 'BKS', 7, 1, 'm');
10382 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('BLvl', 'ldr', 'COM', 7, 1, 'm');
10383 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('BLvl', 'ldr', 'MAP', 7, 1, 'm');
10384 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('BLvl', 'ldr', 'MIX', 7, 1, 'c');
10385 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('BLvl', 'ldr', 'REC', 7, 1, 'm');
10386 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('BLvl', 'ldr', 'SCO', 7, 1, 'm');
10387 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('BLvl', 'ldr', 'SER', 7, 1, 's');
10388 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('BLvl', 'ldr', 'VIS', 7, 1, 'm');
10389 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Biog', '006', 'BKS', 17, 1, ' ');
10390 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Biog', '008', 'BKS', 34, 1, ' ');
10391 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Conf', '006', 'BKS', 7, 4, ' ');
10392 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Conf', '006', 'SER', 8, 3, ' ');
10393 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Conf', '008', 'BKS', 24, 4, ' ');
10394 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Conf', '008', 'SER', 25, 3, ' ');
10395 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Ctrl', 'ldr', 'BKS', 8, 1, ' ');
10396 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Ctrl', 'ldr', 'COM', 8, 1, ' ');
10397 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Ctrl', 'ldr', 'MAP', 8, 1, ' ');
10398 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Ctrl', 'ldr', 'MIX', 8, 1, ' ');
10399 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Ctrl', 'ldr', 'REC', 8, 1, ' ');
10400 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Ctrl', 'ldr', 'SCO', 8, 1, ' ');
10401 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Ctrl', 'ldr', 'SER', 8, 1, ' ');
10402 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Ctrl', 'ldr', 'VIS', 8, 1, ' ');
10403 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Ctry', '008', 'BKS', 15, 3, ' ');
10404 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Ctry', '008', 'COM', 15, 3, ' ');
10405 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Ctry', '008', 'MAP', 15, 3, ' ');
10406 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Ctry', '008', 'MIX', 15, 3, ' ');
10407 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Ctry', '008', 'REC', 15, 3, ' ');
10408 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Ctry', '008', 'SCO', 15, 3, ' ');
10409 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Ctry', '008', 'SER', 15, 3, ' ');
10410 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Ctry', '008', 'VIS', 15, 3, ' ');
10411 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Date1', '008', 'BKS', 7, 4, ' ');
10412 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Date1', '008', 'COM', 7, 4, ' ');
10413 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Date1', '008', 'MAP', 7, 4, ' ');
10414 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Date1', '008', 'MIX', 7, 4, ' ');
10415 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Date1', '008', 'REC', 7, 4, ' ');
10416 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Date1', '008', 'SCO', 7, 4, ' ');
10417 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Date1', '008', 'SER', 7, 4, ' ');
10418 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Date1', '008', 'VIS', 7, 4, ' ');
10419 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Date2', '008', 'BKS', 11, 4, ' ');
10420 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Date2', '008', 'COM', 11, 4, ' ');
10421 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Date2', '008', 'MAP', 11, 4, ' ');
10422 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Date2', '008', 'MIX', 11, 4, ' ');
10423 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Date2', '008', 'REC', 11, 4, ' ');
10424 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Date2', '008', 'SCO', 11, 4, ' ');
10425 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Date2', '008', 'SER', 11, 4, '9');
10426 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Date2', '008', 'VIS', 11, 4, ' ');
10427 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Desc', 'ldr', 'BKS', 18, 1, ' ');
10428 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Desc', 'ldr', 'COM', 18, 1, ' ');
10429 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Desc', 'ldr', 'MAP', 18, 1, ' ');
10430 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Desc', 'ldr', 'MIX', 18, 1, ' ');
10431 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Desc', 'ldr', 'REC', 18, 1, ' ');
10432 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Desc', 'ldr', 'SCO', 18, 1, ' ');
10433 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Desc', 'ldr', 'SER', 18, 1, ' ');
10434 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Desc', 'ldr', 'VIS', 18, 1, ' ');
10435 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('DtSt', '008', 'BKS', 6, 1, ' ');
10436 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('DtSt', '008', 'COM', 6, 1, ' ');
10437 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('DtSt', '008', 'MAP', 6, 1, ' ');
10438 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('DtSt', '008', 'MIX', 6, 1, ' ');
10439 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('DtSt', '008', 'REC', 6, 1, ' ');
10440 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('DtSt', '008', 'SCO', 6, 1, ' ');
10441 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('DtSt', '008', 'SER', 6, 1, 'c');
10442 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('DtSt', '008', 'VIS', 6, 1, ' ');
10443 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('ELvl', 'ldr', 'BKS', 17, 1, ' ');
10444 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('ELvl', 'ldr', 'COM', 17, 1, ' ');
10445 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('ELvl', 'ldr', 'MAP', 17, 1, ' ');
10446 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('ELvl', 'ldr', 'MIX', 17, 1, ' ');
10447 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('ELvl', 'ldr', 'REC', 17, 1, ' ');
10448 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('ELvl', 'ldr', 'SCO', 17, 1, ' ');
10449 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('ELvl', 'ldr', 'SER', 17, 1, ' ');
10450 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('ELvl', 'ldr', 'VIS', 17, 1, ' ');
10451 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Fest', '006', 'BKS', 13, 1, '0');
10452 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Fest', '008', 'BKS', 30, 1, '0');
10453 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Form', '006', 'BKS', 6, 1, ' ');
10454 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Form', '006', 'MAP', 12, 1, ' ');
10455 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Form', '006', 'MIX', 6, 1, ' ');
10456 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Form', '006', 'REC', 6, 1, ' ');
10457 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Form', '006', 'SCO', 6, 1, ' ');
10458 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Form', '006', 'SER', 6, 1, ' ');
10459 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Form', '006', 'VIS', 12, 1, ' ');
10460 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Form', '008', 'BKS', 23, 1, ' ');
10461 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Form', '008', 'MAP', 29, 1, ' ');
10462 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Form', '008', 'MIX', 23, 1, ' ');
10463 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Form', '008', 'REC', 23, 1, ' ');
10464 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Form', '008', 'SCO', 23, 1, ' ');
10465 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Form', '008', 'SER', 23, 1, ' ');
10466 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Form', '008', 'VIS', 29, 1, ' ');
10467 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('GPub', '006', 'BKS', 11, 1, ' ');
10468 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('GPub', '006', 'COM', 11, 1, ' ');
10469 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('GPub', '006', 'MAP', 11, 1, ' ');
10470 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('GPub', '006', 'SER', 11, 1, ' ');
10471 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('GPub', '006', 'VIS', 11, 1, ' ');
10472 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('GPub', '008', 'BKS', 28, 1, ' ');
10473 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('GPub', '008', 'COM', 28, 1, ' ');
10474 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('GPub', '008', 'MAP', 28, 1, ' ');
10475 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('GPub', '008', 'SER', 28, 1, ' ');
10476 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('GPub', '008', 'VIS', 28, 1, ' ');
10477 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Ills', '006', 'BKS', 1, 4, ' ');
10478 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Ills', '008', 'BKS', 18, 4, ' ');
10479 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Indx', '006', 'BKS', 14, 1, '0');
10480 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Indx', '006', 'MAP', 14, 1, '0');
10481 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Indx', '008', 'BKS', 31, 1, '0');
10482 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Indx', '008', 'MAP', 31, 1, '0');
10483 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Lang', '008', 'BKS', 35, 3, ' ');
10484 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Lang', '008', 'COM', 35, 3, ' ');
10485 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Lang', '008', 'MAP', 35, 3, ' ');
10486 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Lang', '008', 'MIX', 35, 3, ' ');
10487 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Lang', '008', 'REC', 35, 3, ' ');
10488 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Lang', '008', 'SCO', 35, 3, ' ');
10489 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Lang', '008', 'SER', 35, 3, ' ');
10490 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Lang', '008', 'VIS', 35, 3, ' ');
10491 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('LitF', '006', 'BKS', 16, 1, '0');
10492 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('LitF', '008', 'BKS', 33, 1, '0');
10493 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('MRec', '008', 'BKS', 38, 1, ' ');
10494 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('MRec', '008', 'COM', 38, 1, ' ');
10495 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('MRec', '008', 'MAP', 38, 1, ' ');
10496 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('MRec', '008', 'MIX', 38, 1, ' ');
10497 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('MRec', '008', 'REC', 38, 1, ' ');
10498 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('MRec', '008', 'SCO', 38, 1, ' ');
10499 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('MRec', '008', 'SER', 38, 1, ' ');
10500 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('MRec', '008', 'VIS', 38, 1, ' ');
10501 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');
10502 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');
10503 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('TMat', '006', 'VIS', 16, 1, ' ');
10504 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('TMat', '008', 'VIS', 33, 1, ' ');
10505 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Type', 'ldr', 'BKS', 6, 1, 'a');
10506 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Type', 'ldr', 'COM', 6, 1, 'm');
10507 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Type', 'ldr', 'MAP', 6, 1, 'e');
10508 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Type', 'ldr', 'MIX', 6, 1, 'p');
10509 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Type', 'ldr', 'REC', 6, 1, 'i');
10510 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Type', 'ldr', 'SCO', 6, 1, 'c');
10511 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Type', 'ldr', 'SER', 6, 1, 'a');
10512 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Type', 'ldr', 'VIS', 6, 1, 'g');
10513
10514 CREATE OR REPLACE FUNCTION biblio.marc21_record_type( rid BIGINT ) RETURNS config.marc21_rec_type_map AS $func$
10515 DECLARE
10516         ldr         RECORD;
10517         tval        TEXT;
10518         tval_rec    RECORD;
10519         bval        TEXT;
10520         bval_rec    RECORD;
10521     retval      config.marc21_rec_type_map%ROWTYPE;
10522 BEGIN
10523     SELECT * INTO ldr FROM metabib.full_rec WHERE record = rid AND tag = 'LDR' LIMIT 1;
10524
10525     IF ldr.id IS NULL THEN
10526         SELECT * INTO retval FROM config.marc21_rec_type_map WHERE code = 'BKS';
10527         RETURN retval;
10528     END IF;
10529
10530     SELECT * INTO tval_rec FROM config.marc21_ff_pos_map WHERE fixed_field = 'Type' LIMIT 1; -- They're all the same
10531     SELECT * INTO bval_rec FROM config.marc21_ff_pos_map WHERE fixed_field = 'BLvl' LIMIT 1; -- They're all the same
10532
10533
10534     tval := SUBSTRING( ldr.value, tval_rec.start_pos + 1, tval_rec.length );
10535     bval := SUBSTRING( ldr.value, bval_rec.start_pos + 1, bval_rec.length );
10536
10537     -- RAISE NOTICE 'type %, blvl %, ldr %', tval, bval, ldr.value;
10538
10539     SELECT * INTO retval FROM config.marc21_rec_type_map WHERE type_val LIKE '%' || tval || '%' AND blvl_val LIKE '%' || bval || '%';
10540
10541
10542     IF retval.code IS NULL THEN
10543         SELECT * INTO retval FROM config.marc21_rec_type_map WHERE code = 'BKS';
10544     END IF;
10545
10546     RETURN retval;
10547 END;
10548 $func$ LANGUAGE PLPGSQL;
10549
10550 CREATE OR REPLACE FUNCTION biblio.marc21_extract_fixed_field( rid BIGINT, ff TEXT ) RETURNS TEXT AS $func$
10551 DECLARE
10552     rtype       TEXT;
10553     ff_pos      RECORD;
10554     tag_data    RECORD;
10555     val         TEXT;
10556 BEGIN
10557     rtype := (biblio.marc21_record_type( rid )).code;
10558     FOR ff_pos IN SELECT * FROM config.marc21_ff_pos_map WHERE fixed_field = ff AND rec_type = rtype ORDER BY tag DESC LOOP
10559         FOR tag_data IN SELECT * FROM metabib.full_rec WHERE tag = UPPER(ff_pos.tag) AND record = rid LOOP
10560             val := SUBSTRING( tag_data.value, ff_pos.start_pos + 1, ff_pos.length );
10561             RETURN val;
10562         END LOOP;
10563         val := REPEAT( ff_pos.default_val, ff_pos.length );
10564         RETURN val;
10565     END LOOP;
10566
10567     RETURN NULL;
10568 END;
10569 $func$ LANGUAGE PLPGSQL;
10570
10571 CREATE TYPE biblio.marc21_physical_characteristics AS ( id INT, record BIGINT, ptype TEXT, subfield INT, value INT );
10572 CREATE OR REPLACE FUNCTION biblio.marc21_physical_characteristics( rid BIGINT ) RETURNS SETOF biblio.marc21_physical_characteristics AS $func$
10573 DECLARE
10574     rowid   INT := 0;
10575     _007    RECORD;
10576     ptype   config.marc21_physical_characteristic_type_map%ROWTYPE;
10577     psf     config.marc21_physical_characteristic_subfield_map%ROWTYPE;
10578     pval    config.marc21_physical_characteristic_value_map%ROWTYPE;
10579     retval  biblio.marc21_physical_characteristics%ROWTYPE;
10580 BEGIN
10581
10582     SELECT * INTO _007 FROM metabib.full_rec WHERE record = rid AND tag = '007' LIMIT 1;
10583
10584     IF _007.id IS NOT NULL THEN
10585         SELECT * INTO ptype FROM config.marc21_physical_characteristic_type_map WHERE ptype_key = SUBSTRING( _007.value, 1, 1 );
10586
10587         IF ptype.ptype_key IS NOT NULL THEN
10588             FOR psf IN SELECT * FROM config.marc21_physical_characteristic_subfield_map WHERE ptype_key = ptype.ptype_key LOOP
10589                 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 );
10590
10591                 IF pval.id IS NOT NULL THEN
10592                     rowid := rowid + 1;
10593                     retval.id := rowid;
10594                     retval.record := rid;
10595                     retval.ptype := ptype.ptype_key;
10596                     retval.subfield := psf.id;
10597                     retval.value := pval.id;
10598                     RETURN NEXT retval;
10599                 END IF;
10600
10601             END LOOP;
10602         END IF;
10603     END IF;
10604
10605     RETURN;
10606 END;
10607 $func$ LANGUAGE PLPGSQL;
10608
10609 DROP VIEW IF EXISTS money.open_usr_circulation_summary;
10610 DROP VIEW IF EXISTS money.open_usr_summary;
10611 DROP VIEW IF EXISTS money.open_billable_xact_summary;
10612
10613 -- The view should supply defaults for numeric (amount) columns
10614 CREATE OR REPLACE VIEW money.billable_xact_summary AS
10615     SELECT  xact.id,
10616         xact.usr,
10617         xact.xact_start,
10618         xact.xact_finish,
10619         COALESCE(credit.amount, 0.0::numeric) AS total_paid,
10620         credit.payment_ts AS last_payment_ts,
10621         credit.note AS last_payment_note,
10622         credit.payment_type AS last_payment_type,
10623         COALESCE(debit.amount, 0.0::numeric) AS total_owed,
10624         debit.billing_ts AS last_billing_ts,
10625         debit.note AS last_billing_note,
10626         debit.billing_type AS last_billing_type,
10627         COALESCE(debit.amount, 0.0::numeric) - COALESCE(credit.amount, 0.0::numeric) AS balance_owed,
10628         p.relname AS xact_type
10629       FROM  money.billable_xact xact
10630         JOIN pg_class p ON xact.tableoid = p.oid
10631         LEFT JOIN (
10632             SELECT  billing.xact,
10633                 sum(billing.amount) AS amount,
10634                 max(billing.billing_ts) AS billing_ts,
10635                 last(billing.note) AS note,
10636                 last(billing.billing_type) AS billing_type
10637               FROM  money.billing
10638               WHERE billing.voided IS FALSE
10639               GROUP BY billing.xact
10640             ) debit ON xact.id = debit.xact
10641         LEFT JOIN (
10642             SELECT  payment_view.xact,
10643                 sum(payment_view.amount) AS amount,
10644                 max(payment_view.payment_ts) AS payment_ts,
10645                 last(payment_view.note) AS note,
10646                 last(payment_view.payment_type) AS payment_type
10647               FROM  money.payment_view
10648               WHERE payment_view.voided IS FALSE
10649               GROUP BY payment_view.xact
10650             ) credit ON xact.id = credit.xact
10651       ORDER BY debit.billing_ts, credit.payment_ts;
10652
10653 CREATE OR REPLACE VIEW money.open_billable_xact_summary AS 
10654     SELECT * FROM money.billable_xact_summary_location_view
10655     WHERE xact_finish IS NULL;
10656
10657 CREATE OR REPLACE VIEW money.open_usr_circulation_summary AS
10658     SELECT 
10659         usr,
10660         SUM(total_paid) AS total_paid,
10661         SUM(total_owed) AS total_owed,
10662         SUM(balance_owed) AS balance_owed
10663     FROM  money.materialized_billable_xact_summary
10664     WHERE xact_type = 'circulation' AND xact_finish IS NULL
10665     GROUP BY usr;
10666
10667 CREATE OR REPLACE VIEW money.usr_summary AS
10668     SELECT 
10669         usr, 
10670         sum(total_paid) AS total_paid, 
10671         sum(total_owed) AS total_owed, 
10672         sum(balance_owed) AS balance_owed
10673     FROM money.materialized_billable_xact_summary
10674     GROUP BY usr;
10675
10676 CREATE OR REPLACE VIEW money.open_usr_summary AS
10677     SELECT 
10678         usr, 
10679         sum(total_paid) AS total_paid, 
10680         sum(total_owed) AS total_owed, 
10681         sum(balance_owed) AS balance_owed
10682     FROM money.materialized_billable_xact_summary
10683     WHERE xact_finish IS NULL
10684     GROUP BY usr;
10685
10686 -- 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;
10687
10688 CREATE TABLE config.biblio_fingerprint (
10689         id                      SERIAL  PRIMARY KEY,
10690         name            TEXT    NOT NULL, 
10691         xpath           TEXT    NOT NULL,
10692     first_word  BOOL    NOT NULL DEFAULT FALSE,
10693         format          TEXT    NOT NULL DEFAULT 'marcxml'
10694 );
10695
10696 INSERT INTO config.biblio_fingerprint (name, xpath, format)
10697     VALUES (
10698         'Title',
10699         '//marc:datafield[@tag="700"]/marc:subfield[@code="t"]|' ||
10700             '//marc:datafield[@tag="240"]/marc:subfield[@code="a"]|' ||
10701             '//marc:datafield[@tag="242"]/marc:subfield[@code="a"]|' ||
10702             '//marc:datafield[@tag="246"]/marc:subfield[@code="a"]|' ||
10703             '//marc:datafield[@tag="245"]/marc:subfield[@code="a"]',
10704         'marcxml'
10705     );
10706
10707 INSERT INTO config.biblio_fingerprint (name, xpath, format, first_word)
10708     VALUES (
10709         'Author',
10710         '//marc:datafield[@tag="700" and ./*[@code="t"]]/marc:subfield[@code="a"]|'
10711             '//marc:datafield[@tag="100"]/marc:subfield[@code="a"]|'
10712             '//marc:datafield[@tag="110"]/marc:subfield[@code="a"]|'
10713             '//marc:datafield[@tag="111"]/marc:subfield[@code="a"]|'
10714             '//marc:datafield[@tag="260"]/marc:subfield[@code="b"]',
10715         'marcxml',
10716         TRUE
10717     );
10718
10719 CREATE OR REPLACE FUNCTION biblio.extract_quality ( marc TEXT, best_lang TEXT, best_type TEXT ) RETURNS INT AS $func$
10720 DECLARE
10721     qual        INT;
10722     ldr         TEXT;
10723     tval        TEXT;
10724     tval_rec    RECORD;
10725     bval        TEXT;
10726     bval_rec    RECORD;
10727     type_map    RECORD;
10728     ff_pos      RECORD;
10729     ff_tag_data TEXT;
10730 BEGIN
10731
10732     IF marc IS NULL OR marc = '' THEN
10733         RETURN NULL;
10734     END IF;
10735
10736     -- First, the count of tags
10737     qual := ARRAY_UPPER(oils_xpath('*[local-name()="datafield"]', marc), 1);
10738
10739     -- now go through a bunch of pain to get the record type
10740     IF best_type IS NOT NULL THEN
10741         ldr := (oils_xpath('//*[local-name()="leader"]/text()', marc))[1];
10742
10743         IF ldr IS NOT NULL THEN
10744             SELECT * INTO tval_rec FROM config.marc21_ff_pos_map WHERE fixed_field = 'Type' LIMIT 1; -- They're all the same
10745             SELECT * INTO bval_rec FROM config.marc21_ff_pos_map WHERE fixed_field = 'BLvl' LIMIT 1; -- They're all the same
10746
10747
10748             tval := SUBSTRING( ldr, tval_rec.start_pos + 1, tval_rec.length );
10749             bval := SUBSTRING( ldr, bval_rec.start_pos + 1, bval_rec.length );
10750
10751             -- RAISE NOTICE 'type %, blvl %, ldr %', tval, bval, ldr;
10752
10753             SELECT * INTO type_map FROM config.marc21_rec_type_map WHERE type_val LIKE '%' || tval || '%' AND blvl_val LIKE '%' || bval || '%';
10754
10755             IF type_map.code IS NOT NULL THEN
10756                 IF best_type = type_map.code THEN
10757                     qual := qual + qual / 2;
10758                 END IF;
10759
10760                 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
10761                     ff_tag_data := SUBSTRING((oils_xpath('//*[@tag="' || ff_pos.tag || '"]/text()',marc))[1], ff_pos.start_pos + 1, ff_pos.length);
10762                     IF ff_tag_data = best_lang THEN
10763                             qual := qual + 100;
10764                     END IF;
10765                 END LOOP;
10766             END IF;
10767         END IF;
10768     END IF;
10769
10770     -- Now look for some quality metrics
10771     -- DCL record?
10772     IF ARRAY_UPPER(oils_xpath('//*[@tag="040"]/*[@code="a" and contains(.,"DLC")]', marc), 1) = 1 THEN
10773         qual := qual + 10;
10774     END IF;
10775
10776     -- From OCLC?
10777     IF (oils_xpath('//*[@tag="003"]/text()', marc))[1] ~* E'oclo?c' THEN
10778         qual := qual + 10;
10779     END IF;
10780
10781     RETURN qual;
10782
10783 END;
10784 $func$ LANGUAGE PLPGSQL;
10785
10786 CREATE OR REPLACE FUNCTION biblio.extract_fingerprint ( marc text ) RETURNS TEXT AS $func$
10787 DECLARE
10788     idx     config.biblio_fingerprint%ROWTYPE;
10789     xfrm        config.xml_transform%ROWTYPE;
10790     prev_xfrm   TEXT;
10791     transformed_xml TEXT;
10792     xml_node    TEXT;
10793     xml_node_list   TEXT[];
10794     raw_text    TEXT;
10795     output_text TEXT := '';
10796 BEGIN
10797
10798     IF marc IS NULL OR marc = '' THEN
10799         RETURN NULL;
10800     END IF;
10801
10802     -- Loop over the indexing entries
10803     FOR idx IN SELECT * FROM config.biblio_fingerprint ORDER BY format, id LOOP
10804
10805         SELECT INTO xfrm * from config.xml_transform WHERE name = idx.format;
10806
10807         -- See if we can skip the XSLT ... it's expensive
10808         IF prev_xfrm IS NULL OR prev_xfrm <> xfrm.name THEN
10809             -- Can't skip the transform
10810             IF xfrm.xslt <> '---' THEN
10811                 transformed_xml := oils_xslt_process(marc,xfrm.xslt);
10812             ELSE
10813                 transformed_xml := marc;
10814             END IF;
10815
10816             prev_xfrm := xfrm.name;
10817         END IF;
10818
10819         raw_text := COALESCE(
10820             naco_normalize(
10821                 ARRAY_TO_STRING(
10822                     oils_xpath(
10823                         '//text()',
10824                         (oils_xpath(
10825                             idx.xpath,
10826                             transformed_xml,
10827                             ARRAY[ARRAY[xfrm.prefix, xfrm.namespace_uri]]
10828                         ))[1]
10829                     ),
10830                     ''
10831                 )
10832             ),
10833             ''
10834         );
10835
10836         raw_text := REGEXP_REPLACE(raw_text, E'\\[.+?\\]', E'');
10837         raw_text := REGEXP_REPLACE(raw_text, E'\\mthe\\M|\\man?d?d\\M', E'', 'g'); -- arg! the pain!
10838
10839         IF idx.first_word IS TRUE THEN
10840             raw_text := REGEXP_REPLACE(raw_text, E'^(\\w+).*?$', E'\\1');
10841         END IF;
10842
10843         output_text := output_text || REGEXP_REPLACE(raw_text, E'\\s+', '', 'g');
10844
10845     END LOOP;
10846
10847     RETURN output_text;
10848
10849 END;
10850 $func$ LANGUAGE PLPGSQL;
10851
10852 -- BEFORE UPDATE OR INSERT trigger for biblio.record_entry
10853 CREATE OR REPLACE FUNCTION biblio.fingerprint_trigger () RETURNS TRIGGER AS $func$
10854 BEGIN
10855
10856     -- For TG_ARGV, first param is language (like 'eng'), second is record type (like 'BKS')
10857
10858     IF NEW.deleted IS TRUE THEN -- we don't much care, then, do we?
10859         RETURN NEW;
10860     END IF;
10861
10862     NEW.fingerprint := biblio.extract_fingerprint(NEW.marc);
10863     NEW.quality := biblio.extract_quality(NEW.marc, TG_ARGV[0], TG_ARGV[1]);
10864
10865     RETURN NEW;
10866
10867 END;
10868 $func$ LANGUAGE PLPGSQL;
10869
10870 CREATE TABLE config.internal_flag (
10871     name    TEXT    PRIMARY KEY,
10872     value   TEXT,
10873     enabled BOOL    NOT NULL DEFAULT FALSE
10874 );
10875 INSERT INTO config.internal_flag (name) VALUES ('ingest.metarecord_mapping.skip_on_insert');
10876 INSERT INTO config.internal_flag (name) VALUES ('ingest.reingest.force_on_same_marc');
10877 INSERT INTO config.internal_flag (name) VALUES ('ingest.reingest.skip_located_uri');
10878 INSERT INTO config.internal_flag (name) VALUES ('ingest.disable_located_uri');
10879 INSERT INTO config.internal_flag (name) VALUES ('ingest.disable_metabib_full_rec');
10880 INSERT INTO config.internal_flag (name) VALUES ('ingest.disable_metabib_rec_descriptor');
10881 INSERT INTO config.internal_flag (name) VALUES ('ingest.disable_metabib_field_entry');
10882 INSERT INTO config.internal_flag (name) VALUES ('ingest.disable_authority_linking');
10883 INSERT INTO config.internal_flag (name) VALUES ('ingest.metarecord_mapping.skip_on_update');
10884 INSERT INTO config.internal_flag (name) VALUES ('ingest.assume_inserts_only');
10885
10886 CREATE TABLE authority.bib_linking (
10887     id          BIGSERIAL   PRIMARY KEY,
10888     bib         BIGINT      NOT NULL REFERENCES biblio.record_entry (id),
10889     authority   BIGINT      NOT NULL REFERENCES authority.record_entry (id)
10890 );
10891 CREATE INDEX authority_bl_bib_idx ON authority.bib_linking ( bib );
10892 CREATE UNIQUE INDEX authority_bl_bib_authority_once_idx ON authority.bib_linking ( authority, bib );
10893
10894 CREATE OR REPLACE FUNCTION public.remove_paren_substring( TEXT ) RETURNS TEXT AS $func$
10895     SELECT regexp_replace($1, $$\([^)]+\)$$, '', 'g');
10896 $func$ LANGUAGE SQL STRICT IMMUTABLE;
10897
10898 CREATE OR REPLACE FUNCTION biblio.map_authority_linking (bibid BIGINT, marc TEXT) RETURNS BIGINT AS $func$
10899     DELETE FROM authority.bib_linking WHERE bib = $1;
10900     INSERT INTO authority.bib_linking (bib, authority)
10901         SELECT  y.bib,
10902                 y.authority
10903           FROM (    SELECT  DISTINCT $1 AS bib,
10904                             BTRIM(remove_paren_substring(txt))::BIGINT AS authority
10905                       FROM  explode_array(oils_xpath('//*[@code="0"]/text()',$2)) x(txt)
10906                       WHERE BTRIM(remove_paren_substring(txt)) ~ $re$^\d+$$re$
10907                 ) y JOIN authority.record_entry r ON r.id = y.authority;
10908     SELECT $1;
10909 $func$ LANGUAGE SQL;
10910
10911 CREATE OR REPLACE FUNCTION metabib.reingest_metabib_rec_descriptor( bib_id BIGINT ) RETURNS VOID AS $func$
10912 BEGIN
10913     PERFORM * FROM config.internal_flag WHERE name = 'ingest.assume_inserts_only' AND enabled;
10914     IF NOT FOUND THEN
10915         DELETE FROM metabib.rec_descriptor WHERE record = bib_id;
10916     END IF;
10917     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)
10918         SELECT  bib_id,
10919                 biblio.marc21_extract_fixed_field( bib_id, 'Type' ),
10920                 biblio.marc21_extract_fixed_field( bib_id, 'Form' ),
10921                 biblio.marc21_extract_fixed_field( bib_id, 'BLvl' ),
10922                 biblio.marc21_extract_fixed_field( bib_id, 'Ctrl' ),
10923                 biblio.marc21_extract_fixed_field( bib_id, 'ELvl' ),
10924                 biblio.marc21_extract_fixed_field( bib_id, 'Audn' ),
10925                 biblio.marc21_extract_fixed_field( bib_id, 'LitF' ),
10926                 biblio.marc21_extract_fixed_field( bib_id, 'TMat' ),
10927                 biblio.marc21_extract_fixed_field( bib_id, 'Desc' ),
10928                 biblio.marc21_extract_fixed_field( bib_id, 'DtSt' ),
10929                 biblio.marc21_extract_fixed_field( bib_id, 'Lang' ),
10930                 (   SELECT  v.value
10931                       FROM  biblio.marc21_physical_characteristics( bib_id) p
10932                             JOIN config.marc21_physical_characteristic_subfield_map s ON (s.id = p.subfield)
10933                             JOIN config.marc21_physical_characteristic_value_map v ON (v.id = p.value)
10934                       WHERE p.ptype = 'v' AND s.subfield = 'e'    ),
10935                 LPAD(NULLIF(REGEXP_REPLACE(NULLIF(biblio.marc21_extract_fixed_field( bib_id, 'Date1'), ''), E'\\D', '0', 'g')::INT,0)::TEXT,4,'0'),
10936                 LPAD(NULLIF(REGEXP_REPLACE(NULLIF(biblio.marc21_extract_fixed_field( bib_id, 'Date2'), ''), E'\\D', '9', 'g')::INT,9999)::TEXT,4,'0');
10937
10938     RETURN;
10939 END;
10940 $func$ LANGUAGE PLPGSQL;
10941
10942 CREATE TABLE config.metabib_class (
10943     name    TEXT    PRIMARY KEY,
10944     label   TEXT    NOT NULL UNIQUE
10945 );
10946
10947 INSERT INTO config.metabib_class ( name, label ) VALUES ( 'keyword', oils_i18n_gettext('keyword', 'Keyword', 'cmc', 'label') );
10948 INSERT INTO config.metabib_class ( name, label ) VALUES ( 'title', oils_i18n_gettext('title', 'Title', 'cmc', 'label') );
10949 INSERT INTO config.metabib_class ( name, label ) VALUES ( 'author', oils_i18n_gettext('author', 'Author', 'cmc', 'label') );
10950 INSERT INTO config.metabib_class ( name, label ) VALUES ( 'subject', oils_i18n_gettext('subject', 'Subject', 'cmc', 'label') );
10951 INSERT INTO config.metabib_class ( name, label ) VALUES ( 'series', oils_i18n_gettext('series', 'Series', 'cmc', 'label') );
10952
10953 CREATE TABLE metabib.facet_entry (
10954         id              BIGSERIAL       PRIMARY KEY,
10955         source          BIGINT          NOT NULL,
10956         field           INT             NOT NULL,
10957         value           TEXT            NOT NULL
10958 );
10959
10960 CREATE OR REPLACE FUNCTION metabib.reingest_metabib_field_entries( bib_id BIGINT ) RETURNS VOID AS $func$
10961 DECLARE
10962     fclass          RECORD;
10963     ind_data        metabib.field_entry_template%ROWTYPE;
10964 BEGIN
10965     PERFORM * FROM config.internal_flag WHERE name = 'ingest.assume_inserts_only' AND enabled;
10966     IF NOT FOUND THEN
10967         FOR fclass IN SELECT * FROM config.metabib_class LOOP
10968             -- RAISE NOTICE 'Emptying out %', fclass.name;
10969             EXECUTE $$DELETE FROM metabib.$$ || fclass.name || $$_field_entry WHERE source = $$ || bib_id;
10970         END LOOP;
10971         DELETE FROM metabib.facet_entry WHERE source = bib_id;
10972     END IF;
10973
10974     FOR ind_data IN SELECT * FROM biblio.extract_metabib_field_entry( bib_id ) LOOP
10975         IF ind_data.field < 0 THEN
10976             ind_data.field = -1 * ind_data.field;
10977             INSERT INTO metabib.facet_entry (field, source, value)
10978                 VALUES (ind_data.field, ind_data.source, ind_data.value);
10979         ELSE
10980             EXECUTE $$
10981                 INSERT INTO metabib.$$ || ind_data.field_class || $$_field_entry (field, source, value)
10982                     VALUES ($$ ||
10983                         quote_literal(ind_data.field) || $$, $$ ||
10984                         quote_literal(ind_data.source) || $$, $$ ||
10985                         quote_literal(ind_data.value) ||
10986                     $$);$$;
10987         END IF;
10988
10989     END LOOP;
10990
10991     RETURN;
10992 END;
10993 $func$ LANGUAGE PLPGSQL;
10994
10995 CREATE OR REPLACE FUNCTION biblio.extract_located_uris( bib_id BIGINT, marcxml TEXT, editor_id INT ) RETURNS VOID AS $func$
10996 DECLARE
10997     uris            TEXT[];
10998     uri_xml         TEXT;
10999     uri_label       TEXT;
11000     uri_href        TEXT;
11001     uri_use         TEXT;
11002     uri_owner       TEXT;
11003     uri_owner_id    INT;
11004     uri_id          INT;
11005     uri_cn_id       INT;
11006     uri_map_id      INT;
11007 BEGIN
11008
11009     uris := oils_xpath('//*[@tag="856" and (@ind1="4" or @ind1="1") and (@ind2="0" or @ind2="1")]',marcxml);
11010     IF ARRAY_UPPER(uris,1) > 0 THEN
11011         FOR i IN 1 .. ARRAY_UPPER(uris, 1) LOOP
11012             -- First we pull info out of the 856
11013             uri_xml     := uris[i];
11014
11015             uri_href    := (oils_xpath('//*[@code="u"]/text()',uri_xml))[1];
11016             CONTINUE WHEN uri_href IS NULL;
11017
11018             uri_label   := (oils_xpath('//*[@code="y"]/text()|//*[@code="3"]/text()|//*[@code="u"]/text()',uri_xml))[1];
11019             CONTINUE WHEN uri_label IS NULL;
11020
11021             uri_owner   := (oils_xpath('//*[@code="9"]/text()|//*[@code="w"]/text()|//*[@code="n"]/text()',uri_xml))[1];
11022             CONTINUE WHEN uri_owner IS NULL;
11023
11024             uri_use     := (oils_xpath('//*[@code="z"]/text()|//*[@code="2"]/text()|//*[@code="n"]/text()|//*[@code="u"]/text()',uri_xml))[1];
11025
11026             uri_owner := REGEXP_REPLACE(uri_owner, $re$^.*?\((\w+)\).*$$re$, E'\\1');
11027
11028             SELECT id INTO uri_owner_id FROM actor.org_unit WHERE shortname = uri_owner;
11029             CONTINUE WHEN NOT FOUND;
11030
11031             -- now we look for a matching uri
11032             SELECT id INTO uri_id FROM asset.uri WHERE label = uri_label AND href = uri_href AND use_restriction = uri_use AND active;
11033             IF NOT FOUND THEN -- create one
11034                 INSERT INTO asset.uri (label, href, use_restriction) VALUES (uri_label, uri_href, uri_use);
11035                 SELECT id INTO uri_id FROM asset.uri WHERE label = uri_label AND href = uri_href AND use_restriction = uri_use AND active;
11036             END IF;
11037
11038             -- we need a call number to link through
11039             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;
11040             IF NOT FOUND THEN
11041                 INSERT INTO asset.call_number (owning_lib, record, create_date, edit_date, creator, editor, label)
11042                     VALUES (uri_owner_id, bib_id, 'now', 'now', editor_id, editor_id, '##URI##');
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             END IF;
11045
11046             -- now, link them if they're not already
11047             SELECT id INTO uri_map_id FROM asset.uri_call_number_map WHERE call_number = uri_cn_id AND uri = uri_id;
11048             IF NOT FOUND THEN
11049                 INSERT INTO asset.uri_call_number_map (call_number, uri) VALUES (uri_cn_id, uri_id);
11050             END IF;
11051
11052         END LOOP;
11053     END IF;
11054
11055     RETURN;
11056 END;
11057 $func$ LANGUAGE PLPGSQL;
11058
11059 CREATE OR REPLACE FUNCTION metabib.remap_metarecord_for_bib( bib_id BIGINT, fp TEXT ) RETURNS BIGINT AS $func$
11060 DECLARE
11061     source_count    INT;
11062     old_mr          BIGINT;
11063     tmp_mr          metabib.metarecord%ROWTYPE;
11064     deleted_mrs     BIGINT[];
11065 BEGIN
11066
11067     DELETE FROM metabib.metarecord_source_map WHERE source = bib_id; -- Rid ourselves of the search-estimate-killing linkage
11068
11069     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
11070
11071         IF old_mr IS NULL AND fp = tmp_mr.fingerprint THEN -- Find the first fingerprint-matching
11072             old_mr := tmp_mr.id;
11073         ELSE
11074             SELECT COUNT(*) INTO source_count FROM metabib.metarecord_source_map WHERE metarecord = tmp_mr.id;
11075             IF source_count = 0 THEN -- No other records
11076                 deleted_mrs := ARRAY_APPEND(deleted_mrs, tmp_mr.id);
11077                 DELETE FROM metabib.metarecord WHERE id = tmp_mr.id;
11078             END IF;
11079         END IF;
11080
11081     END LOOP;
11082
11083     IF old_mr IS NULL THEN -- we found no suitable, preexisting MR based on old source maps
11084         SELECT id INTO old_mr FROM metabib.metarecord WHERE fingerprint = fp; -- is there one for our current fingerprint?
11085         IF old_mr IS NULL THEN -- nope, create one and grab its id
11086             INSERT INTO metabib.metarecord ( fingerprint, master_record ) VALUES ( fp, bib_id );
11087             SELECT id INTO old_mr FROM metabib.metarecord WHERE fingerprint = fp;
11088         ELSE -- indeed there is. update it with a null cache and recalcualated master record
11089             UPDATE  metabib.metarecord
11090               SET   mods = NULL,
11091                     master_record = ( SELECT id FROM biblio.record_entry WHERE fingerprint = fp ORDER BY quality DESC LIMIT 1)
11092               WHERE id = old_mr;
11093         END IF;
11094     ELSE -- there was one we already attached to, update its mods cache and master_record
11095         UPDATE  metabib.metarecord
11096           SET   mods = NULL,
11097                 master_record = ( SELECT id FROM biblio.record_entry WHERE fingerprint = fp ORDER BY quality DESC LIMIT 1)
11098           WHERE id = old_mr;
11099     END IF;
11100
11101     INSERT INTO metabib.metarecord_source_map (metarecord, source) VALUES (old_mr, bib_id); -- new source mapping
11102
11103     IF ARRAY_UPPER(deleted_mrs,1) > 0 THEN
11104         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
11105     END IF;
11106
11107     RETURN old_mr;
11108
11109 END;
11110 $func$ LANGUAGE PLPGSQL;
11111
11112 CREATE OR REPLACE FUNCTION metabib.reingest_metabib_full_rec( bib_id BIGINT ) RETURNS VOID AS $func$
11113 BEGIN
11114     PERFORM * FROM config.internal_flag WHERE name = 'ingest.assume_inserts_only' AND enabled;
11115     IF NOT FOUND THEN
11116         DELETE FROM metabib.real_full_rec WHERE record = bib_id;
11117     END IF;
11118     INSERT INTO metabib.real_full_rec (record, tag, ind1, ind2, subfield, value)
11119         SELECT record, tag, ind1, ind2, subfield, value FROM biblio.flatten_marc( bib_id );
11120
11121     RETURN;
11122 END;
11123 $func$ LANGUAGE PLPGSQL;
11124
11125 -- AFTER UPDATE OR INSERT trigger for biblio.record_entry
11126 CREATE OR REPLACE FUNCTION biblio.indexing_ingest_or_delete () RETURNS TRIGGER AS $func$
11127 BEGIN
11128
11129     IF NEW.deleted IS TRUE THEN -- If this bib is deleted
11130         DELETE FROM metabib.metarecord_source_map WHERE source = NEW.id; -- Rid ourselves of the search-estimate-killing linkage
11131         DELETE FROM authority.bib_linking WHERE bib = NEW.id; -- Avoid updating fields in bibs that are no longer visible
11132         RETURN NEW; -- and we're done
11133     END IF;
11134
11135     IF TG_OP = 'UPDATE' THEN -- re-ingest?
11136         PERFORM * FROM config.internal_flag WHERE name = 'ingest.reingest.force_on_same_marc' AND enabled;
11137
11138         IF NOT FOUND AND OLD.marc = NEW.marc THEN -- don't do anything if the MARC didn't change
11139             RETURN NEW;
11140         END IF;
11141     END IF;
11142
11143     -- Record authority linking
11144     PERFORM * FROM config.internal_flag WHERE name = 'ingest.disable_authority_linking' AND enabled;
11145     IF NOT FOUND THEN
11146         PERFORM biblio.map_authority_linking( NEW.id, NEW.marc );
11147     END IF;
11148
11149     -- Flatten and insert the mfr data
11150     PERFORM * FROM config.internal_flag WHERE name = 'ingest.disable_metabib_full_rec' AND enabled;
11151     IF NOT FOUND THEN
11152         PERFORM metabib.reingest_metabib_full_rec(NEW.id);
11153         PERFORM * FROM config.internal_flag WHERE name = 'ingest.disable_metabib_rec_descriptor' AND enabled;
11154         IF NOT FOUND THEN
11155             PERFORM metabib.reingest_metabib_rec_descriptor(NEW.id);
11156         END IF;
11157     END IF;
11158
11159     -- Gather and insert the field entry data
11160     PERFORM metabib.reingest_metabib_field_entries(NEW.id);
11161
11162     -- Located URI magic
11163     IF TG_OP = 'INSERT' THEN
11164         PERFORM * FROM config.internal_flag WHERE name = 'ingest.disable_located_uri' AND enabled;
11165         IF NOT FOUND THEN
11166             PERFORM biblio.extract_located_uris( NEW.id, NEW.marc, NEW.editor );
11167         END IF;
11168     ELSE
11169         PERFORM * FROM config.internal_flag WHERE name = 'ingest.disable_located_uri' AND enabled;
11170         IF NOT FOUND THEN
11171             PERFORM biblio.extract_located_uris( NEW.id, NEW.marc, NEW.editor );
11172         END IF;
11173     END IF;
11174
11175     -- (re)map metarecord-bib linking
11176     IF TG_OP = 'INSERT' THEN -- if not deleted and performing an insert, check for the flag
11177         PERFORM * FROM config.internal_flag WHERE name = 'ingest.metarecord_mapping.skip_on_insert' AND enabled;
11178         IF NOT FOUND THEN
11179             PERFORM metabib.remap_metarecord_for_bib( NEW.id, NEW.fingerprint );
11180         END IF;
11181     ELSE -- we're doing an update, and we're not deleted, remap
11182         PERFORM * FROM config.internal_flag WHERE name = 'ingest.metarecord_mapping.skip_on_update' AND enabled;
11183         IF NOT FOUND THEN
11184             PERFORM metabib.remap_metarecord_for_bib( NEW.id, NEW.fingerprint );
11185         END IF;
11186     END IF;
11187
11188     RETURN NEW;
11189 END;
11190 $func$ LANGUAGE PLPGSQL;
11191
11192 CREATE TRIGGER fingerprint_tgr BEFORE INSERT OR UPDATE ON biblio.record_entry FOR EACH ROW EXECUTE PROCEDURE biblio.fingerprint_trigger ('eng','BKS');
11193 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 ();
11194
11195 DROP TRIGGER IF EXISTS zzz_update_materialized_simple_rec_delete_tgr ON biblio.record_entry;
11196
11197 CREATE OR REPLACE FUNCTION oils_xpath_table ( key TEXT, document_field TEXT, relation_name TEXT, xpaths TEXT, criteria TEXT ) RETURNS SETOF RECORD AS $func$
11198 DECLARE
11199     xpath_list  TEXT[];
11200     select_list TEXT[];
11201     where_list  TEXT[];
11202     q           TEXT;
11203     out_record  RECORD;
11204     empty_test  RECORD;
11205 BEGIN
11206     xpath_list := STRING_TO_ARRAY( xpaths, '|' );
11207  
11208     select_list := ARRAY_APPEND( select_list, key || '::INT AS key' );
11209  
11210     FOR i IN 1 .. ARRAY_UPPER(xpath_list,1) LOOP
11211         IF xpath_list[i] = 'null()' THEN
11212             select_list := ARRAY_APPEND( select_list, 'NULL::TEXT AS c_' || i );
11213         ELSE
11214             select_list := ARRAY_APPEND(
11215                 select_list,
11216                 $sel$
11217                 EXPLODE_ARRAY(
11218                     COALESCE(
11219                         NULLIF(
11220                             oils_xpath(
11221                                 $sel$ ||
11222                                     quote_literal(
11223                                         CASE
11224                                             WHEN xpath_list[i] ~ $re$/[^/[]*@[^/]+$$re$ OR xpath_list[i] ~ $re$text\(\)$$re$ THEN xpath_list[i]
11225                                             ELSE xpath_list[i] || '//text()'
11226                                         END
11227                                     ) ||
11228                                 $sel$,
11229                                 $sel$ || document_field || $sel$
11230                             ),
11231                            '{}'::TEXT[]
11232                         ),
11233                         '{NULL}'::TEXT[]
11234                     )
11235                 ) AS c_$sel$ || i
11236             );
11237             where_list := ARRAY_APPEND(
11238                 where_list,
11239                 'c_' || i || ' IS NOT NULL'
11240             );
11241         END IF;
11242     END LOOP;
11243  
11244     q := $q$
11245 SELECT * FROM (
11246     SELECT $q$ || ARRAY_TO_STRING( select_list, ', ' ) || $q$ FROM $q$ || relation_name || $q$ WHERE ($q$ || criteria || $q$)
11247 )x WHERE $q$ || ARRAY_TO_STRING( where_list, ' AND ' );
11248     -- RAISE NOTICE 'query: %', q;
11249  
11250     FOR out_record IN EXECUTE q LOOP
11251         RETURN NEXT out_record;
11252     END LOOP;
11253  
11254     RETURN;
11255 END;
11256 $func$ LANGUAGE PLPGSQL IMMUTABLE;
11257
11258 CREATE OR REPLACE FUNCTION vandelay.ingest_items ( import_id BIGINT, attr_def_id BIGINT ) RETURNS SETOF vandelay.import_item AS $$
11259 DECLARE
11260
11261     owning_lib      TEXT;
11262     circ_lib        TEXT;
11263     call_number     TEXT;
11264     copy_number     TEXT;
11265     status          TEXT;
11266     location        TEXT;
11267     circulate       TEXT;
11268     deposit         TEXT;
11269     deposit_amount  TEXT;
11270     ref             TEXT;
11271     holdable        TEXT;
11272     price           TEXT;
11273     barcode         TEXT;
11274     circ_modifier   TEXT;
11275     circ_as_type    TEXT;
11276     alert_message   TEXT;
11277     opac_visible    TEXT;
11278     pub_note        TEXT;
11279     priv_note       TEXT;
11280
11281     attr_def        RECORD;
11282     tmp_attr_set    RECORD;
11283     attr_set        vandelay.import_item%ROWTYPE;
11284
11285     xpath           TEXT;
11286
11287 BEGIN
11288
11289     SELECT * INTO attr_def FROM vandelay.import_item_attr_definition WHERE id = attr_def_id;
11290
11291     IF FOUND THEN
11292
11293         attr_set.definition := attr_def.id; 
11294     
11295         -- Build the combined XPath
11296     
11297         owning_lib :=
11298             CASE
11299                 WHEN attr_def.owning_lib IS NULL THEN 'null()'
11300                 WHEN LENGTH( attr_def.owning_lib ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.owning_lib || '"]'
11301                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.owning_lib
11302             END;
11303     
11304         circ_lib :=
11305             CASE
11306                 WHEN attr_def.circ_lib IS NULL THEN 'null()'
11307                 WHEN LENGTH( attr_def.circ_lib ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.circ_lib || '"]'
11308                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.circ_lib
11309             END;
11310     
11311         call_number :=
11312             CASE
11313                 WHEN attr_def.call_number IS NULL THEN 'null()'
11314                 WHEN LENGTH( attr_def.call_number ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.call_number || '"]'
11315                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.call_number
11316             END;
11317     
11318         copy_number :=
11319             CASE
11320                 WHEN attr_def.copy_number IS NULL THEN 'null()'
11321                 WHEN LENGTH( attr_def.copy_number ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.copy_number || '"]'
11322                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.copy_number
11323             END;
11324     
11325         status :=
11326             CASE
11327                 WHEN attr_def.status IS NULL THEN 'null()'
11328                 WHEN LENGTH( attr_def.status ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.status || '"]'
11329                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.status
11330             END;
11331     
11332         location :=
11333             CASE
11334                 WHEN attr_def.location IS NULL THEN 'null()'
11335                 WHEN LENGTH( attr_def.location ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.location || '"]'
11336                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.location
11337             END;
11338     
11339         circulate :=
11340             CASE
11341                 WHEN attr_def.circulate IS NULL THEN 'null()'
11342                 WHEN LENGTH( attr_def.circulate ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.circulate || '"]'
11343                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.circulate
11344             END;
11345     
11346         deposit :=
11347             CASE
11348                 WHEN attr_def.deposit IS NULL THEN 'null()'
11349                 WHEN LENGTH( attr_def.deposit ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.deposit || '"]'
11350                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.deposit
11351             END;
11352     
11353         deposit_amount :=
11354             CASE
11355                 WHEN attr_def.deposit_amount IS NULL THEN 'null()'
11356                 WHEN LENGTH( attr_def.deposit_amount ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.deposit_amount || '"]'
11357                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.deposit_amount
11358             END;
11359     
11360         ref :=
11361             CASE
11362                 WHEN attr_def.ref IS NULL THEN 'null()'
11363                 WHEN LENGTH( attr_def.ref ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.ref || '"]'
11364                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.ref
11365             END;
11366     
11367         holdable :=
11368             CASE
11369                 WHEN attr_def.holdable IS NULL THEN 'null()'
11370                 WHEN LENGTH( attr_def.holdable ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.holdable || '"]'
11371                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.holdable
11372             END;
11373     
11374         price :=
11375             CASE
11376                 WHEN attr_def.price IS NULL THEN 'null()'
11377                 WHEN LENGTH( attr_def.price ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.price || '"]'
11378                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.price
11379             END;
11380     
11381         barcode :=
11382             CASE
11383                 WHEN attr_def.barcode IS NULL THEN 'null()'
11384                 WHEN LENGTH( attr_def.barcode ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.barcode || '"]'
11385                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.barcode
11386             END;
11387     
11388         circ_modifier :=
11389             CASE
11390                 WHEN attr_def.circ_modifier IS NULL THEN 'null()'
11391                 WHEN LENGTH( attr_def.circ_modifier ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.circ_modifier || '"]'
11392                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.circ_modifier
11393             END;
11394     
11395         circ_as_type :=
11396             CASE
11397                 WHEN attr_def.circ_as_type IS NULL THEN 'null()'
11398                 WHEN LENGTH( attr_def.circ_as_type ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.circ_as_type || '"]'
11399                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.circ_as_type
11400             END;
11401     
11402         alert_message :=
11403             CASE
11404                 WHEN attr_def.alert_message IS NULL THEN 'null()'
11405                 WHEN LENGTH( attr_def.alert_message ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.alert_message || '"]'
11406                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.alert_message
11407             END;
11408     
11409         opac_visible :=
11410             CASE
11411                 WHEN attr_def.opac_visible IS NULL THEN 'null()'
11412                 WHEN LENGTH( attr_def.opac_visible ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.opac_visible || '"]'
11413                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.opac_visible
11414             END;
11415
11416         pub_note :=
11417             CASE
11418                 WHEN attr_def.pub_note IS NULL THEN 'null()'
11419                 WHEN LENGTH( attr_def.pub_note ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.pub_note || '"]'
11420                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.pub_note
11421             END;
11422         priv_note :=
11423             CASE
11424                 WHEN attr_def.priv_note IS NULL THEN 'null()'
11425                 WHEN LENGTH( attr_def.priv_note ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.priv_note || '"]'
11426                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.priv_note
11427             END;
11428     
11429     
11430         xpath := 
11431             owning_lib      || '|' || 
11432             circ_lib        || '|' || 
11433             call_number     || '|' || 
11434             copy_number     || '|' || 
11435             status          || '|' || 
11436             location        || '|' || 
11437             circulate       || '|' || 
11438             deposit         || '|' || 
11439             deposit_amount  || '|' || 
11440             ref             || '|' || 
11441             holdable        || '|' || 
11442             price           || '|' || 
11443             barcode         || '|' || 
11444             circ_modifier   || '|' || 
11445             circ_as_type    || '|' || 
11446             alert_message   || '|' || 
11447             pub_note        || '|' || 
11448             priv_note       || '|' || 
11449             opac_visible;
11450
11451         -- RAISE NOTICE 'XPath: %', xpath;
11452         
11453         FOR tmp_attr_set IN
11454                 SELECT  *
11455                   FROM  oils_xpath_table( 'id', 'marc', 'vandelay.queued_bib_record', xpath, 'id = ' || import_id )
11456                             AS t( id INT, ol TEXT, clib TEXT, cn TEXT, cnum TEXT, cs TEXT, cl TEXT, circ TEXT,
11457                                   dep TEXT, dep_amount TEXT, r TEXT, hold TEXT, pr TEXT, bc TEXT, circ_mod TEXT,
11458                                   circ_as TEXT, amessage TEXT, note TEXT, pnote TEXT, opac_vis TEXT )
11459         LOOP
11460     
11461             tmp_attr_set.pr = REGEXP_REPLACE(tmp_attr_set.pr, E'[^0-9\\.]', '', 'g');
11462             tmp_attr_set.dep_amount = REGEXP_REPLACE(tmp_attr_set.dep_amount, E'[^0-9\\.]', '', 'g');
11463
11464             tmp_attr_set.pr := NULLIF( tmp_attr_set.pr, '' );
11465             tmp_attr_set.dep_amount := NULLIF( tmp_attr_set.dep_amount, '' );
11466     
11467             SELECT id INTO attr_set.owning_lib FROM actor.org_unit WHERE shortname = UPPER(tmp_attr_set.ol); -- INT
11468             SELECT id INTO attr_set.circ_lib FROM actor.org_unit WHERE shortname = UPPER(tmp_attr_set.clib); -- INT
11469             SELECT id INTO attr_set.status FROM config.copy_status WHERE LOWER(name) = LOWER(tmp_attr_set.cs); -- INT
11470     
11471             SELECT  id INTO attr_set.location
11472               FROM  asset.copy_location
11473               WHERE LOWER(name) = LOWER(tmp_attr_set.cl)
11474                     AND asset.copy_location.owning_lib = COALESCE(attr_set.owning_lib, attr_set.circ_lib); -- INT
11475     
11476             attr_set.circulate      :=
11477                 LOWER( SUBSTRING( tmp_attr_set.circ, 1, 1)) IN ('t','y','1')
11478                 OR LOWER(tmp_attr_set.circ) = 'circulating'; -- BOOL
11479
11480             attr_set.deposit        :=
11481                 LOWER( SUBSTRING( tmp_attr_set.dep, 1, 1 ) ) IN ('t','y','1')
11482                 OR LOWER(tmp_attr_set.dep) = 'deposit'; -- BOOL
11483
11484             attr_set.holdable       :=
11485                 LOWER( SUBSTRING( tmp_attr_set.hold, 1, 1 ) ) IN ('t','y','1')
11486                 OR LOWER(tmp_attr_set.hold) = 'holdable'; -- BOOL
11487
11488             attr_set.opac_visible   :=
11489                 LOWER( SUBSTRING( tmp_attr_set.opac_vis, 1, 1 ) ) IN ('t','y','1')
11490                 OR LOWER(tmp_attr_set.opac_vis) = 'visible'; -- BOOL
11491
11492             attr_set.ref            :=
11493                 LOWER( SUBSTRING( tmp_attr_set.r, 1, 1 ) ) IN ('t','y','1')
11494                 OR LOWER(tmp_attr_set.r) = 'reference'; -- BOOL
11495     
11496             attr_set.copy_number    := tmp_attr_set.cnum::INT; -- INT,
11497             attr_set.deposit_amount := tmp_attr_set.dep_amount::NUMERIC(6,2); -- NUMERIC(6,2),
11498             attr_set.price          := tmp_attr_set.pr::NUMERIC(8,2); -- NUMERIC(8,2),
11499     
11500             attr_set.call_number    := tmp_attr_set.cn; -- TEXT
11501             attr_set.barcode        := tmp_attr_set.bc; -- TEXT,
11502             attr_set.circ_modifier  := tmp_attr_set.circ_mod; -- TEXT,
11503             attr_set.circ_as_type   := tmp_attr_set.circ_as; -- TEXT,
11504             attr_set.alert_message  := tmp_attr_set.amessage; -- TEXT,
11505             attr_set.pub_note       := tmp_attr_set.note; -- TEXT,
11506             attr_set.priv_note      := tmp_attr_set.pnote; -- TEXT,
11507             attr_set.alert_message  := tmp_attr_set.amessage; -- TEXT,
11508     
11509             RETURN NEXT attr_set;
11510     
11511         END LOOP;
11512     
11513     END IF;
11514
11515     RETURN;
11516
11517 END;
11518 $$ LANGUAGE PLPGSQL;
11519
11520 CREATE OR REPLACE FUNCTION vandelay.ingest_bib_items ( ) RETURNS TRIGGER AS $func$
11521 DECLARE
11522     attr_def    BIGINT;
11523     item_data   vandelay.import_item%ROWTYPE;
11524 BEGIN
11525
11526     SELECT item_attr_def INTO attr_def FROM vandelay.bib_queue WHERE id = NEW.queue;
11527
11528     FOR item_data IN SELECT * FROM vandelay.ingest_items( NEW.id::BIGINT, attr_def ) LOOP
11529         INSERT INTO vandelay.import_item (
11530             record,
11531             definition,
11532             owning_lib,
11533             circ_lib,
11534             call_number,
11535             copy_number,
11536             status,
11537             location,
11538             circulate,
11539             deposit,
11540             deposit_amount,
11541             ref,
11542             holdable,
11543             price,
11544             barcode,
11545             circ_modifier,
11546             circ_as_type,
11547             alert_message,
11548             pub_note,
11549             priv_note,
11550             opac_visible
11551         ) VALUES (
11552             NEW.id,
11553             item_data.definition,
11554             item_data.owning_lib,
11555             item_data.circ_lib,
11556             item_data.call_number,
11557             item_data.copy_number,
11558             item_data.status,
11559             item_data.location,
11560             item_data.circulate,
11561             item_data.deposit,
11562             item_data.deposit_amount,
11563             item_data.ref,
11564             item_data.holdable,
11565             item_data.price,
11566             item_data.barcode,
11567             item_data.circ_modifier,
11568             item_data.circ_as_type,
11569             item_data.alert_message,
11570             item_data.pub_note,
11571             item_data.priv_note,
11572             item_data.opac_visible
11573         );
11574     END LOOP;
11575
11576     RETURN NULL;
11577 END;
11578 $func$ LANGUAGE PLPGSQL;
11579
11580 CREATE OR REPLACE FUNCTION acq.create_acq_seq     ( sch TEXT, tbl TEXT ) RETURNS BOOL AS $creator$
11581 BEGIN
11582     EXECUTE $$
11583         CREATE SEQUENCE acq.$$ || sch || $$_$$ || tbl || $$_pkey_seq;
11584     $$;
11585         RETURN TRUE;
11586 END;
11587 $creator$ LANGUAGE 'plpgsql';
11588
11589 CREATE OR REPLACE FUNCTION acq.create_acq_history ( sch TEXT, tbl TEXT ) RETURNS BOOL AS $creator$
11590 BEGIN
11591     EXECUTE $$
11592         CREATE TABLE acq.$$ || sch || $$_$$ || tbl || $$_history (
11593             audit_id    BIGINT                          PRIMARY KEY,
11594             audit_time  TIMESTAMP WITH TIME ZONE        NOT NULL,
11595             audit_action        TEXT                            NOT NULL,
11596             LIKE $$ || sch || $$.$$ || tbl || $$
11597         );
11598     $$;
11599         RETURN TRUE;
11600 END;
11601 $creator$ LANGUAGE 'plpgsql';
11602
11603 CREATE OR REPLACE FUNCTION acq.create_acq_func    ( sch TEXT, tbl TEXT ) RETURNS BOOL AS $creator$
11604 BEGIN
11605     EXECUTE $$
11606         CREATE OR REPLACE FUNCTION acq.audit_$$ || sch || $$_$$ || tbl || $$_func ()
11607         RETURNS TRIGGER AS $func$
11608         BEGIN
11609             INSERT INTO acq.$$ || sch || $$_$$ || tbl || $$_history
11610                 SELECT  nextval('acq.$$ || sch || $$_$$ || tbl || $$_pkey_seq'),
11611                     now(),
11612                     SUBSTR(TG_OP,1,1),
11613                     OLD.*;
11614             RETURN NULL;
11615         END;
11616         $func$ LANGUAGE 'plpgsql';
11617     $$;
11618         RETURN TRUE;
11619 END;
11620 $creator$ LANGUAGE 'plpgsql';
11621
11622 CREATE OR REPLACE FUNCTION acq.create_acq_update_trigger ( sch TEXT, tbl TEXT ) RETURNS BOOL AS $creator$
11623 BEGIN
11624     EXECUTE $$
11625         CREATE TRIGGER audit_$$ || sch || $$_$$ || tbl || $$_update_trigger
11626             AFTER UPDATE OR DELETE ON $$ || sch || $$.$$ || tbl || $$ FOR EACH ROW
11627             EXECUTE PROCEDURE acq.audit_$$ || sch || $$_$$ || tbl || $$_func ();
11628     $$;
11629         RETURN TRUE;
11630 END;
11631 $creator$ LANGUAGE 'plpgsql';
11632
11633 CREATE OR REPLACE FUNCTION acq.create_acq_lifecycle     ( sch TEXT, tbl TEXT ) RETURNS BOOL AS $creator$
11634 BEGIN
11635     EXECUTE $$
11636         CREATE OR REPLACE VIEW acq.$$ || sch || $$_$$ || tbl || $$_lifecycle AS
11637             SELECT      -1, now() as audit_time, '-' as audit_action, *
11638               FROM      $$ || sch || $$.$$ || tbl || $$
11639                 UNION ALL
11640             SELECT      *
11641               FROM      acq.$$ || sch || $$_$$ || tbl || $$_history;
11642     $$;
11643         RETURN TRUE;
11644 END;
11645 $creator$ LANGUAGE 'plpgsql';
11646
11647 -- The main event
11648
11649 CREATE OR REPLACE FUNCTION acq.create_acq_auditor ( sch TEXT, tbl TEXT ) RETURNS BOOL AS $creator$
11650 BEGIN
11651     PERFORM acq.create_acq_seq(sch, tbl);
11652     PERFORM acq.create_acq_history(sch, tbl);
11653     PERFORM acq.create_acq_func(sch, tbl);
11654     PERFORM acq.create_acq_update_trigger(sch, tbl);
11655     PERFORM acq.create_acq_lifecycle(sch, tbl);
11656     RETURN TRUE;
11657 END;
11658 $creator$ LANGUAGE 'plpgsql';
11659
11660 ALTER TABLE acq.lineitem DROP COLUMN item_count;
11661
11662 CREATE OR REPLACE VIEW acq.fund_debit_total AS
11663     SELECT  fund.id AS fund,
11664             fund_debit.encumbrance AS encumbrance,
11665             SUM( COALESCE( fund_debit.amount, 0 ) ) AS amount
11666       FROM acq.fund AS fund
11667                         LEFT JOIN acq.fund_debit AS fund_debit
11668                                 ON ( fund.id = fund_debit.fund )
11669       GROUP BY 1,2;
11670
11671 CREATE TABLE acq.debit_attribution (
11672         id                     INT         NOT NULL PRIMARY KEY,
11673         fund_debit             INT         NOT NULL
11674                                            REFERENCES acq.fund_debit
11675                                            DEFERRABLE INITIALLY DEFERRED,
11676     debit_amount           NUMERIC     NOT NULL,
11677         funding_source_credit  INT         REFERENCES acq.funding_source_credit
11678                                            DEFERRABLE INITIALLY DEFERRED,
11679     credit_amount          NUMERIC
11680 );
11681
11682 CREATE INDEX acq_attribution_debit_idx
11683         ON acq.debit_attribution( fund_debit );
11684
11685 CREATE INDEX acq_attribution_credit_idx
11686         ON acq.debit_attribution( funding_source_credit );
11687
11688 CREATE OR REPLACE FUNCTION acq.attribute_debits() RETURNS VOID AS $$
11689 /*
11690 Function to attribute expenditures and encumbrances to funding source credits,
11691 and thereby to funding sources.
11692
11693 Read the debits in chonological order, attributing each one to one or
11694 more funding source credits.  Constraints:
11695
11696 1. Don't attribute more to a credit than the amount of the credit.
11697
11698 2. For a given fund, don't attribute more to a funding source than the
11699 source has allocated to that fund.
11700
11701 3. Attribute debits to credits with deadlines before attributing them to
11702 credits without deadlines.  Otherwise attribute to the earliest credits
11703 first, based on the deadline date when present, or on the effective date
11704 when there is no deadline.  Use funding_source_credit.id as a tie-breaker.
11705 This ordering is defined by an ORDER BY clause on the view
11706 acq.ordered_funding_source_credit.
11707
11708 Start by truncating the table acq.debit_attribution.  Then insert a row
11709 into that table for each attribution.  If a debit cannot be fully
11710 attributed, insert a row for the unattributable balance, with the 
11711 funding_source_credit and credit_amount columns NULL.
11712 */
11713 DECLARE
11714         curr_fund_source_bal RECORD;
11715         seqno                INT;     -- sequence num for credits applicable to a fund
11716         fund_credit          RECORD;  -- current row in temp t_fund_credit table
11717         fc                   RECORD;  -- used for loading t_fund_credit table
11718         sc                   RECORD;  -- used for loading t_fund_credit table
11719         --
11720         -- Used exclusively in the main loop:
11721         --
11722         deb                 RECORD;   -- current row from acq.fund_debit table
11723         curr_credit_bal     RECORD;   -- current row from temp t_credit table
11724         debit_balance       NUMERIC;  -- amount left to attribute for current debit
11725         conv_debit_balance  NUMERIC;  -- debit balance in currency of the fund
11726         attr_amount         NUMERIC;  -- amount being attributed, in currency of debit
11727         conv_attr_amount    NUMERIC;  -- amount being attributed, in currency of source
11728         conv_cred_balance   NUMERIC;  -- credit_balance in the currency of the fund
11729         conv_alloc_balance  NUMERIC;  -- allocated balance in the currency of the fund
11730         attrib_count        INT;      -- populates id of acq.debit_attribution
11731 BEGIN
11732         --
11733         -- Load a temporary table.  For each combination of fund and funding source,
11734         -- load an entry with the total amount allocated to that fund by that source.
11735         -- This sum may reflect transfers as well as original allocations.  We will
11736         -- reduce this balance whenever we attribute debits to it.
11737         --
11738         CREATE TEMP TABLE t_fund_source_bal
11739         ON COMMIT DROP AS
11740                 SELECT
11741                         fund AS fund,
11742                         funding_source AS source,
11743                         sum( amount ) AS balance
11744                 FROM
11745                         acq.fund_allocation
11746                 GROUP BY
11747                         fund,
11748                         funding_source
11749                 HAVING
11750                         sum( amount ) > 0;
11751         --
11752         CREATE INDEX t_fund_source_bal_idx
11753                 ON t_fund_source_bal( fund, source );
11754         -------------------------------------------------------------------------------
11755         --
11756         -- Load another temporary table.  For each fund, load zero or more
11757         -- funding source credits from which that fund can get money.
11758         --
11759         CREATE TEMP TABLE t_fund_credit (
11760                 fund        INT,
11761                 seq         INT,
11762                 credit      INT
11763         ) ON COMMIT DROP;
11764         --
11765         FOR fc IN
11766                 SELECT DISTINCT fund
11767                 FROM acq.fund_allocation
11768                 ORDER BY fund
11769         LOOP                  -- Loop over the funds
11770                 seqno := 1;
11771                 FOR sc IN
11772                         SELECT
11773                                 ofsc.id
11774                         FROM
11775                                 acq.ordered_funding_source_credit AS ofsc
11776                         WHERE
11777                                 ofsc.funding_source IN
11778                                 (
11779                                         SELECT funding_source
11780                                         FROM acq.fund_allocation
11781                                         WHERE fund = fc.fund
11782                                 )
11783                 ORDER BY
11784                     ofsc.sort_priority,
11785                     ofsc.sort_date,
11786                     ofsc.id
11787                 LOOP                        -- Add each credit to the list
11788                         INSERT INTO t_fund_credit (
11789                                 fund,
11790                                 seq,
11791                                 credit
11792                         ) VALUES (
11793                                 fc.fund,
11794                                 seqno,
11795                                 sc.id
11796                         );
11797                         --RAISE NOTICE 'Fund % credit %', fc.fund, sc.id;
11798                         seqno := seqno + 1;
11799                 END LOOP;     -- Loop over credits for a given fund
11800         END LOOP;         -- Loop over funds
11801         --
11802         CREATE INDEX t_fund_credit_idx
11803                 ON t_fund_credit( fund, seq );
11804         -------------------------------------------------------------------------------
11805         --
11806         -- Load yet another temporary table.  This one is a list of funding source
11807         -- credits, with their balances.  We shall reduce those balances as we
11808         -- attribute debits to them.
11809         --
11810         CREATE TEMP TABLE t_credit
11811         ON COMMIT DROP AS
11812         SELECT
11813             fsc.id AS credit,
11814             fsc.funding_source AS source,
11815             fsc.amount AS balance,
11816             fs.currency_type AS currency_type
11817         FROM
11818             acq.funding_source_credit AS fsc,
11819             acq.funding_source fs
11820         WHERE
11821             fsc.funding_source = fs.id
11822                         AND fsc.amount > 0;
11823         --
11824         CREATE INDEX t_credit_idx
11825                 ON t_credit( credit );
11826         --
11827         -------------------------------------------------------------------------------
11828         --
11829         -- Now that we have loaded the lookup tables: loop through the debits,
11830         -- attributing each one to one or more funding source credits.
11831         -- 
11832         truncate table acq.debit_attribution;
11833         --
11834         attrib_count := 0;
11835         FOR deb in
11836                 SELECT
11837                         fd.id,
11838                         fd.fund,
11839                         fd.amount,
11840                         f.currency_type,
11841                         fd.encumbrance
11842                 FROM
11843                         acq.fund_debit fd,
11844                         acq.fund f
11845                 WHERE
11846                         fd.fund = f.id
11847                 ORDER BY
11848                         fd.id
11849         LOOP
11850                 --RAISE NOTICE 'Debit %, fund %', deb.id, deb.fund;
11851                 --
11852                 debit_balance := deb.amount;
11853                 --
11854                 -- Loop over the funding source credits that are eligible
11855                 -- to pay for this debit
11856                 --
11857                 FOR fund_credit IN
11858                         SELECT
11859                                 credit
11860                         FROM
11861                                 t_fund_credit
11862                         WHERE
11863                                 fund = deb.fund
11864                         ORDER BY
11865                                 seq
11866                 LOOP
11867                         --RAISE NOTICE '   Examining credit %', fund_credit.credit;
11868                         --
11869                         -- Look up the balance for this credit.  If it's zero, then
11870                         -- it's not useful, so treat it as if you didn't find it.
11871                         -- (Actually there shouldn't be any zero balances in the table,
11872                         -- but we check just to make sure.)
11873                         --
11874                         SELECT *
11875                         INTO curr_credit_bal
11876                         FROM t_credit
11877                         WHERE
11878                                 credit = fund_credit.credit
11879                                 AND balance > 0;
11880                         --
11881                         IF curr_credit_bal IS NULL THEN
11882                                 --
11883                                 -- This credit is exhausted; try the next one.
11884                                 --
11885                                 CONTINUE;
11886                         END IF;
11887                         --
11888                         --
11889                         -- At this point we have an applicable credit with some money left.
11890                         -- Now see if the relevant funding_source has any money left.
11891                         --
11892                         -- Look up the balance of the allocation for this combination of
11893                         -- fund and source.  If you find such an entry, but it has a zero
11894                         -- balance, then it's not useful, so treat it as unfound.
11895                         -- (Actually there shouldn't be any zero balances in the table,
11896                         -- but we check just to make sure.)
11897                         --
11898                         SELECT *
11899                         INTO curr_fund_source_bal
11900                         FROM t_fund_source_bal
11901                         WHERE
11902                                 fund = deb.fund
11903                                 AND source = curr_credit_bal.source
11904                                 AND balance > 0;
11905                         --
11906                         IF curr_fund_source_bal IS NULL THEN
11907                                 --
11908                                 -- This fund/source doesn't exist or is already exhausted,
11909                                 -- so we can't use this credit.  Go on to the next one.
11910                                 --
11911                                 CONTINUE;
11912                         END IF;
11913                         --
11914                         -- Convert the available balances to the currency of the fund
11915                         --
11916                         conv_alloc_balance := curr_fund_source_bal.balance * acq.exchange_ratio(
11917                                 curr_credit_bal.currency_type, deb.currency_type );
11918                         conv_cred_balance := curr_credit_bal.balance * acq.exchange_ratio(
11919                                 curr_credit_bal.currency_type, deb.currency_type );
11920                         --
11921                         -- Determine how much we can attribute to this credit: the minimum
11922                         -- of the debit amount, the fund/source balance, and the
11923                         -- credit balance
11924                         --
11925                         --RAISE NOTICE '   deb bal %', debit_balance;
11926                         --RAISE NOTICE '      source % balance %', curr_credit_bal.source, conv_alloc_balance;
11927                         --RAISE NOTICE '      credit % balance %', curr_credit_bal.credit, conv_cred_balance;
11928                         --
11929                         conv_attr_amount := NULL;
11930                         attr_amount := debit_balance;
11931                         --
11932                         IF attr_amount > conv_alloc_balance THEN
11933                                 attr_amount := conv_alloc_balance;
11934                                 conv_attr_amount := curr_fund_source_bal.balance;
11935                         END IF;
11936                         IF attr_amount > conv_cred_balance THEN
11937                                 attr_amount := conv_cred_balance;
11938                                 conv_attr_amount := curr_credit_bal.balance;
11939                         END IF;
11940                         --
11941                         -- If we're attributing all of one of the balances, then that's how
11942                         -- much we will deduct from the balances, and we already captured
11943                         -- that amount above.  Otherwise we must convert the amount of the
11944                         -- attribution from the currency of the fund back to the currency of
11945                         -- the funding source.
11946                         --
11947                         IF conv_attr_amount IS NULL THEN
11948                                 conv_attr_amount := attr_amount * acq.exchange_ratio(
11949                                         deb.currency_type, curr_credit_bal.currency_type );
11950                         END IF;
11951                         --
11952                         -- Insert a row to record the attribution
11953                         --
11954                         attrib_count := attrib_count + 1;
11955                         INSERT INTO acq.debit_attribution (
11956                                 id,
11957                                 fund_debit,
11958                                 debit_amount,
11959                                 funding_source_credit,
11960                                 credit_amount
11961                         ) VALUES (
11962                                 attrib_count,
11963                                 deb.id,
11964                                 attr_amount,
11965                                 curr_credit_bal.credit,
11966                                 conv_attr_amount
11967                         );
11968                         --
11969                         -- Subtract the attributed amount from the various balances
11970                         --
11971                         debit_balance := debit_balance - attr_amount;
11972                         curr_fund_source_bal.balance := curr_fund_source_bal.balance - conv_attr_amount;
11973                         --
11974                         IF curr_fund_source_bal.balance <= 0 THEN
11975                                 --
11976                                 -- This allocation is exhausted.  Delete it so
11977                                 -- that we don't waste time looking at it again.
11978                                 --
11979                                 DELETE FROM t_fund_source_bal
11980                                 WHERE
11981                                         fund = curr_fund_source_bal.fund
11982                                         AND source = curr_fund_source_bal.source;
11983                         ELSE
11984                                 UPDATE t_fund_source_bal
11985                                 SET balance = balance - conv_attr_amount
11986                                 WHERE
11987                                         fund = curr_fund_source_bal.fund
11988                                         AND source = curr_fund_source_bal.source;
11989                         END IF;
11990                         --
11991                         IF curr_credit_bal.balance <= 0 THEN
11992                                 --
11993                                 -- This funding source credit is exhausted.  Delete it
11994                                 -- so that we don't waste time looking at it again.
11995                                 --
11996                                 --DELETE FROM t_credit
11997                                 --WHERE
11998                                 --      credit = curr_credit_bal.credit;
11999                                 --
12000                                 DELETE FROM t_fund_credit
12001                                 WHERE
12002                                         credit = curr_credit_bal.credit;
12003                         ELSE
12004                                 UPDATE t_credit
12005                                 SET balance = curr_credit_bal.balance
12006                                 WHERE
12007                                         credit = curr_credit_bal.credit;
12008                         END IF;
12009                         --
12010                         -- Are we done with this debit yet?
12011                         --
12012                         IF debit_balance <= 0 THEN
12013                                 EXIT;       -- We've fully attributed this debit; stop looking at credits.
12014                         END IF;
12015                 END LOOP;       -- End loop over credits
12016                 --
12017                 IF debit_balance <> 0 THEN
12018                         --
12019                         -- We weren't able to attribute this debit, or at least not
12020                         -- all of it.  Insert a row for the unattributed balance.
12021                         --
12022                         attrib_count := attrib_count + 1;
12023                         INSERT INTO acq.debit_attribution (
12024                                 id,
12025                                 fund_debit,
12026                                 debit_amount,
12027                                 funding_source_credit,
12028                                 credit_amount
12029                         ) VALUES (
12030                                 attrib_count,
12031                                 deb.id,
12032                                 debit_balance,
12033                                 NULL,
12034                                 NULL
12035                         );
12036                 END IF;
12037         END LOOP;   -- End of loop over debits
12038 END;
12039 $$ LANGUAGE 'plpgsql';
12040
12041 CREATE OR REPLACE FUNCTION extract_marc_field ( TEXT, BIGINT, TEXT, TEXT ) RETURNS TEXT AS $$
12042 DECLARE
12043     query TEXT;
12044     output TEXT;
12045 BEGIN
12046     query := $q$
12047         SELECT  regexp_replace(
12048                     oils_xpath_string(
12049                         $q$ || quote_literal($3) || $q$,
12050                         marc,
12051                         ' '
12052                     ),
12053                     $q$ || quote_literal($4) || $q$,
12054                     '',
12055                     'g')
12056           FROM  $q$ || $1 || $q$
12057           WHERE id = $q$ || $2;
12058
12059     EXECUTE query INTO output;
12060
12061     -- RAISE NOTICE 'query: %, output; %', query, output;
12062
12063     RETURN output;
12064 END;
12065 $$ LANGUAGE PLPGSQL IMMUTABLE;
12066
12067 CREATE OR REPLACE FUNCTION extract_marc_field ( TEXT, BIGINT, TEXT ) RETURNS TEXT AS $$
12068     SELECT extract_marc_field($1,$2,$3,'');
12069 $$ LANGUAGE SQL IMMUTABLE;
12070
12071 CREATE OR REPLACE FUNCTION asset.merge_record_assets( target_record BIGINT, source_record BIGINT ) RETURNS INT AS $func$
12072 DECLARE
12073     moved_objects INT := 0;
12074     source_cn     asset.call_number%ROWTYPE;
12075     target_cn     asset.call_number%ROWTYPE;
12076     metarec       metabib.metarecord%ROWTYPE;
12077     hold          action.hold_request%ROWTYPE;
12078     ser_rec       serial.record_entry%ROWTYPE;
12079     uri_count     INT := 0;
12080     counter       INT := 0;
12081     uri_datafield TEXT;
12082     uri_text      TEXT := '';
12083 BEGIN
12084
12085     -- move any 856 entries on records that have at least one MARC-mapped URI entry
12086     SELECT  INTO uri_count COUNT(*)
12087       FROM  asset.uri_call_number_map m
12088             JOIN asset.call_number cn ON (m.call_number = cn.id)
12089       WHERE cn.record = source_record;
12090
12091     IF uri_count > 0 THEN
12092
12093         SELECT  COUNT(*) INTO counter
12094           FROM  oils_xpath_table(
12095                     'id',
12096                     'marc',
12097                     'biblio.record_entry',
12098                     '//*[@tag="856"]',
12099                     'id=' || source_record
12100                 ) as t(i int,c text);
12101
12102         FOR i IN 1 .. counter LOOP
12103             SELECT  '<datafield xmlns="http://www.loc.gov/MARC21/slim"' ||
12104                         ' tag="856"' || 
12105                         ' ind1="' || FIRST(ind1) || '"'  || 
12106                         ' ind2="' || FIRST(ind2) || '">' || 
12107                         array_to_string(
12108                             array_accum(
12109                                 '<subfield code="' || subfield || '">' ||
12110                                 regexp_replace(
12111                                     regexp_replace(
12112                                         regexp_replace(data,'&','&amp;','g'),
12113                                         '>', '&gt;', 'g'
12114                                     ),
12115                                     '<', '&lt;', 'g'
12116                                 ) || '</subfield>'
12117                             ), ''
12118                         ) || '</datafield>' INTO uri_datafield
12119               FROM  oils_xpath_table(
12120                         'id',
12121                         'marc',
12122                         'biblio.record_entry',
12123                         '//*[@tag="856"][position()=' || i || ']/@ind1|' || 
12124                         '//*[@tag="856"][position()=' || i || ']/@ind2|' || 
12125                         '//*[@tag="856"][position()=' || i || ']/*/@code|' ||
12126                         '//*[@tag="856"][position()=' || i || ']/*[@code]',
12127                         'id=' || source_record
12128                     ) as t(id int,ind1 text, ind2 text,subfield text,data text);
12129
12130             uri_text := uri_text || uri_datafield;
12131         END LOOP;
12132
12133         IF uri_text <> '' THEN
12134             UPDATE  biblio.record_entry
12135               SET   marc = regexp_replace(marc,'(</[^>]*record>)', uri_text || E'\\1')
12136               WHERE id = target_record;
12137         END IF;
12138
12139     END IF;
12140
12141     -- Find and move metarecords to the target record
12142     SELECT  INTO metarec *
12143       FROM  metabib.metarecord
12144       WHERE master_record = source_record;
12145
12146     IF FOUND THEN
12147         UPDATE  metabib.metarecord
12148           SET   master_record = target_record,
12149             mods = NULL
12150           WHERE id = metarec.id;
12151
12152         moved_objects := moved_objects + 1;
12153     END IF;
12154
12155     -- Find call numbers attached to the source ...
12156     FOR source_cn IN SELECT * FROM asset.call_number WHERE record = source_record LOOP
12157
12158         SELECT  INTO target_cn *
12159           FROM  asset.call_number
12160           WHERE label = source_cn.label
12161             AND owning_lib = source_cn.owning_lib
12162             AND record = target_record;
12163
12164         -- ... and if there's a conflicting one on the target ...
12165         IF FOUND THEN
12166
12167             -- ... move the copies to that, and ...
12168             UPDATE  asset.copy
12169               SET   call_number = target_cn.id
12170               WHERE call_number = source_cn.id;
12171
12172             -- ... move V holds to the move-target call number
12173             FOR hold IN SELECT * FROM action.hold_request WHERE target = source_cn.id AND hold_type = 'V' LOOP
12174
12175                 UPDATE  action.hold_request
12176                   SET   target = target_cn.id
12177                   WHERE id = hold.id;
12178
12179                 moved_objects := moved_objects + 1;
12180             END LOOP;
12181
12182         -- ... if not ...
12183         ELSE
12184             -- ... just move the call number to the target record
12185             UPDATE  asset.call_number
12186               SET   record = target_record
12187               WHERE id = source_cn.id;
12188         END IF;
12189
12190         moved_objects := moved_objects + 1;
12191     END LOOP;
12192
12193     -- Find T holds targeting the source record ...
12194     FOR hold IN SELECT * FROM action.hold_request WHERE target = source_record AND hold_type = 'T' LOOP
12195
12196         -- ... and move them to the target record
12197         UPDATE  action.hold_request
12198           SET   target = target_record
12199           WHERE id = hold.id;
12200
12201         moved_objects := moved_objects + 1;
12202     END LOOP;
12203
12204     -- Find serial records targeting the source record ...
12205     FOR ser_rec IN SELECT * FROM serial.record_entry WHERE record = source_record LOOP
12206         -- ... and move them to the target record
12207         UPDATE  serial.record_entry
12208           SET   record = target_record
12209           WHERE id = ser_rec.id;
12210
12211         moved_objects := moved_objects + 1;
12212     END LOOP;
12213
12214     -- Finally, "delete" the source record
12215     DELETE FROM biblio.record_entry WHERE id = source_record;
12216
12217     -- That's all, folks!
12218     RETURN moved_objects;
12219 END;
12220 $func$ LANGUAGE plpgsql;
12221
12222 CREATE OR REPLACE FUNCTION acq.transfer_fund(
12223         old_fund   IN INT,
12224         old_amount IN NUMERIC,     -- in currency of old fund
12225         new_fund   IN INT,
12226         new_amount IN NUMERIC,     -- in currency of new fund
12227         user_id    IN INT,
12228         xfer_note  IN TEXT         -- to be recorded in acq.fund_transfer
12229         -- ,funding_source_in IN INT  -- if user wants to specify a funding source (see notes)
12230 ) RETURNS VOID AS $$
12231 /* -------------------------------------------------------------------------------
12232
12233 Function to transfer money from one fund to another.
12234
12235 A transfer is represented as a pair of entries in acq.fund_allocation, with a
12236 negative amount for the old (losing) fund and a positive amount for the new
12237 (gaining) fund.  In some cases there may be more than one such pair of entries
12238 in order to pull the money from different funding sources, or more specifically
12239 from different funding source credits.  For each such pair there is also an
12240 entry in acq.fund_transfer.
12241
12242 Since funding_source is a non-nullable column in acq.fund_allocation, we must
12243 choose a funding source for the transferred money to come from.  This choice
12244 must meet two constraints, so far as possible:
12245
12246 1. The amount transferred from a given funding source must not exceed the
12247 amount allocated to the old fund by the funding source.  To that end we
12248 compare the amount being transferred to the amount allocated.
12249
12250 2. We shouldn't transfer money that has already been spent or encumbered, as
12251 defined by the funding attribution process.  We attribute expenses to the
12252 oldest funding source credits first.  In order to avoid transferring that
12253 attributed money, we reverse the priority, transferring from the newest funding
12254 source credits first.  There can be no guarantee that this approach will
12255 avoid overcommitting a fund, but no other approach can do any better.
12256
12257 In this context the age of a funding source credit is defined by the
12258 deadline_date for credits with deadline_dates, and by the effective_date for
12259 credits without deadline_dates, with the proviso that credits with deadline_dates
12260 are all considered "older" than those without.
12261
12262 ----------
12263
12264 In the signature for this function, there is one last parameter commented out,
12265 named "funding_source_in".  Correspondingly, the WHERE clause for the query
12266 driving the main loop has an OR clause commented out, which references the
12267 funding_source_in parameter.
12268
12269 If these lines are uncommented, this function will allow the user optionally to
12270 restrict a fund transfer to a specified funding source.  If the source
12271 parameter is left NULL, then there will be no such restriction.
12272
12273 ------------------------------------------------------------------------------- */ 
12274 DECLARE
12275         same_currency      BOOLEAN;
12276         currency_ratio     NUMERIC;
12277         old_fund_currency  TEXT;
12278         old_remaining      NUMERIC;  -- in currency of old fund
12279         new_fund_currency  TEXT;
12280         new_fund_active    BOOLEAN;
12281         new_remaining      NUMERIC;  -- in currency of new fund
12282         curr_old_amt       NUMERIC;  -- in currency of old fund
12283         curr_new_amt       NUMERIC;  -- in currency of new fund
12284         source_addition    NUMERIC;  -- in currency of funding source
12285         source_deduction   NUMERIC;  -- in currency of funding source
12286         orig_allocated_amt NUMERIC;  -- in currency of funding source
12287         allocated_amt      NUMERIC;  -- in currency of fund
12288         source             RECORD;
12289 BEGIN
12290         --
12291         -- Sanity checks
12292         --
12293         IF old_fund IS NULL THEN
12294                 RAISE EXCEPTION 'acq.transfer_fund: old fund id is NULL';
12295         END IF;
12296         --
12297         IF old_amount IS NULL THEN
12298                 RAISE EXCEPTION 'acq.transfer_fund: amount to transfer is NULL';
12299         END IF;
12300         --
12301         -- The new fund and its amount must be both NULL or both not NULL.
12302         --
12303         IF new_fund IS NOT NULL AND new_amount IS NULL THEN
12304                 RAISE EXCEPTION 'acq.transfer_fund: amount to transfer to receiving fund is NULL';
12305         END IF;
12306         --
12307         IF new_fund IS NULL AND new_amount IS NOT NULL THEN
12308                 RAISE EXCEPTION 'acq.transfer_fund: receiving fund is NULL, its amount is not NULL';
12309         END IF;
12310         --
12311         IF user_id IS NULL THEN
12312                 RAISE EXCEPTION 'acq.transfer_fund: user id is NULL';
12313         END IF;
12314         --
12315         -- Initialize the amounts to be transferred, each denominated
12316         -- in the currency of its respective fund.  They will be
12317         -- reduced on each iteration of the loop.
12318         --
12319         old_remaining := old_amount;
12320         new_remaining := new_amount;
12321         --
12322         -- RAISE NOTICE 'Transferring % in fund % to % in fund %',
12323         --      old_amount, old_fund, new_amount, new_fund;
12324         --
12325         -- Get the currency types of the old and new funds.
12326         --
12327         SELECT
12328                 currency_type
12329         INTO
12330                 old_fund_currency
12331         FROM
12332                 acq.fund
12333         WHERE
12334                 id = old_fund;
12335         --
12336         IF old_fund_currency IS NULL THEN
12337                 RAISE EXCEPTION 'acq.transfer_fund: old fund id % is not defined', old_fund;
12338         END IF;
12339         --
12340         IF new_fund IS NOT NULL THEN
12341                 SELECT
12342                         currency_type,
12343                         active
12344                 INTO
12345                         new_fund_currency,
12346                         new_fund_active
12347                 FROM
12348                         acq.fund
12349                 WHERE
12350                         id = new_fund;
12351                 --
12352                 IF new_fund_currency IS NULL THEN
12353                         RAISE EXCEPTION 'acq.transfer_fund: new fund id % is not defined', new_fund;
12354                 ELSIF NOT new_fund_active THEN
12355                         --
12356                         -- No point in putting money into a fund from whence you can't spend it
12357                         --
12358                         RAISE EXCEPTION 'acq.transfer_fund: new fund id % is inactive', new_fund;
12359                 END IF;
12360                 --
12361                 IF new_amount = old_amount THEN
12362                         same_currency := true;
12363                         currency_ratio := 1;
12364                 ELSE
12365                         --
12366                         -- We'll have to translate currency between funds.  We presume that
12367                         -- the calling code has already applied an appropriate exchange rate,
12368                         -- so we'll apply the same conversion to each sub-transfer.
12369                         --
12370                         same_currency := false;
12371                         currency_ratio := new_amount / old_amount;
12372                 END IF;
12373         END IF;
12374         --
12375         -- Identify the funding source(s) from which we want to transfer the money.
12376         -- The principle is that we want to transfer the newest money first, because
12377         -- we spend the oldest money first.  The priority for spending is defined
12378         -- by a sort of the view acq.ordered_funding_source_credit.
12379         --
12380         FOR source in
12381                 SELECT
12382                         ofsc.id,
12383                         ofsc.funding_source,
12384                         ofsc.amount,
12385                         ofsc.amount * acq.exchange_ratio( fs.currency_type, old_fund_currency )
12386                                 AS converted_amt,
12387                         fs.currency_type
12388                 FROM
12389                         acq.ordered_funding_source_credit AS ofsc,
12390                         acq.funding_source fs
12391                 WHERE
12392                         ofsc.funding_source = fs.id
12393                         and ofsc.funding_source IN
12394                         (
12395                                 SELECT funding_source
12396                                 FROM acq.fund_allocation
12397                                 WHERE fund = old_fund
12398                         )
12399                         -- and
12400                         -- (
12401                         --      ofsc.funding_source = funding_source_in
12402                         --      OR funding_source_in IS NULL
12403                         -- )
12404                 ORDER BY
12405                         ofsc.sort_priority desc,
12406                         ofsc.sort_date desc,
12407                         ofsc.id desc
12408         LOOP
12409                 --
12410                 -- Determine how much money the old fund got from this funding source,
12411                 -- denominated in the currency types of the source and of the fund.
12412                 -- This result may reflect transfers from previous iterations.
12413                 --
12414                 SELECT
12415                         COALESCE( sum( amount ), 0 ),
12416                         COALESCE( sum( amount )
12417                                 * acq.exchange_ratio( source.currency_type, old_fund_currency ), 0 )
12418                 INTO
12419                         orig_allocated_amt,     -- in currency of the source
12420                         allocated_amt           -- in currency of the old fund
12421                 FROM
12422                         acq.fund_allocation
12423                 WHERE
12424                         fund = old_fund
12425                         and funding_source = source.funding_source;
12426                 --      
12427                 -- Determine how much to transfer from this credit, in the currency
12428                 -- of the fund.   Begin with the amount remaining to be attributed:
12429                 --
12430                 curr_old_amt := old_remaining;
12431                 --
12432                 -- Can't attribute more than was allocated from the fund:
12433                 --
12434                 IF curr_old_amt > allocated_amt THEN
12435                         curr_old_amt := allocated_amt;
12436                 END IF;
12437                 --
12438                 -- Can't attribute more than the amount of the current credit:
12439                 --
12440                 IF curr_old_amt > source.converted_amt THEN
12441                         curr_old_amt := source.converted_amt;
12442                 END IF;
12443                 --
12444                 curr_old_amt := trunc( curr_old_amt, 2 );
12445                 --
12446                 old_remaining := old_remaining - curr_old_amt;
12447                 --
12448                 -- Determine the amount to be deducted, if any,
12449                 -- from the old allocation.
12450                 --
12451                 IF old_remaining > 0 THEN
12452                         --
12453                         -- In this case we're using the whole allocation, so use that
12454                         -- amount directly instead of applying a currency translation
12455                         -- and thereby inviting round-off errors.
12456                         --
12457                         source_deduction := - orig_allocated_amt;
12458                 ELSE 
12459                         source_deduction := trunc(
12460                                 ( - curr_old_amt ) *
12461                                         acq.exchange_ratio( old_fund_currency, source.currency_type ),
12462                                 2 );
12463                 END IF;
12464                 --
12465                 IF source_deduction <> 0 THEN
12466                         --
12467                         -- Insert negative allocation for old fund in fund_allocation,
12468                         -- converted into the currency of the funding source
12469                         --
12470                         INSERT INTO acq.fund_allocation (
12471                                 funding_source,
12472                                 fund,
12473                                 amount,
12474                                 allocator,
12475                                 note
12476                         ) VALUES (
12477                                 source.funding_source,
12478                                 old_fund,
12479                                 source_deduction,
12480                                 user_id,
12481                                 'Transfer to fund ' || new_fund
12482                         );
12483                 END IF;
12484                 --
12485                 IF new_fund IS NOT NULL THEN
12486                         --
12487                         -- Determine how much to add to the new fund, in
12488                         -- its currency, and how much remains to be added:
12489                         --
12490                         IF same_currency THEN
12491                                 curr_new_amt := curr_old_amt;
12492                         ELSE
12493                                 IF old_remaining = 0 THEN
12494                                         --
12495                                         -- This is the last iteration, so nothing should be left
12496                                         --
12497                                         curr_new_amt := new_remaining;
12498                                         new_remaining := 0;
12499                                 ELSE
12500                                         curr_new_amt := trunc( curr_old_amt * currency_ratio, 2 );
12501                                         new_remaining := new_remaining - curr_new_amt;
12502                                 END IF;
12503                         END IF;
12504                         --
12505                         -- Determine how much to add, if any,
12506                         -- to the new fund's allocation.
12507                         --
12508                         IF old_remaining > 0 THEN
12509                                 --
12510                                 -- In this case we're using the whole allocation, so use that amount
12511                                 -- amount directly instead of applying a currency translation and
12512                                 -- thereby inviting round-off errors.
12513                                 --
12514                                 source_addition := orig_allocated_amt;
12515                         ELSIF source.currency_type = old_fund_currency THEN
12516                                 --
12517                                 -- In this case we don't need a round trip currency translation,
12518                                 -- thereby inviting round-off errors:
12519                                 --
12520                                 source_addition := curr_old_amt;
12521                         ELSE 
12522                                 source_addition := trunc(
12523                                         curr_new_amt *
12524                                                 acq.exchange_ratio( new_fund_currency, source.currency_type ),
12525                                         2 );
12526                         END IF;
12527                         --
12528                         IF source_addition <> 0 THEN
12529                                 --
12530                                 -- Insert positive allocation for new fund in fund_allocation,
12531                                 -- converted to the currency of the founding source
12532                                 --
12533                                 INSERT INTO acq.fund_allocation (
12534                                         funding_source,
12535                                         fund,
12536                                         amount,
12537                                         allocator,
12538                                         note
12539                                 ) VALUES (
12540                                         source.funding_source,
12541                                         new_fund,
12542                                         source_addition,
12543                                         user_id,
12544                                         'Transfer from fund ' || old_fund
12545                                 );
12546                         END IF;
12547                 END IF;
12548                 --
12549                 IF trunc( curr_old_amt, 2 ) <> 0
12550                 OR trunc( curr_new_amt, 2 ) <> 0 THEN
12551                         --
12552                         -- Insert row in fund_transfer, using amounts in the currency of the funds
12553                         --
12554                         INSERT INTO acq.fund_transfer (
12555                                 src_fund,
12556                                 src_amount,
12557                                 dest_fund,
12558                                 dest_amount,
12559                                 transfer_user,
12560                                 note,
12561                                 funding_source_credit
12562                         ) VALUES (
12563                                 old_fund,
12564                                 trunc( curr_old_amt, 2 ),
12565                                 new_fund,
12566                                 trunc( curr_new_amt, 2 ),
12567                                 user_id,
12568                                 xfer_note,
12569                                 source.id
12570                         );
12571                 END IF;
12572                 --
12573                 if old_remaining <= 0 THEN
12574                         EXIT;                   -- Nothing more to be transferred
12575                 END IF;
12576         END LOOP;
12577 END;
12578 $$ LANGUAGE plpgsql;
12579
12580 CREATE OR REPLACE FUNCTION acq.propagate_funds_by_org_unit(
12581         old_year INTEGER,
12582         user_id INTEGER,
12583         org_unit_id INTEGER
12584 ) RETURNS VOID AS $$
12585 DECLARE
12586 --
12587 new_id      INT;
12588 old_fund    RECORD;
12589 org_found   BOOLEAN;
12590 --
12591 BEGIN
12592         --
12593         -- Sanity checks
12594         --
12595         IF old_year IS NULL THEN
12596                 RAISE EXCEPTION 'Input year argument is NULL';
12597         ELSIF old_year NOT BETWEEN 2008 and 2200 THEN
12598                 RAISE EXCEPTION 'Input year is out of range';
12599         END IF;
12600         --
12601         IF user_id IS NULL THEN
12602                 RAISE EXCEPTION 'Input user id argument is NULL';
12603         END IF;
12604         --
12605         IF org_unit_id IS NULL THEN
12606                 RAISE EXCEPTION 'Org unit id argument is NULL';
12607         ELSE
12608                 SELECT TRUE INTO org_found
12609                 FROM actor.org_unit
12610                 WHERE id = org_unit_id;
12611                 --
12612                 IF org_found IS NULL THEN
12613                         RAISE EXCEPTION 'Org unit id is invalid';
12614                 END IF;
12615         END IF;
12616         --
12617         -- Loop over the applicable funds
12618         --
12619         FOR old_fund in SELECT * FROM acq.fund
12620         WHERE
12621                 year = old_year
12622                 AND propagate
12623                 AND org = org_unit_id
12624         LOOP
12625                 BEGIN
12626                         INSERT INTO acq.fund (
12627                                 org,
12628                                 name,
12629                                 year,
12630                                 currency_type,
12631                                 code,
12632                                 rollover,
12633                                 propagate,
12634                                 balance_warning_percent,
12635                                 balance_stop_percent
12636                         ) VALUES (
12637                                 old_fund.org,
12638                                 old_fund.name,
12639                                 old_year + 1,
12640                                 old_fund.currency_type,
12641                                 old_fund.code,
12642                                 old_fund.rollover,
12643                                 true,
12644                                 old_fund.balance_warning_percent,
12645                                 old_fund.balance_stop_percent
12646                         )
12647                         RETURNING id INTO new_id;
12648                 EXCEPTION
12649                         WHEN unique_violation THEN
12650                                 --RAISE NOTICE 'Fund % already propagated', old_fund.id;
12651                                 CONTINUE;
12652                 END;
12653                 --RAISE NOTICE 'Propagating fund % to fund %',
12654                 --      old_fund.code, new_id;
12655         END LOOP;
12656 END;
12657 $$ LANGUAGE plpgsql;
12658
12659 CREATE OR REPLACE FUNCTION acq.propagate_funds_by_org_tree(
12660         old_year INTEGER,
12661         user_id INTEGER,
12662         org_unit_id INTEGER
12663 ) RETURNS VOID AS $$
12664 DECLARE
12665 --
12666 new_id      INT;
12667 old_fund    RECORD;
12668 org_found   BOOLEAN;
12669 --
12670 BEGIN
12671         --
12672         -- Sanity checks
12673         --
12674         IF old_year IS NULL THEN
12675                 RAISE EXCEPTION 'Input year argument is NULL';
12676         ELSIF old_year NOT BETWEEN 2008 and 2200 THEN
12677                 RAISE EXCEPTION 'Input year is out of range';
12678         END IF;
12679         --
12680         IF user_id IS NULL THEN
12681                 RAISE EXCEPTION 'Input user id argument is NULL';
12682         END IF;
12683         --
12684         IF org_unit_id IS NULL THEN
12685                 RAISE EXCEPTION 'Org unit id argument is NULL';
12686         ELSE
12687                 SELECT TRUE INTO org_found
12688                 FROM actor.org_unit
12689                 WHERE id = org_unit_id;
12690                 --
12691                 IF org_found IS NULL THEN
12692                         RAISE EXCEPTION 'Org unit id is invalid';
12693                 END IF;
12694         END IF;
12695         --
12696         -- Loop over the applicable funds
12697         --
12698         FOR old_fund in SELECT * FROM acq.fund
12699         WHERE
12700                 year = old_year
12701                 AND propagate
12702                 AND org in (
12703                         SELECT id FROM actor.org_unit_descendants( org_unit_id )
12704                 )
12705         LOOP
12706                 BEGIN
12707                         INSERT INTO acq.fund (
12708                                 org,
12709                                 name,
12710                                 year,
12711                                 currency_type,
12712                                 code,
12713                                 rollover,
12714                                 propagate,
12715                                 balance_warning_percent,
12716                                 balance_stop_percent
12717                         ) VALUES (
12718                                 old_fund.org,
12719                                 old_fund.name,
12720                                 old_year + 1,
12721                                 old_fund.currency_type,
12722                                 old_fund.code,
12723                                 old_fund.rollover,
12724                                 true,
12725                                 old_fund.balance_warning_percent,
12726                                 old_fund.balance_stop_percent
12727                         )
12728                         RETURNING id INTO new_id;
12729                 EXCEPTION
12730                         WHEN unique_violation THEN
12731                                 --RAISE NOTICE 'Fund % already propagated', old_fund.id;
12732                                 CONTINUE;
12733                 END;
12734                 --RAISE NOTICE 'Propagating fund % to fund %',
12735                 --      old_fund.code, new_id;
12736         END LOOP;
12737 END;
12738 $$ LANGUAGE plpgsql;
12739
12740 CREATE OR REPLACE FUNCTION acq.rollover_funds_by_org_unit(
12741         old_year INTEGER,
12742         user_id INTEGER,
12743         org_unit_id INTEGER
12744 ) RETURNS VOID AS $$
12745 DECLARE
12746 --
12747 new_fund    INT;
12748 new_year    INT := old_year + 1;
12749 org_found   BOOL;
12750 xfer_amount NUMERIC;
12751 roll_fund   RECORD;
12752 deb         RECORD;
12753 detail      RECORD;
12754 --
12755 BEGIN
12756         --
12757         -- Sanity checks
12758         --
12759         IF old_year IS NULL THEN
12760                 RAISE EXCEPTION 'Input year argument is NULL';
12761     ELSIF old_year NOT BETWEEN 2008 and 2200 THEN
12762         RAISE EXCEPTION 'Input year is out of range';
12763         END IF;
12764         --
12765         IF user_id IS NULL THEN
12766                 RAISE EXCEPTION 'Input user id argument is NULL';
12767         END IF;
12768         --
12769         IF org_unit_id IS NULL THEN
12770                 RAISE EXCEPTION 'Org unit id argument is NULL';
12771         ELSE
12772                 --
12773                 -- Validate the org unit
12774                 --
12775                 SELECT TRUE
12776                 INTO org_found
12777                 FROM actor.org_unit
12778                 WHERE id = org_unit_id;
12779                 --
12780                 IF org_found IS NULL THEN
12781                         RAISE EXCEPTION 'Org unit id % is invalid', org_unit_id;
12782                 END IF;
12783         END IF;
12784         --
12785         -- Loop over the propagable funds to identify the details
12786         -- from the old fund plus the id of the new one, if it exists.
12787         --
12788         FOR roll_fund in
12789         SELECT
12790             oldf.id AS old_fund,
12791             oldf.org,
12792             oldf.name,
12793             oldf.currency_type,
12794             oldf.code,
12795                 oldf.rollover,
12796             newf.id AS new_fund_id
12797         FROM
12798         acq.fund AS oldf
12799         LEFT JOIN acq.fund AS newf
12800                 ON ( oldf.code = newf.code )
12801         WHERE
12802                     oldf.org = org_unit_id
12803                 and oldf.year = old_year
12804                 and oldf.propagate
12805         and newf.year = new_year
12806         LOOP
12807                 --RAISE NOTICE 'Processing fund %', roll_fund.old_fund;
12808                 --
12809                 IF roll_fund.new_fund_id IS NULL THEN
12810                         --
12811                         -- The old fund hasn't been propagated yet.  Propagate it now.
12812                         --
12813                         INSERT INTO acq.fund (
12814                                 org,
12815                                 name,
12816                                 year,
12817                                 currency_type,
12818                                 code,
12819                                 rollover,
12820                                 propagate,
12821                                 balance_warning_percent,
12822                                 balance_stop_percent
12823                         ) VALUES (
12824                                 roll_fund.org,
12825                                 roll_fund.name,
12826                                 new_year,
12827                                 roll_fund.currency_type,
12828                                 roll_fund.code,
12829                                 true,
12830                                 true,
12831                                 roll_fund.balance_warning_percent,
12832                                 roll_fund.balance_stop_percent
12833                         )
12834                         RETURNING id INTO new_fund;
12835                 ELSE
12836                         new_fund = roll_fund.new_fund_id;
12837                 END IF;
12838                 --
12839                 -- Determine the amount to transfer
12840                 --
12841                 SELECT amount
12842                 INTO xfer_amount
12843                 FROM acq.fund_spent_balance
12844                 WHERE fund = roll_fund.old_fund;
12845                 --
12846                 IF xfer_amount <> 0 THEN
12847                         IF roll_fund.rollover THEN
12848                                 --
12849                                 -- Transfer balance from old fund to new
12850                                 --
12851                                 --RAISE NOTICE 'Transferring % from fund % to %', xfer_amount, roll_fund.old_fund, new_fund;
12852                                 --
12853                                 PERFORM acq.transfer_fund(
12854                                         roll_fund.old_fund,
12855                                         xfer_amount,
12856                                         new_fund,
12857                                         xfer_amount,
12858                                         user_id,
12859                                         'Rollover'
12860                                 );
12861                         ELSE
12862                                 --
12863                                 -- Transfer balance from old fund to the void
12864                                 --
12865                                 -- RAISE NOTICE 'Transferring % from fund % to the void', xfer_amount, roll_fund.old_fund;
12866                                 --
12867                                 PERFORM acq.transfer_fund(
12868                                         roll_fund.old_fund,
12869                                         xfer_amount,
12870                                         NULL,
12871                                         NULL,
12872                                         user_id,
12873                                         'Rollover'
12874                                 );
12875                         END IF;
12876                 END IF;
12877                 --
12878                 IF roll_fund.rollover THEN
12879                         --
12880                         -- Move any lineitems from the old fund to the new one
12881                         -- where the associated debit is an encumbrance.
12882                         --
12883                         -- Any other tables tying expenditure details to funds should
12884                         -- receive similar treatment.  At this writing there are none.
12885                         --
12886                         UPDATE acq.lineitem_detail
12887                         SET fund = new_fund
12888                         WHERE
12889                         fund = roll_fund.old_fund -- this condition may be redundant
12890                         AND fund_debit in
12891                         (
12892                                 SELECT id
12893                                 FROM acq.fund_debit
12894                                 WHERE
12895                                 fund = roll_fund.old_fund
12896                                 AND encumbrance
12897                         );
12898                         --
12899                         -- Move encumbrance debits from the old fund to the new fund
12900                         --
12901                         UPDATE acq.fund_debit
12902                         SET fund = new_fund
12903                         wHERE
12904                                 fund = roll_fund.old_fund
12905                                 AND encumbrance;
12906                 END IF;
12907                 --
12908                 -- Mark old fund as inactive, now that we've closed it
12909                 --
12910                 UPDATE acq.fund
12911                 SET active = FALSE
12912                 WHERE id = roll_fund.old_fund;
12913         END LOOP;
12914 END;
12915 $$ LANGUAGE plpgsql;
12916
12917 CREATE OR REPLACE FUNCTION acq.rollover_funds_by_org_tree(
12918         old_year INTEGER,
12919         user_id INTEGER,
12920         org_unit_id INTEGER
12921 ) RETURNS VOID AS $$
12922 DECLARE
12923 --
12924 new_fund    INT;
12925 new_year    INT := old_year + 1;
12926 org_found   BOOL;
12927 xfer_amount NUMERIC;
12928 roll_fund   RECORD;
12929 deb         RECORD;
12930 detail      RECORD;
12931 --
12932 BEGIN
12933         --
12934         -- Sanity checks
12935         --
12936         IF old_year IS NULL THEN
12937                 RAISE EXCEPTION 'Input year argument is NULL';
12938     ELSIF old_year NOT BETWEEN 2008 and 2200 THEN
12939         RAISE EXCEPTION 'Input year is out of range';
12940         END IF;
12941         --
12942         IF user_id IS NULL THEN
12943                 RAISE EXCEPTION 'Input user id argument is NULL';
12944         END IF;
12945         --
12946         IF org_unit_id IS NULL THEN
12947                 RAISE EXCEPTION 'Org unit id argument is NULL';
12948         ELSE
12949                 --
12950                 -- Validate the org unit
12951                 --
12952                 SELECT TRUE
12953                 INTO org_found
12954                 FROM actor.org_unit
12955                 WHERE id = org_unit_id;
12956                 --
12957                 IF org_found IS NULL THEN
12958                         RAISE EXCEPTION 'Org unit id % is invalid', org_unit_id;
12959                 END IF;
12960         END IF;
12961         --
12962         -- Loop over the propagable funds to identify the details
12963         -- from the old fund plus the id of the new one, if it exists.
12964         --
12965         FOR roll_fund in
12966         SELECT
12967             oldf.id AS old_fund,
12968             oldf.org,
12969             oldf.name,
12970             oldf.currency_type,
12971             oldf.code,
12972                 oldf.rollover,
12973             newf.id AS new_fund_id
12974         FROM
12975         acq.fund AS oldf
12976         LEFT JOIN acq.fund AS newf
12977                 ON ( oldf.code = newf.code )
12978         WHERE
12979                     oldf.year = old_year
12980                 AND oldf.propagate
12981         AND newf.year = new_year
12982                 AND oldf.org in (
12983                         SELECT id FROM actor.org_unit_descendants( org_unit_id )
12984                 )
12985         LOOP
12986                 --RAISE NOTICE 'Processing fund %', roll_fund.old_fund;
12987                 --
12988                 IF roll_fund.new_fund_id IS NULL THEN
12989                         --
12990                         -- The old fund hasn't been propagated yet.  Propagate it now.
12991                         --
12992                         INSERT INTO acq.fund (
12993                                 org,
12994                                 name,
12995                                 year,
12996                                 currency_type,
12997                                 code,
12998                                 rollover,
12999                                 propagate,
13000                                 balance_warning_percent,
13001                                 balance_stop_percent
13002                         ) VALUES (
13003                                 roll_fund.org,
13004                                 roll_fund.name,
13005                                 new_year,
13006                                 roll_fund.currency_type,
13007                                 roll_fund.code,
13008                                 true,
13009                                 true,
13010                                 roll_fund.balance_warning_percent,
13011                                 roll_fund.balance_stop_percent
13012                         )
13013                         RETURNING id INTO new_fund;
13014                 ELSE
13015                         new_fund = roll_fund.new_fund_id;
13016                 END IF;
13017                 --
13018                 -- Determine the amount to transfer
13019                 --
13020                 SELECT amount
13021                 INTO xfer_amount
13022                 FROM acq.fund_spent_balance
13023                 WHERE fund = roll_fund.old_fund;
13024                 --
13025                 IF xfer_amount <> 0 THEN
13026                         IF roll_fund.rollover THEN
13027                                 --
13028                                 -- Transfer balance from old fund to new
13029                                 --
13030                                 --RAISE NOTICE 'Transferring % from fund % to %', xfer_amount, roll_fund.old_fund, new_fund;
13031                                 --
13032                                 PERFORM acq.transfer_fund(
13033                                         roll_fund.old_fund,
13034                                         xfer_amount,
13035                                         new_fund,
13036                                         xfer_amount,
13037                                         user_id,
13038                                         'Rollover'
13039                                 );
13040                         ELSE
13041                                 --
13042                                 -- Transfer balance from old fund to the void
13043                                 --
13044                                 -- RAISE NOTICE 'Transferring % from fund % to the void', xfer_amount, roll_fund.old_fund;
13045                                 --
13046                                 PERFORM acq.transfer_fund(
13047                                         roll_fund.old_fund,
13048                                         xfer_amount,
13049                                         NULL,
13050                                         NULL,
13051                                         user_id,
13052                                         'Rollover'
13053                                 );
13054                         END IF;
13055                 END IF;
13056                 --
13057                 IF roll_fund.rollover THEN
13058                         --
13059                         -- Move any lineitems from the old fund to the new one
13060                         -- where the associated debit is an encumbrance.
13061                         --
13062                         -- Any other tables tying expenditure details to funds should
13063                         -- receive similar treatment.  At this writing there are none.
13064                         --
13065                         UPDATE acq.lineitem_detail
13066                         SET fund = new_fund
13067                         WHERE
13068                         fund = roll_fund.old_fund -- this condition may be redundant
13069                         AND fund_debit in
13070                         (
13071                                 SELECT id
13072                                 FROM acq.fund_debit
13073                                 WHERE
13074                                 fund = roll_fund.old_fund
13075                                 AND encumbrance
13076                         );
13077                         --
13078                         -- Move encumbrance debits from the old fund to the new fund
13079                         --
13080                         UPDATE acq.fund_debit
13081                         SET fund = new_fund
13082                         wHERE
13083                                 fund = roll_fund.old_fund
13084                                 AND encumbrance;
13085                 END IF;
13086                 --
13087                 -- Mark old fund as inactive, now that we've closed it
13088                 --
13089                 UPDATE acq.fund
13090                 SET active = FALSE
13091                 WHERE id = roll_fund.old_fund;
13092         END LOOP;
13093 END;
13094 $$ LANGUAGE plpgsql;
13095
13096 CREATE OR REPLACE FUNCTION public.remove_commas( TEXT ) RETURNS TEXT AS $$
13097     SELECT regexp_replace($1, ',', '', 'g');
13098 $$ LANGUAGE SQL STRICT IMMUTABLE;
13099
13100 CREATE OR REPLACE FUNCTION public.remove_whitespace( TEXT ) RETURNS TEXT AS $$
13101     SELECT regexp_replace(normalize_space($1), E'\\s+', '', 'g');
13102 $$ LANGUAGE SQL STRICT IMMUTABLE;
13103
13104 CREATE TABLE acq.distribution_formula_application (
13105     id BIGSERIAL PRIMARY KEY,
13106     creator INT NOT NULL REFERENCES actor.usr(id) DEFERRABLE INITIALLY DEFERRED,
13107     create_time TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(),
13108     formula INT NOT NULL
13109         REFERENCES acq.distribution_formula(id) DEFERRABLE INITIALLY DEFERRED,
13110     lineitem INT NOT NULL
13111         REFERENCES acq.lineitem( id )
13112                 ON DELETE CASCADE
13113                 DEFERRABLE INITIALLY DEFERRED
13114 );
13115
13116 CREATE INDEX acqdfa_df_idx
13117     ON acq.distribution_formula_application(formula);
13118 CREATE INDEX acqdfa_li_idx
13119     ON acq.distribution_formula_application(lineitem);
13120 CREATE INDEX acqdfa_creator_idx
13121     ON acq.distribution_formula_application(creator);
13122
13123 CREATE TABLE acq.user_request_type (
13124     id      SERIAL  PRIMARY KEY,
13125     label   TEXT    NOT NULL UNIQUE -- i18n-ize
13126 );
13127
13128 INSERT INTO acq.user_request_type (id,label) VALUES (1, oils_i18n_gettext('1', 'Books', 'aurt', 'label'));
13129 INSERT INTO acq.user_request_type (id,label) VALUES (2, oils_i18n_gettext('2', 'Journal/Magazine & Newspaper Articles', 'aurt', 'label'));
13130 INSERT INTO acq.user_request_type (id,label) VALUES (3, oils_i18n_gettext('3', 'Audiobooks', 'aurt', 'label'));
13131 INSERT INTO acq.user_request_type (id,label) VALUES (4, oils_i18n_gettext('4', 'Music', 'aurt', 'label'));
13132 INSERT INTO acq.user_request_type (id,label) VALUES (5, oils_i18n_gettext('5', 'DVDs', 'aurt', 'label'));
13133
13134 SELECT SETVAL('acq.user_request_type_id_seq'::TEXT, 6);
13135
13136 CREATE TABLE acq.cancel_reason (
13137         id            SERIAL            PRIMARY KEY,
13138         org_unit      INTEGER           NOT NULL REFERENCES actor.org_unit( id )
13139                                         DEFERRABLE INITIALLY DEFERRED,
13140         label         TEXT              NOT NULL,
13141         description   TEXT              NOT NULL,
13142         keep_debits   BOOL              NOT NULL DEFAULT FALSE,
13143         CONSTRAINT acq_cancel_reason_one_per_org_unit UNIQUE( org_unit, label )
13144 );
13145
13146 -- Reserve ids 1-999 for stock reasons
13147 -- Reserve ids 1000-1999 for EDI reasons
13148 -- 2000+ are available for staff to create
13149
13150 SELECT SETVAL('acq.cancel_reason_id_seq'::TEXT, 2000);
13151
13152 CREATE TABLE acq.user_request (
13153     id                  SERIAL  PRIMARY KEY,
13154     usr                 INT     NOT NULL REFERENCES actor.usr (id), -- requesting user
13155     hold                BOOL    NOT NULL DEFAULT TRUE,
13156
13157     pickup_lib          INT     NOT NULL REFERENCES actor.org_unit (id), -- pickup lib
13158     holdable_formats    TEXT,           -- nullable, for use in hold creation
13159     phone_notify        TEXT,
13160     email_notify        BOOL    NOT NULL DEFAULT TRUE,
13161     lineitem            INT     REFERENCES acq.lineitem (id) ON DELETE CASCADE,
13162     eg_bib              BIGINT  REFERENCES biblio.record_entry (id) ON DELETE CASCADE,
13163     request_date        TIMESTAMPTZ NOT NULL DEFAULT NOW(), -- when they requested it
13164     need_before         TIMESTAMPTZ,    -- don't create holds after this
13165     max_fee             TEXT,
13166
13167     request_type        INT     NOT NULL REFERENCES acq.user_request_type (id), 
13168     isxn                TEXT,
13169     title               TEXT,
13170     volume              TEXT,
13171     author              TEXT,
13172     article_title       TEXT,
13173     article_pages       TEXT,
13174     publisher           TEXT,
13175     location            TEXT,
13176     pubdate             TEXT,
13177     mentioned           TEXT,
13178     other_info          TEXT,
13179         cancel_reason       INT              REFERENCES acq.cancel_reason( id )
13180                                              DEFERRABLE INITIALLY DEFERRED
13181 );
13182
13183 CREATE TABLE acq.lineitem_alert_text (
13184         id               SERIAL         PRIMARY KEY,
13185         code             TEXT           NOT NULL,
13186         description      TEXT,
13187         owning_lib       INT            NOT NULL
13188                                         REFERENCES actor.org_unit(id)
13189                                         DEFERRABLE INITIALLY DEFERRED,
13190         CONSTRAINT alert_one_code_per_org UNIQUE (code, owning_lib)
13191 );
13192
13193 ALTER TABLE acq.lineitem_note
13194         ADD COLUMN alert_text    INT     REFERENCES acq.lineitem_alert_text(id)
13195                                          DEFERRABLE INITIALLY DEFERRED;
13196
13197 -- add ON DELETE CASCADE clause
13198
13199 ALTER TABLE acq.lineitem_note
13200         DROP CONSTRAINT lineitem_note_lineitem_fkey;
13201
13202 ALTER TABLE acq.lineitem_note
13203         ADD FOREIGN KEY (lineitem) REFERENCES acq.lineitem( id )
13204                 ON DELETE CASCADE
13205                 DEFERRABLE INITIALLY DEFERRED;
13206
13207 ALTER TABLE acq.lineitem_note
13208         ADD COLUMN vendor_public BOOLEAN NOT NULL DEFAULT FALSE;
13209
13210 CREATE TABLE acq.invoice_method (
13211     code    TEXT    PRIMARY KEY,
13212     name    TEXT    NOT NULL -- i18n-ize
13213 );
13214 INSERT INTO acq.invoice_method (code,name) VALUES ('EDI',oils_i18n_gettext('EDI', 'EDI', 'acqim', 'name'));
13215 INSERT INTO acq.invoice_method (code,name) VALUES ('PPR',oils_i18n_gettext('PPR', 'Paper', 'acqit', 'name'));
13216
13217 CREATE TABLE acq.invoice_payment_method (
13218         code      TEXT     PRIMARY KEY,
13219         name      TEXT     NOT NULL
13220 );
13221
13222 CREATE TABLE acq.invoice (
13223     id             SERIAL      PRIMARY KEY,
13224     receiver       INT         NOT NULL REFERENCES actor.org_unit (id),
13225     provider       INT         NOT NULL REFERENCES acq.provider (id),
13226     shipper        INT         NOT NULL REFERENCES acq.provider (id),
13227     recv_date      TIMESTAMPTZ NOT NULL DEFAULT NOW(),
13228     recv_method    TEXT        NOT NULL REFERENCES acq.invoice_method (code) DEFAULT 'EDI',
13229     inv_type       TEXT,       -- A "type" field is desired, but no idea what goes here
13230     inv_ident      TEXT        NOT NULL, -- vendor-supplied invoice id/number
13231         payment_auth   TEXT,
13232         payment_method TEXT        REFERENCES acq.invoice_payment_method (code)
13233                                    DEFERRABLE INITIALLY DEFERRED,
13234         note           TEXT,
13235     complete       BOOL        NOT NULL DEFAULT FALSE,
13236     CONSTRAINT inv_ident_once_per_provider UNIQUE(provider, inv_ident)
13237 );
13238
13239 CREATE TABLE acq.invoice_entry (
13240     id              SERIAL      PRIMARY KEY,
13241     invoice         INT         NOT NULL REFERENCES acq.invoice (id) ON DELETE CASCADE,
13242     purchase_order  INT         REFERENCES acq.purchase_order (id) ON UPDATE CASCADE ON DELETE SET NULL,
13243     lineitem        INT         REFERENCES acq.lineitem (id) ON UPDATE CASCADE ON DELETE SET NULL,
13244     inv_item_count  INT         NOT NULL, -- How many acqlids did they say they sent
13245     phys_item_count INT, -- and how many did staff count
13246     note            TEXT,
13247     billed_per_item BOOL,
13248     cost_billed     NUMERIC(8,2),
13249     actual_cost     NUMERIC(8,2),
13250         amount_paid     NUMERIC (8,2)
13251 );
13252
13253 CREATE TABLE acq.invoice_item_type (
13254     code    TEXT    PRIMARY KEY,
13255     name    TEXT    NOT NULL, -- i18n-ize
13256         prorate BOOL    NOT NULL DEFAULT FALSE
13257 );
13258
13259 INSERT INTO acq.invoice_item_type (code,name) VALUES ('TAX',oils_i18n_gettext('TAX', 'Tax', 'aiit', 'name'));
13260 INSERT INTO acq.invoice_item_type (code,name) VALUES ('PRO',oils_i18n_gettext('PRO', 'Processing Fee', 'aiit', 'name'));
13261 INSERT INTO acq.invoice_item_type (code,name) VALUES ('SHP',oils_i18n_gettext('SHP', 'Shipping Charge', 'aiit', 'name'));
13262 INSERT INTO acq.invoice_item_type (code,name) VALUES ('HND',oils_i18n_gettext('HND', 'Handling Charge', 'aiit', 'name'));
13263 INSERT INTO acq.invoice_item_type (code,name) VALUES ('ITM',oils_i18n_gettext('ITM', 'Non-library Item', 'aiit', 'name'));
13264 INSERT INTO acq.invoice_item_type (code,name) VALUES ('SUB',oils_i18n_gettext('SUB', 'Serial Subscription', 'aiit', 'name'));
13265
13266 CREATE TABLE acq.po_item (
13267         id              SERIAL      PRIMARY KEY,
13268         purchase_order  INT         REFERENCES acq.purchase_order (id)
13269                                     ON UPDATE CASCADE ON DELETE SET NULL
13270                                     DEFERRABLE INITIALLY DEFERRED,
13271         fund_debit      INT         REFERENCES acq.fund_debit (id)
13272                                     DEFERRABLE INITIALLY DEFERRED,
13273         inv_item_type   TEXT        NOT NULL
13274                                     REFERENCES acq.invoice_item_type (code)
13275                                     DEFERRABLE INITIALLY DEFERRED,
13276         title           TEXT,
13277         author          TEXT,
13278         note            TEXT,
13279         estimated_cost  NUMERIC(8,2),
13280         fund            INT         REFERENCES acq.fund (id)
13281                                     DEFERRABLE INITIALLY DEFERRED,
13282         target          BIGINT
13283 );
13284
13285 CREATE TABLE acq.invoice_item ( -- for invoice-only debits: taxes/fees/non-bib items/etc
13286     id              SERIAL      PRIMARY KEY,
13287     invoice         INT         NOT NULL REFERENCES acq.invoice (id) ON UPDATE CASCADE ON DELETE CASCADE,
13288     purchase_order  INT         REFERENCES acq.purchase_order (id) ON UPDATE CASCADE ON DELETE SET NULL,
13289     fund_debit      INT         REFERENCES acq.fund_debit (id),
13290     inv_item_type   TEXT        NOT NULL REFERENCES acq.invoice_item_type (code),
13291     title           TEXT,
13292     author          TEXT,
13293     note            TEXT,
13294     cost_billed     NUMERIC(8,2),
13295     actual_cost     NUMERIC(8,2),
13296     fund            INT         REFERENCES acq.fund (id)
13297                                 DEFERRABLE INITIALLY DEFERRED,
13298     amount_paid     NUMERIC (8,2),
13299     po_item         INT         REFERENCES acq.po_item (id)
13300                                 DEFERRABLE INITIALLY DEFERRED,
13301     target          BIGINT
13302 );
13303
13304 CREATE TABLE acq.edi_message (
13305     id               SERIAL          PRIMARY KEY,
13306     account          INTEGER         REFERENCES acq.edi_account(id)
13307                                      DEFERRABLE INITIALLY DEFERRED,
13308     remote_file      TEXT,
13309     create_time      TIMESTAMPTZ     NOT NULL DEFAULT now(),
13310     translate_time   TIMESTAMPTZ,
13311     process_time     TIMESTAMPTZ,
13312     error_time       TIMESTAMPTZ,
13313     status           TEXT            NOT NULL DEFAULT 'new'
13314                                      CONSTRAINT status_value CHECK
13315                                      ( status IN (
13316                                         'new',          -- needs to be translated
13317                                         'translated',   -- needs to be processed
13318                                         'trans_error',  -- error in translation step
13319                                         'processed',    -- needs to have remote_file deleted
13320                                         'proc_error',   -- error in processing step
13321                                         'delete_error', -- error in deletion
13322                                         'retry',        -- need to retry
13323                                         'complete'      -- done
13324                                      )),
13325     edi              TEXT,
13326     jedi             TEXT,
13327     error            TEXT,
13328     purchase_order   INT             REFERENCES acq.purchase_order
13329                                      DEFERRABLE INITIALLY DEFERRED,
13330     message_type     TEXT            NOT NULL CONSTRAINT valid_message_type
13331                                      CHECK ( message_type IN (
13332                                         'ORDERS',
13333                                         'ORDRSP',
13334                                         'INVOIC',
13335                                         'OSTENQ',
13336                                         'OSTRPT'
13337                                      ))
13338 );
13339
13340 ALTER TABLE actor.org_address ADD COLUMN san TEXT;
13341
13342 ALTER TABLE acq.provider_address
13343         ADD COLUMN fax_phone TEXT;
13344
13345 ALTER TABLE acq.provider_contact_address
13346         ADD COLUMN fax_phone TEXT;
13347
13348 CREATE TABLE acq.provider_note (
13349     id      SERIAL              PRIMARY KEY,
13350     provider    INT             NOT NULL REFERENCES acq.provider (id) DEFERRABLE INITIALLY DEFERRED,
13351     creator     INT             NOT NULL REFERENCES actor.usr (id) DEFERRABLE INITIALLY DEFERRED,
13352     editor      INT             NOT NULL REFERENCES actor.usr (id) DEFERRABLE INITIALLY DEFERRED,
13353     create_time TIMESTAMP WITH TIME ZONE    NOT NULL DEFAULT NOW(),
13354     edit_time   TIMESTAMP WITH TIME ZONE    NOT NULL DEFAULT NOW(),
13355     value       TEXT            NOT NULL
13356 );
13357 CREATE INDEX acq_pro_note_pro_idx      ON acq.provider_note ( provider );
13358 CREATE INDEX acq_pro_note_creator_idx  ON acq.provider_note ( creator );
13359 CREATE INDEX acq_pro_note_editor_idx   ON acq.provider_note ( editor );
13360
13361 -- For each fund: the total allocation from all sources, in the
13362 -- currency of the fund (or 0 if there are no allocations)
13363
13364 CREATE VIEW acq.all_fund_allocation_total AS
13365 SELECT
13366     f.id AS fund,
13367     COALESCE( SUM( a.amount * acq.exchange_ratio(
13368         s.currency_type, f.currency_type))::numeric(100,2), 0 )
13369     AS amount
13370 FROM
13371     acq.fund f
13372         LEFT JOIN acq.fund_allocation a
13373             ON a.fund = f.id
13374         LEFT JOIN acq.funding_source s
13375             ON a.funding_source = s.id
13376 GROUP BY
13377     f.id;
13378
13379 -- For every fund: the total encumbrances (or 0 if none),
13380 -- in the currency of the fund.
13381
13382 CREATE VIEW acq.all_fund_encumbrance_total AS
13383 SELECT
13384         f.id AS fund,
13385         COALESCE( encumb.amount, 0 ) AS amount
13386 FROM
13387         acq.fund AS f
13388                 LEFT JOIN (
13389                         SELECT
13390                                 fund,
13391                                 sum( amount ) AS amount
13392                         FROM
13393                                 acq.fund_debit
13394                         WHERE
13395                                 encumbrance
13396                         GROUP BY fund
13397                 ) AS encumb
13398                         ON f.id = encumb.fund;
13399
13400 -- For every fund: the total spent (or 0 if none),
13401 -- in the currency of the fund.
13402
13403 CREATE VIEW acq.all_fund_spent_total AS
13404 SELECT
13405     f.id AS fund,
13406     COALESCE( spent.amount, 0 ) AS amount
13407 FROM
13408     acq.fund AS f
13409         LEFT JOIN (
13410             SELECT
13411                 fund,
13412                 sum( amount ) AS amount
13413             FROM
13414                 acq.fund_debit
13415             WHERE
13416                 NOT encumbrance
13417             GROUP BY fund
13418         ) AS spent
13419             ON f.id = spent.fund;
13420
13421 -- For each fund: the amount not yet spent, in the currency
13422 -- of the fund.  May include encumbrances.
13423
13424 CREATE VIEW acq.all_fund_spent_balance AS
13425 SELECT
13426         c.fund,
13427         c.amount - d.amount AS amount
13428 FROM acq.all_fund_allocation_total c
13429     LEFT JOIN acq.all_fund_spent_total d USING (fund);
13430
13431 -- For each fund: the amount neither spent nor encumbered,
13432 -- in the currency of the fund
13433
13434 CREATE VIEW acq.all_fund_combined_balance AS
13435 SELECT
13436      a.fund,
13437      a.amount - COALESCE( c.amount, 0 ) AS amount
13438 FROM
13439      acq.all_fund_allocation_total a
13440         LEFT OUTER JOIN (
13441             SELECT
13442                 fund,
13443                 SUM( amount ) AS amount
13444             FROM
13445                 acq.fund_debit
13446             GROUP BY
13447                 fund
13448         ) AS c USING ( fund );
13449
13450 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 $$
13451 DECLARE
13452         suffix TEXT;
13453         bucket_row RECORD;
13454         picklist_row RECORD;
13455         queue_row RECORD;
13456         folder_row RECORD;
13457 BEGIN
13458
13459     -- do some initial cleanup 
13460     UPDATE actor.usr SET card = NULL WHERE id = src_usr;
13461     UPDATE actor.usr SET mailing_address = NULL WHERE id = src_usr;
13462     UPDATE actor.usr SET billing_address = NULL WHERE id = src_usr;
13463
13464     -- actor.*
13465     IF del_cards THEN
13466         DELETE FROM actor.card where usr = src_usr;
13467     ELSE
13468         IF deactivate_cards THEN
13469             UPDATE actor.card SET active = 'f' WHERE usr = src_usr;
13470         END IF;
13471         UPDATE actor.card SET usr = dest_usr WHERE usr = src_usr;
13472     END IF;
13473
13474
13475     IF del_addrs THEN
13476         DELETE FROM actor.usr_address WHERE usr = src_usr;
13477     ELSE
13478         UPDATE actor.usr_address SET usr = dest_usr WHERE usr = src_usr;
13479     END IF;
13480
13481     UPDATE actor.usr_note SET usr = dest_usr WHERE usr = src_usr;
13482     -- dupes are technically OK in actor.usr_standing_penalty, should manually delete them...
13483     UPDATE actor.usr_standing_penalty SET usr = dest_usr WHERE usr = src_usr;
13484     PERFORM actor.usr_merge_rows('actor.usr_org_unit_opt_in', 'usr', src_usr, dest_usr);
13485     PERFORM actor.usr_merge_rows('actor.usr_setting', 'usr', src_usr, dest_usr);
13486
13487     -- permission.*
13488     PERFORM actor.usr_merge_rows('permission.usr_perm_map', 'usr', src_usr, dest_usr);
13489     PERFORM actor.usr_merge_rows('permission.usr_object_perm_map', 'usr', src_usr, dest_usr);
13490     PERFORM actor.usr_merge_rows('permission.usr_grp_map', 'usr', src_usr, dest_usr);
13491     PERFORM actor.usr_merge_rows('permission.usr_work_ou_map', 'usr', src_usr, dest_usr);
13492
13493
13494     -- container.*
13495         
13496         -- For each *_bucket table: transfer every bucket belonging to src_usr
13497         -- into the custody of dest_usr.
13498         --
13499         -- In order to avoid colliding with an existing bucket owned by
13500         -- the destination user, append the source user's id (in parenthesese)
13501         -- to the name.  If you still get a collision, add successive
13502         -- spaces to the name and keep trying until you succeed.
13503         --
13504         FOR bucket_row in
13505                 SELECT id, name
13506                 FROM   container.biblio_record_entry_bucket
13507                 WHERE  owner = src_usr
13508         LOOP
13509                 suffix := ' (' || src_usr || ')';
13510                 LOOP
13511                         BEGIN
13512                                 UPDATE  container.biblio_record_entry_bucket
13513                                 SET     owner = dest_usr, name = name || suffix
13514                                 WHERE   id = bucket_row.id;
13515                         EXCEPTION WHEN unique_violation THEN
13516                                 suffix := suffix || ' ';
13517                                 CONTINUE;
13518                         END;
13519                         EXIT;
13520                 END LOOP;
13521         END LOOP;
13522
13523         FOR bucket_row in
13524                 SELECT id, name
13525                 FROM   container.call_number_bucket
13526                 WHERE  owner = src_usr
13527         LOOP
13528                 suffix := ' (' || src_usr || ')';
13529                 LOOP
13530                         BEGIN
13531                                 UPDATE  container.call_number_bucket
13532                                 SET     owner = dest_usr, name = name || suffix
13533                                 WHERE   id = bucket_row.id;
13534                         EXCEPTION WHEN unique_violation THEN
13535                                 suffix := suffix || ' ';
13536                                 CONTINUE;
13537                         END;
13538                         EXIT;
13539                 END LOOP;
13540         END LOOP;
13541
13542         FOR bucket_row in
13543                 SELECT id, name
13544                 FROM   container.copy_bucket
13545                 WHERE  owner = src_usr
13546         LOOP
13547                 suffix := ' (' || src_usr || ')';
13548                 LOOP
13549                         BEGIN
13550                                 UPDATE  container.copy_bucket
13551                                 SET     owner = dest_usr, name = name || suffix
13552                                 WHERE   id = bucket_row.id;
13553                         EXCEPTION WHEN unique_violation THEN
13554                                 suffix := suffix || ' ';
13555                                 CONTINUE;
13556                         END;
13557                         EXIT;
13558                 END LOOP;
13559         END LOOP;
13560
13561         FOR bucket_row in
13562                 SELECT id, name
13563                 FROM   container.user_bucket
13564                 WHERE  owner = src_usr
13565         LOOP
13566                 suffix := ' (' || src_usr || ')';
13567                 LOOP
13568                         BEGIN
13569                                 UPDATE  container.user_bucket
13570                                 SET     owner = dest_usr, name = name || suffix
13571                                 WHERE   id = bucket_row.id;
13572                         EXCEPTION WHEN unique_violation THEN
13573                                 suffix := suffix || ' ';
13574                                 CONTINUE;
13575                         END;
13576                         EXIT;
13577                 END LOOP;
13578         END LOOP;
13579
13580         UPDATE container.user_bucket_item SET target_user = dest_usr WHERE target_user = src_usr;
13581
13582     -- vandelay.*
13583         -- transfer queues the same way we transfer buckets (see above)
13584         FOR queue_row in
13585                 SELECT id, name
13586                 FROM   vandelay.queue
13587                 WHERE  owner = src_usr
13588         LOOP
13589                 suffix := ' (' || src_usr || ')';
13590                 LOOP
13591                         BEGIN
13592                                 UPDATE  vandelay.queue
13593                                 SET     owner = dest_usr, name = name || suffix
13594                                 WHERE   id = queue_row.id;
13595                         EXCEPTION WHEN unique_violation THEN
13596                                 suffix := suffix || ' ';
13597                                 CONTINUE;
13598                         END;
13599                         EXIT;
13600                 END LOOP;
13601         END LOOP;
13602
13603     -- money.*
13604     PERFORM actor.usr_merge_rows('money.collections_tracker', 'usr', src_usr, dest_usr);
13605     PERFORM actor.usr_merge_rows('money.collections_tracker', 'collector', src_usr, dest_usr);
13606     UPDATE money.billable_xact SET usr = dest_usr WHERE usr = src_usr;
13607     UPDATE money.billing SET voider = dest_usr WHERE voider = src_usr;
13608     UPDATE money.bnm_payment SET accepting_usr = dest_usr WHERE accepting_usr = src_usr;
13609
13610     -- action.*
13611     UPDATE action.circulation SET usr = dest_usr WHERE usr = src_usr;
13612     UPDATE action.circulation SET circ_staff = dest_usr WHERE circ_staff = src_usr;
13613     UPDATE action.circulation SET checkin_staff = dest_usr WHERE checkin_staff = src_usr;
13614
13615     UPDATE action.hold_request SET usr = dest_usr WHERE usr = src_usr;
13616     UPDATE action.hold_request SET fulfillment_staff = dest_usr WHERE fulfillment_staff = src_usr;
13617     UPDATE action.hold_request SET requestor = dest_usr WHERE requestor = src_usr;
13618     UPDATE action.hold_notification SET notify_staff = dest_usr WHERE notify_staff = src_usr;
13619
13620     UPDATE action.in_house_use SET staff = dest_usr WHERE staff = src_usr;
13621     UPDATE action.non_cataloged_circulation SET staff = dest_usr WHERE staff = src_usr;
13622     UPDATE action.non_cataloged_circulation SET patron = dest_usr WHERE patron = src_usr;
13623     UPDATE action.non_cat_in_house_use SET staff = dest_usr WHERE staff = src_usr;
13624     UPDATE action.survey_response SET usr = dest_usr WHERE usr = src_usr;
13625
13626     -- acq.*
13627     UPDATE acq.fund_allocation SET allocator = dest_usr WHERE allocator = src_usr;
13628         UPDATE acq.fund_transfer SET transfer_user = dest_usr WHERE transfer_user = src_usr;
13629
13630         -- transfer picklists the same way we transfer buckets (see above)
13631         FOR picklist_row in
13632                 SELECT id, name
13633                 FROM   acq.picklist
13634                 WHERE  owner = src_usr
13635         LOOP
13636                 suffix := ' (' || src_usr || ')';
13637                 LOOP
13638                         BEGIN
13639                                 UPDATE  acq.picklist
13640                                 SET     owner = dest_usr, name = name || suffix
13641                                 WHERE   id = picklist_row.id;
13642                         EXCEPTION WHEN unique_violation THEN
13643                                 suffix := suffix || ' ';
13644                                 CONTINUE;
13645                         END;
13646                         EXIT;
13647                 END LOOP;
13648         END LOOP;
13649
13650     UPDATE acq.purchase_order SET owner = dest_usr WHERE owner = src_usr;
13651     UPDATE acq.po_note SET creator = dest_usr WHERE creator = src_usr;
13652     UPDATE acq.po_note SET editor = dest_usr WHERE editor = src_usr;
13653     UPDATE acq.provider_note SET creator = dest_usr WHERE creator = src_usr;
13654     UPDATE acq.provider_note SET editor = dest_usr WHERE editor = src_usr;
13655     UPDATE acq.lineitem_note SET creator = dest_usr WHERE creator = src_usr;
13656     UPDATE acq.lineitem_note SET editor = dest_usr WHERE editor = src_usr;
13657     UPDATE acq.lineitem_usr_attr_definition SET usr = dest_usr WHERE usr = src_usr;
13658
13659     -- asset.*
13660     UPDATE asset.copy SET creator = dest_usr WHERE creator = src_usr;
13661     UPDATE asset.copy SET editor = dest_usr WHERE editor = src_usr;
13662     UPDATE asset.copy_note SET creator = dest_usr WHERE creator = src_usr;
13663     UPDATE asset.call_number SET creator = dest_usr WHERE creator = src_usr;
13664     UPDATE asset.call_number SET editor = dest_usr WHERE editor = src_usr;
13665     UPDATE asset.call_number_note SET creator = dest_usr WHERE creator = src_usr;
13666
13667     -- serial.*
13668     UPDATE serial.record_entry SET creator = dest_usr WHERE creator = src_usr;
13669     UPDATE serial.record_entry SET editor = dest_usr WHERE editor = src_usr;
13670
13671     -- reporter.*
13672     -- It's not uncommon to define the reporter schema in a replica 
13673     -- DB only, so don't assume these tables exist in the write DB.
13674     BEGIN
13675         UPDATE reporter.template SET owner = dest_usr WHERE owner = src_usr;
13676     EXCEPTION WHEN undefined_table THEN
13677         -- do nothing
13678     END;
13679     BEGIN
13680         UPDATE reporter.report SET owner = dest_usr WHERE owner = src_usr;
13681     EXCEPTION WHEN undefined_table THEN
13682         -- do nothing
13683     END;
13684     BEGIN
13685         UPDATE reporter.schedule SET runner = dest_usr WHERE runner = src_usr;
13686     EXCEPTION WHEN undefined_table THEN
13687         -- do nothing
13688     END;
13689     BEGIN
13690                 -- transfer folders the same way we transfer buckets (see above)
13691                 FOR folder_row in
13692                         SELECT id, name
13693                         FROM   reporter.template_folder
13694                         WHERE  owner = src_usr
13695                 LOOP
13696                         suffix := ' (' || src_usr || ')';
13697                         LOOP
13698                                 BEGIN
13699                                         UPDATE  reporter.template_folder
13700                                         SET     owner = dest_usr, name = name || suffix
13701                                         WHERE   id = folder_row.id;
13702                                 EXCEPTION WHEN unique_violation THEN
13703                                         suffix := suffix || ' ';
13704                                         CONTINUE;
13705                                 END;
13706                                 EXIT;
13707                         END LOOP;
13708                 END LOOP;
13709     EXCEPTION WHEN undefined_table THEN
13710         -- do nothing
13711     END;
13712     BEGIN
13713                 -- transfer folders the same way we transfer buckets (see above)
13714                 FOR folder_row in
13715                         SELECT id, name
13716                         FROM   reporter.report_folder
13717                         WHERE  owner = src_usr
13718                 LOOP
13719                         suffix := ' (' || src_usr || ')';
13720                         LOOP
13721                                 BEGIN
13722                                         UPDATE  reporter.report_folder
13723                                         SET     owner = dest_usr, name = name || suffix
13724                                         WHERE   id = folder_row.id;
13725                                 EXCEPTION WHEN unique_violation THEN
13726                                         suffix := suffix || ' ';
13727                                         CONTINUE;
13728                                 END;
13729                                 EXIT;
13730                         END LOOP;
13731                 END LOOP;
13732     EXCEPTION WHEN undefined_table THEN
13733         -- do nothing
13734     END;
13735     BEGIN
13736                 -- transfer folders the same way we transfer buckets (see above)
13737                 FOR folder_row in
13738                         SELECT id, name
13739                         FROM   reporter.output_folder
13740                         WHERE  owner = src_usr
13741                 LOOP
13742                         suffix := ' (' || src_usr || ')';
13743                         LOOP
13744                                 BEGIN
13745                                         UPDATE  reporter.output_folder
13746                                         SET     owner = dest_usr, name = name || suffix
13747                                         WHERE   id = folder_row.id;
13748                                 EXCEPTION WHEN unique_violation THEN
13749                                         suffix := suffix || ' ';
13750                                         CONTINUE;
13751                                 END;
13752                                 EXIT;
13753                         END LOOP;
13754                 END LOOP;
13755     EXCEPTION WHEN undefined_table THEN
13756         -- do nothing
13757     END;
13758
13759     -- Finally, delete the source user
13760     DELETE FROM actor.usr WHERE id = src_usr;
13761
13762 END;
13763 $$ LANGUAGE plpgsql;
13764
13765 -- The "add" trigger functions should protect against existing NULLed values, just in case
13766 CREATE OR REPLACE FUNCTION money.materialized_summary_billing_add () RETURNS TRIGGER AS $$
13767 BEGIN
13768     IF NOT NEW.voided THEN
13769         UPDATE  money.materialized_billable_xact_summary
13770           SET   total_owed = COALESCE(total_owed, 0.0::numeric) + NEW.amount,
13771             last_billing_ts = NEW.billing_ts,
13772             last_billing_note = NEW.note,
13773             last_billing_type = NEW.billing_type,
13774             balance_owed = balance_owed + NEW.amount
13775           WHERE id = NEW.xact;
13776     END IF;
13777
13778     RETURN NEW;
13779 END;
13780 $$ LANGUAGE PLPGSQL;
13781
13782 CREATE OR REPLACE FUNCTION money.materialized_summary_payment_add () RETURNS TRIGGER AS $$
13783 BEGIN
13784     IF NOT NEW.voided THEN
13785         UPDATE  money.materialized_billable_xact_summary
13786           SET   total_paid = COALESCE(total_paid, 0.0::numeric) + NEW.amount,
13787             last_payment_ts = NEW.payment_ts,
13788             last_payment_note = NEW.note,
13789             last_payment_type = TG_ARGV[0],
13790             balance_owed = balance_owed - NEW.amount
13791           WHERE id = NEW.xact;
13792     END IF;
13793
13794     RETURN NEW;
13795 END;
13796 $$ LANGUAGE PLPGSQL;
13797
13798 -- Refresh the mat view with the corrected underlying view
13799 TRUNCATE money.materialized_billable_xact_summary;
13800 INSERT INTO money.materialized_billable_xact_summary SELECT * FROM money.billable_xact_summary;
13801
13802 -- Now redefine the view as a window onto the materialized view
13803 CREATE OR REPLACE VIEW money.billable_xact_summary AS
13804     SELECT * FROM money.materialized_billable_xact_summary;
13805
13806 CREATE OR REPLACE FUNCTION permission.usr_has_perm_at_nd(
13807     user_id    IN INTEGER,
13808     perm_code  IN TEXT
13809 )
13810 RETURNS SETOF INTEGER AS $$
13811 --
13812 -- Return a set of all the org units for which a given user has a given
13813 -- permission, granted directly (not through inheritance from a parent
13814 -- org unit).
13815 --
13816 -- The permissions apply to a minimum depth of the org unit hierarchy,
13817 -- for the org unit(s) to which the user is assigned.  (They also apply
13818 -- to the subordinates of those org units, but we don't report the
13819 -- subordinates here.)
13820 --
13821 -- For purposes of this function, the permission.usr_work_ou_map table
13822 -- defines which users belong to which org units.  I.e. we ignore the
13823 -- home_ou column of actor.usr.
13824 --
13825 -- The result set may contain duplicates, which should be eliminated
13826 -- by a DISTINCT clause.
13827 --
13828 DECLARE
13829     b_super       BOOLEAN;
13830     n_perm        INTEGER;
13831     n_min_depth   INTEGER;
13832     n_work_ou     INTEGER;
13833     n_curr_ou     INTEGER;
13834     n_depth       INTEGER;
13835     n_curr_depth  INTEGER;
13836 BEGIN
13837     --
13838     -- Check for superuser
13839     --
13840     SELECT INTO b_super
13841         super_user
13842     FROM
13843         actor.usr
13844     WHERE
13845         id = user_id;
13846     --
13847     IF NOT FOUND THEN
13848         return;             -- No user?  No permissions.
13849     ELSIF b_super THEN
13850         --
13851         -- Super user has all permissions everywhere
13852         --
13853         FOR n_work_ou IN
13854             SELECT
13855                 id
13856             FROM
13857                 actor.org_unit
13858             WHERE
13859                 parent_ou IS NULL
13860         LOOP
13861             RETURN NEXT n_work_ou;
13862         END LOOP;
13863         RETURN;
13864     END IF;
13865     --
13866     -- Translate the permission name
13867     -- to a numeric permission id
13868     --
13869     SELECT INTO n_perm
13870         id
13871     FROM
13872         permission.perm_list
13873     WHERE
13874         code = perm_code;
13875     --
13876     IF NOT FOUND THEN
13877         RETURN;               -- No such permission
13878     END IF;
13879     --
13880     -- Find the highest-level org unit (i.e. the minimum depth)
13881     -- to which the permission is applied for this user
13882     --
13883     -- This query is modified from the one in permission.usr_perms().
13884     --
13885     SELECT INTO n_min_depth
13886         min( depth )
13887     FROM    (
13888         SELECT depth
13889           FROM permission.usr_perm_map upm
13890          WHERE upm.usr = user_id
13891            AND (upm.perm = n_perm OR upm.perm = -1)
13892                     UNION
13893         SELECT  gpm.depth
13894           FROM  permission.grp_perm_map gpm
13895           WHERE (gpm.perm = n_perm OR gpm.perm = -1)
13896             AND gpm.grp IN (
13897                SELECT   (permission.grp_ancestors(
13898                     (SELECT profile FROM actor.usr WHERE id = user_id)
13899                 )).id
13900             )
13901                     UNION
13902         SELECT  p.depth
13903           FROM  permission.grp_perm_map p
13904           WHERE (p.perm = n_perm OR p.perm = -1)
13905             AND p.grp IN (
13906                 SELECT (permission.grp_ancestors(m.grp)).id
13907                 FROM   permission.usr_grp_map m
13908                 WHERE  m.usr = user_id
13909             )
13910     ) AS x;
13911     --
13912     IF NOT FOUND THEN
13913         RETURN;                -- No such permission for this user
13914     END IF;
13915     --
13916     -- Identify the org units to which the user is assigned.  Note that
13917     -- we pay no attention to the home_ou column in actor.usr.
13918     --
13919     FOR n_work_ou IN
13920         SELECT
13921             work_ou
13922         FROM
13923             permission.usr_work_ou_map
13924         WHERE
13925             usr = user_id
13926     LOOP            -- For each org unit to which the user is assigned
13927         --
13928         -- Determine the level of the org unit by a lookup in actor.org_unit_type.
13929         -- We take it on faith that this depth agrees with the actual hierarchy
13930         -- defined in actor.org_unit.
13931         --
13932         SELECT INTO n_depth
13933             type.depth
13934         FROM
13935             actor.org_unit_type type
13936                 INNER JOIN actor.org_unit ou
13937                     ON ( ou.ou_type = type.id )
13938         WHERE
13939             ou.id = n_work_ou;
13940         --
13941         IF NOT FOUND THEN
13942             CONTINUE;        -- Maybe raise exception?
13943         END IF;
13944         --
13945         -- Compare the depth of the work org unit to the
13946         -- minimum depth, and branch accordingly
13947         --
13948         IF n_depth = n_min_depth THEN
13949             --
13950             -- The org unit is at the right depth, so return it.
13951             --
13952             RETURN NEXT n_work_ou;
13953         ELSIF n_depth > n_min_depth THEN
13954             --
13955             -- Traverse the org unit tree toward the root,
13956             -- until you reach the minimum depth determined above
13957             --
13958             n_curr_depth := n_depth;
13959             n_curr_ou := n_work_ou;
13960             WHILE n_curr_depth > n_min_depth LOOP
13961                 SELECT INTO n_curr_ou
13962                     parent_ou
13963                 FROM
13964                     actor.org_unit
13965                 WHERE
13966                     id = n_curr_ou;
13967                 --
13968                 IF FOUND THEN
13969                     n_curr_depth := n_curr_depth - 1;
13970                 ELSE
13971                     --
13972                     -- This can happen only if the hierarchy defined in
13973                     -- actor.org_unit is corrupted, or out of sync with
13974                     -- the depths defined in actor.org_unit_type.
13975                     -- Maybe we should raise an exception here, instead
13976                     -- of silently ignoring the problem.
13977                     --
13978                     n_curr_ou = NULL;
13979                     EXIT;
13980                 END IF;
13981             END LOOP;
13982             --
13983             IF n_curr_ou IS NOT NULL THEN
13984                 RETURN NEXT n_curr_ou;
13985             END IF;
13986         ELSE
13987             --
13988             -- The permission applies only at a depth greater than the work org unit.
13989             -- Use connectby() to find all dependent org units at the specified depth.
13990             --
13991             FOR n_curr_ou IN
13992                 SELECT ou::INTEGER
13993                 FROM connectby(
13994                         'actor.org_unit',         -- table name
13995                         'id',                     -- key column
13996                         'parent_ou',              -- recursive foreign key
13997                         n_work_ou::TEXT,          -- id of starting point
13998                         (n_min_depth - n_depth)   -- max depth to search, relative
13999                     )                             --   to starting point
14000                     AS t(
14001                         ou text,            -- dependent org unit
14002                         parent_ou text,     -- (ignore)
14003                         level int           -- depth relative to starting point
14004                     )
14005                 WHERE
14006                     level = n_min_depth - n_depth
14007             LOOP
14008                 RETURN NEXT n_curr_ou;
14009             END LOOP;
14010         END IF;
14011         --
14012     END LOOP;
14013     --
14014     RETURN;
14015     --
14016 END;
14017 $$ LANGUAGE 'plpgsql';
14018
14019 ALTER TABLE acq.purchase_order
14020         ADD COLUMN cancel_reason INT
14021                 REFERENCES acq.cancel_reason( id )
14022             DEFERRABLE INITIALLY DEFERRED,
14023         ADD COLUMN prepayment_required BOOLEAN NOT NULL DEFAULT FALSE;
14024
14025 -- Build the history table and lifecycle view
14026 -- for acq.purchase_order
14027
14028 SELECT acq.create_acq_auditor ( 'acq', 'purchase_order' );
14029
14030 CREATE INDEX acq_po_hist_id_idx            ON acq.acq_purchase_order_history( id );
14031
14032 ALTER TABLE acq.lineitem
14033         ADD COLUMN cancel_reason INT
14034                 REFERENCES acq.cancel_reason( id )
14035             DEFERRABLE INITIALLY DEFERRED,
14036         ADD COLUMN estimated_unit_price NUMERIC,
14037         ADD COLUMN claim_policy INT
14038                 REFERENCES acq.claim_policy
14039                 DEFERRABLE INITIALLY DEFERRED,
14040         ALTER COLUMN eg_bib_id SET DATA TYPE bigint;
14041
14042 -- Build the history table and lifecycle view
14043 -- for acq.lineitem
14044
14045 SELECT acq.create_acq_auditor ( 'acq', 'lineitem' );
14046 CREATE INDEX acq_lineitem_hist_id_idx            ON acq.acq_lineitem_history( id );
14047
14048 ALTER TABLE acq.lineitem_detail
14049         ADD COLUMN cancel_reason        INT REFERENCES acq.cancel_reason( id )
14050                                             DEFERRABLE INITIALLY DEFERRED;
14051
14052 ALTER TABLE acq.lineitem_detail
14053         DROP CONSTRAINT lineitem_detail_lineitem_fkey;
14054
14055 ALTER TABLE acq.lineitem_detail
14056         ADD FOREIGN KEY (lineitem) REFERENCES acq.lineitem( id )
14057                 ON DELETE CASCADE
14058                 DEFERRABLE INITIALLY DEFERRED;
14059
14060 ALTER TABLE acq.lineitem_detail DROP CONSTRAINT lineitem_detail_eg_copy_id_fkey;
14061
14062 INSERT INTO acq.cancel_reason ( id, org_unit, label, description ) VALUES (
14063         1, 1, 'invalid_isbn', oils_i18n_gettext( 1, 'ISBN is unrecognizable', 'acqcr', 'label' ));
14064
14065 INSERT INTO acq.cancel_reason ( id, org_unit, label, description ) VALUES (
14066         2, 1, 'postpone', oils_i18n_gettext( 2, 'Title has been postponed', 'acqcr', 'label' ));
14067
14068 CREATE OR REPLACE FUNCTION vandelay.add_field ( target_xml TEXT, source_xml TEXT, field TEXT, force_add INT ) RETURNS TEXT AS $_$
14069
14070     use MARC::Record;
14071     use MARC::File::XML (BinaryEncoding => 'UTF-8');
14072     use strict;
14073
14074     my $target_xml = shift;
14075     my $source_xml = shift;
14076     my $field_spec = shift;
14077     my $force_add = shift || 0;
14078
14079     my $target_r = MARC::Record->new_from_xml( $target_xml );
14080     my $source_r = MARC::Record->new_from_xml( $source_xml );
14081
14082     return $target_xml unless ($target_r && $source_r);
14083
14084     my @field_list = split(',', $field_spec);
14085
14086     my %fields;
14087     for my $f (@field_list) {
14088         $f =~ s/^\s*//; $f =~ s/\s*$//;
14089         if ($f =~ /^(.{3})(\w*)(?:\[([^]]*)\])?$/) {
14090             my $field = $1;
14091             $field =~ s/\s+//;
14092             my $sf = $2;
14093             $sf =~ s/\s+//;
14094             my $match = $3;
14095             $match =~ s/^\s*//; $match =~ s/\s*$//;
14096             $fields{$field} = { sf => [ split('', $sf) ] };
14097             if ($match) {
14098                 my ($msf,$mre) = split('~', $match);
14099                 if (length($msf) > 0 and length($mre) > 0) {
14100                     $msf =~ s/^\s*//; $msf =~ s/\s*$//;
14101                     $mre =~ s/^\s*//; $mre =~ s/\s*$//;
14102                     $fields{$field}{match} = { sf => $msf, re => qr/$mre/ };
14103                 }
14104             }
14105         }
14106     }
14107
14108     for my $f ( keys %fields) {
14109         if ( @{$fields{$f}{sf}} ) {
14110             for my $from_field ($source_r->field( $f )) {
14111                 my @tos = $target_r->field( $f );
14112                 if (!@tos) {
14113                     next if (exists($fields{$f}{match}) and !$force_add);
14114                     my @new_fields = map { $_->clone } $source_r->field( $f );
14115                     $target_r->insert_fields_ordered( @new_fields );
14116                 } else {
14117                     for my $to_field (@tos) {
14118                         if (exists($fields{$f}{match})) {
14119                             next unless (grep { $_ =~ $fields{$f}{match}{re} } $to_field->subfield($fields{$f}{match}{sf}));
14120                         }
14121                         my @new_sf = map { ($_ => $from_field->subfield($_)) } @{$fields{$f}{sf}};
14122                         $to_field->add_subfields( @new_sf );
14123                     }
14124                 }
14125             }
14126         } else {
14127             my @new_fields = map { $_->clone } $source_r->field( $f );
14128             $target_r->insert_fields_ordered( @new_fields );
14129         }
14130     }
14131
14132     $target_xml = $target_r->as_xml_record;
14133     $target_xml =~ s/^<\?.+?\?>$//mo;
14134     $target_xml =~ s/\n//sgo;
14135     $target_xml =~ s/>\s+</></sgo;
14136
14137     return $target_xml;
14138
14139 $_$ LANGUAGE PLPERLU;
14140
14141 CREATE OR REPLACE FUNCTION vandelay.add_field ( target_xml TEXT, source_xml TEXT, field TEXT ) RETURNS TEXT AS $_$
14142     SELECT vandelay.add_field( $1, $2 $3, 0 );
14143 $_$ LANGUAGE SQL;
14144
14145 CREATE OR REPLACE FUNCTION vandelay.strip_field ( xml TEXT, field TEXT ) RETURNS TEXT AS $_$
14146
14147     use MARC::Record;
14148     use MARC::File::XML (BinaryEncoding => 'UTF-8');
14149     use strict;
14150
14151     my $xml = shift;
14152     my $r = MARC::Record->new_from_xml( $xml );
14153
14154     return $xml unless ($r);
14155
14156     my $field_spec = shift;
14157     my @field_list = split(',', $field_spec);
14158
14159     my %fields;
14160     for my $f (@field_list) {
14161         $f =~ s/^\s*//; $f =~ s/\s*$//;
14162         if ($f =~ /^(.{3})(\w*)(?:\[([^]]*)\])?$/) {
14163             my $field = $1;
14164             $field =~ s/\s+//;
14165             my $sf = $2;
14166             $sf =~ s/\s+//;
14167             my $match = $3;
14168             $match =~ s/^\s*//; $match =~ s/\s*$//;
14169             $fields{$field} = { sf => [ split('', $sf) ] };
14170             if ($match) {
14171                 my ($msf,$mre) = split('~', $match);
14172                 if (length($msf) > 0 and length($mre) > 0) {
14173                     $msf =~ s/^\s*//; $msf =~ s/\s*$//;
14174                     $mre =~ s/^\s*//; $mre =~ s/\s*$//;
14175                     $fields{$field}{match} = { sf => $msf, re => qr/$mre/ };
14176                 }
14177             }
14178         }
14179     }
14180
14181     for my $f ( keys %fields) {
14182         for my $to_field ($r->field( $f )) {
14183             if (exists($fields{$f}{match})) {
14184                 next unless (grep { $_ =~ $fields{$f}{match}{re} } $to_field->subfield($fields{$f}{match}{sf}));
14185             }
14186
14187             if ( @{$fields{$f}{sf}} ) {
14188                 $to_field->delete_subfield(code => $fields{$f}{sf});
14189             } else {
14190                 $r->delete_field( $to_field );
14191             }
14192         }
14193     }
14194
14195     $xml = $r->as_xml_record;
14196     $xml =~ s/^<\?.+?\?>$//mo;
14197     $xml =~ s/\n//sgo;
14198     $xml =~ s/>\s+</></sgo;
14199
14200     return $xml;
14201
14202 $_$ LANGUAGE PLPERLU;
14203
14204 CREATE OR REPLACE FUNCTION vandelay.replace_field ( target_xml TEXT, source_xml TEXT, field TEXT ) RETURNS TEXT AS $_$
14205 DECLARE
14206     xml_output TEXT;
14207 BEGIN
14208     xml_output := vandelay.strip_field( target_xml, field);
14209
14210     IF xml_output <> target_xml  AND field ~ E'~' THEN
14211         -- we removed something, and there was a regexp restriction in the field definition, so proceed
14212         xml_output := vandelay.add_field( xml_output, source_xml, field, 1 );
14213     ELSIF field !~ E'~' THEN
14214         -- No regexp restriction, add the field
14215         xml_output := vandelay.add_field( xml_output, source_xml, field, 0 );
14216     END IF;
14217
14218     RETURN xml_output;
14219 END;
14220 $_$ LANGUAGE PLPGSQL;
14221
14222 CREATE OR REPLACE FUNCTION vandelay.preserve_field ( incumbent_xml TEXT, incoming_xml TEXT, field TEXT ) RETURNS TEXT AS $_$
14223     SELECT vandelay.add_field( vandelay.strip_field( $2, $3), $1, $3 );
14224 $_$ LANGUAGE SQL;
14225
14226 CREATE VIEW action.unfulfilled_hold_max_loop AS
14227         SELECT  hold,
14228                 max(count) AS max
14229         FROM    action.unfulfilled_hold_loops
14230         GROUP BY 1;
14231
14232 ALTER TABLE acq.lineitem_attr
14233         DROP CONSTRAINT lineitem_attr_lineitem_fkey;
14234
14235 ALTER TABLE acq.lineitem_attr
14236         ADD FOREIGN KEY (lineitem) REFERENCES acq.lineitem( id )
14237                 ON DELETE CASCADE
14238                 DEFERRABLE INITIALLY DEFERRED;
14239
14240 ALTER TABLE acq.po_note
14241         ADD COLUMN vendor_public BOOLEAN NOT NULL DEFAULT FALSE;
14242
14243 CREATE TABLE vandelay.merge_profile (
14244     id              BIGSERIAL   PRIMARY KEY,
14245     owner           INT         NOT NULL REFERENCES actor.org_unit (id) ON UPDATE CASCADE ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED,
14246     name            TEXT        NOT NULL,
14247     add_spec        TEXT,
14248     replace_spec    TEXT,
14249     strip_spec      TEXT,
14250     preserve_spec   TEXT,
14251     CONSTRAINT vand_merge_prof_owner_name_idx UNIQUE (owner,name),
14252     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))
14253 );
14254
14255 CREATE OR REPLACE FUNCTION vandelay.match_bib_record ( ) RETURNS TRIGGER AS $func$
14256 DECLARE
14257     attr        RECORD;
14258     attr_def    RECORD;
14259     eg_rec      RECORD;
14260     id_value    TEXT;
14261     exact_id    BIGINT;
14262 BEGIN
14263
14264     DELETE FROM vandelay.bib_match WHERE queued_record = NEW.id;
14265
14266     SELECT * INTO attr_def FROM vandelay.bib_attr_definition WHERE xpath = '//*[@tag="901"]/*[@code="c"]' ORDER BY id LIMIT 1;
14267
14268     IF attr_def IS NOT NULL AND attr_def.id IS NOT NULL THEN
14269         id_value := extract_marc_field('vandelay.queued_bib_record', NEW.id, attr_def.xpath, attr_def.remove);
14270
14271         IF id_value IS NOT NULL AND id_value <> '' AND id_value ~ $r$^\d+$$r$ THEN
14272             SELECT id INTO exact_id FROM biblio.record_entry WHERE id = id_value::BIGINT AND NOT deleted;
14273             SELECT * INTO attr FROM vandelay.queued_bib_record_attr WHERE record = NEW.id and field = attr_def.id LIMIT 1;
14274             IF exact_id IS NOT NULL THEN
14275                 INSERT INTO vandelay.bib_match (field_type, matched_attr, queued_record, eg_record) VALUES ('id', attr.id, NEW.id, exact_id);
14276             END IF;
14277         END IF;
14278     END IF;
14279
14280     IF exact_id IS NULL THEN
14281         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
14282
14283             -- All numbers? check for an id match
14284             IF (attr.attr_value ~ $r$^\d+$$r$) THEN
14285                 FOR eg_rec IN SELECT * FROM biblio.record_entry WHERE id = attr.attr_value::BIGINT AND deleted IS FALSE LOOP
14286                     INSERT INTO vandelay.bib_match (field_type, matched_attr, queued_record, eg_record) VALUES ('id', attr.id, NEW.id, eg_rec.id);
14287                 END LOOP;
14288             END IF;
14289
14290             -- Looks like an ISBN? check for an isbn match
14291             IF (attr.attr_value ~* $r$^[0-9x]+$$r$ AND character_length(attr.attr_value) IN (10,13)) THEN
14292                 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
14293                     PERFORM id FROM biblio.record_entry WHERE id = eg_rec.record AND deleted IS FALSE;
14294                     IF FOUND THEN
14295                         INSERT INTO vandelay.bib_match (field_type, matched_attr, queued_record, eg_record) VALUES ('isbn', attr.id, NEW.id, eg_rec.record);
14296                     END IF;
14297                 END LOOP;
14298
14299                 -- subcheck for isbn-as-tcn
14300                 FOR eg_rec IN SELECT * FROM biblio.record_entry WHERE tcn_value = 'i' || attr.attr_value AND deleted IS FALSE LOOP
14301                     INSERT INTO vandelay.bib_match (field_type, matched_attr, queued_record, eg_record) VALUES ('tcn_value', attr.id, NEW.id, eg_rec.id);
14302                 END LOOP;
14303             END IF;
14304
14305             -- check for an OCLC tcn_value match
14306             IF (attr.attr_value ~ $r$^o\d+$$r$) THEN
14307                 FOR eg_rec IN SELECT * FROM biblio.record_entry WHERE tcn_value = regexp_replace(attr.attr_value,'^o','ocm') AND deleted IS FALSE LOOP
14308                     INSERT INTO vandelay.bib_match (field_type, matched_attr, queued_record, eg_record) VALUES ('tcn_value', attr.id, NEW.id, eg_rec.id);
14309                 END LOOP;
14310             END IF;
14311
14312             -- check for a direct tcn_value match
14313             FOR eg_rec IN SELECT * FROM biblio.record_entry WHERE tcn_value = attr.attr_value AND deleted IS FALSE LOOP
14314                 INSERT INTO vandelay.bib_match (field_type, matched_attr, queued_record, eg_record) VALUES ('tcn_value', attr.id, NEW.id, eg_rec.id);
14315             END LOOP;
14316
14317             -- check for a direct item barcode match
14318             FOR eg_rec IN
14319                     SELECT  DISTINCT b.*
14320                       FROM  biblio.record_entry b
14321                             JOIN asset.call_number cn ON (cn.record = b.id)
14322                             JOIN asset.copy cp ON (cp.call_number = cn.id)
14323                       WHERE cp.barcode = attr.attr_value AND cp.deleted IS FALSE
14324             LOOP
14325                 INSERT INTO vandelay.bib_match (field_type, matched_attr, queued_record, eg_record) VALUES ('id', attr.id, NEW.id, eg_rec.id);
14326             END LOOP;
14327
14328         END LOOP;
14329     END IF;
14330
14331     RETURN NULL;
14332 END;
14333 $func$ LANGUAGE PLPGSQL;
14334
14335 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 $_$
14336     SELECT vandelay.replace_field( vandelay.add_field( vandelay.strip_field( $1, $5) , $2, $3 ), $2, $4);
14337 $_$ LANGUAGE SQL;
14338
14339 CREATE TYPE vandelay.compile_profile AS (add_rule TEXT, replace_rule TEXT, preserve_rule TEXT, strip_rule TEXT);
14340 CREATE OR REPLACE FUNCTION vandelay.compile_profile ( incoming_xml TEXT ) RETURNS vandelay.compile_profile AS $_$
14341 DECLARE
14342     output              vandelay.compile_profile%ROWTYPE;
14343     profile             vandelay.merge_profile%ROWTYPE;
14344     profile_tmpl        TEXT;
14345     profile_tmpl_owner  TEXT;
14346     add_rule            TEXT := '';
14347     strip_rule          TEXT := '';
14348     replace_rule        TEXT := '';
14349     preserve_rule       TEXT := '';
14350
14351 BEGIN
14352
14353     profile_tmpl := (oils_xpath('//*[@tag="905"]/*[@code="t"]/text()',incoming_xml))[1];
14354     profile_tmpl_owner := (oils_xpath('//*[@tag="905"]/*[@code="o"]/text()',incoming_xml))[1];
14355
14356     IF profile_tmpl IS NOT NULL AND profile_tmpl <> '' AND profile_tmpl_owner IS NOT NULL AND profile_tmpl_owner <> '' THEN
14357         SELECT  p.* INTO profile
14358           FROM  vandelay.merge_profile p
14359                 JOIN actor.org_unit u ON (u.id = p.owner)
14360           WHERE p.name = profile_tmpl
14361                 AND u.shortname = profile_tmpl_owner;
14362
14363         IF profile.id IS NOT NULL THEN
14364             add_rule := COALESCE(profile.add_spec,'');
14365             strip_rule := COALESCE(profile.strip_spec,'');
14366             replace_rule := COALESCE(profile.replace_spec,'');
14367             preserve_rule := COALESCE(profile.preserve_spec,'');
14368         END IF;
14369     END IF;
14370
14371     add_rule := add_rule || ',' || COALESCE(ARRAY_TO_STRING(oils_xpath('//*[@tag="905"]/*[@code="a"]/text()',incoming_xml),','),'');
14372     strip_rule := strip_rule || ',' || COALESCE(ARRAY_TO_STRING(oils_xpath('//*[@tag="905"]/*[@code="d"]/text()',incoming_xml),','),'');
14373     replace_rule := replace_rule || ',' || COALESCE(ARRAY_TO_STRING(oils_xpath('//*[@tag="905"]/*[@code="r"]/text()',incoming_xml),','),'');
14374     preserve_rule := preserve_rule || ',' || COALESCE(ARRAY_TO_STRING(oils_xpath('//*[@tag="905"]/*[@code="p"]/text()',incoming_xml),','),'');
14375
14376     output.add_rule := BTRIM(add_rule,',');
14377     output.replace_rule := BTRIM(replace_rule,',');
14378     output.strip_rule := BTRIM(strip_rule,',');
14379     output.preserve_rule := BTRIM(preserve_rule,',');
14380
14381     RETURN output;
14382 END;
14383 $_$ LANGUAGE PLPGSQL;
14384
14385 -- Template-based marc munging functions
14386 CREATE OR REPLACE FUNCTION vandelay.template_overlay_bib_record ( v_marc TEXT, eg_id BIGINT, merge_profile_id INT ) RETURNS BOOL AS $$
14387 DECLARE
14388     merge_profile   vandelay.merge_profile%ROWTYPE;
14389     dyn_profile     vandelay.compile_profile%ROWTYPE;
14390     editor_string   TEXT;
14391     editor_id       INT;
14392     source_marc     TEXT;
14393     target_marc     TEXT;
14394     eg_marc         TEXT;
14395     replace_rule    TEXT;
14396     match_count     INT;
14397 BEGIN
14398
14399     SELECT  b.marc INTO eg_marc
14400       FROM  biblio.record_entry b
14401       WHERE b.id = eg_id
14402       LIMIT 1;
14403
14404     IF eg_marc IS NULL OR v_marc IS NULL THEN
14405         -- RAISE NOTICE 'no marc for template or bib record';
14406         RETURN FALSE;
14407     END IF;
14408
14409     dyn_profile := vandelay.compile_profile( v_marc );
14410
14411     IF merge_profile_id IS NOT NULL THEN
14412         SELECT * INTO merge_profile FROM vandelay.merge_profile WHERE id = merge_profile_id;
14413         IF FOUND THEN
14414             dyn_profile.add_rule := BTRIM( dyn_profile.add_rule || ',' || COALESCE(merge_profile.add_spec,''), ',');
14415             dyn_profile.strip_rule := BTRIM( dyn_profile.strip_rule || ',' || COALESCE(merge_profile.strip_spec,''), ',');
14416             dyn_profile.replace_rule := BTRIM( dyn_profile.replace_rule || ',' || COALESCE(merge_profile.replace_spec,''), ',');
14417             dyn_profile.preserve_rule := BTRIM( dyn_profile.preserve_rule || ',' || COALESCE(merge_profile.preserve_spec,''), ',');
14418         END IF;
14419     END IF;
14420
14421     IF dyn_profile.replace_rule <> '' AND dyn_profile.preserve_rule <> '' THEN
14422         -- RAISE NOTICE 'both replace [%] and preserve [%] specified', dyn_profile.replace_rule, dyn_profile.preserve_rule;
14423         RETURN FALSE;
14424     END IF;
14425
14426     IF dyn_profile.replace_rule <> '' THEN
14427         source_marc = v_marc;
14428         target_marc = eg_marc;
14429         replace_rule = dyn_profile.replace_rule;
14430     ELSE
14431         source_marc = eg_marc;
14432         target_marc = v_marc;
14433         replace_rule = dyn_profile.preserve_rule;
14434     END IF;
14435
14436     UPDATE  biblio.record_entry
14437       SET   marc = vandelay.merge_record_xml( target_marc, source_marc, dyn_profile.add_rule, replace_rule, dyn_profile.strip_rule )
14438       WHERE id = eg_id;
14439
14440     IF NOT FOUND THEN
14441         -- RAISE NOTICE 'update of biblio.record_entry failed';
14442         RETURN FALSE;
14443     END IF;
14444
14445     RETURN TRUE;
14446
14447 END;
14448 $$ LANGUAGE PLPGSQL;
14449
14450 CREATE OR REPLACE FUNCTION vandelay.template_overlay_bib_record ( v_marc TEXT, eg_id BIGINT) RETURNS BOOL AS $$
14451     SELECT vandelay.template_overlay_bib_record( $1, $2, NULL);
14452 $$ LANGUAGE SQL;
14453
14454 CREATE OR REPLACE FUNCTION vandelay.overlay_bib_record ( import_id BIGINT, eg_id BIGINT, merge_profile_id INT ) RETURNS BOOL AS $$
14455 DECLARE
14456     merge_profile   vandelay.merge_profile%ROWTYPE;
14457     dyn_profile     vandelay.compile_profile%ROWTYPE;
14458     editor_string   TEXT;
14459     editor_id       INT;
14460     source_marc     TEXT;
14461     target_marc     TEXT;
14462     eg_marc         TEXT;
14463     v_marc          TEXT;
14464     replace_rule    TEXT;
14465     match_count     INT;
14466 BEGIN
14467
14468     SELECT  q.marc INTO v_marc
14469       FROM  vandelay.queued_record q
14470             JOIN vandelay.bib_match m ON (m.queued_record = q.id AND q.id = import_id)
14471       LIMIT 1;
14472
14473     IF v_marc IS NULL THEN
14474         -- RAISE NOTICE 'no marc for vandelay or bib record';
14475         RETURN FALSE;
14476     END IF;
14477
14478     IF vandelay.template_overlay_bib_record( v_marc, eg_id, merge_profile_id) THEN
14479         UPDATE  vandelay.queued_bib_record
14480           SET   imported_as = eg_id,
14481                 import_time = NOW()
14482           WHERE id = import_id;
14483
14484         editor_string := (oils_xpath('//*[@tag="905"]/*[@code="u"]/text()',v_marc))[1];
14485
14486         IF editor_string IS NOT NULL AND editor_string <> '' THEN
14487             SELECT usr INTO editor_id FROM actor.card WHERE barcode = editor_string;
14488
14489             IF editor_id IS NULL THEN
14490                 SELECT id INTO editor_id FROM actor.usr WHERE usrname = editor_string;
14491             END IF;
14492
14493             IF editor_id IS NOT NULL THEN
14494                 UPDATE biblio.record_entry SET editor = editor_id WHERE id = eg_id;
14495             END IF;
14496         END IF;
14497
14498         RETURN TRUE;
14499     END IF;
14500
14501     -- RAISE NOTICE 'update of biblio.record_entry failed';
14502
14503     RETURN FALSE;
14504
14505 END;
14506 $$ LANGUAGE PLPGSQL;
14507
14508 CREATE OR REPLACE FUNCTION vandelay.auto_overlay_bib_record ( import_id BIGINT, merge_profile_id INT ) RETURNS BOOL AS $$
14509 DECLARE
14510     eg_id           BIGINT;
14511     match_count     INT;
14512     match_attr      vandelay.bib_attr_definition%ROWTYPE;
14513 BEGIN
14514
14515     PERFORM * FROM vandelay.queued_bib_record WHERE import_time IS NOT NULL AND id = import_id;
14516
14517     IF FOUND THEN
14518         -- RAISE NOTICE 'already imported, cannot auto-overlay'
14519         RETURN FALSE;
14520     END IF;
14521
14522     SELECT COUNT(*) INTO match_count FROM vandelay.bib_match WHERE queued_record = import_id;
14523
14524     IF match_count <> 1 THEN
14525         -- RAISE NOTICE 'not an exact match';
14526         RETURN FALSE;
14527     END IF;
14528
14529     SELECT  d.* INTO match_attr
14530       FROM  vandelay.bib_attr_definition d
14531             JOIN vandelay.queued_bib_record_attr a ON (a.field = d.id)
14532             JOIN vandelay.bib_match m ON (m.matched_attr = a.id)
14533       WHERE m.queued_record = import_id;
14534
14535     IF NOT (match_attr.xpath ~ '@tag="901"' AND match_attr.xpath ~ '@code="c"') THEN
14536         -- RAISE NOTICE 'not a 901c match: %', match_attr.xpath;
14537         RETURN FALSE;
14538     END IF;
14539
14540     SELECT  m.eg_record INTO eg_id
14541       FROM  vandelay.bib_match m
14542       WHERE m.queued_record = import_id
14543       LIMIT 1;
14544
14545     IF eg_id IS NULL THEN
14546         RETURN FALSE;
14547     END IF;
14548
14549     RETURN vandelay.overlay_bib_record( import_id, eg_id, merge_profile_id );
14550 END;
14551 $$ LANGUAGE PLPGSQL;
14552
14553 CREATE OR REPLACE FUNCTION vandelay.auto_overlay_bib_queue ( queue_id BIGINT, merge_profile_id INT ) RETURNS SETOF BIGINT AS $$
14554 DECLARE
14555     queued_record   vandelay.queued_bib_record%ROWTYPE;
14556 BEGIN
14557
14558     FOR queued_record IN SELECT * FROM vandelay.queued_bib_record WHERE queue = queue_id AND import_time IS NULL LOOP
14559
14560         IF vandelay.auto_overlay_bib_record( queued_record.id, merge_profile_id ) THEN
14561             RETURN NEXT queued_record.id;
14562         END IF;
14563
14564     END LOOP;
14565
14566     RETURN;
14567
14568 END;
14569 $$ LANGUAGE PLPGSQL;
14570
14571 CREATE OR REPLACE FUNCTION vandelay.auto_overlay_bib_queue ( queue_id BIGINT ) RETURNS SETOF BIGINT AS $$
14572     SELECT * FROM vandelay.auto_overlay_bib_queue( $1, NULL );
14573 $$ LANGUAGE SQL;
14574
14575 CREATE OR REPLACE FUNCTION vandelay.overlay_authority_record ( import_id BIGINT, eg_id BIGINT, merge_profile_id INT ) RETURNS BOOL AS $$
14576 DECLARE
14577     merge_profile   vandelay.merge_profile%ROWTYPE;
14578     dyn_profile     vandelay.compile_profile%ROWTYPE;
14579     source_marc     TEXT;
14580     target_marc     TEXT;
14581     eg_marc         TEXT;
14582     v_marc          TEXT;
14583     replace_rule    TEXT;
14584     match_count     INT;
14585 BEGIN
14586
14587     SELECT  b.marc INTO eg_marc
14588       FROM  authority.record_entry b
14589             JOIN vandelay.authority_match m ON (m.eg_record = b.id AND m.queued_record = import_id)
14590       LIMIT 1;
14591
14592     SELECT  q.marc INTO v_marc
14593       FROM  vandelay.queued_record q
14594             JOIN vandelay.authority_match m ON (m.queued_record = q.id AND q.id = import_id)
14595       LIMIT 1;
14596
14597     IF eg_marc IS NULL OR v_marc IS NULL THEN
14598         -- RAISE NOTICE 'no marc for vandelay or authority record';
14599         RETURN FALSE;
14600     END IF;
14601
14602     dyn_profile := vandelay.compile_profile( v_marc );
14603
14604     IF merge_profile_id IS NOT NULL THEN
14605         SELECT * INTO merge_profile FROM vandelay.merge_profile WHERE id = merge_profile_id;
14606         IF FOUND THEN
14607             dyn_profile.add_rule := BTRIM( dyn_profile.add_rule || ',' || COALESCE(merge_profile.add_spec,''), ',');
14608             dyn_profile.strip_rule := BTRIM( dyn_profile.strip_rule || ',' || COALESCE(merge_profile.strip_spec,''), ',');
14609             dyn_profile.replace_rule := BTRIM( dyn_profile.replace_rule || ',' || COALESCE(merge_profile.replace_spec,''), ',');
14610             dyn_profile.preserve_rule := BTRIM( dyn_profile.preserve_rule || ',' || COALESCE(merge_profile.preserve_spec,''), ',');
14611         END IF;
14612     END IF;
14613
14614     IF dyn_profile.replace_rule <> '' AND dyn_profile.preserve_rule <> '' THEN
14615         -- RAISE NOTICE 'both replace [%] and preserve [%] specified', dyn_profile.replace_rule, dyn_profile.preserve_rule;
14616         RETURN FALSE;
14617     END IF;
14618
14619     IF dyn_profile.replace_rule <> '' THEN
14620         source_marc = v_marc;
14621         target_marc = eg_marc;
14622         replace_rule = dyn_profile.replace_rule;
14623     ELSE
14624         source_marc = eg_marc;
14625         target_marc = v_marc;
14626         replace_rule = dyn_profile.preserve_rule;
14627     END IF;
14628
14629     UPDATE  authority.record_entry
14630       SET   marc = vandelay.merge_record_xml( target_marc, source_marc, dyn_profile.add_rule, replace_rule, dyn_profile.strip_rule )
14631       WHERE id = eg_id;
14632
14633     IF FOUND THEN
14634         UPDATE  vandelay.queued_authority_record
14635           SET   imported_as = eg_id,
14636                 import_time = NOW()
14637           WHERE id = import_id;
14638         RETURN TRUE;
14639     END IF;
14640
14641     -- RAISE NOTICE 'update of authority.record_entry failed';
14642
14643     RETURN FALSE;
14644
14645 END;
14646 $$ LANGUAGE PLPGSQL;
14647
14648 CREATE OR REPLACE FUNCTION vandelay.auto_overlay_authority_record ( import_id BIGINT, merge_profile_id INT ) RETURNS BOOL AS $$
14649 DECLARE
14650     eg_id           BIGINT;
14651     match_count     INT;
14652 BEGIN
14653     SELECT COUNT(*) INTO match_count FROM vandelay.authority_match WHERE queued_record = import_id;
14654
14655     IF match_count <> 1 THEN
14656         -- RAISE NOTICE 'not an exact match';
14657         RETURN FALSE;
14658     END IF;
14659
14660     SELECT  m.eg_record INTO eg_id
14661       FROM  vandelay.authority_match m
14662       WHERE m.queued_record = import_id
14663       LIMIT 1;
14664
14665     IF eg_id IS NULL THEN
14666         RETURN FALSE;
14667     END IF;
14668
14669     RETURN vandelay.overlay_authority_record( import_id, eg_id, merge_profile_id );
14670 END;
14671 $$ LANGUAGE PLPGSQL;
14672
14673 CREATE OR REPLACE FUNCTION vandelay.auto_overlay_authority_queue ( queue_id BIGINT, merge_profile_id INT ) RETURNS SETOF BIGINT AS $$
14674 DECLARE
14675     queued_record   vandelay.queued_authority_record%ROWTYPE;
14676 BEGIN
14677
14678     FOR queued_record IN SELECT * FROM vandelay.queued_authority_record WHERE queue = queue_id AND import_time IS NULL LOOP
14679
14680         IF vandelay.auto_overlay_authority_record( queued_record.id, merge_profile_id ) THEN
14681             RETURN NEXT queued_record.id;
14682         END IF;
14683
14684     END LOOP;
14685
14686     RETURN;
14687
14688 END;
14689 $$ LANGUAGE PLPGSQL;
14690
14691 CREATE OR REPLACE FUNCTION vandelay.auto_overlay_authority_queue ( queue_id BIGINT ) RETURNS SETOF BIGINT AS $$
14692     SELECT * FROM vandelay.auto_overlay_authority_queue( $1, NULL );
14693 $$ LANGUAGE SQL;
14694
14695 CREATE TYPE vandelay.tcn_data AS (tcn TEXT, tcn_source TEXT, used BOOL);
14696 CREATE OR REPLACE FUNCTION vandelay.find_bib_tcn_data ( xml TEXT ) RETURNS SETOF vandelay.tcn_data AS $_$
14697 DECLARE
14698     eg_tcn          TEXT;
14699     eg_tcn_source   TEXT;
14700     output          vandelay.tcn_data%ROWTYPE;
14701 BEGIN
14702
14703     -- 001/003
14704     eg_tcn := BTRIM((oils_xpath('//*[@tag="001"]/text()',xml))[1]);
14705     IF eg_tcn IS NOT NULL AND eg_tcn <> '' THEN
14706
14707         eg_tcn_source := BTRIM((oils_xpath('//*[@tag="003"]/text()',xml))[1]);
14708         IF eg_tcn_source IS NULL OR eg_tcn_source = '' THEN
14709             eg_tcn_source := 'System Local';
14710         END IF;
14711
14712         PERFORM id FROM biblio.record_entry WHERE tcn_value = eg_tcn  AND NOT deleted;
14713
14714         IF NOT FOUND THEN
14715             output.used := FALSE;
14716         ELSE
14717             output.used := TRUE;
14718         END IF;
14719
14720         output.tcn := eg_tcn;
14721         output.tcn_source := eg_tcn_source;
14722         RETURN NEXT output;
14723
14724     END IF;
14725
14726     -- 901 ab
14727     eg_tcn := BTRIM((oils_xpath('//*[@tag="901"]/*[@code="a"]/text()',xml))[1]);
14728     IF eg_tcn IS NOT NULL AND eg_tcn <> '' THEN
14729
14730         eg_tcn_source := BTRIM((oils_xpath('//*[@tag="901"]/*[@code="b"]/text()',xml))[1]);
14731         IF eg_tcn_source IS NULL OR eg_tcn_source = '' THEN
14732             eg_tcn_source := 'System Local';
14733         END IF;
14734
14735         PERFORM id FROM biblio.record_entry WHERE tcn_value = eg_tcn  AND NOT deleted;
14736
14737         IF NOT FOUND THEN
14738             output.used := FALSE;
14739         ELSE
14740             output.used := TRUE;
14741         END IF;
14742
14743         output.tcn := eg_tcn;
14744         output.tcn_source := eg_tcn_source;
14745         RETURN NEXT output;
14746
14747     END IF;
14748
14749     -- 039 ab
14750     eg_tcn := BTRIM((oils_xpath('//*[@tag="039"]/*[@code="a"]/text()',xml))[1]);
14751     IF eg_tcn IS NOT NULL AND eg_tcn <> '' THEN
14752
14753         eg_tcn_source := BTRIM((oils_xpath('//*[@tag="039"]/*[@code="b"]/text()',xml))[1]);
14754         IF eg_tcn_source IS NULL OR eg_tcn_source = '' THEN
14755             eg_tcn_source := 'System Local';
14756         END IF;
14757
14758         PERFORM id FROM biblio.record_entry WHERE tcn_value = eg_tcn  AND NOT deleted;
14759
14760         IF NOT FOUND THEN
14761             output.used := FALSE;
14762         ELSE
14763             output.used := TRUE;
14764         END IF;
14765
14766         output.tcn := eg_tcn;
14767         output.tcn_source := eg_tcn_source;
14768         RETURN NEXT output;
14769
14770     END IF;
14771
14772     -- 020 a
14773     eg_tcn := REGEXP_REPLACE((oils_xpath('//*[@tag="020"]/*[@code="a"]/text()',xml))[1], $re$^(\w+).*?$$re$, $re$\1$re$);
14774     IF eg_tcn IS NOT NULL AND eg_tcn <> '' THEN
14775
14776         eg_tcn_source := 'ISBN';
14777
14778         PERFORM id FROM biblio.record_entry WHERE tcn_value = eg_tcn  AND NOT deleted;
14779
14780         IF NOT FOUND THEN
14781             output.used := FALSE;
14782         ELSE
14783             output.used := TRUE;
14784         END IF;
14785
14786         output.tcn := eg_tcn;
14787         output.tcn_source := eg_tcn_source;
14788         RETURN NEXT output;
14789
14790     END IF;
14791
14792     -- 022 a
14793     eg_tcn := REGEXP_REPLACE((oils_xpath('//*[@tag="022"]/*[@code="a"]/text()',xml))[1], $re$^(\w+).*?$$re$, $re$\1$re$);
14794     IF eg_tcn IS NOT NULL AND eg_tcn <> '' THEN
14795
14796         eg_tcn_source := 'ISSN';
14797
14798         PERFORM id FROM biblio.record_entry WHERE tcn_value = eg_tcn  AND NOT deleted;
14799
14800         IF NOT FOUND THEN
14801             output.used := FALSE;
14802         ELSE
14803             output.used := TRUE;
14804         END IF;
14805
14806         output.tcn := eg_tcn;
14807         output.tcn_source := eg_tcn_source;
14808         RETURN NEXT output;
14809
14810     END IF;
14811
14812     -- 010 a
14813     eg_tcn := REGEXP_REPLACE((oils_xpath('//*[@tag="010"]/*[@code="a"]/text()',xml))[1], $re$^(\w+).*?$$re$, $re$\1$re$);
14814     IF eg_tcn IS NOT NULL AND eg_tcn <> '' THEN
14815
14816         eg_tcn_source := 'LCCN';
14817
14818         PERFORM id FROM biblio.record_entry WHERE tcn_value = eg_tcn  AND NOT deleted;
14819
14820         IF NOT FOUND THEN
14821             output.used := FALSE;
14822         ELSE
14823             output.used := TRUE;
14824         END IF;
14825
14826         output.tcn := eg_tcn;
14827         output.tcn_source := eg_tcn_source;
14828         RETURN NEXT output;
14829
14830     END IF;
14831
14832     -- 035 a
14833     eg_tcn := REGEXP_REPLACE((oils_xpath('//*[@tag="035"]/*[@code="a"]/text()',xml))[1], $re$^.*?(\w+)$$re$, $re$\1$re$);
14834     IF eg_tcn IS NOT NULL AND eg_tcn <> '' THEN
14835
14836         eg_tcn_source := 'System Legacy';
14837
14838         PERFORM id FROM biblio.record_entry WHERE tcn_value = eg_tcn  AND NOT deleted;
14839
14840         IF NOT FOUND THEN
14841             output.used := FALSE;
14842         ELSE
14843             output.used := TRUE;
14844         END IF;
14845
14846         output.tcn := eg_tcn;
14847         output.tcn_source := eg_tcn_source;
14848         RETURN NEXT output;
14849
14850     END IF;
14851
14852     RETURN;
14853 END;
14854 $_$ LANGUAGE PLPGSQL;
14855
14856 CREATE INDEX claim_lid_idx ON acq.claim( lineitem_detail );
14857
14858 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);
14859
14860 UPDATE biblio.record_entry SET marc = '<record xmlns="http://www.loc.gov/MARC21/slim"/>' WHERE id = -1;
14861
14862 CREATE INDEX metabib_title_field_entry_value_idx ON metabib.title_field_entry (SUBSTRING(value,1,1024)) WHERE index_vector = ''::TSVECTOR;
14863 CREATE INDEX metabib_author_field_entry_value_idx ON metabib.author_field_entry (SUBSTRING(value,1,1024)) WHERE index_vector = ''::TSVECTOR;
14864 CREATE INDEX metabib_subject_field_entry_value_idx ON metabib.subject_field_entry (SUBSTRING(value,1,1024)) WHERE index_vector = ''::TSVECTOR;
14865 CREATE INDEX metabib_keyword_field_entry_value_idx ON metabib.keyword_field_entry (SUBSTRING(value,1,1024)) WHERE index_vector = ''::TSVECTOR;
14866 CREATE INDEX metabib_series_field_entry_value_idx ON metabib.series_field_entry (SUBSTRING(value,1,1024)) WHERE index_vector = ''::TSVECTOR;
14867
14868 CREATE INDEX metabib_author_field_entry_source_idx ON metabib.author_field_entry (source);
14869 CREATE INDEX metabib_keyword_field_entry_source_idx ON metabib.keyword_field_entry (source);
14870 CREATE INDEX metabib_title_field_entry_source_idx ON metabib.title_field_entry (source);
14871 CREATE INDEX metabib_series_field_entry_source_idx ON metabib.series_field_entry (source);
14872
14873 ALTER TABLE metabib.series_field_entry
14874         ADD CONSTRAINT metabib_series_field_entry_source_pkey FOREIGN KEY (source)
14875                 REFERENCES biblio.record_entry (id)
14876                 ON DELETE CASCADE
14877                 DEFERRABLE INITIALLY DEFERRED;
14878
14879 ALTER TABLE metabib.series_field_entry
14880         ADD CONSTRAINT metabib_series_field_entry_field_pkey FOREIGN KEY (field)
14881                 REFERENCES config.metabib_field (id)
14882                 ON DELETE CASCADE
14883                 DEFERRABLE INITIALLY DEFERRED;
14884
14885 CREATE TABLE acq.claim_policy_action (
14886         id              SERIAL       PRIMARY KEY,
14887         claim_policy    INT          NOT NULL REFERENCES acq.claim_policy
14888                                  ON DELETE CASCADE
14889                                      DEFERRABLE INITIALLY DEFERRED,
14890         action_interval INTERVAL     NOT NULL,
14891         action          INT          NOT NULL REFERENCES acq.claim_event_type
14892                                      DEFERRABLE INITIALLY DEFERRED,
14893         CONSTRAINT action_sequence UNIQUE (claim_policy, action_interval)
14894 );
14895
14896 CREATE OR REPLACE FUNCTION public.ingest_acq_marc ( ) RETURNS TRIGGER AS $function$
14897 DECLARE
14898     value       TEXT; 
14899     atype       TEXT; 
14900     prov        INT;
14901     pos         INT;
14902     adef        RECORD;
14903     xpath_string    TEXT;
14904 BEGIN
14905     FOR adef IN SELECT *,tableoid FROM acq.lineitem_attr_definition LOOP
14906     
14907         SELECT relname::TEXT INTO atype FROM pg_class WHERE oid = adef.tableoid;
14908       
14909         IF (atype NOT IN ('lineitem_usr_attr_definition','lineitem_local_attr_definition')) THEN
14910             IF (atype = 'lineitem_provider_attr_definition') THEN
14911                 SELECT provider INTO prov FROM acq.lineitem_provider_attr_definition WHERE id = adef.id;
14912                 CONTINUE WHEN NEW.provider IS NULL OR prov <> NEW.provider;
14913             END IF;
14914             
14915             IF (atype = 'lineitem_provider_attr_definition') THEN
14916                 SELECT xpath INTO xpath_string FROM acq.lineitem_provider_attr_definition WHERE id = adef.id;
14917             ELSIF (atype = 'lineitem_marc_attr_definition') THEN
14918                 SELECT xpath INTO xpath_string FROM acq.lineitem_marc_attr_definition WHERE id = adef.id;
14919             ELSIF (atype = 'lineitem_generated_attr_definition') THEN
14920                 SELECT xpath INTO xpath_string FROM acq.lineitem_generated_attr_definition WHERE id = adef.id;
14921             END IF;
14922       
14923             xpath_string := REGEXP_REPLACE(xpath_string,$re$//?text\(\)$$re$,'');
14924
14925             IF (adef.code = 'title' OR adef.code = 'author') THEN
14926                 -- title and author should not be split
14927                 -- FIXME: once oils_xpath can grok XPATH 2.0 functions, we can use
14928                 -- string-join in the xpath and remove this special case
14929                 SELECT extract_acq_marc_field(id, xpath_string, adef.remove) INTO value FROM acq.lineitem WHERE id = NEW.id;
14930                 IF (value IS NOT NULL AND value <> '') THEN
14931                     INSERT INTO acq.lineitem_attr (lineitem, definition, attr_type, attr_name, attr_value)
14932                         VALUES (NEW.id, adef.id, atype, adef.code, value);
14933                 END IF;
14934             ELSE
14935                 pos := 1;
14936
14937                 LOOP
14938                     SELECT extract_acq_marc_field(id, xpath_string || '[' || pos || ']', adef.remove) INTO value FROM acq.lineitem WHERE id = NEW.id;
14939       
14940                     IF (value IS NOT NULL AND value <> '') THEN
14941                         INSERT INTO acq.lineitem_attr (lineitem, definition, attr_type, attr_name, attr_value)
14942                             VALUES (NEW.id, adef.id, atype, adef.code, value);
14943                     ELSE
14944                         EXIT;
14945                     END IF;
14946
14947                     pos := pos + 1;
14948                 END LOOP;
14949             END IF;
14950
14951         END IF;
14952
14953     END LOOP;
14954
14955     RETURN NULL;
14956 END;
14957 $function$ LANGUAGE PLPGSQL;
14958
14959 UPDATE config.metabib_field SET label = name;
14960 ALTER TABLE config.metabib_field ALTER COLUMN label SET NOT NULL;
14961
14962 ALTER TABLE config.metabib_field ADD CONSTRAINT metabib_field_field_class_fkey
14963          FOREIGN KEY (field_class) REFERENCES config.metabib_class (name);
14964
14965 ALTER TABLE config.metabib_field DROP CONSTRAINT metabib_field_field_class_check;
14966
14967 ALTER TABLE config.metabib_field ADD CONSTRAINT metabib_field_format_fkey FOREIGN KEY (format) REFERENCES config.xml_transform (name);
14968
14969 CREATE TABLE config.metabib_search_alias (
14970     alias       TEXT    PRIMARY KEY,
14971     field_class TEXT    NOT NULL REFERENCES config.metabib_class (name),
14972     field       INT     REFERENCES config.metabib_field (id)
14973 );
14974
14975 INSERT INTO config.metabib_search_alias (alias,field_class) VALUES ('kw','keyword');
14976 INSERT INTO config.metabib_search_alias (alias,field_class) VALUES ('eg.keyword','keyword');
14977 INSERT INTO config.metabib_search_alias (alias,field_class) VALUES ('dc.publisher','keyword');
14978 INSERT INTO config.metabib_search_alias (alias,field_class) VALUES ('dc.identifier','keyword');
14979 INSERT INTO config.metabib_search_alias (alias,field_class) VALUES ('bib.subjecttitle','keyword');
14980 INSERT INTO config.metabib_search_alias (alias,field_class) VALUES ('bib.genre','keyword');
14981 INSERT INTO config.metabib_search_alias (alias,field_class) VALUES ('bib.edition','keyword');
14982 INSERT INTO config.metabib_search_alias (alias,field_class) VALUES ('srw.serverchoice','keyword');
14983
14984 INSERT INTO config.metabib_search_alias (alias,field_class) VALUES ('au','author');
14985 INSERT INTO config.metabib_search_alias (alias,field_class) VALUES ('name','author');
14986 INSERT INTO config.metabib_search_alias (alias,field_class) VALUES ('creator','author');
14987 INSERT INTO config.metabib_search_alias (alias,field_class) VALUES ('eg.author','author');
14988 INSERT INTO config.metabib_search_alias (alias,field_class) VALUES ('eg.name','author');
14989 INSERT INTO config.metabib_search_alias (alias,field_class) VALUES ('dc.creator','author');
14990 INSERT INTO config.metabib_search_alias (alias,field_class) VALUES ('dc.contributor','author');
14991 INSERT INTO config.metabib_search_alias (alias,field_class) VALUES ('bib.name','author');
14992 INSERT INTO config.metabib_search_alias (alias,field_class,field) VALUES ('bib.namepersonal','author',8);
14993 INSERT INTO config.metabib_search_alias (alias,field_class,field) VALUES ('bib.namepersonalfamily','author',8);
14994 INSERT INTO config.metabib_search_alias (alias,field_class,field) VALUES ('bib.namepersonalgiven','author',8);
14995 INSERT INTO config.metabib_search_alias (alias,field_class,field) VALUES ('bib.namecorporate','author',7);
14996 INSERT INTO config.metabib_search_alias (alias,field_class,field) VALUES ('bib.nameconference','author',9);
14997
14998 INSERT INTO config.metabib_search_alias (alias,field_class) VALUES ('ti','title');
14999 INSERT INTO config.metabib_search_alias (alias,field_class) VALUES ('eg.title','title');
15000 INSERT INTO config.metabib_search_alias (alias,field_class) VALUES ('dc.title','title');
15001 INSERT INTO config.metabib_search_alias (alias,field_class,field) VALUES ('bib.titleabbreviated','title',2);
15002 INSERT INTO config.metabib_search_alias (alias,field_class,field) VALUES ('bib.titleuniform','title',5);
15003 INSERT INTO config.metabib_search_alias (alias,field_class,field) VALUES ('bib.titletranslated','title',3);
15004 INSERT INTO config.metabib_search_alias (alias,field_class,field) VALUES ('bib.titlealternative','title',4);
15005 INSERT INTO config.metabib_search_alias (alias,field_class,field) VALUES ('bib.title','title',2);
15006
15007 INSERT INTO config.metabib_search_alias (alias,field_class) VALUES ('su','subject');
15008 INSERT INTO config.metabib_search_alias (alias,field_class) VALUES ('eg.subject','subject');
15009 INSERT INTO config.metabib_search_alias (alias,field_class) VALUES ('dc.subject','subject');
15010 INSERT INTO config.metabib_search_alias (alias,field_class,field) VALUES ('bib.subjectplace','subject',11);
15011 INSERT INTO config.metabib_search_alias (alias,field_class,field) VALUES ('bib.subjectname','subject',12);
15012 INSERT INTO config.metabib_search_alias (alias,field_class,field) VALUES ('bib.subjectoccupation','subject',16);
15013
15014 INSERT INTO config.metabib_search_alias (alias,field_class) VALUES ('se','series');
15015 INSERT INTO config.metabib_search_alias (alias,field_class) VALUES ('eg.series','series');
15016 INSERT INTO config.metabib_search_alias (alias,field_class,field) VALUES ('bib.titleseries','series',1);
15017
15018 UPDATE config.metabib_field SET facet_field=TRUE WHERE id = 1;
15019 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;
15020 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;
15021 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;
15022 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;
15023
15024 UPDATE config.metabib_field SET facet_field=TRUE WHERE id = 11;
15025 UPDATE config.metabib_field SET facet_field=TRUE , facet_xpath=$$*[local-name()='namePart']$$ WHERE id = 12;
15026 UPDATE config.metabib_field SET facet_field=TRUE WHERE id = 13;
15027 UPDATE config.metabib_field SET facet_field=TRUE WHERE id = 14;
15028
15029 CREATE INDEX metabib_rec_descriptor_item_type_idx ON metabib.rec_descriptor (item_type);
15030 CREATE INDEX metabib_rec_descriptor_item_form_idx ON metabib.rec_descriptor (item_form);
15031 CREATE INDEX metabib_rec_descriptor_bib_level_idx ON metabib.rec_descriptor (bib_level);
15032 CREATE INDEX metabib_rec_descriptor_control_type_idx ON metabib.rec_descriptor (control_type);
15033 CREATE INDEX metabib_rec_descriptor_char_encoding_idx ON metabib.rec_descriptor (char_encoding);
15034 CREATE INDEX metabib_rec_descriptor_enc_level_idx ON metabib.rec_descriptor (enc_level);
15035 CREATE INDEX metabib_rec_descriptor_audience_idx ON metabib.rec_descriptor (audience);
15036 CREATE INDEX metabib_rec_descriptor_lit_form_idx ON metabib.rec_descriptor (lit_form);
15037 CREATE INDEX metabib_rec_descriptor_cat_form_idx ON metabib.rec_descriptor (cat_form);
15038 CREATE INDEX metabib_rec_descriptor_pub_status_idx ON metabib.rec_descriptor (pub_status);
15039 CREATE INDEX metabib_rec_descriptor_item_lang_idx ON metabib.rec_descriptor (item_lang);
15040 CREATE INDEX metabib_rec_descriptor_vr_format_idx ON metabib.rec_descriptor (vr_format);
15041 CREATE INDEX metabib_rec_descriptor_date1_idx ON metabib.rec_descriptor (date1);
15042 CREATE INDEX metabib_rec_descriptor_dates_idx ON metabib.rec_descriptor (date1,date2);
15043
15044 CREATE TABLE asset.opac_visible_copies (
15045   id        BIGINT primary key, -- copy id
15046   record    BIGINT,
15047   circ_lib  INTEGER
15048 );
15049 COMMENT ON TABLE asset.opac_visible_copies IS $$
15050 Materialized view of copies that are visible in the OPAC, used by
15051 search.query_parser_fts() to speed up OPAC visibility checks on large
15052 databases.  Contents are maintained by a set of triggers.
15053 $$;
15054 CREATE INDEX opac_visible_copies_idx1 on asset.opac_visible_copies (record, circ_lib);
15055
15056 CREATE OR REPLACE FUNCTION search.query_parser_fts (
15057
15058     param_search_ou INT,
15059     param_depth     INT,
15060     param_query     TEXT,
15061     param_statuses  INT[],
15062     param_locations INT[],
15063     param_offset    INT,
15064     param_check     INT,
15065     param_limit     INT,
15066     metarecord      BOOL,
15067     staff           BOOL
15068  
15069 ) RETURNS SETOF search.search_result AS $func$
15070 DECLARE
15071
15072     current_res         search.search_result%ROWTYPE;
15073     search_org_list     INT[];
15074
15075     check_limit         INT;
15076     core_limit          INT;
15077     core_offset         INT;
15078     tmp_int             INT;
15079
15080     core_result         RECORD;
15081     core_cursor         REFCURSOR;
15082     core_rel_query      TEXT;
15083
15084     total_count         INT := 0;
15085     check_count         INT := 0;
15086     deleted_count       INT := 0;
15087     visible_count       INT := 0;
15088     excluded_count      INT := 0;
15089
15090 BEGIN
15091
15092     check_limit := COALESCE( param_check, 1000 );
15093     core_limit  := COALESCE( param_limit, 25000 );
15094     core_offset := COALESCE( param_offset, 0 );
15095
15096     -- core_skip_chk := COALESCE( param_skip_chk, 1 );
15097
15098     IF param_search_ou > 0 THEN
15099         IF param_depth IS NOT NULL THEN
15100             SELECT array_accum(distinct id) INTO search_org_list FROM actor.org_unit_descendants( param_search_ou, param_depth );
15101         ELSE
15102             SELECT array_accum(distinct id) INTO search_org_list FROM actor.org_unit_descendants( param_search_ou );
15103         END IF;
15104     ELSIF param_search_ou < 0 THEN
15105         SELECT array_accum(distinct org_unit) INTO search_org_list FROM actor.org_lasso_map WHERE lasso = -param_search_ou;
15106     ELSIF param_search_ou = 0 THEN
15107         -- reserved for user lassos (ou_buckets/type='lasso') with ID passed in depth ... hack? sure.
15108     END IF;
15109
15110     OPEN core_cursor FOR EXECUTE param_query;
15111
15112     LOOP
15113
15114         FETCH core_cursor INTO core_result;
15115         EXIT WHEN NOT FOUND;
15116         EXIT WHEN total_count >= core_limit;
15117
15118         total_count := total_count + 1;
15119
15120         CONTINUE WHEN total_count NOT BETWEEN  core_offset + 1 AND check_limit + core_offset;
15121
15122         check_count := check_count + 1;
15123
15124         PERFORM 1 FROM biblio.record_entry b WHERE NOT b.deleted AND b.id IN ( SELECT * FROM search.explode_array( core_result.records ) );
15125         IF NOT FOUND THEN
15126             -- RAISE NOTICE ' % were all deleted ... ', core_result.records;
15127             deleted_count := deleted_count + 1;
15128             CONTINUE;
15129         END IF;
15130
15131         PERFORM 1
15132           FROM  biblio.record_entry b
15133                 JOIN config.bib_source s ON (b.source = s.id)
15134           WHERE s.transcendant
15135                 AND b.id IN ( SELECT * FROM search.explode_array( core_result.records ) );
15136
15137         IF FOUND THEN
15138             -- RAISE NOTICE ' % were all transcendant ... ', core_result.records;
15139             visible_count := visible_count + 1;
15140
15141             current_res.id = core_result.id;
15142             current_res.rel = core_result.rel;
15143
15144             tmp_int := 1;
15145             IF metarecord THEN
15146                 SELECT COUNT(DISTINCT s.source) INTO tmp_int FROM metabib.metarecord_source_map s WHERE s.metarecord = core_result.id;
15147             END IF;
15148
15149             IF tmp_int = 1 THEN
15150                 current_res.record = core_result.records[1];
15151             ELSE
15152                 current_res.record = NULL;
15153             END IF;
15154
15155             RETURN NEXT current_res;
15156
15157             CONTINUE;
15158         END IF;
15159
15160         PERFORM 1
15161           FROM  asset.call_number cn
15162                 JOIN asset.uri_call_number_map map ON (map.call_number = cn.id)
15163                 JOIN asset.uri uri ON (map.uri = uri.id)
15164           WHERE NOT cn.deleted
15165                 AND cn.label = '##URI##'
15166                 AND uri.active
15167                 AND ( param_locations IS NULL OR array_upper(param_locations, 1) IS NULL )
15168                 AND cn.record IN ( SELECT * FROM search.explode_array( core_result.records ) )
15169                 AND cn.owning_lib IN ( SELECT * FROM search.explode_array( search_org_list ) )
15170           LIMIT 1;
15171
15172         IF FOUND THEN
15173             -- RAISE NOTICE ' % have at least one URI ... ', core_result.records;
15174             visible_count := visible_count + 1;
15175
15176             current_res.id = core_result.id;
15177             current_res.rel = core_result.rel;
15178
15179             tmp_int := 1;
15180             IF metarecord THEN
15181                 SELECT COUNT(DISTINCT s.source) INTO tmp_int FROM metabib.metarecord_source_map s WHERE s.metarecord = core_result.id;
15182             END IF;
15183
15184             IF tmp_int = 1 THEN
15185                 current_res.record = core_result.records[1];
15186             ELSE
15187                 current_res.record = NULL;
15188             END IF;
15189
15190             RETURN NEXT current_res;
15191
15192             CONTINUE;
15193         END IF;
15194
15195         IF param_statuses IS NOT NULL AND array_upper(param_statuses, 1) > 0 THEN
15196
15197             PERFORM 1
15198               FROM  asset.call_number cn
15199                     JOIN asset.copy cp ON (cp.call_number = cn.id)
15200               WHERE NOT cn.deleted
15201                     AND NOT cp.deleted
15202                     AND cp.status IN ( SELECT * FROM search.explode_array( param_statuses ) )
15203                     AND cn.record IN ( SELECT * FROM search.explode_array( core_result.records ) )
15204                     AND cp.circ_lib IN ( SELECT * FROM search.explode_array( search_org_list ) )
15205               LIMIT 1;
15206
15207             IF NOT FOUND THEN
15208                 -- RAISE NOTICE ' % were all status-excluded ... ', core_result.records;
15209                 excluded_count := excluded_count + 1;
15210                 CONTINUE;
15211             END IF;
15212
15213         END IF;
15214
15215         IF param_locations IS NOT NULL AND array_upper(param_locations, 1) > 0 THEN
15216
15217             PERFORM 1
15218               FROM  asset.call_number cn
15219                     JOIN asset.copy cp ON (cp.call_number = cn.id)
15220               WHERE NOT cn.deleted
15221                     AND NOT cp.deleted
15222                     AND cp.location IN ( SELECT * FROM search.explode_array( param_locations ) )
15223                     AND cn.record IN ( SELECT * FROM search.explode_array( core_result.records ) )
15224                     AND cp.circ_lib IN ( SELECT * FROM search.explode_array( search_org_list ) )
15225               LIMIT 1;
15226
15227             IF NOT FOUND THEN
15228                 -- RAISE NOTICE ' % were all copy_location-excluded ... ', core_result.records;
15229                 excluded_count := excluded_count + 1;
15230                 CONTINUE;
15231             END IF;
15232
15233         END IF;
15234
15235         IF staff IS NULL OR NOT staff THEN
15236
15237             PERFORM 1
15238               FROM  asset.opac_visible_copies
15239               WHERE circ_lib IN ( SELECT * FROM search.explode_array( search_org_list ) )
15240                     AND record IN ( SELECT * FROM search.explode_array( core_result.records ) )
15241               LIMIT 1;
15242
15243             IF NOT FOUND THEN
15244                 -- RAISE NOTICE ' % were all visibility-excluded ... ', core_result.records;
15245                 excluded_count := excluded_count + 1;
15246                 CONTINUE;
15247             END IF;
15248
15249         ELSE
15250
15251             PERFORM 1
15252               FROM  asset.call_number cn
15253                     JOIN asset.copy cp ON (cp.call_number = cn.id)
15254                     JOIN actor.org_unit a ON (cp.circ_lib = a.id)
15255               WHERE NOT cn.deleted
15256                     AND NOT cp.deleted
15257                     AND cp.circ_lib IN ( SELECT * FROM search.explode_array( search_org_list ) )
15258                     AND cn.record IN ( SELECT * FROM search.explode_array( core_result.records ) )
15259               LIMIT 1;
15260
15261             IF NOT FOUND THEN
15262
15263                 PERFORM 1
15264                   FROM  asset.call_number cn
15265                   WHERE cn.record IN ( SELECT * FROM search.explode_array( core_result.records ) )
15266                   LIMIT 1;
15267
15268                 IF FOUND THEN
15269                     -- RAISE NOTICE ' % were all visibility-excluded ... ', core_result.records;
15270                     excluded_count := excluded_count + 1;
15271                     CONTINUE;
15272                 END IF;
15273
15274             END IF;
15275
15276         END IF;
15277
15278         visible_count := visible_count + 1;
15279
15280         current_res.id = core_result.id;
15281         current_res.rel = core_result.rel;
15282
15283         tmp_int := 1;
15284         IF metarecord THEN
15285             SELECT COUNT(DISTINCT s.source) INTO tmp_int FROM metabib.metarecord_source_map s WHERE s.metarecord = core_result.id;
15286         END IF;
15287
15288         IF tmp_int = 1 THEN
15289             current_res.record = core_result.records[1];
15290         ELSE
15291             current_res.record = NULL;
15292         END IF;
15293
15294         RETURN NEXT current_res;
15295
15296         IF visible_count % 1000 = 0 THEN
15297             -- RAISE NOTICE ' % visible so far ... ', visible_count;
15298         END IF;
15299
15300     END LOOP;
15301
15302     current_res.id = NULL;
15303     current_res.rel = NULL;
15304     current_res.record = NULL;
15305     current_res.total = total_count;
15306     current_res.checked = check_count;
15307     current_res.deleted = deleted_count;
15308     current_res.visible = visible_count;
15309     current_res.excluded = excluded_count;
15310
15311     CLOSE core_cursor;
15312
15313     RETURN NEXT current_res;
15314
15315 END;
15316 $func$ LANGUAGE PLPGSQL;
15317
15318 ALTER TABLE biblio.record_entry ADD COLUMN owner INT;
15319 ALTER TABLE biblio.record_entry
15320          ADD CONSTRAINT biblio_record_entry_owner_fkey FOREIGN KEY (owner)
15321          REFERENCES actor.org_unit (id)
15322          DEFERRABLE INITIALLY DEFERRED;
15323
15324 ALTER TABLE biblio.record_entry ADD COLUMN share_depth INT;
15325
15326 ALTER TABLE auditor.biblio_record_entry_history ADD COLUMN owner INT;
15327 ALTER TABLE auditor.biblio_record_entry_history ADD COLUMN share_depth INT;
15328
15329 DROP VIEW auditor.biblio_record_entry_lifecycle;
15330
15331 SELECT auditor.create_auditor_lifecycle( 'biblio', 'record_entry' );
15332
15333 CREATE OR REPLACE FUNCTION public.first_word ( TEXT ) RETURNS TEXT AS $$
15334         SELECT COALESCE(SUBSTRING( $1 FROM $_$^\S+$_$), '');
15335 $$ LANGUAGE SQL STRICT IMMUTABLE;
15336
15337 CREATE OR REPLACE FUNCTION public.normalize_space( TEXT ) RETURNS TEXT AS $$
15338     SELECT regexp_replace(regexp_replace(regexp_replace($1, E'\\n', ' ', 'g'), E'(?:^\\s+)|(\\s+$)', '', 'g'), E'\\s+', ' ', 'g');
15339 $$ LANGUAGE SQL STRICT IMMUTABLE;
15340
15341 CREATE OR REPLACE FUNCTION public.lowercase( TEXT ) RETURNS TEXT AS $$
15342     return lc(shift);
15343 $$ LANGUAGE PLPERLU STRICT IMMUTABLE;
15344
15345 CREATE OR REPLACE FUNCTION public.uppercase( TEXT ) RETURNS TEXT AS $$
15346     return uc(shift);
15347 $$ LANGUAGE PLPERLU STRICT IMMUTABLE;
15348
15349 CREATE OR REPLACE FUNCTION public.remove_diacritics( TEXT ) RETURNS TEXT AS $$
15350     use Unicode::Normalize;
15351
15352     my $x = NFD(shift);
15353     $x =~ s/\pM+//go;
15354     return $x;
15355
15356 $$ LANGUAGE PLPERLU STRICT IMMUTABLE;
15357
15358 CREATE OR REPLACE FUNCTION public.entityize( TEXT ) RETURNS TEXT AS $$
15359     use Unicode::Normalize;
15360
15361     my $x = NFC(shift);
15362     $x =~ s/([\x{0080}-\x{fffd}])/sprintf('&#x%X;',ord($1))/sgoe;
15363     return $x;
15364
15365 $$ LANGUAGE PLPERLU STRICT IMMUTABLE;
15366
15367 CREATE OR REPLACE FUNCTION actor.org_unit_ancestor_setting( setting_name TEXT, org_id INT ) RETURNS SETOF actor.org_unit_setting AS $$
15368 DECLARE
15369     setting RECORD;
15370     cur_org INT;
15371 BEGIN
15372     cur_org := org_id;
15373     LOOP
15374         SELECT INTO setting * FROM actor.org_unit_setting WHERE org_unit = cur_org AND name = setting_name;
15375         IF FOUND THEN
15376             RETURN NEXT setting;
15377         END IF;
15378         SELECT INTO cur_org parent_ou FROM actor.org_unit WHERE id = cur_org;
15379         EXIT WHEN cur_org IS NULL;
15380     END LOOP;
15381     RETURN;
15382 END;
15383 $$ LANGUAGE plpgsql STABLE;
15384
15385 CREATE OR REPLACE FUNCTION acq.extract_holding_attr_table (lineitem int, tag text) RETURNS SETOF acq.flat_lineitem_holding_subfield AS $$
15386 DECLARE
15387     counter INT;
15388     lida    acq.flat_lineitem_holding_subfield%ROWTYPE;
15389 BEGIN
15390
15391     SELECT  COUNT(*) INTO counter
15392       FROM  oils_xpath_table(
15393                 'id',
15394                 'marc',
15395                 'acq.lineitem',
15396                 '//*[@tag="' || tag || '"]',
15397                 'id=' || lineitem
15398             ) as t(i int,c text);
15399
15400     FOR i IN 1 .. counter LOOP
15401         FOR lida IN
15402             SELECT  *
15403               FROM  (   SELECT  id,i,t,v
15404                           FROM  oils_xpath_table(
15405                                     'id',
15406                                     'marc',
15407                                     'acq.lineitem',
15408                                     '//*[@tag="' || tag || '"][position()=' || i || ']/*/@code|' ||
15409                                         '//*[@tag="' || tag || '"][position()=' || i || ']/*[@code]',
15410                                     'id=' || lineitem
15411                                 ) as t(id int,t text,v text)
15412                     )x
15413         LOOP
15414             RETURN NEXT lida;
15415         END LOOP;
15416     END LOOP;
15417
15418     RETURN;
15419 END;
15420 $$ LANGUAGE PLPGSQL;
15421
15422 CREATE OR REPLACE FUNCTION oils_i18n_xlate ( keytable TEXT, keyclass TEXT, keycol TEXT, identcol TEXT, keyvalue TEXT, raw_locale TEXT ) RETURNS TEXT AS $func$
15423 DECLARE
15424     locale      TEXT := REGEXP_REPLACE( REGEXP_REPLACE( raw_locale, E'[;, ].+$', '' ), E'_', '-', 'g' );
15425     language    TEXT := REGEXP_REPLACE( locale, E'-.+$', '' );
15426     result      config.i18n_core%ROWTYPE;
15427     fallback    TEXT;
15428     keyfield    TEXT := keyclass || '.' || keycol;
15429 BEGIN
15430
15431     -- Try the full locale
15432     SELECT  * INTO result
15433       FROM  config.i18n_core
15434       WHERE fq_field = keyfield
15435             AND identity_value = keyvalue
15436             AND translation = locale;
15437
15438     -- Try just the language
15439     IF NOT FOUND THEN
15440         SELECT  * INTO result
15441           FROM  config.i18n_core
15442           WHERE fq_field = keyfield
15443                 AND identity_value = keyvalue
15444                 AND translation = language;
15445     END IF;
15446
15447     -- Fall back to the string we passed in in the first place
15448     IF NOT FOUND THEN
15449     EXECUTE
15450             'SELECT ' ||
15451                 keycol ||
15452             ' FROM ' || keytable ||
15453             ' WHERE ' || identcol || ' = ' || quote_literal(keyvalue)
15454                 INTO fallback;
15455         RETURN fallback;
15456     END IF;
15457
15458     RETURN result.string;
15459 END;
15460 $func$ LANGUAGE PLPGSQL STABLE;
15461
15462 SELECT auditor.create_auditor ( 'acq', 'invoice' );
15463
15464 SELECT auditor.create_auditor ( 'acq', 'invoice_item' );
15465
15466 SELECT auditor.create_auditor ( 'acq', 'invoice_entry' );
15467
15468 INSERT INTO acq.cancel_reason ( id, org_unit, label, description, keep_debits ) VALUES (
15469     3, 1, 'delivered_but_lost',
15470     oils_i18n_gettext( 2, 'Delivered but not received; presumed lost', 'acqcr', 'label' ), TRUE );
15471
15472 CREATE TABLE config.global_flag (
15473     label   TEXT    NOT NULL
15474 ) INHERITS (config.internal_flag);
15475 ALTER TABLE config.global_flag ADD PRIMARY KEY (name);
15476
15477 INSERT INTO config.global_flag (name, label) -- defaults to enabled=FALSE
15478     VALUES (
15479         'cat.bib.use_id_for_tcn',
15480         oils_i18n_gettext(
15481             'cat.bib.use_id_for_tcn',
15482             'Cat: Use Internal ID for TCN Value',
15483             'cgf', 
15484             'label'
15485         )
15486     );
15487
15488 -- resolves performance issue noted by EG Indiana
15489
15490 CREATE INDEX scecm_owning_copy_idx ON asset.stat_cat_entry_copy_map(owning_copy);
15491
15492 INSERT INTO config.metabib_class ( name, label ) VALUES ( 'identifier', oils_i18n_gettext('identifier', 'Identifier', 'cmc', 'name') );
15493
15494 INSERT INTO config.metabib_field ( id, field_class, name, label, format, xpath, facet_field ) VALUES
15495     (17, 'identifier', 'accession', oils_i18n_gettext(17, 'Accession Number', 'cmf', 'label'), 'marcxml', $$//marcxml:datafield[tag="001"]/text()$$, TRUE );
15496 INSERT INTO config.metabib_field ( id, field_class, name, label, format, xpath, facet_field ) VALUES
15497     (18, 'identifier', 'isbn', oils_i18n_gettext(18, 'ISBN', 'cmf', 'label'), 'marcxml', $$//marcxml:datafield[tag="020"]/marcxml:subfield[code="a" or code="z"]/text()$$, TRUE );
15498 INSERT INTO config.metabib_field ( id, field_class, name, label, format, xpath, facet_field ) VALUES
15499     (19, 'identifier', 'issn', oils_i18n_gettext(19, 'ISSN', 'cmf', 'label'), 'marcxml', $$//marcxml:datafield[tag="022"]/marcxml:subfield[code="a" or code="z"]/text()$$, TRUE );
15500 INSERT INTO config.metabib_field ( id, field_class, name, label, format, xpath, facet_field ) VALUES
15501     (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 );
15502 INSERT INTO config.metabib_field ( id, field_class, name, label, format, xpath, facet_field ) VALUES
15503     (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 );
15504 INSERT INTO config.metabib_field ( id, field_class, name, label, format, xpath, facet_field ) VALUES
15505     (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 );
15506 INSERT INTO config.metabib_field ( id, field_class, name, label, format, xpath, facet_field ) VALUES
15507     (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 );
15508 INSERT INTO config.metabib_field ( id, field_class, name, label, format, xpath, facet_field ) VALUES
15509     (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 );
15510 INSERT INTO config.metabib_field ( id, field_class, name, label, format, xpath, facet_field ) VALUES
15511     (25, 'identifier', 'bibcn', oils_i18n_gettext(25, 'Local Free-Text Call Number', 'cmf', 'label'), 'marcxml', $$//marcxml:datafield[tag="099"]//text()$$, TRUE );
15512
15513 SELECT SETVAL('config.metabib_field_id_seq'::TEXT, (SELECT MAX(id) FROM config.metabib_field), TRUE);
15514  
15515
15516 DELETE FROM config.metabib_search_alias WHERE alias = 'dc.identifier';
15517
15518 INSERT INTO config.metabib_search_alias (alias,field_class) VALUES ('id','identifier');
15519 INSERT INTO config.metabib_search_alias (alias,field_class) VALUES ('dc.identifier','identifier');
15520 INSERT INTO config.metabib_search_alias (alias,field_class,field) VALUES ('eg.isbn','identifier', 18);
15521 INSERT INTO config.metabib_search_alias (alias,field_class,field) VALUES ('eg.issn','identifier', 19);
15522 INSERT INTO config.metabib_search_alias (alias,field_class,field) VALUES ('eg.upc','identifier', 20);
15523 INSERT INTO config.metabib_search_alias (alias,field_class,field) VALUES ('eg.callnumber','identifier', 25);
15524
15525 CREATE TABLE metabib.identifier_field_entry (
15526         id              BIGSERIAL       PRIMARY KEY,
15527         source          BIGINT          NOT NULL,
15528         field           INT             NOT NULL,
15529         value           TEXT            NOT NULL,
15530         index_vector    tsvector        NOT NULL
15531 );
15532 CREATE TRIGGER metabib_identifier_field_entry_fti_trigger
15533         BEFORE UPDATE OR INSERT ON metabib.identifier_field_entry
15534         FOR EACH ROW EXECUTE PROCEDURE oils_tsearch2('keyword');
15535
15536 CREATE INDEX metabib_identifier_field_entry_index_vector_idx ON metabib.identifier_field_entry USING GIST (index_vector);
15537 CREATE INDEX metabib_identifier_field_entry_value_idx ON metabib.identifier_field_entry
15538     (SUBSTRING(value,1,1024)) WHERE index_vector = ''::TSVECTOR;
15539 CREATE INDEX metabib_identifier_field_entry_source_idx ON metabib.identifier_field_entry (source);
15540
15541 ALTER TABLE metabib.identifier_field_entry ADD CONSTRAINT metabib_identifier_field_entry_source_pkey
15542     FOREIGN KEY (source) REFERENCES biblio.record_entry (id) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED;
15543 ALTER TABLE metabib.identifier_field_entry ADD CONSTRAINT metabib_identifier_field_entry_field_pkey
15544     FOREIGN KEY (field) REFERENCES config.metabib_field (id) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED;
15545
15546 CREATE OR REPLACE FUNCTION public.translate_isbn1013( TEXT ) RETURNS TEXT AS $func$
15547     use Business::ISBN;
15548     use strict;
15549     use warnings;
15550
15551     # For each ISBN found in a single string containing a set of ISBNs:
15552     #   * Normalize an incoming ISBN to have the correct checksum and no hyphens
15553     #   * Convert an incoming ISBN10 or ISBN13 to its counterpart and return
15554
15555     my $input = shift;
15556     my $output = '';
15557
15558     foreach my $word (split(/\s/, $input)) {
15559         my $isbn = Business::ISBN->new($word);
15560
15561         # First check the checksum; if it is not valid, fix it and add the original
15562         # bad-checksum ISBN to the output
15563         if ($isbn && $isbn->is_valid_checksum() == Business::ISBN::BAD_CHECKSUM) {
15564             $output .= $isbn->isbn() . " ";
15565             $isbn->fix_checksum();
15566         }
15567
15568         # If we now have a valid ISBN, convert it to its counterpart ISBN10/ISBN13
15569         # and add the normalized original ISBN to the output
15570         if ($isbn && $isbn->is_valid()) {
15571             my $isbn_xlated = ($isbn->type eq "ISBN13") ? $isbn->as_isbn10 : $isbn->as_isbn13;
15572             $output .= $isbn->isbn . " ";
15573
15574             # If we successfully converted the ISBN to its counterpart, add the
15575             # converted ISBN to the output as well
15576             $output .= ($isbn_xlated->isbn . " ") if ($isbn_xlated);
15577         }
15578     }
15579     return $output if $output;
15580
15581     # If there were no valid ISBNs, just return the raw input
15582     return $input;
15583 $func$ LANGUAGE PLPERLU;
15584
15585 COMMENT ON FUNCTION public.translate_isbn1013(TEXT) IS $$
15586 /*
15587  * Copyright (C) 2010 Merrimack Valley Library Consortium
15588  * Jason Stephenson <jstephenson@mvlc.org>
15589  * Copyright (C) 2010 Laurentian University
15590  * Dan Scott <dscott@laurentian.ca>
15591  *
15592  * The translate_isbn1013 function takes an input ISBN and returns the
15593  * following in a single space-delimited string if the input ISBN is valid:
15594  *   - The normalized input ISBN (hyphens stripped)
15595  *   - The normalized input ISBN with a fixed checksum if the checksum was bad
15596  *   - The ISBN converted to its ISBN10 or ISBN13 counterpart, if possible
15597  */
15598 $$;
15599
15600 UPDATE config.metabib_field SET facet_field = FALSE WHERE id BETWEEN 17 AND 25;
15601 UPDATE config.metabib_field SET xpath = REPLACE(xpath,'marcxml','marc') WHERE id BETWEEN 17 AND 25;
15602 UPDATE config.metabib_field SET xpath = REPLACE(xpath,'tag','@tag') WHERE id BETWEEN 17 AND 25;
15603 UPDATE config.metabib_field SET xpath = REPLACE(xpath,'code','@code') WHERE id BETWEEN 17 AND 25;
15604 UPDATE config.metabib_field SET xpath = REPLACE(xpath,'"',E'\'') WHERE id BETWEEN 17 AND 25;
15605 UPDATE config.metabib_field SET xpath = REPLACE(xpath,'/text()','') WHERE id BETWEEN 17 AND 24;
15606
15607 INSERT INTO config.index_normalizer (name, description, func, param_count) VALUES (
15608         'ISBN 10/13 conversion',
15609         'Translate ISBN10 to ISBN13, and vice versa, for indexing purposes.',
15610         'translate_isbn1013',
15611         0
15612 );
15613
15614 INSERT INTO config.index_normalizer (name, description, func, param_count) VALUES (
15615         'Replace',
15616         'Replace all occurences of first parameter in the string with the second parameter.',
15617         'replace',
15618         2
15619 );
15620
15621 INSERT INTO config.metabib_field_index_norm_map (field,norm,pos)
15622     SELECT  m.id, i.id, 1
15623       FROM  config.metabib_field m,
15624             config.index_normalizer i
15625       WHERE i.func IN ('first_word')
15626             AND m.id IN (18);
15627
15628 INSERT INTO config.metabib_field_index_norm_map (field,norm,pos)
15629     SELECT  m.id, i.id, 2
15630       FROM  config.metabib_field m,
15631             config.index_normalizer i
15632       WHERE i.func IN ('translate_isbn1013')
15633             AND m.id IN (18);
15634
15635 INSERT INTO config.metabib_field_index_norm_map (field,norm,params)
15636     SELECT  m.id, i.id, $$['-','']$$
15637       FROM  config.metabib_field m,
15638             config.index_normalizer i
15639       WHERE i.func IN ('replace')
15640             AND m.id IN (19);
15641
15642 INSERT INTO config.metabib_field_index_norm_map (field,norm,params)
15643     SELECT  m.id, i.id, $$[' ','']$$
15644       FROM  config.metabib_field m,
15645             config.index_normalizer i
15646       WHERE i.func IN ('replace')
15647             AND m.id IN (19);
15648
15649 DELETE FROM config.metabib_field_index_norm_map WHERE norm IN (1,2) and field > 16;
15650
15651 UPDATE  config.metabib_field_index_norm_map
15652   SET   params = REPLACE(params,E'\'','"')
15653   WHERE params IS NOT NULL AND params <> '';
15654
15655 DROP TRIGGER IF EXISTS metabib_identifier_field_entry_fti_trigger ON metabib.identifier_field_entry;
15656
15657 CREATE TEXT SEARCH CONFIGURATION identifier ( COPY = title );
15658
15659 ALTER TABLE config.circ_modifier
15660         ADD COLUMN avg_wait_time INTERVAL;
15661
15662 --CREATE TABLE actor.usr_password_reset (
15663 --  id SERIAL PRIMARY KEY,
15664 --  uuid TEXT NOT NULL, 
15665 --  usr BIGINT NOT NULL REFERENCES actor.usr(id) DEFERRABLE INITIALLY DEFERRED, 
15666 --  request_time TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(), 
15667 --  has_been_reset BOOL NOT NULL DEFAULT false
15668 --);
15669 --COMMENT ON TABLE actor.usr_password_reset IS $$
15670 --/*
15671 -- * Copyright (C) 2010 Laurentian University
15672 -- * Dan Scott <dscott@laurentian.ca>
15673 -- *
15674 -- * Self-serve password reset requests
15675 -- *
15676 -- * ****
15677 -- *
15678 -- * This program is free software; you can redistribute it and/or
15679 -- * modify it under the terms of the GNU General Public License
15680 -- * as published by the Free Software Foundation; either version 2
15681 -- * of the License, or (at your option) any later version.
15682 -- *
15683 -- * This program is distributed in the hope that it will be useful,
15684 -- * but WITHOUT ANY WARRANTY; without even the implied warranty of
15685 -- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15686 -- * GNU General Public License for more details.
15687 -- */
15688 --$$;
15689 --CREATE UNIQUE INDEX actor_usr_password_reset_uuid_idx ON actor.usr_password_reset (uuid);
15690 --CREATE INDEX actor_usr_password_reset_usr_idx ON actor.usr_password_reset (usr);
15691 --CREATE INDEX actor_usr_password_reset_request_time_idx ON actor.usr_password_reset (request_time);
15692 --CREATE INDEX actor_usr_password_reset_has_been_reset_idx ON actor.usr_password_reset (has_been_reset);
15693
15694 -- Use the identifier search class tsconfig
15695 DROP TRIGGER IF EXISTS metabib_identifier_field_entry_fti_trigger ON metabib.identifier_field_entry;
15696 CREATE TRIGGER metabib_identifier_field_entry_fti_trigger
15697     BEFORE INSERT OR UPDATE ON metabib.identifier_field_entry
15698     FOR EACH ROW
15699     EXECUTE PROCEDURE public.oils_tsearch2('identifier');
15700
15701 INSERT INTO config.global_flag (name,label,enabled)
15702     VALUES ('history.circ.retention_age',oils_i18n_gettext('history.circ.retention_age', 'Historical Circulation Retention Age', 'cgf', 'label'), TRUE);
15703 INSERT INTO config.global_flag (name,label,enabled)
15704     VALUES ('history.circ.retention_count',oils_i18n_gettext('history.circ.retention_count', 'Historical Circulations per Copy', 'cgf', 'label'), TRUE);
15705
15706 -- turn a JSON scalar into an SQL TEXT value
15707 CREATE OR REPLACE FUNCTION oils_json_to_text( TEXT ) RETURNS TEXT AS $f$
15708     use JSON::XS;                    
15709     my $json = shift();
15710     my $txt;
15711     eval { $txt = JSON::XS->new->allow_nonref->decode( $json ) };   
15712     return undef if ($@);
15713     return $txt
15714 $f$ LANGUAGE PLPERLU;
15715
15716 -- Return the list of circ chain heads in xact_start order that the user has chosen to "retain"
15717 CREATE OR REPLACE FUNCTION action.usr_visible_circs (usr_id INT) RETURNS SETOF action.circulation AS $func$
15718 DECLARE
15719     c               action.circulation%ROWTYPE;
15720     view_age        INTERVAL;
15721     usr_view_age    actor.usr_setting%ROWTYPE;
15722     usr_view_start  actor.usr_setting%ROWTYPE;
15723 BEGIN
15724     SELECT * INTO usr_view_age FROM actor.usr_setting WHERE usr = usr_id AND name = 'history.circ.retention_age';
15725     SELECT * INTO usr_view_start FROM actor.usr_setting WHERE usr = usr_id AND name = 'history.circ.retention_start';
15726
15727     IF usr_view_age.value IS NOT NULL AND usr_view_start.value IS NOT NULL THEN
15728         -- User opted in and supplied a retention age
15729         IF oils_json_to_text(usr_view_age.value)::INTERVAL > AGE(NOW(), oils_json_to_text(usr_view_start.value)::TIMESTAMPTZ) THEN
15730             view_age := AGE(NOW(), oils_json_to_text(usr_view_start.value)::TIMESTAMPTZ);
15731         ELSE
15732             view_age := oils_json_to_text(usr_view_age.value)::INTERVAL;
15733         END IF;
15734     ELSIF usr_view_start.value IS NOT NULL THEN
15735         -- User opted in
15736         view_age := AGE(NOW(), oils_json_to_text(usr_view_start.value)::TIMESTAMPTZ);
15737     ELSE
15738         -- User did not opt in
15739         RETURN;
15740     END IF;
15741
15742     FOR c IN
15743         SELECT  *
15744           FROM  action.circulation
15745           WHERE usr = usr_id
15746                 AND parent_circ IS NULL
15747                 AND xact_start > NOW() - view_age
15748           ORDER BY xact_start
15749     LOOP
15750         RETURN NEXT c;
15751     END LOOP;
15752
15753     RETURN;
15754 END;
15755 $func$ LANGUAGE PLPGSQL;
15756
15757 CREATE OR REPLACE FUNCTION action.purge_circulations () RETURNS INT AS $func$
15758 DECLARE
15759     usr_keep_age    actor.usr_setting%ROWTYPE;
15760     usr_keep_start  actor.usr_setting%ROWTYPE;
15761     org_keep_age    INTERVAL;
15762     org_keep_count  INT;
15763
15764     keep_age        INTERVAL;
15765
15766     target_acp      RECORD;
15767     circ_chain_head action.circulation%ROWTYPE;
15768     circ_chain_tail action.circulation%ROWTYPE;
15769
15770     purge_position  INT;
15771     count_purged    INT;
15772 BEGIN
15773
15774     count_purged := 0;
15775
15776     SELECT value::INTERVAL INTO org_keep_age FROM config.global_flag WHERE name = 'history.circ.retention_age' AND enabled;
15777
15778     SELECT value::INT INTO org_keep_count FROM config.global_flag WHERE name = 'history.circ.retention_count' AND enabled;
15779     IF org_keep_count IS NULL THEN
15780         RETURN count_purged; -- Gimme a count to keep, or I keep them all, forever
15781     END IF;
15782
15783     -- First, find copies with more than keep_count non-renewal circs
15784     FOR target_acp IN
15785         SELECT  target_copy,
15786                 COUNT(*) AS total_real_circs
15787           FROM  action.circulation
15788           WHERE parent_circ IS NULL
15789                 AND xact_finish IS NOT NULL
15790           GROUP BY target_copy
15791           HAVING COUNT(*) > org_keep_count
15792     LOOP
15793         purge_position := 0;
15794         -- And, for those, select circs that are finished and older than keep_age
15795         FOR circ_chain_head IN
15796             SELECT  *
15797               FROM  action.circulation
15798               WHERE target_copy = target_acp.target_copy
15799                     AND parent_circ IS NULL
15800               ORDER BY xact_start
15801         LOOP
15802
15803             -- Stop once we've purged enough circs to hit org_keep_count
15804             EXIT WHEN target_acp.total_real_circs - purge_position <= org_keep_count;
15805
15806             SELECT * INTO circ_chain_tail FROM action.circ_chain(circ_chain_head.id) ORDER BY xact_start DESC LIMIT 1;
15807             EXIT WHEN circ_chain_tail.xact_finish IS NULL;
15808
15809             -- Now get the user settings, if any, to block purging if the user wants to keep more circs
15810             usr_keep_age.value := NULL;
15811             SELECT * INTO usr_keep_age FROM actor.usr_setting WHERE usr = circ_chain_head.usr AND name = 'history.circ.retention_age';
15812
15813             usr_keep_start.value := NULL;
15814             SELECT * INTO usr_keep_start FROM actor.usr_setting WHERE usr = circ_chain_head.usr AND name = 'history.circ.retention_start';
15815
15816             IF usr_keep_age.value IS NOT NULL AND usr_keep_start.value IS NOT NULL THEN
15817                 IF oils_json_to_text(usr_keep_age.value)::INTERVAL > AGE(NOW(), oils_json_to_text(usr_keep_start.value)::TIMESTAMPTZ) THEN
15818                     keep_age := AGE(NOW(), oils_json_to_text(usr_keep_start.value)::TIMESTAMPTZ);
15819                 ELSE
15820                     keep_age := oils_json_to_text(usr_keep_age.value)::INTERVAL;
15821                 END IF;
15822             ELSIF usr_keep_start.value IS NOT NULL THEN
15823                 keep_age := AGE(NOW(), oils_json_to_text(usr_keep_start.value)::TIMESTAMPTZ);
15824             ELSE
15825                 keep_age := COALESCE( org_keep_age::INTERVAL, '2000 years'::INTERVAL );
15826             END IF;
15827
15828             EXIT WHEN AGE(NOW(), circ_chain_tail.xact_finish) < keep_age;
15829
15830             -- We've passed the purging tests, purge the circ chain starting at the end
15831             DELETE FROM action.circulation WHERE id = circ_chain_tail.id;
15832             WHILE circ_chain_tail.parent_circ IS NOT NULL LOOP
15833                 SELECT * INTO circ_chain_tail FROM action.circulation WHERE id = circ_chain_tail.parent_circ;
15834                 DELETE FROM action.circulation WHERE id = circ_chain_tail.id;
15835             END LOOP;
15836
15837             count_purged := count_purged + 1;
15838             purge_position := purge_position + 1;
15839
15840         END LOOP;
15841     END LOOP;
15842 END;
15843 $func$ LANGUAGE PLPGSQL;
15844
15845 CREATE OR REPLACE FUNCTION action.usr_visible_holds (usr_id INT) RETURNS SETOF action.hold_request AS $func$
15846 DECLARE
15847     h               action.hold_request%ROWTYPE;
15848     view_age        INTERVAL;
15849     view_count      INT;
15850     usr_view_count  actor.usr_setting%ROWTYPE;
15851     usr_view_age    actor.usr_setting%ROWTYPE;
15852     usr_view_start  actor.usr_setting%ROWTYPE;
15853 BEGIN
15854     SELECT * INTO usr_view_count FROM actor.usr_setting WHERE usr = usr_id AND name = 'history.hold.retention_count';
15855     SELECT * INTO usr_view_age FROM actor.usr_setting WHERE usr = usr_id AND name = 'history.hold.retention_age';
15856     SELECT * INTO usr_view_start FROM actor.usr_setting WHERE usr = usr_id AND name = 'history.hold.retention_start';
15857
15858     FOR h IN
15859         SELECT  *
15860           FROM  action.hold_request
15861           WHERE usr = usr_id
15862                 AND fulfillment_time IS NULL
15863                 AND cancel_time IS NULL
15864           ORDER BY request_time DESC
15865     LOOP
15866         RETURN NEXT h;
15867     END LOOP;
15868
15869     IF usr_view_start.value IS NULL THEN
15870         RETURN;
15871     END IF;
15872
15873     IF usr_view_age.value IS NOT NULL THEN
15874         -- User opted in and supplied a retention age
15875         IF oils_json_to_string(usr_view_age.value)::INTERVAL > AGE(NOW(), oils_json_to_string(usr_view_start.value)::TIMESTAMPTZ) THEN
15876             view_age := AGE(NOW(), oils_json_to_string(usr_view_start.value)::TIMESTAMPTZ);
15877         ELSE
15878             view_age := oils_json_to_string(usr_view_age.value)::INTERVAL;
15879         END IF;
15880     ELSE
15881         -- User opted in
15882         view_age := AGE(NOW(), oils_json_to_string(usr_view_start.value)::TIMESTAMPTZ);
15883     END IF;
15884
15885     IF usr_view_count.value IS NOT NULL THEN
15886         view_count := oils_json_to_text(usr_view_count.value)::INT;
15887     ELSE
15888         view_count := 1000;
15889     END IF;
15890
15891     -- show some fulfilled/canceled holds
15892     FOR h IN
15893         SELECT  *
15894           FROM  action.hold_request
15895           WHERE usr = usr_id
15896                 AND ( fulfillment_time IS NOT NULL OR cancel_time IS NOT NULL )
15897                 AND request_time > NOW() - view_age
15898           ORDER BY request_time DESC
15899           LIMIT view_count
15900     LOOP
15901         RETURN NEXT h;
15902     END LOOP;
15903
15904     RETURN;
15905 END;
15906 $func$ LANGUAGE PLPGSQL;
15907
15908 DROP TABLE IF EXISTS serial.bib_summary CASCADE;
15909
15910 DROP TABLE IF EXISTS serial.index_summary CASCADE;
15911
15912 DROP TABLE IF EXISTS serial.sup_summary CASCADE;
15913
15914 DROP TABLE IF EXISTS serial.issuance CASCADE;
15915
15916 DROP TABLE IF EXISTS serial.binding_unit CASCADE;
15917
15918 DROP TABLE IF EXISTS serial.subscription CASCADE;
15919
15920 CREATE TABLE asset.copy_template (
15921         id             SERIAL   PRIMARY KEY,
15922         owning_lib     INT      NOT NULL
15923                                 REFERENCES actor.org_unit (id)
15924                                 DEFERRABLE INITIALLY DEFERRED,
15925         creator        BIGINT   NOT NULL
15926                                 REFERENCES actor.usr (id)
15927                                 DEFERRABLE INITIALLY DEFERRED,
15928         editor         BIGINT   NOT NULL
15929                                 REFERENCES actor.usr (id)
15930                                 DEFERRABLE INITIALLY DEFERRED,
15931         create_date    TIMESTAMP WITH TIME ZONE    DEFAULT NOW(),
15932         edit_date      TIMESTAMP WITH TIME ZONE    DEFAULT NOW(),
15933         name           TEXT     NOT NULL,
15934         -- columns above this point are attributes of the template itself
15935         -- columns after this point are attributes of the copy this template modifies/creates
15936         circ_lib       INT      REFERENCES actor.org_unit (id)
15937                                 DEFERRABLE INITIALLY DEFERRED,
15938         status         INT      REFERENCES config.copy_status (id)
15939                                 DEFERRABLE INITIALLY DEFERRED,
15940         location       INT      REFERENCES asset.copy_location (id)
15941                                 DEFERRABLE INITIALLY DEFERRED,
15942         loan_duration  INT      CONSTRAINT valid_loan_duration CHECK (
15943                                     loan_duration IS NULL OR loan_duration IN (1,2,3)),
15944         fine_level     INT      CONSTRAINT valid_fine_level CHECK (
15945                                     fine_level IS NULL OR loan_duration IN (1,2,3)),
15946         age_protect    INT,
15947         circulate      BOOL,
15948         deposit        BOOL,
15949         ref            BOOL,
15950         holdable       BOOL,
15951         deposit_amount NUMERIC(6,2),
15952         price          NUMERIC(8,2),
15953         circ_modifier  TEXT,
15954         circ_as_type   TEXT,
15955         alert_message  TEXT,
15956         opac_visible   BOOL,
15957         floating       BOOL,
15958         mint_condition BOOL
15959 );
15960
15961 CREATE TABLE serial.subscription (
15962         id                     SERIAL       PRIMARY KEY,
15963         owning_lib             INT          NOT NULL DEFAULT 1
15964                                             REFERENCES actor.org_unit (id)
15965                                             ON DELETE SET NULL
15966                                             DEFERRABLE INITIALLY DEFERRED,
15967         start_date             TIMESTAMP WITH TIME ZONE     NOT NULL,
15968         end_date               TIMESTAMP WITH TIME ZONE,    -- interpret NULL as current subscription
15969         record_entry           BIGINT       REFERENCES biblio.record_entry (id)
15970                                             ON DELETE SET NULL
15971                                             DEFERRABLE INITIALLY DEFERRED,
15972         expected_date_offset   INTERVAL
15973         -- acquisitions/business-side tables link to here
15974 );
15975 CREATE INDEX serial_subscription_record_idx ON serial.subscription (record_entry);
15976 CREATE INDEX serial_subscription_owner_idx ON serial.subscription (owning_lib);
15977
15978 --at least one distribution per org_unit holding issues
15979 CREATE TABLE serial.distribution (
15980         id                    SERIAL  PRIMARY KEY,
15981         record_entry          BIGINT  REFERENCES serial.record_entry (id)
15982                                       ON DELETE SET NULL
15983                                       DEFERRABLE INITIALLY DEFERRED,
15984         summary_method        TEXT    CONSTRAINT sdist_summary_method_check CHECK (
15985                                           summary_method IS NULL
15986                                           OR summary_method IN ( 'add_to_sre',
15987                                           'merge_with_sre', 'use_sre_only',
15988                                           'use_sdist_only')),
15989         subscription          INT     NOT NULL
15990                                       REFERENCES serial.subscription (id)
15991                                                                   ON DELETE CASCADE
15992                                                                   DEFERRABLE INITIALLY DEFERRED,
15993         holding_lib           INT     NOT NULL
15994                                       REFERENCES actor.org_unit (id)
15995                                                                   DEFERRABLE INITIALLY DEFERRED,
15996         label                 TEXT    NOT NULL,
15997         receive_call_number   BIGINT  REFERENCES asset.call_number (id)
15998                                       DEFERRABLE INITIALLY DEFERRED,
15999         receive_unit_template INT     REFERENCES asset.copy_template (id)
16000                                       DEFERRABLE INITIALLY DEFERRED,
16001         bind_call_number      BIGINT  REFERENCES asset.call_number (id)
16002                                       DEFERRABLE INITIALLY DEFERRED,
16003         bind_unit_template    INT     REFERENCES asset.copy_template (id)
16004                                       DEFERRABLE INITIALLY DEFERRED,
16005         unit_label_prefix     TEXT,
16006         unit_label_suffix     TEXT
16007 );
16008 CREATE INDEX serial_distribution_sub_idx ON serial.distribution (subscription);
16009 CREATE INDEX serial_distribution_holding_lib_idx ON serial.distribution (holding_lib);
16010
16011 CREATE UNIQUE INDEX one_dist_per_sre_idx ON serial.distribution (record_entry);
16012
16013 CREATE TABLE serial.stream (
16014         id              SERIAL  PRIMARY KEY,
16015         distribution    INT     NOT NULL
16016                                 REFERENCES serial.distribution (id)
16017                                 ON DELETE CASCADE
16018                                 DEFERRABLE INITIALLY DEFERRED,
16019         routing_label   TEXT
16020 );
16021 CREATE INDEX serial_stream_dist_idx ON serial.stream (distribution);
16022
16023 CREATE UNIQUE INDEX label_once_per_dist
16024         ON serial.stream (distribution, routing_label)
16025         WHERE routing_label IS NOT NULL;
16026
16027 CREATE TABLE serial.routing_list_user (
16028         id             SERIAL       PRIMARY KEY,
16029         stream         INT          NOT NULL
16030                                     REFERENCES serial.stream
16031                                     ON DELETE CASCADE
16032                                     DEFERRABLE INITIALLY DEFERRED,
16033         pos            INT          NOT NULL DEFAULT 1,
16034         reader         INT          REFERENCES actor.usr
16035                                     ON DELETE CASCADE
16036                                     DEFERRABLE INITIALLY DEFERRED,
16037         department     TEXT,
16038         note           TEXT,
16039         CONSTRAINT one_pos_per_routing_list UNIQUE ( stream, pos ),
16040         CONSTRAINT reader_or_dept CHECK
16041         (
16042             -- Recipient is a person or a department, but not both
16043                 (reader IS NOT NULL AND department IS NULL) OR
16044                 (reader IS NULL AND department IS NOT NULL)
16045         )
16046 );
16047 CREATE INDEX serial_routing_list_user_stream_idx ON serial.routing_list_user (stream);
16048 CREATE INDEX serial_routing_list_user_reader_idx ON serial.routing_list_user (reader);
16049
16050 CREATE TABLE serial.caption_and_pattern (
16051         id           SERIAL       PRIMARY KEY,
16052         subscription INT          NOT NULL REFERENCES serial.subscription (id)
16053                                   ON DELETE CASCADE
16054                                   DEFERRABLE INITIALLY DEFERRED,
16055         type         TEXT         NOT NULL
16056                                   CONSTRAINT cap_type CHECK ( type in
16057                                   ( 'basic', 'supplement', 'index' )),
16058         create_date  TIMESTAMPTZ  NOT NULL DEFAULT now(),
16059         start_date   TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(),
16060         end_date     TIMESTAMP WITH TIME ZONE,
16061         active       BOOL         NOT NULL DEFAULT FALSE,
16062         pattern_code TEXT         NOT NULL,       -- must contain JSON
16063         enum_1       TEXT,
16064         enum_2       TEXT,
16065         enum_3       TEXT,
16066         enum_4       TEXT,
16067         enum_5       TEXT,
16068         enum_6       TEXT,
16069         chron_1      TEXT,
16070         chron_2      TEXT,
16071         chron_3      TEXT,
16072         chron_4      TEXT,
16073         chron_5      TEXT
16074 );
16075 CREATE INDEX serial_caption_and_pattern_sub_idx ON serial.caption_and_pattern (subscription);
16076
16077 CREATE TABLE serial.issuance (
16078         id              SERIAL    PRIMARY KEY,
16079         creator         INT       NOT NULL
16080                                   REFERENCES actor.usr (id)
16081                                                           DEFERRABLE INITIALLY DEFERRED,
16082         editor          INT       NOT NULL
16083                                   REFERENCES actor.usr (id)
16084                                   DEFERRABLE INITIALLY DEFERRED,
16085         create_date     TIMESTAMP WITH TIME ZONE        NOT NULL DEFAULT now(),
16086         edit_date       TIMESTAMP WITH TIME ZONE        NOT NULL DEFAULT now(),
16087         subscription    INT       NOT NULL
16088                                   REFERENCES serial.subscription (id)
16089                                   ON DELETE CASCADE
16090                                   DEFERRABLE INITIALLY DEFERRED,
16091         label           TEXT,
16092         date_published  TIMESTAMP WITH TIME ZONE,
16093         caption_and_pattern  INT  REFERENCES serial.caption_and_pattern (id)
16094                               DEFERRABLE INITIALLY DEFERRED,
16095         holding_code    TEXT,
16096         holding_type    TEXT      CONSTRAINT valid_holding_type CHECK
16097                                   (
16098                                       holding_type IS NULL
16099                                       OR holding_type IN ('basic','supplement','index')
16100                                   ),
16101         holding_link_id INT
16102         -- TODO: add columns for separate enumeration/chronology values
16103 );
16104 CREATE INDEX serial_issuance_sub_idx ON serial.issuance (subscription);
16105 CREATE INDEX serial_issuance_caption_and_pattern_idx ON serial.issuance (caption_and_pattern);
16106 CREATE INDEX serial_issuance_date_published_idx ON serial.issuance (date_published);
16107
16108 CREATE TABLE serial.unit (
16109         label           TEXT,
16110         label_sort_key  TEXT,
16111         contents        TEXT    NOT NULL
16112 ) INHERITS (asset.copy);
16113 CREATE UNIQUE INDEX unit_barcode_key ON serial.unit (barcode) WHERE deleted = FALSE OR deleted IS FALSE;
16114 CREATE INDEX unit_cn_idx ON serial.unit (call_number);
16115 CREATE INDEX unit_avail_cn_idx ON serial.unit (call_number);
16116 CREATE INDEX unit_creator_idx  ON serial.unit ( creator );
16117 CREATE INDEX unit_editor_idx   ON serial.unit ( editor );
16118
16119 ALTER TABLE serial.unit ADD PRIMARY KEY (id);
16120
16121 ALTER TABLE serial.unit ADD CONSTRAINT serial_unit_call_number_fkey FOREIGN KEY (call_number) REFERENCES asset.call_number (id) DEFERRABLE INITIALLY DEFERRED;
16122
16123 ALTER TABLE serial.unit ADD CONSTRAINT serial_unit_creator_fkey FOREIGN KEY (creator) REFERENCES actor.usr (id) ON DELETE SET NULL DEFERRABLE INITIALLY DEFERRED;
16124
16125 ALTER TABLE serial.unit ADD CONSTRAINT serial_unit_editor_fkey FOREIGN KEY (editor) REFERENCES actor.usr (id) ON DELETE SET NULL DEFERRABLE INITIALLY DEFERRED;
16126
16127 CREATE TABLE serial.item (
16128         id              SERIAL  PRIMARY KEY,
16129         creator         INT     NOT NULL
16130                                 REFERENCES actor.usr (id)
16131                                 DEFERRABLE INITIALLY DEFERRED,
16132         editor          INT     NOT NULL
16133                                 REFERENCES actor.usr (id)
16134                                 DEFERRABLE INITIALLY DEFERRED,
16135         create_date     TIMESTAMP WITH TIME ZONE        NOT NULL DEFAULT now(),
16136         edit_date       TIMESTAMP WITH TIME ZONE        NOT NULL DEFAULT now(),
16137         issuance        INT     NOT NULL
16138                                 REFERENCES serial.issuance (id)
16139                                 ON DELETE CASCADE
16140                                 DEFERRABLE INITIALLY DEFERRED,
16141         stream          INT     NOT NULL
16142                                 REFERENCES serial.stream (id)
16143                                 ON DELETE CASCADE
16144                                 DEFERRABLE INITIALLY DEFERRED,
16145         unit            INT     REFERENCES serial.unit (id)
16146                                 ON DELETE SET NULL
16147                                 DEFERRABLE INITIALLY DEFERRED,
16148         uri             INT     REFERENCES asset.uri (id)
16149                                 ON DELETE SET NULL
16150                                 DEFERRABLE INITIALLY DEFERRED,
16151         date_expected   TIMESTAMP WITH TIME ZONE,
16152         date_received   TIMESTAMP WITH TIME ZONE,
16153         status          TEXT    CONSTRAINT valid_status CHECK (
16154                                status IN ( 'Bindery', 'Bound', 'Claimed', 'Discarded',
16155                                'Expected', 'Not Held', 'Not Published', 'Received'))
16156                             DEFAULT 'Expected',
16157         shadowed        BOOL    NOT NULL DEFAULT FALSE
16158 );
16159 CREATE INDEX serial_item_stream_idx ON serial.item (stream);
16160 CREATE INDEX serial_item_issuance_idx ON serial.item (issuance);
16161 CREATE INDEX serial_item_unit_idx ON serial.item (unit);
16162 CREATE INDEX serial_item_uri_idx ON serial.item (uri);
16163 CREATE INDEX serial_item_date_received_idx ON serial.item (date_received);
16164 CREATE INDEX serial_item_status_idx ON serial.item (status);
16165
16166 CREATE TABLE serial.item_note (
16167         id          SERIAL  PRIMARY KEY,
16168         item        INT     NOT NULL
16169                             REFERENCES serial.item (id)
16170                             ON DELETE CASCADE
16171                             DEFERRABLE INITIALLY DEFERRED,
16172         creator     INT     NOT NULL
16173                             REFERENCES actor.usr (id)
16174                             DEFERRABLE INITIALLY DEFERRED,
16175         create_date TIMESTAMP WITH TIME ZONE    DEFAULT NOW(),
16176         pub         BOOL    NOT NULL    DEFAULT FALSE,
16177         title       TEXT    NOT NULL,
16178         value       TEXT    NOT NULL
16179 );
16180 CREATE INDEX serial_item_note_item_idx ON serial.item_note (item);
16181
16182 CREATE TABLE serial.basic_summary (
16183         id                  SERIAL  PRIMARY KEY,
16184         distribution        INT     NOT NULL
16185                                     REFERENCES serial.distribution (id)
16186                                     ON DELETE CASCADE
16187                                     DEFERRABLE INITIALLY DEFERRED,
16188         generated_coverage  TEXT    NOT NULL,
16189         textual_holdings    TEXT,
16190         show_generated      BOOL    NOT NULL DEFAULT TRUE
16191 );
16192 CREATE INDEX serial_basic_summary_dist_idx ON serial.basic_summary (distribution);
16193
16194 CREATE TABLE serial.supplement_summary (
16195         id                  SERIAL  PRIMARY KEY,
16196         distribution        INT     NOT NULL
16197                                     REFERENCES serial.distribution (id)
16198                                     ON DELETE CASCADE
16199                                     DEFERRABLE INITIALLY DEFERRED,
16200         generated_coverage  TEXT    NOT NULL,
16201         textual_holdings    TEXT,
16202         show_generated      BOOL    NOT NULL DEFAULT TRUE
16203 );
16204 CREATE INDEX serial_supplement_summary_dist_idx ON serial.supplement_summary (distribution);
16205
16206 CREATE TABLE serial.index_summary (
16207         id                  SERIAL  PRIMARY KEY,
16208         distribution        INT     NOT NULL
16209                                     REFERENCES serial.distribution (id)
16210                                     ON DELETE CASCADE
16211                                     DEFERRABLE INITIALLY DEFERRED,
16212         generated_coverage  TEXT    NOT NULL,
16213         textual_holdings    TEXT,
16214         show_generated      BOOL    NOT NULL DEFAULT TRUE
16215 );
16216 CREATE INDEX serial_index_summary_dist_idx ON serial.index_summary (distribution);
16217
16218 -- 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.
16219
16220 DROP INDEX IF EXISTS authority.authority_record_unique_tcn;
16221 CREATE UNIQUE INDEX authority_record_unique_tcn ON authority.record_entry (arn_source,arn_value) WHERE deleted = FALSE OR deleted IS FALSE;
16222
16223 DROP INDEX IF EXISTS asset.asset_call_number_label_once_per_lib;
16224 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;
16225
16226 DROP INDEX IF EXISTS biblio.biblio_record_unique_tcn;
16227 CREATE UNIQUE INDEX biblio_record_unique_tcn ON biblio.record_entry (tcn_value) WHERE deleted = FALSE OR deleted IS FALSE;
16228
16229 CREATE OR REPLACE FUNCTION config.interval_to_seconds( interval_val INTERVAL )
16230 RETURNS INTEGER AS $$
16231 BEGIN
16232         RETURN EXTRACT( EPOCH FROM interval_val );
16233 END;
16234 $$ LANGUAGE plpgsql;
16235
16236 CREATE OR REPLACE FUNCTION config.interval_to_seconds( interval_string TEXT )
16237 RETURNS INTEGER AS $$
16238 BEGIN
16239         RETURN config.interval_to_seconds( interval_string::INTERVAL );
16240 END;
16241 $$ LANGUAGE plpgsql;
16242
16243 INSERT INTO container.biblio_record_entry_bucket_type( code, label ) VALUES (
16244     'temp',
16245     oils_i18n_gettext(
16246         'temp',
16247         'Temporary bucket which gets deleted after use.',
16248         'cbrebt',
16249         'label'
16250     )
16251 );
16252
16253 -- 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.
16254
16255 CREATE OR REPLACE FUNCTION biblio.check_marcxml_well_formed () RETURNS TRIGGER AS $func$
16256 BEGIN
16257
16258     IF xml_is_well_formed(NEW.marc) THEN
16259         RETURN NEW;
16260     ELSE
16261         RAISE EXCEPTION 'Attempted to % MARCXML that is not well formed', TG_OP;
16262     END IF;
16263     
16264 END;
16265 $func$ LANGUAGE PLPGSQL;
16266
16267 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();
16268
16269 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();
16270
16271 ALTER TABLE serial.record_entry
16272         ALTER COLUMN marc DROP NOT NULL;
16273
16274 insert INTO CONFIG.xml_transform(name, namespace_uri, prefix, xslt)
16275 VALUES ('marc21expand880', 'http://www.loc.gov/MARC21/slim', 'marc', $$<?xml version="1.0" encoding="UTF-8"?>
16276 <xsl:stylesheet
16277     xmlns:xsl="http://www.w3.org/1999/XSL/Transform" 
16278     xmlns:marc="http://www.loc.gov/MARC21/slim"
16279     version="1.0">
16280 <!--
16281 Copyright (C) 2010  Equinox Software, Inc.
16282 Galen Charlton <gmc@esilibrary.cOM.
16283
16284 This program is free software; you can redistribute it and/or
16285 modify it under the terms of the GNU General Public License
16286 as published by the Free Software Foundation; either version 2
16287 of the License, or (at your option) any later version.
16288
16289 This program is distributed in the hope that it will be useful,
16290 but WITHOUT ANY WARRANTY; without even the implied warranty of
16291 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16292 GNU General Public License for more details.
16293
16294 marc21_expand_880.xsl - stylesheet used during indexing to
16295                         map alternative graphical representations
16296                         of MARC fields stored in 880 fields
16297                         to the corresponding tag name and value.
16298
16299 For example, if a MARC record for a Chinese book has
16300
16301 245.00 $6 880-01 $a Ba shi san nian duan pian xiao shuo xuan
16302 880.00 $6 245-01/$1 $a八十三年短篇小說選
16303
16304 this stylesheet will transform it to the equivalent of
16305
16306 245.00 $6 880-01 $a Ba shi san nian duan pian xiao shuo xuan
16307 245.00 $6 245-01/$1 $a八十三年短篇小說選
16308
16309 -->
16310     <xsl:output encoding="UTF-8" indent="yes" method="xml"/>
16311
16312     <xsl:template match="@*|node()">
16313         <xsl:copy>
16314             <xsl:apply-templates select="@*|node()"/>
16315         </xsl:copy>
16316     </xsl:template>
16317
16318     <xsl:template match="//marc:datafield[@tag='880']">
16319         <xsl:if test="./marc:subfield[@code='6'] and string-length(./marc:subfield[@code='6']) &gt;= 6">
16320             <marc:datafield>
16321                 <xsl:attribute name="tag">
16322                     <xsl:value-of select="substring(./marc:subfield[@code='6'], 1, 3)" />
16323                 </xsl:attribute>
16324                 <xsl:attribute name="ind1">
16325                     <xsl:value-of select="@ind1" />
16326                 </xsl:attribute>
16327                 <xsl:attribute name="ind2">
16328                     <xsl:value-of select="@ind2" />
16329                 </xsl:attribute>
16330                 <xsl:apply-templates />
16331             </marc:datafield>
16332         </xsl:if>
16333     </xsl:template>
16334     
16335 </xsl:stylesheet>$$);
16336
16337 -- Splitting the ingest trigger up into little bits
16338
16339 CREATE TEMPORARY TABLE eg_0301_check_if_has_contents (
16340     flag INTEGER PRIMARY KEY
16341 ) ON COMMIT DROP;
16342 INSERT INTO eg_0301_check_if_has_contents VALUES (1);
16343
16344 -- cause failure if either of the tables we want to drop have rows
16345 INSERT INTO eg_0301_check_if_has_contents SELECT 1 FROM asset.copy_transparency LIMIT 1;
16346 INSERT INTO eg_0301_check_if_has_contents SELECT 1 FROM asset.copy_transparency_map LIMIT 1;
16347
16348 DROP TABLE IF EXISTS asset.copy_transparency_map;
16349 DROP TABLE IF EXISTS asset.copy_transparency;
16350
16351 UPDATE config.metabib_field SET facet_xpath = '//' || facet_xpath WHERE facet_xpath IS NOT NULL;
16352
16353 -- We won't necessarily use all of these, but they are here for completeness.
16354 -- Source is the EDI spec 1229 codelist, eg: http://www.stylusstudio.com/edifact/D04B/1229.htm
16355 -- Values are the EDI code value + 1000
16356
16357 INSERT INTO acq.cancel_reason (keep_debits, id, org_unit, label, description) VALUES 
16358 ('t',(  1+1000), 1, 'Added',     'The information is to be or has been added.'),
16359 ('f',(  2+1000), 1, 'Deleted',   'The information is to be or has been deleted.'),
16360 ('t',(  3+1000), 1, 'Changed',   'The information is to be or has been changed.'),
16361 ('t',(  4+1000), 1, 'No action',                  'This line item is not affected by the actual message.'),
16362 ('t',(  5+1000), 1, 'Accepted without amendment', 'This line item is entirely accepted by the seller.'),
16363 ('t',(  6+1000), 1, 'Accepted with amendment',    'This line item is accepted but amended by the seller.'),
16364 ('f',(  7+1000), 1, 'Not accepted',               'This line item is not accepted by the seller.'),
16365 ('t',(  8+1000), 1, 'Schedule only', 'Code specifying that the message is a schedule only.'),
16366 ('t',(  9+1000), 1, 'Amendments',    'Code specifying that amendments are requested/notified.'),
16367 ('f',( 10+1000), 1, 'Not found',   'This line item is not found in the referenced message.'),
16368 ('t',( 11+1000), 1, 'Not amended', 'This line is not amended by the buyer.'),
16369 ('t',( 12+1000), 1, 'Line item numbers changed', 'Code specifying that the line item numbers have changed.'),
16370 ('t',( 13+1000), 1, 'Buyer has deducted amount', 'Buyer has deducted amount from payment.'),
16371 ('t',( 14+1000), 1, 'Buyer claims against invoice', 'Buyer has a claim against an outstanding invoice.'),
16372 ('t',( 15+1000), 1, 'Charge back by seller', 'Factor has been requested to charge back the outstanding item.'),
16373 ('t',( 16+1000), 1, 'Seller will issue credit note', 'Seller agrees to issue a credit note.'),
16374 ('t',( 17+1000), 1, 'Terms changed for new terms', 'New settlement terms have been agreed.'),
16375 ('t',( 18+1000), 1, 'Abide outcome of negotiations', 'Factor agrees to abide by the outcome of negotiations between seller and buyer.'),
16376 ('t',( 19+1000), 1, 'Seller rejects dispute', 'Seller does not accept validity of dispute.'),
16377 ('t',( 20+1000), 1, 'Settlement', 'The reported situation is settled.'),
16378 ('t',( 21+1000), 1, 'No delivery', 'Code indicating that no delivery will be required.'),
16379 ('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).'),
16380 ('t',( 23+1000), 1, 'Proposed amendment', 'A code used to indicate an amendment suggested by the sender.'),
16381 ('t',( 24+1000), 1, 'Accepted with amendment, no confirmation required', 'Accepted with changes which require no confirmation.'),
16382 ('t',( 25+1000), 1, 'Equipment provisionally repaired', 'The equipment or component has been provisionally repaired.'),
16383 ('t',( 26+1000), 1, 'Included', 'Code indicating that the entity is included.'),
16384 ('t',( 27+1000), 1, 'Verified documents for coverage', 'Upon receipt and verification of documents we shall cover you when due as per your instructions.'),
16385 ('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.'),
16386 ('t',( 29+1000), 1, 'Authenticated advice for coverage',      'On receipt of your authenticated advice we shall cover you when due as per your instructions.'),
16387 ('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.'),
16388 ('t',( 31+1000), 1, 'Authenticated advice for credit',        'On receipt of your authenticated advice we shall credit your account with us when due.'),
16389 ('t',( 32+1000), 1, 'Credit advice requested for direct debit',           'A credit advice is requested for the direct debit.'),
16390 ('t',( 33+1000), 1, 'Credit advice and acknowledgement for direct debit', 'A credit advice and acknowledgement are requested for the direct debit.'),
16391 ('t',( 34+1000), 1, 'Inquiry',     'Request for information.'),
16392 ('t',( 35+1000), 1, 'Checked',     'Checked.'),
16393 ('t',( 36+1000), 1, 'Not checked', 'Not checked.'),
16394 ('f',( 37+1000), 1, 'Cancelled',   'Discontinued.'),
16395 ('t',( 38+1000), 1, 'Replaced',    'Provide a replacement.'),
16396 ('t',( 39+1000), 1, 'New',         'Not existing before.'),
16397 ('t',( 40+1000), 1, 'Agreed',      'Consent.'),
16398 ('t',( 41+1000), 1, 'Proposed',    'Put forward for consideration.'),
16399 ('t',( 42+1000), 1, 'Already delivered', 'Delivery has taken place.'),
16400 ('t',( 43+1000), 1, 'Additional subordinate structures will follow', 'Additional subordinate structures will follow the current hierarchy level.'),
16401 ('t',( 44+1000), 1, 'Additional subordinate structures will not follow', 'No additional subordinate structures will follow the current hierarchy level.'),
16402 ('t',( 45+1000), 1, 'Result opposed',         'A notification that the result is opposed.'),
16403 ('t',( 46+1000), 1, 'Auction held',           'A notification that an auction was held.'),
16404 ('t',( 47+1000), 1, 'Legal action pursued',   'A notification that legal action has been pursued.'),
16405 ('t',( 48+1000), 1, 'Meeting held',           'A notification that a meeting was held.'),
16406 ('t',( 49+1000), 1, 'Result set aside',       'A notification that the result has been set aside.'),
16407 ('t',( 50+1000), 1, 'Result disputed',        'A notification that the result has been disputed.'),
16408 ('t',( 51+1000), 1, 'Countersued',            'A notification that a countersuit has been filed.'),
16409 ('t',( 52+1000), 1, 'Pending',                'A notification that an action is awaiting settlement.'),
16410 ('f',( 53+1000), 1, 'Court action dismissed', 'A notification that a court action will no longer be heard.'),
16411 ('t',( 54+1000), 1, 'Referred item, accepted', 'The item being referred to has been accepted.'),
16412 ('f',( 55+1000), 1, 'Referred item, rejected', 'The item being referred to has been rejected.'),
16413 ('t',( 56+1000), 1, 'Debit advice statement line',  'Notification that the statement line is a debit advice.'),
16414 ('t',( 57+1000), 1, 'Credit advice statement line', 'Notification that the statement line is a credit advice.'),
16415 ('t',( 58+1000), 1, 'Grouped credit advices',       'Notification that the credit advices are grouped.'),
16416 ('t',( 59+1000), 1, 'Grouped debit advices',        'Notification that the debit advices are grouped.'),
16417 ('t',( 60+1000), 1, 'Registered', 'The name is registered.'),
16418 ('f',( 61+1000), 1, 'Payment denied', 'The payment has been denied.'),
16419 ('t',( 62+1000), 1, 'Approved as amended', 'Approved with modifications.'),
16420 ('t',( 63+1000), 1, 'Approved as submitted', 'The request has been approved as submitted.'),
16421 ('f',( 64+1000), 1, 'Cancelled, no activity', 'Cancelled due to the lack of activity.'),
16422 ('t',( 65+1000), 1, 'Under investigation', 'Investigation is being done.'),
16423 ('t',( 66+1000), 1, 'Initial claim received', 'Notification that the initial claim was received.'),
16424 ('f',( 67+1000), 1, 'Not in process', 'Not in process.'),
16425 ('f',( 68+1000), 1, 'Rejected, duplicate', 'Rejected because it is a duplicate.'),
16426 ('f',( 69+1000), 1, 'Rejected, resubmit with corrections', 'Rejected but may be resubmitted when corrected.'),
16427 ('t',( 70+1000), 1, 'Pending, incomplete', 'Pending because of incomplete information.'),
16428 ('t',( 71+1000), 1, 'Under field office investigation', 'Investigation by the field is being done.'),
16429 ('t',( 72+1000), 1, 'Pending, awaiting additional material', 'Pending awaiting receipt of additional material.'),
16430 ('t',( 73+1000), 1, 'Pending, awaiting review', 'Pending while awaiting review.'),
16431 ('t',( 74+1000), 1, 'Reopened', 'Opened again.'),
16432 ('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).'),
16433 ('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).'),
16434 ('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).'),
16435 ('t',( 78+1000), 1, 'Previous payment decision reversed', 'A previous payment decision has been reversed.'),
16436 ('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).'),
16437 ('t',( 80+1000), 1, 'Transferred to correct insurance carrier', 'The request has been transferred to the correct insurance carrier for processing.'),
16438 ('t',( 81+1000), 1, 'Not paid, predetermination pricing only', 'Payment has not been made and the enclosed response is predetermination pricing only.'),
16439 ('t',( 82+1000), 1, 'Documentation claim', 'The claim is for documentation purposes only, no payment required.'),
16440 ('t',( 83+1000), 1, 'Reviewed', 'Assessed.'),
16441 ('f',( 84+1000), 1, 'Repriced', 'This price was changed.'),
16442 ('t',( 85+1000), 1, 'Audited', 'An official examination has occurred.'),
16443 ('t',( 86+1000), 1, 'Conditionally paid', 'Payment has been conditionally made.'),
16444 ('t',( 87+1000), 1, 'On appeal', 'Reconsideration of the decision has been applied for.'),
16445 ('t',( 88+1000), 1, 'Closed', 'Shut.'),
16446 ('t',( 89+1000), 1, 'Reaudited', 'A subsequent official examination has occurred.'),
16447 ('t',( 90+1000), 1, 'Reissued', 'Issued again.'),
16448 ('t',( 91+1000), 1, 'Closed after reopening', 'Reopened and then closed.'),
16449 ('t',( 92+1000), 1, 'Redetermined', 'Determined again or differently.'),
16450 ('t',( 93+1000), 1, 'Processed as primary',   'Processed as the first.'),
16451 ('t',( 94+1000), 1, 'Processed as secondary', 'Processed as the second.'),
16452 ('t',( 95+1000), 1, 'Processed as tertiary',  'Processed as the third.'),
16453 ('t',( 96+1000), 1, 'Correction of error', 'A correction to information previously communicated which contained an error.'),
16454 ('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.'),
16455 ('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.'),
16456 ('t',( 99+1000), 1, 'Interim response', 'The response is an interim one.'),
16457 ('t',(100+1000), 1, 'Final response',   'The response is an final one.'),
16458 ('t',(101+1000), 1, 'Debit advice requested', 'A debit advice is requested for the transaction.'),
16459 ('t',(102+1000), 1, 'Transaction not impacted', 'Advice that the transaction is not impacted.'),
16460 ('t',(103+1000), 1, 'Patient to be notified',                    'The action to take is to notify the patient.'),
16461 ('t',(104+1000), 1, 'Healthcare provider to be notified',        'The action to take is to notify the healthcare provider.'),
16462 ('t',(105+1000), 1, 'Usual general practitioner to be notified', 'The action to take is to notify the usual general practitioner.'),
16463 ('t',(106+1000), 1, 'Advice without details', 'An advice without details is requested or notified.'),
16464 ('t',(107+1000), 1, 'Advice with details', 'An advice with details is requested or notified.'),
16465 ('t',(108+1000), 1, 'Amendment requested', 'An amendment is requested.'),
16466 ('t',(109+1000), 1, 'For information', 'Included for information only.'),
16467 ('f',(110+1000), 1, 'Withdraw', 'A code indicating discontinuance or retraction.'),
16468 ('t',(111+1000), 1, 'Delivery date change', 'The action / notiification is a change of the delivery date.'),
16469 ('f',(112+1000), 1, 'Quantity change',      'The action / notification is a change of quantity.'),
16470 ('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.'),
16471 ('t',(114+1000), 1, 'Resale',           'The identified items have been sold by the distributor to the end customer.'),
16472 ('t',(115+1000), 1, 'Prior addition', 'This existing line item becomes available at an earlier date.');
16473
16474 INSERT INTO config.metabib_field ( id, field_class, name, label, format, xpath, facet_field, search_field ) VALUES
16475     (26, 'identifier', 'arcn', oils_i18n_gettext(26, 'Authority record control number', 'cmf', 'label'), 'marcxml', $$//marc:subfield[@code='0']$$, TRUE, FALSE );
16476  
16477 SELECT SETVAL('config.metabib_field_id_seq'::TEXT, (SELECT MAX(id) FROM config.metabib_field), TRUE);
16478  
16479 INSERT INTO config.index_normalizer (name, description, func, param_count) VALUES (
16480         'Remove Parenthesized Substring',
16481         'Remove any parenthesized substrings from the extracted text, such as the agency code preceding authority record control numbers in subfield 0.',
16482         'remove_paren_substring',
16483         0
16484 );
16485
16486 INSERT INTO config.index_normalizer (name, description, func, param_count) VALUES (
16487         'Trim Surrounding Space',
16488         'Trim leading and trailing spaces from extracted text.',
16489         'btrim',
16490         0
16491 );
16492
16493 INSERT INTO config.metabib_field_index_norm_map (field,norm,pos)
16494     SELECT  m.id,
16495             i.id,
16496             -2
16497       FROM  config.metabib_field m,
16498             config.index_normalizer i
16499       WHERE i.func IN ('remove_paren_substring')
16500             AND m.id IN (26);
16501
16502 INSERT INTO config.metabib_field_index_norm_map (field,norm,pos)
16503     SELECT  m.id,
16504             i.id,
16505             -1
16506       FROM  config.metabib_field m,
16507             config.index_normalizer i
16508       WHERE i.func IN ('btrim')
16509             AND m.id IN (26);
16510
16511 -- Function that takes, and returns, marcxml and compiles an embedded ruleset for you, and they applys it
16512 CREATE OR REPLACE FUNCTION vandelay.merge_record_xml ( target_marc TEXT, template_marc TEXT ) RETURNS TEXT AS $$
16513 DECLARE
16514     dyn_profile     vandelay.compile_profile%ROWTYPE;
16515     replace_rule    TEXT;
16516     tmp_marc        TEXT;
16517     trgt_marc        TEXT;
16518     tmpl_marc        TEXT;
16519     match_count     INT;
16520 BEGIN
16521
16522     IF target_marc IS NULL OR template_marc IS NULL THEN
16523         -- RAISE NOTICE 'no marc for target or template record';
16524         RETURN NULL;
16525     END IF;
16526
16527     dyn_profile := vandelay.compile_profile( template_marc );
16528
16529     IF dyn_profile.replace_rule <> '' AND dyn_profile.preserve_rule <> '' THEN
16530         -- RAISE NOTICE 'both replace [%] and preserve [%] specified', dyn_profile.replace_rule, dyn_profile.preserve_rule;
16531         RETURN NULL;
16532     END IF;
16533
16534     IF dyn_profile.replace_rule <> '' THEN
16535         trgt_marc = target_marc;
16536         tmpl_marc = template_marc;
16537         replace_rule = dyn_profile.replace_rule;
16538     ELSE
16539         tmp_marc = target_marc;
16540         trgt_marc = template_marc;
16541         tmpl_marc = tmp_marc;
16542         replace_rule = dyn_profile.preserve_rule;
16543     END IF;
16544
16545     RETURN vandelay.merge_record_xml( trgt_marc, tmpl_marc, dyn_profile.add_rule, replace_rule, dyn_profile.strip_rule );
16546
16547 END;
16548 $$ LANGUAGE PLPGSQL;
16549
16550 -- Function to generate an ephemeral overlay template from an authority record
16551 CREATE OR REPLACE FUNCTION authority.generate_overlay_template ( TEXT, BIGINT ) RETURNS TEXT AS $func$
16552
16553     use MARC::Record;
16554     use MARC::File::XML (BinaryEncoding => 'UTF-8');
16555
16556     my $xml = shift;
16557     my $r = MARC::Record->new_from_xml( $xml );
16558
16559     return undef unless ($r);
16560
16561     my $id = shift() || $r->subfield( '901' => 'c' );
16562     $id =~ s/^\s*(?:\([^)]+\))?\s*(.+)\s*?$/$1/;
16563     return undef unless ($id); # We need an ID!
16564
16565     my $tmpl = MARC::Record->new();
16566
16567     my @rule_fields;
16568     for my $field ( $r->field( '1..' ) ) { # Get main entry fields from the authority record
16569
16570         my $tag = $field->tag;
16571         my $i1 = $field->indicator(1);
16572         my $i2 = $field->indicator(2);
16573         my $sf = join '', map { $_->[0] } $field->subfields;
16574         my @data = map { @$_ } $field->subfields;
16575
16576         my @replace_them;
16577
16578         # Map the authority field to bib fields it can control.
16579         if ($tag >= 100 and $tag <= 111) {       # names
16580             @replace_them = map { $tag + $_ } (0, 300, 500, 600, 700);
16581         } elsif ($tag eq '130') {                # uniform title
16582             @replace_them = qw/130 240 440 730 830/;
16583         } elsif ($tag >= 150 and $tag <= 155) {  # subjects
16584             @replace_them = ($tag + 500);
16585         } elsif ($tag >= 180 and $tag <= 185) {  # floating subdivisions
16586             @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/;
16587         } else {
16588             next;
16589         }
16590
16591         # Dummy up the bib-side data
16592         $tmpl->append_fields(
16593             map {
16594                 MARC::Field->new( $_, $i1, $i2, @data )
16595             } @replace_them
16596         );
16597
16598         # Construct some 'replace' rules
16599         push @rule_fields, map { $_ . $sf . '[0~\)' .$id . '$]' } @replace_them;
16600     }
16601
16602     # Insert the replace rules into the template
16603     $tmpl->append_fields(
16604         MARC::Field->new( '905' => ' ' => ' ' => 'r' => join(',', @rule_fields ) )
16605     );
16606
16607     $xml = $tmpl->as_xml_record;
16608     $xml =~ s/^<\?.+?\?>$//mo;
16609     $xml =~ s/\n//sgo;
16610     $xml =~ s/>\s+</></sgo;
16611
16612     return $xml;
16613
16614 $func$ LANGUAGE PLPERLU;
16615
16616 CREATE OR REPLACE FUNCTION authority.generate_overlay_template ( BIGINT ) RETURNS TEXT AS $func$
16617     SELECT authority.generate_overlay_template( marc, id ) FROM authority.record_entry WHERE id = $1;
16618 $func$ LANGUAGE SQL;
16619
16620 CREATE OR REPLACE FUNCTION authority.generate_overlay_template ( TEXT ) RETURNS TEXT AS $func$
16621     SELECT authority.generate_overlay_template( $1, NULL );
16622 $func$ LANGUAGE SQL;
16623
16624 DELETE FROM config.metabib_field_index_norm_map WHERE field = 26;
16625 DELETE FROM config.metabib_field WHERE id = 26;
16626
16627 -- Making this a global_flag (UI accessible) instead of an internal_flag
16628 INSERT INTO config.global_flag (name, label) -- defaults to enabled=FALSE
16629     VALUES (
16630         'ingest.disable_authority_linking',
16631         oils_i18n_gettext(
16632             'ingest.disable_authority_linking',
16633             'Authority Automation: Disable bib-authority link tracking',
16634             'cgf', 
16635             'label'
16636         )
16637     );
16638 UPDATE config.global_flag SET enabled = (SELECT enabled FROM ONLY config.internal_flag WHERE name = 'ingest.disable_authority_linking');
16639 DELETE FROM config.internal_flag WHERE name = 'ingest.disable_authority_linking';
16640
16641 INSERT INTO config.global_flag (name, label) -- defaults to enabled=FALSE
16642     VALUES (
16643         'ingest.disable_authority_auto_update',
16644         oils_i18n_gettext(
16645             'ingest.disable_authority_auto_update',
16646             'Authority Automation: Disable automatic authority updating (requires link tracking)',
16647             'cgf', 
16648             'label'
16649         )
16650     );
16651
16652 -- Enable automated ingest of authority records; just insert the row into
16653 -- authority.record_entry and authority.full_rec will automatically be populated
16654
16655 CREATE OR REPLACE FUNCTION authority.propagate_changes (aid BIGINT, bid BIGINT) RETURNS BIGINT AS $func$
16656     UPDATE  biblio.record_entry
16657       SET   marc = vandelay.merge_record_xml( marc, authority.generate_overlay_template( $1 ) )
16658       WHERE id = $2;
16659     SELECT $1;
16660 $func$ LANGUAGE SQL;
16661
16662 CREATE OR REPLACE FUNCTION authority.propagate_changes (aid BIGINT) RETURNS SETOF BIGINT AS $func$
16663     SELECT authority.propagate_changes( authority, bib ) FROM authority.bib_linking WHERE authority = $1;
16664 $func$ LANGUAGE SQL;
16665
16666 CREATE OR REPLACE FUNCTION authority.flatten_marc ( TEXT ) RETURNS SETOF authority.full_rec AS $func$
16667
16668 use MARC::Record;
16669 use MARC::File::XML (BinaryEncoding => 'UTF-8');
16670
16671 my $xml = shift;
16672 my $r = MARC::Record->new_from_xml( $xml );
16673
16674 return_next( { tag => 'LDR', value => $r->leader } );
16675
16676 for my $f ( $r->fields ) {
16677     if ($f->is_control_field) {
16678         return_next({ tag => $f->tag, value => $f->data });
16679     } else {
16680         for my $s ($f->subfields) {
16681             return_next({
16682                 tag      => $f->tag,
16683                 ind1     => $f->indicator(1),
16684                 ind2     => $f->indicator(2),
16685                 subfield => $s->[0],
16686                 value    => $s->[1]
16687             });
16688
16689         }
16690     }
16691 }
16692
16693 return undef;
16694
16695 $func$ LANGUAGE PLPERLU;
16696
16697 CREATE OR REPLACE FUNCTION authority.flatten_marc ( rid BIGINT ) RETURNS SETOF authority.full_rec AS $func$
16698 DECLARE
16699     auth    authority.record_entry%ROWTYPE;
16700     output    authority.full_rec%ROWTYPE;
16701     field    RECORD;
16702 BEGIN
16703     SELECT INTO auth * FROM authority.record_entry WHERE id = rid;
16704
16705     FOR field IN SELECT * FROM authority.flatten_marc( auth.marc ) LOOP
16706         output.record := rid;
16707         output.ind1 := field.ind1;
16708         output.ind2 := field.ind2;
16709         output.tag := field.tag;
16710         output.subfield := field.subfield;
16711         IF field.subfield IS NOT NULL THEN
16712             output.value := naco_normalize(field.value, field.subfield);
16713         ELSE
16714             output.value := field.value;
16715         END IF;
16716
16717         CONTINUE WHEN output.value IS NULL;
16718
16719         RETURN NEXT output;
16720     END LOOP;
16721 END;
16722 $func$ LANGUAGE PLPGSQL;
16723
16724 -- authority.rec_descriptor appears to be unused currently
16725 CREATE OR REPLACE FUNCTION authority.reingest_authority_rec_descriptor( auth_id BIGINT ) RETURNS VOID AS $func$
16726 BEGIN
16727     DELETE FROM authority.rec_descriptor WHERE record = auth_id;
16728 --    INSERT INTO authority.rec_descriptor (record, record_status, char_encoding)
16729 --        SELECT  auth_id, ;
16730
16731     RETURN;
16732 END;
16733 $func$ LANGUAGE PLPGSQL;
16734
16735 CREATE OR REPLACE FUNCTION authority.reingest_authority_full_rec( auth_id BIGINT ) RETURNS VOID AS $func$
16736 BEGIN
16737     DELETE FROM authority.full_rec WHERE record = auth_id;
16738     INSERT INTO authority.full_rec (record, tag, ind1, ind2, subfield, value)
16739         SELECT record, tag, ind1, ind2, subfield, value FROM authority.flatten_marc( auth_id );
16740
16741     RETURN;
16742 END;
16743 $func$ LANGUAGE PLPGSQL;
16744
16745 -- AFTER UPDATE OR INSERT trigger for authority.record_entry
16746 CREATE OR REPLACE FUNCTION authority.indexing_ingest_or_delete () RETURNS TRIGGER AS $func$
16747 BEGIN
16748
16749     IF NEW.deleted IS TRUE THEN -- If this authority is deleted
16750         DELETE FROM authority.bib_linking WHERE authority = NEW.id; -- Avoid updating fields in bibs that are no longer visible
16751         DELETE FROM authority.full_rec WHERE record = NEW.id; -- Avoid validating fields against deleted authority records
16752           -- Should remove matching $0 from controlled fields at the same time?
16753         RETURN NEW; -- and we're done
16754     END IF;
16755
16756     IF TG_OP = 'UPDATE' THEN -- re-ingest?
16757         PERFORM * FROM config.internal_flag WHERE name = 'ingest.reingest.force_on_same_marc' AND enabled;
16758
16759         IF NOT FOUND AND OLD.marc = NEW.marc THEN -- don't do anything if the MARC didn't change
16760             RETURN NEW;
16761         END IF;
16762     END IF;
16763
16764     -- Flatten and insert the afr data
16765     PERFORM * FROM config.internal_flag WHERE name = 'ingest.disable_authority_full_rec' AND enabled;
16766     IF NOT FOUND THEN
16767         PERFORM authority.reingest_authority_full_rec(NEW.id);
16768 -- authority.rec_descriptor is not currently used
16769 --        PERFORM * FROM config.internal_flag WHERE name = 'ingest.disable_authority_rec_descriptor' AND enabled;
16770 --        IF NOT FOUND THEN
16771 --            PERFORM authority.reingest_authority_rec_descriptor(NEW.id);
16772 --        END IF;
16773     END IF;
16774
16775     RETURN NEW;
16776 END;
16777 $func$ LANGUAGE PLPGSQL;
16778
16779 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 ();
16780
16781 -- Some records manage to get XML namespace declarations into each element,
16782 -- like <datafield xmlns:marc="http://www.loc.gov/MARC21/slim"
16783 -- This broke the old maintain_901(), so we'll make the regex more robust
16784
16785 CREATE OR REPLACE FUNCTION maintain_901 () RETURNS TRIGGER AS $func$
16786 BEGIN
16787     -- Remove any existing 901 fields before we insert the authoritative one
16788     NEW.marc := REGEXP_REPLACE(NEW.marc, E'<datafield\s*[^<>]*?\s*tag="901".+?</datafield>', '', 'g');
16789     IF TG_TABLE_SCHEMA = 'biblio' THEN
16790         NEW.marc := REGEXP_REPLACE(
16791             NEW.marc,
16792             E'(</(?:[^:]*?:)?record>)',
16793             E'<datafield tag="901" ind1=" " ind2=" ">' ||
16794                 '<subfield code="a">' || NEW.tcn_value || E'</subfield>' ||
16795                 '<subfield code="b">' || NEW.tcn_source || E'</subfield>' ||
16796                 '<subfield code="c">' || NEW.id || E'</subfield>' ||
16797                 '<subfield code="t">' || TG_TABLE_SCHEMA || E'</subfield>' ||
16798                 CASE WHEN NEW.owner IS NOT NULL THEN '<subfield code="o">' || NEW.owner || E'</subfield>' ELSE '' END ||
16799                 CASE WHEN NEW.share_depth IS NOT NULL THEN '<subfield code="d">' || NEW.share_depth || E'</subfield>' ELSE '' END ||
16800              E'</datafield>\\1'
16801         );
16802     ELSIF TG_TABLE_SCHEMA = 'authority' THEN
16803         NEW.marc := REGEXP_REPLACE(
16804             NEW.marc,
16805             E'(</(?:[^:]*?:)?record>)',
16806             E'<datafield tag="901" ind1=" " ind2=" ">' ||
16807                 '<subfield code="c">' || NEW.id || E'</subfield>' ||
16808                 '<subfield code="t">' || TG_TABLE_SCHEMA || E'</subfield>' ||
16809              E'</datafield>\\1'
16810         );
16811     ELSIF TG_TABLE_SCHEMA = 'serial' THEN
16812         NEW.marc := REGEXP_REPLACE(
16813             NEW.marc,
16814             E'(</(?:[^:]*?:)?record>)',
16815             E'<datafield tag="901" ind1=" " ind2=" ">' ||
16816                 '<subfield code="c">' || NEW.id || E'</subfield>' ||
16817                 '<subfield code="t">' || TG_TABLE_SCHEMA || E'</subfield>' ||
16818                 '<subfield code="o">' || NEW.owning_lib || E'</subfield>' ||
16819                 CASE WHEN NEW.record IS NOT NULL THEN '<subfield code="r">' || NEW.record || E'</subfield>' ELSE '' END ||
16820              E'</datafield>\\1'
16821         );
16822     ELSE
16823         NEW.marc := REGEXP_REPLACE(
16824             NEW.marc,
16825             E'(</(?:[^:]*?:)?record>)',
16826             E'<datafield tag="901" ind1=" " ind2=" ">' ||
16827                 '<subfield code="c">' || NEW.id || E'</subfield>' ||
16828                 '<subfield code="t">' || TG_TABLE_SCHEMA || E'</subfield>' ||
16829              E'</datafield>\\1'
16830         );
16831     END IF;
16832
16833     RETURN NEW;
16834 END;
16835 $func$ LANGUAGE PLPGSQL;
16836
16837 CREATE TRIGGER b_maintain_901 BEFORE INSERT OR UPDATE ON biblio.record_entry FOR EACH ROW EXECUTE PROCEDURE maintain_901();
16838 CREATE TRIGGER b_maintain_901 BEFORE INSERT OR UPDATE ON authority.record_entry FOR EACH ROW EXECUTE PROCEDURE maintain_901();
16839 CREATE TRIGGER b_maintain_901 BEFORE INSERT OR UPDATE ON serial.record_entry FOR EACH ROW EXECUTE PROCEDURE maintain_901();
16840  
16841 -- In booking, elbow room defines:
16842 --  a) how far in the future you must make a reservation on a given item if
16843 --      that item will have to transit somewhere to fulfill the reservation.
16844 --  b) how soon a reservation must be starting for the reserved item to
16845 --      be op-captured by the checkin interface.
16846
16847 -- We don't want to clobber any default_elbow room at any level:
16848
16849 CREATE OR REPLACE FUNCTION pg_temp.default_elbow() RETURNS INTEGER AS $$
16850 DECLARE
16851     existing    actor.org_unit_setting%ROWTYPE;
16852 BEGIN
16853     SELECT INTO existing id FROM actor.org_unit_setting WHERE name = 'circ.booking_reservation.default_elbow_room';
16854     IF NOT FOUND THEN
16855         INSERT INTO actor.org_unit_setting (org_unit, name, value) VALUES (
16856             (SELECT id FROM actor.org_unit WHERE parent_ou IS NULL),
16857             'circ.booking_reservation.default_elbow_room',
16858             '"1 day"'
16859         );
16860         RETURN 1;
16861     END IF;
16862     RETURN 0;
16863 END;
16864 $$ LANGUAGE plpgsql;
16865
16866 SELECT pg_temp.default_elbow();
16867
16868 DROP FUNCTION IF EXISTS action.usr_visible_circ_copies( INTEGER );
16869
16870 -- returns the distinct set of target copy IDs from a user's visible circulation history
16871 CREATE OR REPLACE FUNCTION action.usr_visible_circ_copies( INTEGER ) RETURNS SETOF BIGINT AS $$
16872     SELECT DISTINCT(target_copy) FROM action.usr_visible_circs($1)
16873 $$ LANGUAGE SQL;
16874
16875 ALTER TABLE action.in_house_use DROP CONSTRAINT in_house_use_item_fkey;
16876 ALTER TABLE action.transit_copy DROP CONSTRAINT transit_copy_target_copy_fkey;
16877 ALTER TABLE action.hold_transit_copy DROP CONSTRAINT ahtc_tc_fkey;
16878 ALTER TABLE action.hold_copy_map DROP CONSTRAINT hold_copy_map_target_copy_fkey;
16879
16880 ALTER TABLE asset.stat_cat_entry_copy_map DROP CONSTRAINT a_sc_oc_fkey;
16881
16882 ALTER TABLE authority.record_entry ADD COLUMN owner INT;
16883 ALTER TABLE serial.record_entry ADD COLUMN owner INT;
16884
16885 INSERT INTO config.global_flag (name, label) -- defaults to enabled=FALSE
16886     VALUES (
16887         'cat.maintain_control_numbers',
16888         oils_i18n_gettext(
16889             'cat.maintain_control_numbers',
16890             'Cat: Maintain 001/003/035 according to the MARC21 specification',
16891             'cgf', 
16892             'label'
16893         )
16894     );
16895
16896 INSERT INTO config.global_flag (name, label, enabled)
16897     VALUES (
16898         'circ.holds.empty_issuance_ok',
16899         oils_i18n_gettext(
16900             'circ.holds.empty_issuance_ok',
16901             'Holds: Allow holds on empty issuances',
16902             'cgf',
16903             'label'
16904         ),
16905         TRUE
16906     );
16907
16908 INSERT INTO config.global_flag (name, label, enabled)
16909     VALUES (
16910         'circ.holds.usr_not_requestor',
16911         oils_i18n_gettext(
16912             'circ.holds.usr_not_requestor',
16913             'Holds: When testing hold matrix matchpoints, use the profile group of the receiving user instead of that of the requestor (affects staff-placed holds)',
16914             'cgf',
16915             'label'
16916         ),
16917         TRUE
16918     );
16919
16920 CREATE OR REPLACE FUNCTION maintain_control_numbers() RETURNS TRIGGER AS $func$
16921 use strict;
16922 use MARC::Record;
16923 use MARC::File::XML (BinaryEncoding => 'UTF-8');
16924 use Encode;
16925 use Unicode::Normalize;
16926
16927 my $record = MARC::Record->new_from_xml($_TD->{new}{marc});
16928 my $schema = $_TD->{table_schema};
16929 my $rec_id = $_TD->{new}{id};
16930
16931 # Short-circuit if maintaining control numbers per MARC21 spec is not enabled
16932 my $enable = spi_exec_query("SELECT enabled FROM config.global_flag WHERE name = 'cat.maintain_control_numbers'");
16933 if (!($enable->{processed}) or $enable->{rows}[0]->{enabled} eq 'f') {
16934     return;
16935 }
16936
16937 # Get the control number identifier from an OU setting based on $_TD->{new}{owner}
16938 my $ou_cni = 'EVRGRN';
16939
16940 my $owner;
16941 if ($schema eq 'serial') {
16942     $owner = $_TD->{new}{owning_lib};
16943 } else {
16944     # are.owner and bre.owner can be null, so fall back to the consortial setting
16945     $owner = $_TD->{new}{owner} || 1;
16946 }
16947
16948 my $ous_rv = spi_exec_query("SELECT value FROM actor.org_unit_ancestor_setting('cat.marc_control_number_identifier', $owner)");
16949 if ($ous_rv->{processed}) {
16950     $ou_cni = $ous_rv->{rows}[0]->{value};
16951     $ou_cni =~ s/"//g; # Stupid VIM syntax highlighting"
16952 } else {
16953     # Fall back to the shortname of the OU if there was no OU setting
16954     $ous_rv = spi_exec_query("SELECT shortname FROM actor.org_unit WHERE id = $owner");
16955     if ($ous_rv->{processed}) {
16956         $ou_cni = $ous_rv->{rows}[0]->{shortname};
16957     }
16958 }
16959
16960 my ($create, $munge) = (0, 0);
16961 my ($orig_001, $orig_003) = ('', '');
16962
16963 # Incoming MARC records may have multiple 001s or 003s, despite the spec
16964 my @control_ids = $record->field('003');
16965 my @scns = $record->field('035');
16966
16967 foreach my $id_field ('001', '003') {
16968     my $spec_value;
16969     my @controls = $record->field($id_field);
16970
16971     if ($id_field eq '001') {
16972         $spec_value = $rec_id;
16973     } else {
16974         $spec_value = $ou_cni;
16975     }
16976
16977     # Create the 001/003 if none exist
16978     if (scalar(@controls) == 0) {
16979         $record->insert_fields_ordered(MARC::Field->new($id_field, $spec_value));
16980         $create = 1;
16981     } elsif (scalar(@controls) > 1) {
16982         # Do we already have the right 001/003 value in the existing set?
16983         unless (grep $_->data() eq $spec_value, @controls) {
16984             $munge = 1;
16985         }
16986
16987         # Delete the other fields, as with more than 1 001/003 we do not know which 003/001 to match
16988         foreach my $control (@controls) {
16989             unless ($control->data() eq $spec_value) {
16990                 $record->delete_field($control);
16991             }
16992         }
16993     } else {
16994         # Only one field; check to see if we need to munge it
16995         unless (grep $_->data() eq $spec_value, @controls) {
16996             $munge = 1;
16997         }
16998     }
16999 }
17000
17001 # Now, if we need to munge the 001, we will first push the existing 001/003 into the 035
17002 if ($munge) {
17003     my $scn = "(" . $record->field('003')->data() . ")" . $record->field('001')->data();
17004
17005     # Do not create duplicate 035 fields
17006     unless (grep $_->subfield('a') eq $scn, @scns) {
17007         $record->insert_fields_ordered(MARC::Field->new('035', '', '', 'a' => $scn));
17008     }
17009 }
17010
17011 # Set the 001/003 and update the MARC
17012 if ($create or $munge) {
17013     $record->field('001')->data($rec_id);
17014     $record->field('003')->data($ou_cni);
17015
17016     my $xml = $record->as_xml_record();
17017     $xml =~ s/\n//sgo;
17018     $xml =~ s/^<\?xml.+\?\s*>//go;
17019     $xml =~ s/>\s+</></go;
17020     $xml =~ s/\p{Cc}//go;
17021
17022     # Embed a version of OpenILS::Application::AppUtils->entityize()
17023     # to avoid having to set PERL5LIB for PostgreSQL as well
17024
17025     # If we are going to convert non-ASCII characters to XML entities,
17026     # we had better be dealing with a UTF8 string to begin with
17027     $xml = decode_utf8($xml);
17028
17029     $xml = NFC($xml);
17030
17031     # Convert raw ampersands to entities
17032     $xml =~ s/&(?!\S+;)/&amp;/gso;
17033
17034     # Convert Unicode characters to entities
17035     $xml =~ s/([\x{0080}-\x{fffd}])/sprintf('&#x%X;',ord($1))/sgoe;
17036
17037     $xml =~ s/[\x00-\x1f]//go;
17038     $_TD->{new}{marc} = $xml;
17039
17040     return "MODIFY";
17041 }
17042
17043 return;
17044 $func$ LANGUAGE PLPERLU;
17045
17046 CREATE TRIGGER c_maintain_control_numbers BEFORE INSERT OR UPDATE ON authority.record_entry FOR EACH ROW EXECUTE PROCEDURE maintain_control_numbers();
17047 CREATE TRIGGER c_maintain_control_numbers BEFORE INSERT OR UPDATE ON biblio.record_entry FOR EACH ROW EXECUTE PROCEDURE maintain_control_numbers();
17048 CREATE TRIGGER c_maintain_control_numbers BEFORE INSERT OR UPDATE ON serial.record_entry FOR EACH ROW EXECUTE PROCEDURE maintain_control_numbers();
17049
17050 INSERT INTO metabib.facet_entry (source, field, value)
17051     SELECT source, field, value FROM (
17052         SELECT * FROM metabib.author_field_entry
17053             UNION ALL
17054         SELECT * FROM metabib.keyword_field_entry
17055             UNION ALL
17056         SELECT * FROM metabib.identifier_field_entry
17057             UNION ALL
17058         SELECT * FROM metabib.title_field_entry
17059             UNION ALL
17060         SELECT * FROM metabib.subject_field_entry
17061             UNION ALL
17062         SELECT * FROM metabib.series_field_entry
17063         )x
17064     WHERE x.index_vector = '';
17065         
17066 DELETE FROM metabib.author_field_entry WHERE index_vector = '';
17067 DELETE FROM metabib.keyword_field_entry WHERE index_vector = '';
17068 DELETE FROM metabib.identifier_field_entry WHERE index_vector = '';
17069 DELETE FROM metabib.title_field_entry WHERE index_vector = '';
17070 DELETE FROM metabib.subject_field_entry WHERE index_vector = '';
17071 DELETE FROM metabib.series_field_entry WHERE index_vector = '';
17072
17073 CREATE INDEX metabib_facet_entry_field_idx ON metabib.facet_entry (field);
17074 CREATE INDEX metabib_facet_entry_value_idx ON metabib.facet_entry (SUBSTRING(value,1,1024));
17075 CREATE INDEX metabib_facet_entry_source_idx ON metabib.facet_entry (source);
17076
17077 -- copy OPAC visibility materialized view
17078 CREATE OR REPLACE FUNCTION asset.refresh_opac_visible_copies_mat_view () RETURNS VOID AS $$
17079
17080     TRUNCATE TABLE asset.opac_visible_copies;
17081
17082     INSERT INTO asset.opac_visible_copies (id, circ_lib, record)
17083     SELECT  cp.id, cp.circ_lib, cn.record
17084     FROM  asset.copy cp
17085         JOIN asset.call_number cn ON (cn.id = cp.call_number)
17086         JOIN actor.org_unit a ON (cp.circ_lib = a.id)
17087         JOIN asset.copy_location cl ON (cp.location = cl.id)
17088         JOIN config.copy_status cs ON (cp.status = cs.id)
17089         JOIN biblio.record_entry b ON (cn.record = b.id)
17090     WHERE NOT cp.deleted
17091         AND NOT cn.deleted
17092         AND NOT b.deleted
17093         AND cs.opac_visible
17094         AND cl.opac_visible
17095         AND cp.opac_visible
17096         AND a.opac_visible;
17097
17098 $$ LANGUAGE SQL;
17099 COMMENT ON FUNCTION asset.refresh_opac_visible_copies_mat_view() IS $$
17100 Rebuild the copy OPAC visibility cache.  Useful during migrations.
17101 $$;
17102
17103 -- and actually populate the table
17104 SELECT asset.refresh_opac_visible_copies_mat_view();
17105
17106 CREATE OR REPLACE FUNCTION asset.cache_copy_visibility () RETURNS TRIGGER as $func$
17107 DECLARE
17108     add_query       TEXT;
17109     remove_query    TEXT;
17110     do_add          BOOLEAN := false;
17111     do_remove       BOOLEAN := false;
17112 BEGIN
17113     add_query := $$
17114             INSERT INTO asset.opac_visible_copies (id, circ_lib, record)
17115                 SELECT  cp.id, cp.circ_lib, cn.record
17116                   FROM  asset.copy cp
17117                         JOIN asset.call_number cn ON (cn.id = cp.call_number)
17118                         JOIN actor.org_unit a ON (cp.circ_lib = a.id)
17119                         JOIN asset.copy_location cl ON (cp.location = cl.id)
17120                         JOIN config.copy_status cs ON (cp.status = cs.id)
17121                         JOIN biblio.record_entry b ON (cn.record = b.id)
17122                   WHERE NOT cp.deleted
17123                         AND NOT cn.deleted
17124                         AND NOT b.deleted
17125                         AND cs.opac_visible
17126                         AND cl.opac_visible
17127                         AND cp.opac_visible
17128                         AND a.opac_visible
17129     $$;
17130  
17131     remove_query := $$ DELETE FROM asset.opac_visible_copies WHERE id IN ( SELECT id FROM asset.copy WHERE $$;
17132
17133     IF TG_OP = 'INSERT' THEN
17134
17135         IF TG_TABLE_NAME IN ('copy', 'unit') THEN
17136             add_query := add_query || 'AND cp.id = ' || NEW.id || ';';
17137             EXECUTE add_query;
17138         END IF;
17139
17140         RETURN NEW;
17141
17142     END IF;
17143
17144     -- handle items first, since with circulation activity
17145     -- their statuses change frequently
17146     IF TG_TABLE_NAME IN ('copy', 'unit') THEN
17147
17148         IF OLD.location    <> NEW.location OR
17149            OLD.call_number <> NEW.call_number OR
17150            OLD.status      <> NEW.status OR
17151            OLD.circ_lib    <> NEW.circ_lib THEN
17152             -- any of these could change visibility, but
17153             -- we'll save some queries and not try to calculate
17154             -- the change directly
17155             do_remove := true;
17156             do_add := true;
17157         ELSE
17158
17159             IF OLD.deleted <> NEW.deleted THEN
17160                 IF NEW.deleted THEN
17161                     do_remove := true;
17162                 ELSE
17163                     do_add := true;
17164                 END IF;
17165             END IF;
17166
17167             IF OLD.opac_visible <> NEW.opac_visible THEN
17168                 IF OLD.opac_visible THEN
17169                     do_remove := true;
17170                 ELSIF NOT do_remove THEN -- handle edge case where deleted item
17171                                         -- is also marked opac_visible
17172                     do_add := true;
17173                 END IF;
17174             END IF;
17175
17176         END IF;
17177
17178         IF do_remove THEN
17179             DELETE FROM asset.opac_visible_copies WHERE id = NEW.id;
17180         END IF;
17181         IF do_add THEN
17182             add_query := add_query || 'AND cp.id = ' || NEW.id || ';';
17183             EXECUTE add_query;
17184         END IF;
17185
17186         RETURN NEW;
17187
17188     END IF;
17189
17190     IF TG_TABLE_NAME IN ('call_number', 'record_entry') THEN -- these have a 'deleted' column
17191  
17192         IF OLD.deleted AND NEW.deleted THEN -- do nothing
17193
17194             RETURN NEW;
17195  
17196         ELSIF NEW.deleted THEN -- remove rows
17197  
17198             IF TG_TABLE_NAME = 'call_number' THEN
17199                 DELETE FROM asset.opac_visible_copies WHERE id IN (SELECT id FROM asset.copy WHERE call_number = NEW.id);
17200             ELSIF TG_TABLE_NAME = 'record_entry' THEN
17201                 DELETE FROM asset.opac_visible_copies WHERE record = NEW.id;
17202             END IF;
17203  
17204             RETURN NEW;
17205  
17206         ELSIF OLD.deleted THEN -- add rows
17207  
17208             IF TG_TABLE_NAME IN ('copy','unit') THEN
17209                 add_query := add_query || 'AND cp.id = ' || NEW.id || ';';
17210             ELSIF TG_TABLE_NAME = 'call_number' THEN
17211                 add_query := add_query || 'AND cp.call_number = ' || NEW.id || ';';
17212             ELSIF TG_TABLE_NAME = 'record_entry' THEN
17213                 add_query := add_query || 'AND cn.record = ' || NEW.id || ';';
17214             END IF;
17215  
17216             EXECUTE add_query;
17217             RETURN NEW;
17218  
17219         END IF;
17220  
17221     END IF;
17222
17223     IF TG_TABLE_NAME = 'call_number' THEN
17224
17225         IF OLD.record <> NEW.record THEN
17226             -- call number is linked to different bib
17227             remove_query := remove_query || 'call_number = ' || NEW.id || ');';
17228             EXECUTE remove_query;
17229             add_query := add_query || 'AND cp.call_number = ' || NEW.id || ';';
17230             EXECUTE add_query;
17231         END IF;
17232
17233         RETURN NEW;
17234
17235     END IF;
17236
17237     IF TG_TABLE_NAME IN ('record_entry') THEN
17238         RETURN NEW; -- don't have 'opac_visible'
17239     END IF;
17240
17241     -- actor.org_unit, asset.copy_location, asset.copy_status
17242     IF NEW.opac_visible = OLD.opac_visible THEN -- do nothing
17243
17244         RETURN NEW;
17245
17246     ELSIF NEW.opac_visible THEN -- add rows
17247
17248         IF TG_TABLE_NAME = 'org_unit' THEN
17249             add_query := add_query || 'AND cp.circ_lib = ' || NEW.id || ';';
17250         ELSIF TG_TABLE_NAME = 'copy_location' THEN
17251             add_query := add_query || 'AND cp.location = ' || NEW.id || ';';
17252         ELSIF TG_TABLE_NAME = 'copy_status' THEN
17253             add_query := add_query || 'AND cp.status = ' || NEW.id || ';';
17254         END IF;
17255  
17256         EXECUTE add_query;
17257  
17258     ELSE -- delete rows
17259
17260         IF TG_TABLE_NAME = 'org_unit' THEN
17261             remove_query := 'DELETE FROM asset.opac_visible_copies WHERE circ_lib = ' || NEW.id || ';';
17262         ELSIF TG_TABLE_NAME = 'copy_location' THEN
17263             remove_query := remove_query || 'location = ' || NEW.id || ');';
17264         ELSIF TG_TABLE_NAME = 'copy_status' THEN
17265             remove_query := remove_query || 'status = ' || NEW.id || ');';
17266         END IF;
17267  
17268         EXECUTE remove_query;
17269  
17270     END IF;
17271  
17272     RETURN NEW;
17273 END;
17274 $func$ LANGUAGE PLPGSQL;
17275 COMMENT ON FUNCTION asset.cache_copy_visibility() IS $$
17276 Trigger function to update the copy OPAC visiblity cache.
17277 $$;
17278 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();
17279 CREATE TRIGGER a_opac_vis_mat_view_tgr AFTER INSERT OR UPDATE ON asset.copy FOR EACH ROW EXECUTE PROCEDURE asset.cache_copy_visibility();
17280 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();
17281 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();
17282 CREATE TRIGGER a_opac_vis_mat_view_tgr AFTER INSERT OR UPDATE ON serial.unit FOR EACH ROW EXECUTE PROCEDURE asset.cache_copy_visibility();
17283 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();
17284 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();
17285
17286 -- must create this rule explicitly; it is not inherited from asset.copy
17287 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;
17288
17289 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);
17290
17291 CREATE OR REPLACE FUNCTION authority.merge_records ( target_record BIGINT, source_record BIGINT ) RETURNS INT AS $func$
17292 DECLARE
17293     moved_objects INT := 0;
17294     bib_id        INT := 0;
17295     bib_rec       biblio.record_entry%ROWTYPE;
17296     auth_link     authority.bib_linking%ROWTYPE;
17297     ingest_same   boolean;
17298 BEGIN
17299
17300     -- 1. Update all bib records with the ID from target_record in their $0
17301     FOR bib_rec IN SELECT bre.* FROM biblio.record_entry bre
17302       INNER JOIN authority.bib_linking abl ON abl.bib = bre.id
17303       WHERE abl.authority = source_record LOOP
17304
17305         UPDATE biblio.record_entry
17306           SET marc = REGEXP_REPLACE(marc,
17307             E'(<subfield\\s+code="0"\\s*>[^<]*?\\))' || source_record || '<',
17308             E'\\1' || target_record || '<', 'g')
17309           WHERE id = bib_rec.id;
17310
17311           moved_objects := moved_objects + 1;
17312     END LOOP;
17313
17314     -- 2. Grab the current value of reingest on same MARC flag
17315     SELECT enabled INTO ingest_same
17316       FROM config.internal_flag
17317       WHERE name = 'ingest.reingest.force_on_same_marc'
17318     ;
17319
17320     -- 3. Temporarily set reingest on same to TRUE
17321     UPDATE config.internal_flag
17322       SET enabled = TRUE
17323       WHERE name = 'ingest.reingest.force_on_same_marc'
17324     ;
17325
17326     -- 4. Make a harmless update to target_record to trigger auto-update
17327     --    in linked bibliographic records
17328     UPDATE authority.record_entry
17329       SET DELETED = FALSE
17330       WHERE id = source_record;
17331
17332     -- 5. "Delete" source_record
17333     DELETE FROM authority.record_entry
17334       WHERE id = source_record;
17335
17336     -- 6. Set "reingest on same MARC" flag back to initial value
17337     UPDATE config.internal_flag
17338       SET enabled = ingest_same
17339       WHERE name = 'ingest.reingest.force_on_same_marc'
17340     ;
17341
17342     RETURN moved_objects;
17343 END;
17344 $func$ LANGUAGE plpgsql;
17345
17346 -- serial.record_entry already had an owner column spelled "owning_lib"
17347 -- Adjust the table and affected functions accordingly
17348
17349 ALTER TABLE serial.record_entry DROP COLUMN owner;
17350
17351 CREATE TABLE actor.usr_saved_search (
17352     id              SERIAL          PRIMARY KEY,
17353         owner           INT             NOT NULL REFERENCES actor.usr (id)
17354                                         ON DELETE CASCADE
17355                                         DEFERRABLE INITIALLY DEFERRED,
17356         name            TEXT            NOT NULL,
17357         create_date     TIMESTAMPTZ     NOT NULL DEFAULT now(),
17358         query_text      TEXT            NOT NULL,
17359         query_type      TEXT            NOT NULL
17360                                         CONSTRAINT valid_query_text CHECK (
17361                                         query_type IN ( 'URL' )) DEFAULT 'URL',
17362                                         -- we may add other types someday
17363         target          TEXT            NOT NULL
17364                                         CONSTRAINT valid_target CHECK (
17365                                         target IN ( 'record', 'metarecord', 'callnumber' )),
17366         CONSTRAINT name_once_per_user UNIQUE (owner, name)
17367 );
17368
17369 -- Apply Dan Wells' changes to the serial schema, from the
17370 -- seials-integration branch
17371
17372 CREATE TABLE serial.subscription_note (
17373         id           SERIAL PRIMARY KEY,
17374         subscription INT    NOT NULL
17375                             REFERENCES serial.subscription (id)
17376                             ON DELETE CASCADE
17377                             DEFERRABLE INITIALLY DEFERRED,
17378         creator      INT    NOT NULL
17379                             REFERENCES actor.usr (id)
17380                             DEFERRABLE INITIALLY DEFERRED,
17381         create_date  TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
17382         pub          BOOL   NOT NULL DEFAULT FALSE,
17383         title        TEXT   NOT NULL,
17384         value        TEXT   NOT NULL
17385 );
17386 CREATE INDEX serial_subscription_note_sub_idx ON serial.subscription_note (subscription);
17387
17388 CREATE TABLE serial.distribution_note (
17389         id           SERIAL PRIMARY KEY,
17390         distribution INT    NOT NULL
17391                             REFERENCES serial.distribution (id)
17392                             ON DELETE CASCADE
17393                             DEFERRABLE INITIALLY DEFERRED,
17394         creator      INT    NOT NULL
17395                             REFERENCES actor.usr (id)
17396                             DEFERRABLE INITIALLY DEFERRED,
17397         create_date  TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
17398         pub          BOOL   NOT NULL DEFAULT FALSE,
17399         title        TEXT   NOT NULL,
17400         value        TEXT   NOT NULL
17401 );
17402 CREATE INDEX serial_distribution_note_dist_idx ON serial.distribution_note (distribution);
17403
17404 ------- Begin surgery on serial.unit
17405
17406 ALTER TABLE serial.unit
17407         DROP COLUMN label;
17408
17409 ALTER TABLE serial.unit
17410         RENAME COLUMN label_sort_key TO sort_key;
17411
17412 ALTER TABLE serial.unit
17413         RENAME COLUMN contents TO detailed_contents;
17414
17415 ALTER TABLE serial.unit
17416         ADD COLUMN summary_contents TEXT;
17417
17418 UPDATE serial.unit
17419 SET summary_contents = detailed_contents;
17420
17421 ALTER TABLE serial.unit
17422         ALTER column summary_contents SET NOT NULL;
17423
17424 ------- End surgery on serial.unit
17425
17426 -- 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' );
17427
17428 -- Now rebuild the constraints dropped via cascade.
17429 -- ALTER TABLE acq.provider    ADD CONSTRAINT provider_edi_default_fkey FOREIGN KEY (edi_default) REFERENCES acq.edi_account (id) DEFERRABLE INITIALLY DEFERRED;
17430 DROP INDEX IF EXISTS money.money_mat_summary_id_idx;
17431 ALTER TABLE money.materialized_billable_xact_summary ADD PRIMARY KEY (id);
17432
17433 -- ALTER TABLE staging.billing_address_stage ADD PRIMARY KEY (row_id);
17434
17435 DELETE FROM config.metabib_field_index_norm_map
17436     WHERE norm IN (
17437         SELECT id 
17438             FROM config.index_normalizer
17439             WHERE func IN ('first_word', 'naco_normalize', 'split_date_range')
17440     )
17441     AND field = 18
17442 ;
17443
17444 -- We won't necessarily use all of these, but they are here for completeness.
17445 -- Source is the EDI spec 6063 codelist, eg: http://www.stylusstudio.com/edifact/D04B/6063.htm
17446 -- Values are the EDI code value + 1200
17447
17448 INSERT INTO acq.cancel_reason (org_unit, keep_debits, id, label, description) VALUES 
17449 (1, 't', 1201, 'Discrete quantity', 'Individually separated and distinct quantity.'),
17450 (1, 't', 1202, 'Charge', 'Quantity relevant for charge.'),
17451 (1, 't', 1203, 'Cumulative quantity', 'Quantity accumulated.'),
17452 (1, 't', 1204, 'Interest for overdrawn account', 'Interest for overdrawing the account.'),
17453 (1, 't', 1205, 'Active ingredient dose per unit', 'The dosage of active ingredient per unit.'),
17454 (1, 't', 1206, 'Auditor', 'The number of entities that audit accounts.'),
17455 (1, 't', 1207, 'Branch locations, leased', 'The number of branch locations being leased by an entity.'),
17456 (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.'),
17457 (1, 't', 1209, 'Branch locations, owned', 'The number of branch locations owned by an entity.'),
17458 (1, 't', 1210, 'Judgements registered', 'The number of judgements registered against an entity.'),
17459 (1, 't', 1211, 'Split quantity', 'Part of the whole quantity.'),
17460 (1, 't', 1212, 'Despatch quantity', 'Quantity despatched by the seller.'),
17461 (1, 't', 1213, 'Liens registered', 'The number of liens registered against an entity.'),
17462 (1, 't', 1214, 'Livestock', 'The number of animals kept for use or profit.'),
17463 (1, 't', 1215, 'Insufficient funds returned cheques', 'The number of cheques returned due to insufficient funds.'),
17464 (1, 't', 1216, 'Stolen cheques', 'The number of stolen cheques.'),
17465 (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.'),
17466 (1, 't', 1218, 'Previous quantity', 'Quantity previously referenced.'),
17467 (1, 't', 1219, 'Paid-in security shares', 'The number of security shares issued and for which full payment has been made.'),
17468 (1, 't', 1220, 'Unusable quantity', 'Quantity not usable.'),
17469 (1, 't', 1221, 'Ordered quantity', '[6024] The quantity which has been ordered.'),
17470 (1, 't', 1222, 'Quantity at 100%', 'Equivalent quantity at 100% purity.'),
17471 (1, 't', 1223, 'Active ingredient', 'Quantity at 100% active agent content.'),
17472 (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.'),
17473 (1, 't', 1225, 'Retail sales', 'Quantity of retail point of sale activity.'),
17474 (1, 't', 1226, 'Promotion quantity', 'A quantity associated with a promotional event.'),
17475 (1, 't', 1227, 'On hold for shipment', 'Article received which cannot be shipped in its present form.'),
17476 (1, 't', 1228, 'Military sales quantity', 'Quantity of goods or services sold to a military organization.'),
17477 (1, 't', 1229, 'On premises sales',  'Sale of product in restaurants or bars.'),
17478 (1, 't', 1230, 'Off premises sales', 'Sale of product directly to a store.'),
17479 (1, 't', 1231, 'Estimated annual volume', 'Volume estimated for a year.'),
17480 (1, 't', 1232, 'Minimum delivery batch', 'Minimum quantity of goods delivered at one time.'),
17481 (1, 't', 1233, 'Maximum delivery batch', 'Maximum quantity of goods delivered at one time.'),
17482 (1, 't', 1234, 'Pipes', 'The number of tubes used to convey a substance.'),
17483 (1, 't', 1235, 'Price break from', 'The minimum quantity of a quantity range for a specified (unit) price.'),
17484 (1, 't', 1236, 'Price break to', 'Maximum quantity to which the price break applies.'),
17485 (1, 't', 1237, 'Poultry', 'The number of domestic fowl.'),
17486 (1, 't', 1238, 'Secured charges registered', 'The number of secured charges registered against an entity.'),
17487 (1, 't', 1239, 'Total properties owned', 'The total number of properties owned by an entity.'),
17488 (1, 't', 1240, 'Normal delivery', 'Quantity normally delivered by the seller.'),
17489 (1, 't', 1241, 'Sales quantity not included in the replenishment', 'calculation Sales which will not be included in the calculation of replenishment requirements.'),
17490 (1, 't', 1242, 'Maximum supply quantity, supplier endorsed', 'Maximum supply quantity endorsed by a supplier.'),
17491 (1, 't', 1243, 'Buyer', 'The number of buyers.'),
17492 (1, 't', 1244, 'Debenture bond', 'The number of fixed-interest bonds of an entity backed by general credit rather than specified assets.'),
17493 (1, 't', 1245, 'Debentures filed against directors', 'The number of notices of indebtedness filed against an entity''s directors.'),
17494 (1, 't', 1246, 'Pieces delivered', 'Number of pieces actually received at the final destination.'),
17495 (1, 't', 1247, 'Invoiced quantity', 'The quantity as per invoice.'),
17496 (1, 't', 1248, 'Received quantity', 'The quantity which has been received.'),
17497 (1, 't', 1249, 'Chargeable distance', '[6110] The distance between two points for which a specific tariff applies.'),
17498 (1, 't', 1250, 'Disposition undetermined quantity', 'Product quantity that has not yet had its disposition determined.'),
17499 (1, 't', 1251, 'Inventory category transfer', 'Inventory that has been moved from one inventory category to another.'),
17500 (1, 't', 1252, 'Quantity per pack', 'Quantity for each pack.'),
17501 (1, 't', 1253, 'Minimum order quantity', 'Minimum quantity of goods for an order.'),
17502 (1, 't', 1254, 'Maximum order quantity', 'Maximum quantity of goods for an order.'),
17503 (1, 't', 1255, 'Total sales', 'The summation of total quantity sales.'),
17504 (1, 't', 1256, 'Wholesaler to wholesaler sales', 'Sale of product to other wholesalers by a wholesaler.'),
17505 (1, 't', 1257, 'In transit quantity', 'A quantity that is en route.'),
17506 (1, 't', 1258, 'Quantity withdrawn', 'Quantity withdrawn from a location.'),
17507 (1, 't', 1259, 'Numbers of consumer units in the traded unit', 'Number of units for consumer sales in a unit for trading.'),
17508 (1, 't', 1260, 'Current inventory quantity available for shipment', 'Current inventory quantity available for shipment.'),
17509 (1, 't', 1261, 'Return quantity', 'Quantity of goods returned.'),
17510 (1, 't', 1262, 'Sorted quantity', 'The quantity that is sorted.'),
17511 (1, 'f', 1263, 'Sorted quantity rejected', 'The sorted quantity that is rejected.'),
17512 (1, 't', 1264, 'Scrap quantity', 'Remainder of the total quantity after split deliveries.'),
17513 (1, 'f', 1265, 'Destroyed quantity', 'Quantity of goods destroyed.'),
17514 (1, 't', 1266, 'Committed quantity', 'Quantity a party is committed to.'),
17515 (1, 't', 1267, 'Estimated reading quantity', 'The value that is estimated to be the reading of a measuring device (e.g. meter).'),
17516 (1, 't', 1268, 'End quantity', 'The quantity recorded at the end of an agreement or period.'),
17517 (1, 't', 1269, 'Start quantity', 'The quantity recorded at the start of an agreement or period.'),
17518 (1, 't', 1270, 'Cumulative quantity received', 'Cumulative quantity of all deliveries of this article received by the buyer.'),
17519 (1, 't', 1271, 'Cumulative quantity ordered', 'Cumulative quantity of all deliveries, outstanding and scheduled orders.'),
17520 (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.'),
17521 (1, 't', 1273, 'Outstanding quantity', 'Difference between quantity ordered and quantity received.'),
17522 (1, 't', 1274, 'Latest cumulative quantity', 'Cumulative quantity after complete delivery of all scheduled quantities of the product.'),
17523 (1, 't', 1275, 'Previous highest cumulative quantity', 'Cumulative quantity after complete delivery of all scheduled quantities of the product from a prior schedule period.'),
17524 (1, 't', 1276, 'Adjusted corrector reading', 'A corrector reading after it has been adjusted.'),
17525 (1, 't', 1277, 'Work days', 'Number of work days, e.g. per respective period.'),
17526 (1, 't', 1278, 'Cumulative quantity scheduled', 'Adding the quantity actually scheduled to previous cumulative quantity.'),
17527 (1, 't', 1279, 'Previous cumulative quantity', 'Cumulative quantity prior the actual order.'),
17528 (1, 't', 1280, 'Unadjusted corrector reading', 'A corrector reading before it has been adjusted.'),
17529 (1, 't', 1281, 'Extra unplanned delivery', 'Non scheduled additional quantity.'),
17530 (1, 't', 1282, 'Quantity requirement for sample inspection', 'Required quantity for sample inspection.'),
17531 (1, 't', 1283, 'Backorder quantity', 'The quantity of goods that is on back-order.'),
17532 (1, 't', 1284, 'Urgent delivery quantity', 'Quantity for urgent delivery.'),
17533 (1, 'f', 1285, 'Previous order quantity to be cancelled', 'Quantity ordered previously to be cancelled.'),
17534 (1, 't', 1286, 'Normal reading quantity', 'The value recorded or read from a measuring device (e.g. meter) in the normal conditions.'),
17535 (1, 't', 1287, 'Customer reading quantity', 'The value recorded or read from a measuring device (e.g. meter) by the customer.'),
17536 (1, 't', 1288, 'Information reading quantity', 'The value recorded or read from a measuring device (e.g. meter) for information purposes.'),
17537 (1, 't', 1289, 'Quality control held', 'Quantity of goods held pending completion of a quality control assessment.'),
17538 (1, 't', 1290, 'As is quantity', 'Quantity as it is in the existing circumstances.'),
17539 (1, 't', 1291, 'Open quantity', 'Quantity remaining after partial delivery.'),
17540 (1, 't', 1292, 'Final delivery quantity', 'Quantity of final delivery to a respective order.'),
17541 (1, 't', 1293, 'Subsequent delivery quantity', 'Quantity delivered to a respective order after it''s final delivery.'),
17542 (1, 't', 1294, 'Substitutional quantity', 'Quantity delivered replacing previous deliveries.'),
17543 (1, 't', 1295, 'Redelivery after post processing', 'Quantity redelivered after post processing.'),
17544 (1, 'f', 1296, 'Quality control failed', 'Quantity of goods which have failed quality control.'),
17545 (1, 't', 1297, 'Minimum inventory', 'Minimum stock quantity on which replenishment is based.'),
17546 (1, 't', 1298, 'Maximum inventory', 'Maximum stock quantity on which replenishment is based.'),
17547 (1, 't', 1299, 'Estimated quantity', 'Quantity estimated.'),
17548 (1, 't', 1300, 'Chargeable weight', 'The weight on which charges are based.'),
17549 (1, 't', 1301, 'Chargeable gross weight', 'The gross weight on which charges are based.'),
17550 (1, 't', 1302, 'Chargeable tare weight', 'The tare weight on which charges are based.'),
17551 (1, 't', 1303, 'Chargeable number of axles', 'The number of axles on which charges are based.'),
17552 (1, 't', 1304, 'Chargeable number of containers', 'The number of containers on which charges are based.'),
17553 (1, 't', 1305, 'Chargeable number of rail wagons', 'The number of rail wagons on which charges are based.'),
17554 (1, 't', 1306, 'Chargeable number of packages', 'The number of packages on which charges are based.'),
17555 (1, 't', 1307, 'Chargeable number of units', 'The number of units on which charges are based.'),
17556 (1, 't', 1308, 'Chargeable period', 'The period of time on which charges are based.'),
17557 (1, 't', 1309, 'Chargeable volume', 'The volume on which charges are based.'),
17558 (1, 't', 1310, 'Chargeable cubic measurements', 'The cubic measurements on which charges are based.'),
17559 (1, 't', 1311, 'Chargeable surface', 'The surface area on which charges are based.'),
17560 (1, 't', 1312, 'Chargeable length', 'The length on which charges are based.'),
17561 (1, 't', 1313, 'Quantity to be delivered', 'The quantity to be delivered.'),
17562 (1, 't', 1314, 'Number of passengers', 'Total number of passengers on the conveyance.'),
17563 (1, 't', 1315, 'Number of crew', 'Total number of crew members on the conveyance.'),
17564 (1, 't', 1316, 'Number of transport documents', 'Total number of air waybills, bills of lading, etc. being reported for a specific conveyance.'),
17565 (1, 't', 1317, 'Quantity landed', 'Quantity of goods actually arrived.'),
17566 (1, 't', 1318, 'Quantity manifested', 'Quantity of goods contracted for delivery by the carrier.'),
17567 (1, 't', 1319, 'Short shipped', 'Indication that part of the consignment was not shipped.'),
17568 (1, 't', 1320, 'Split shipment', 'Indication that the consignment has been split into two or more shipments.'),
17569 (1, 't', 1321, 'Over shipped', 'The quantity of goods shipped that exceeds the quantity contracted.'),
17570 (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.'),
17571 (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.'),
17572 (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.'),
17573 (1, 'f', 1325, 'Pilferage goods', 'Quantity of goods stolen during transport.'),
17574 (1, 'f', 1326, 'Lost goods', 'Quantity of goods that disappeared in transport.'),
17575 (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.'),
17576 (1, 't', 1328, 'Quantity loaded', 'Quantity of goods loaded onto a means of transport.'),
17577 (1, 't', 1329, 'Units per unit price', 'Number of units per unit price.'),
17578 (1, 't', 1330, 'Allowance', 'Quantity relevant for allowance.'),
17579 (1, 't', 1331, 'Delivery quantity', 'Quantity required by buyer to be delivered.'),
17580 (1, 't', 1332, 'Cumulative quantity, preceding period, planned', 'Cumulative quantity originally planned for the preceding period.'),
17581 (1, 't', 1333, 'Cumulative quantity, preceding period, reached', 'Cumulative quantity reached in the preceding period.'),
17582 (1, 't', 1334, 'Cumulative quantity, actual planned',            'Cumulative quantity planned for now.'),
17583 (1, 't', 1335, 'Period quantity, planned', 'Quantity planned for this period.'),
17584 (1, 't', 1336, 'Period quantity, reached', 'Quantity reached during this period.'),
17585 (1, 't', 1337, 'Cumulative quantity, preceding period, estimated', 'Estimated cumulative quantity reached in the preceding period.'),
17586 (1, 't', 1338, 'Cumulative quantity, actual estimated',            'Estimated cumulative quantity reached now.'),
17587 (1, 't', 1339, 'Cumulative quantity, preceding period, measured', 'Surveyed cumulative quantity reached in the preceding period.'),
17588 (1, 't', 1340, 'Cumulative quantity, actual measured', 'Surveyed cumulative quantity reached now.'),
17589 (1, 't', 1341, 'Period quantity, measured',            'Surveyed quantity reached during this period.'),
17590 (1, 't', 1342, 'Total quantity, planned', 'Total quantity planned.'),
17591 (1, 't', 1343, 'Quantity, remaining', 'Quantity remaining.'),
17592 (1, 't', 1344, 'Tolerance', 'Plus or minus tolerance expressed as a monetary amount.'),
17593 (1, 't', 1345, 'Actual stock',          'The stock on hand, undamaged, and available for despatch, sale or use.'),
17594 (1, 't', 1346, 'Model or target stock', 'The stock quantity required or planned to have on hand, undamaged and available for use.'),
17595 (1, 't', 1347, 'Direct shipment quantity', 'Quantity to be shipped directly to a customer from a manufacturing site.'),
17596 (1, 't', 1348, 'Amortization total quantity',     'Indication of final quantity for amortization.'),
17597 (1, 't', 1349, 'Amortization order quantity',     'Indication of actual share of the order quantity for amortization.'),
17598 (1, 't', 1350, 'Amortization cumulated quantity', 'Indication of actual cumulated quantity of previous and actual amortization order quantity.'),
17599 (1, 't', 1351, 'Quantity advised',  'Quantity advised by supplier or shipper, in contrast to quantity actually received.'),
17600 (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.'),
17601 (1, 't', 1353, 'Statistical sales quantity', 'Quantity of goods sold in a specified period.'),
17602 (1, 't', 1354, 'Sales quantity planned',     'Quantity of goods required to meet future demands. - Market intelligence quantity.'),
17603 (1, 't', 1355, 'Replenishment quantity',     'Quantity required to maintain the requisite on-hand stock of goods.'),
17604 (1, 't', 1356, 'Inventory movement quantity', 'To specify the quantity of an inventory movement.'),
17605 (1, 't', 1357, 'Opening stock balance quantity', 'To specify the quantity of an opening stock balance.'),
17606 (1, 't', 1358, 'Closing stock balance quantity', 'To specify the quantity of a closing stock balance.'),
17607 (1, 't', 1359, 'Number of stops', 'Number of times a means of transport stops before arriving at destination.'),
17608 (1, 't', 1360, 'Minimum production batch', 'The quantity specified is the minimum output from a single production run.'),
17609 (1, 't', 1361, 'Dimensional sample quantity', 'The quantity defined is a sample for the purpose of validating dimensions.'),
17610 (1, 't', 1362, 'Functional sample quantity', 'The quantity defined is a sample for the purpose of validating function and performance.'),
17611 (1, 't', 1363, 'Pre-production quantity', 'Quantity of the referenced item required prior to full production.'),
17612 (1, 't', 1364, 'Delivery batch', 'Quantity of the referenced item which constitutes a standard batch for deliver purposes.'),
17613 (1, 't', 1365, 'Delivery batch multiple', 'The multiples in which delivery batches can be supplied.'),
17614 (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.'),
17615 (1, 't', 1367, 'Total delivery quantity',  'The total quantity required by the buyer to be delivered.'),
17616 (1, 't', 1368, 'Single delivery quantity', 'The quantity required by the buyer to be delivered in a single shipment.'),
17617 (1, 't', 1369, 'Supplied quantity',  'Quantity of the referenced item actually shipped.'),
17618 (1, 't', 1370, 'Allocated quantity', 'Quantity of the referenced item allocated from available stock for delivery.'),
17619 (1, 't', 1371, 'Maximum stackability', 'The number of pallets/handling units which can be safely stacked one on top of another.'),
17620 (1, 't', 1372, 'Amortisation quantity', 'The quantity of the referenced item which has a cost for tooling amortisation included in the item price.'),
17621 (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.'),
17622 (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.'),
17623 (1, 't', 1375, 'Number of moulds', 'The number of pressing moulds contained within a single piece of the referenced tooling.'),
17624 (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.'),
17625 (1, 't', 1377, 'Periodic capacity of tooling', 'Maximum production output of the referenced tool over a period of time.'),
17626 (1, 't', 1378, 'Lifetime capacity of tooling', 'Maximum production output of the referenced tool over its productive lifetime.'),
17627 (1, 't', 1379, 'Number of deliveries per despatch period', 'The number of deliveries normally expected to be despatched within each despatch period.'),
17628 (1, 't', 1380, 'Provided quantity', 'The quantity of a referenced component supplied by the buyer for manufacturing of an ordered item.'),
17629 (1, 't', 1381, 'Maximum production batch', 'The quantity specified is the maximum output from a single production run.'),
17630 (1, 'f', 1382, 'Cancelled quantity', 'Quantity of the referenced item which has previously been ordered and is now cancelled.'),
17631 (1, 't', 1383, 'No delivery requirement in this instruction', 'This delivery instruction does not contain any delivery requirements.'),
17632 (1, 't', 1384, 'Quantity of material in ordered time', 'Quantity of the referenced material within the ordered time.'),
17633 (1, 'f', 1385, 'Rejected quantity', 'The quantity of received goods rejected for quantity reasons.'),
17634 (1, 't', 1386, 'Cumulative quantity scheduled up to accumulation start date', 'The cumulative quantity scheduled up to the accumulation start date.'),
17635 (1, 't', 1387, 'Quantity scheduled', 'The quantity scheduled for delivery.'),
17636 (1, 't', 1388, 'Number of identical handling units', 'Number of identical handling units in terms of type and contents.'),
17637 (1, 't', 1389, 'Number of packages in handling unit', 'The number of packages contained in one handling unit.'),
17638 (1, 't', 1390, 'Despatch note quantity', 'The item quantity specified on the despatch note.'),
17639 (1, 't', 1391, 'Adjustment to inventory quantity', 'An adjustment to inventory quantity.'),
17640 (1, 't', 1392, 'Free goods quantity',    'Quantity of goods which are free of charge.'),
17641 (1, 't', 1393, 'Free quantity included', 'Quantity included to which no charge is applicable.'),
17642 (1, 't', 1394, 'Received and accepted',  'Quantity which has been received and accepted at a given location.'),
17643 (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.'),
17644 (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.'),
17645 (1, 't', 1397, 'Reordering level', 'Quantity at which an order may be triggered to replenish.'),
17646 (1, 't', 1399, 'Inventory withdrawal quantity', 'Quantity which has been withdrawn from inventory since the last inventory report.'),
17647 (1, 't', 1400, 'Free quantity not included', 'Free quantity not included in ordered quantity.'),
17648 (1, 't', 1401, 'Recommended overhaul and repair quantity', 'To indicate the recommended quantity of an article required to support overhaul and repair activities.'),
17649 (1, 't', 1402, 'Quantity per next higher assembly', 'To indicate the quantity required for the next higher assembly.'),
17650 (1, 't', 1403, 'Quantity per unit of issue', 'Provides the standard quantity of an article in which one unit can be issued.'),
17651 (1, 't', 1404, 'Cumulative scrap quantity',  'Provides the cumulative quantity of an item which has been identified as scrapped.'),
17652 (1, 't', 1405, 'Publication turn size', 'The quantity of magazines or newspapers grouped together with the spine facing alternate directions in a bundle.'),
17653 (1, 't', 1406, 'Recommended maintenance quantity', 'Recommended quantity of an article which is required to meet an agreed level of maintenance.'),
17654 (1, 't', 1407, 'Labour hours', 'Number of labour hours.'),
17655 (1, 't', 1408, 'Quantity requirement for maintenance and repair of', 'equipment Quantity of the material needed to maintain and repair equipment.'),
17656 (1, 't', 1409, 'Additional replenishment demand quantity', 'Incremental needs over and above normal replenishment calculations, but not intended to permanently change the model parameters.'),
17657 (1, 't', 1410, 'Returned by consumer quantity', 'Quantity returned by a consumer.'),
17658 (1, 't', 1411, 'Replenishment override quantity', 'Quantity to override the normal replenishment model calculations, but not intended to permanently change the model parameters.'),
17659 (1, 't', 1412, 'Quantity sold, net', 'Net quantity sold which includes returns of saleable inventory and other adjustments.'),
17660 (1, 't', 1413, 'Transferred out quantity',   'Quantity which was transferred out of this location.'),
17661 (1, 't', 1414, 'Transferred in quantity',    'Quantity which was transferred into this location.'),
17662 (1, 't', 1415, 'Unsaleable quantity',        'Quantity of inventory received which cannot be sold in its present condition.'),
17663 (1, 't', 1416, 'Consumer reserved quantity', 'Quantity reserved for consumer delivery or pickup and not yet withdrawn from inventory.'),
17664 (1, 't', 1417, 'Out of inventory quantity',  'Quantity of inventory which was requested but was not available.'),
17665 (1, 't', 1418, 'Quantity returned, defective or damaged', 'Quantity returned in a damaged or defective condition.'),
17666 (1, 't', 1419, 'Taxable quantity',           'Quantity subject to taxation.'),
17667 (1, 't', 1420, 'Meter reading', 'The numeric value of measure units counted by a meter.'),
17668 (1, 't', 1421, 'Maximum requestable quantity', 'The maximum quantity which may be requested.'),
17669 (1, 't', 1422, 'Minimum requestable quantity', 'The minimum quantity which may be requested.'),
17670 (1, 't', 1423, 'Daily average quantity', 'The quantity for a defined period divided by the number of days of the period.'),
17671 (1, 't', 1424, 'Budgeted hours',     'The number of budgeted hours.'),
17672 (1, 't', 1425, 'Actual hours',       'The number of actual hours.'),
17673 (1, 't', 1426, 'Earned value hours', 'The number of earned value hours.'),
17674 (1, 't', 1427, 'Estimated hours',    'The number of estimated hours.'),
17675 (1, 't', 1428, 'Level resource task quantity', 'Quantity of a resource that is level for the duration of the task.'),
17676 (1, 't', 1429, 'Available resource task quantity', 'Quantity of a resource available to complete a task.'),
17677 (1, 't', 1430, 'Work time units',   'Quantity of work units of time.'),
17678 (1, 't', 1431, 'Daily work shifts', 'Quantity of work shifts per day.'),
17679 (1, 't', 1432, 'Work time units per shift', 'Work units of time per work shift.'),
17680 (1, 't', 1433, 'Work calendar units',       'Work calendar units of time.'),
17681 (1, 't', 1434, 'Elapsed duration',   'Quantity representing the elapsed duration.'),
17682 (1, 't', 1435, 'Remaining duration', 'Quantity representing the remaining duration.'),
17683 (1, 't', 1436, 'Original duration',  'Quantity representing the original duration.'),
17684 (1, 't', 1437, 'Current duration',   'Quantity representing the current duration.'),
17685 (1, 't', 1438, 'Total float time',   'Quantity representing the total float time.'),
17686 (1, 't', 1439, 'Free float time',    'Quantity representing the free float time.'),
17687 (1, 't', 1440, 'Lag time',           'Quantity representing lag time.'),
17688 (1, 't', 1441, 'Lead time',          'Quantity representing lead time.'),
17689 (1, 't', 1442, 'Number of months', 'The number of months.'),
17690 (1, 't', 1443, 'Reserved quantity customer direct delivery sales', 'Quantity of products reserved for sales delivered direct to the customer.'),
17691 (1, 't', 1444, 'Reserved quantity retail sales', 'Quantity of products reserved for retail sales.'),
17692 (1, 't', 1445, 'Consolidated discount inventory', 'A quantity of inventory supplied at consolidated discount terms.'),
17693 (1, 't', 1446, 'Returns replacement quantity',    'A quantity of goods issued as a replacement for a returned quantity.'),
17694 (1, 't', 1447, 'Additional promotion sales forecast quantity', 'A forecast of additional quantity which will be sold during a period of promotional activity.'),
17695 (1, 't', 1448, 'Reserved quantity', 'Quantity reserved for specific purposes.'),
17696 (1, 't', 1449, 'Quantity displayed not available for sale', 'Quantity displayed within a retail outlet but not available for sale.'),
17697 (1, 't', 1450, 'Inventory discrepancy', 'The difference recorded between theoretical and physical inventory.'),
17698 (1, 't', 1451, 'Incremental order quantity', 'The incremental quantity by which ordering is carried out.'),
17699 (1, 't', 1452, 'Quantity requiring manipulation before despatch', 'A quantity of goods which needs manipulation before despatch.'),
17700 (1, 't', 1453, 'Quantity in quarantine',              'A quantity of goods which are held in a restricted area for quarantine purposes.'),
17701 (1, 't', 1454, 'Quantity withheld by owner of goods', 'A quantity of goods which has been withheld by the owner of the goods.'),
17702 (1, 't', 1455, 'Quantity not available for despatch', 'A quantity of goods not available for despatch.'),
17703 (1, 't', 1456, 'Quantity awaiting delivery', 'Quantity of goods which are awaiting delivery.'),
17704 (1, 't', 1457, 'Quantity in physical inventory',      'A quantity of goods held in physical inventory.'),
17705 (1, 't', 1458, 'Quantity held by logistic service provider', 'Quantity of goods under the control of a logistic service provider.'),
17706 (1, 't', 1459, 'Optimal quantity', 'The optimal quantity for a given purpose.'),
17707 (1, 't', 1460, 'Delivery quantity balance', 'The difference between the scheduled quantity and the quantity delivered to the consignee at a given date.'),
17708 (1, 't', 1461, 'Cumulative quantity shipped', 'Cumulative quantity of all shipments.'),
17709 (1, 't', 1462, 'Quantity suspended', 'The quantity of something which is suspended.'),
17710 (1, 't', 1463, 'Control quantity', 'The quantity designated for control purposes.'),
17711 (1, 't', 1464, 'Equipment quantity', 'A count of a quantity of equipment.'),
17712 (1, 't', 1465, 'Factor', 'Number by which the measured unit has to be multiplied to calculate the units used.'),
17713 (1, 't', 1466, 'Unsold quantity held by wholesaler', 'Unsold quantity held by the wholesaler.'),
17714 (1, 't', 1467, 'Quantity held by delivery vehicle', 'Quantity of goods held by the delivery vehicle.'),
17715 (1, 't', 1468, 'Quantity held by retail outlet', 'Quantity held by the retail outlet.'),
17716 (1, 'f', 1469, 'Rejected return quantity', 'A quantity for return which has been rejected.'),
17717 (1, 't', 1470, 'Accounts', 'The number of accounts.'),
17718 (1, 't', 1471, 'Accounts placed for collection', 'The number of accounts placed for collection.'),
17719 (1, 't', 1472, 'Activity codes', 'The number of activity codes.'),
17720 (1, 't', 1473, 'Agents', 'The number of agents.'),
17721 (1, 't', 1474, 'Airline attendants', 'The number of airline attendants.'),
17722 (1, 't', 1475, 'Authorised shares',  'The number of shares authorised for issue.'),
17723 (1, 't', 1476, 'Employee average',   'The average number of employees.'),
17724 (1, 't', 1477, 'Branch locations',   'The number of branch locations.'),
17725 (1, 't', 1478, 'Capital changes',    'The number of capital changes made.'),
17726 (1, 't', 1479, 'Clerks', 'The number of clerks.'),
17727 (1, 't', 1480, 'Companies in same activity', 'The number of companies doing business in the same activity category.'),
17728 (1, 't', 1481, 'Companies included in consolidated financial statement', 'The number of companies included in a consolidated financial statement.'),
17729 (1, 't', 1482, 'Cooperative shares', 'The number of cooperative shares.'),
17730 (1, 't', 1483, 'Creditors',   'The number of creditors.'),
17731 (1, 't', 1484, 'Departments', 'The number of departments.'),
17732 (1, 't', 1485, 'Design employees', 'The number of employees involved in the design process.'),
17733 (1, 't', 1486, 'Physicians', 'The number of medical doctors.'),
17734 (1, 't', 1487, 'Domestic affiliated companies', 'The number of affiliated companies located within the country.'),
17735 (1, 't', 1488, 'Drivers', 'The number of drivers.'),
17736 (1, 't', 1489, 'Employed at location',     'The number of employees at the specified location.'),
17737 (1, 't', 1490, 'Employed by this company', 'The number of employees at the specified company.'),
17738 (1, 't', 1491, 'Total employees',    'The total number of employees.'),
17739 (1, 't', 1492, 'Employees shared',   'The number of employees shared among entities.'),
17740 (1, 't', 1493, 'Engineers',          'The number of engineers.'),
17741 (1, 't', 1494, 'Estimated accounts', 'The estimated number of accounts.'),
17742 (1, 't', 1495, 'Estimated employees at location', 'The estimated number of employees at the specified location.'),
17743 (1, 't', 1496, 'Estimated total employees',       'The total estimated number of employees.'),
17744 (1, 't', 1497, 'Executives', 'The number of executives.'),
17745 (1, 't', 1498, 'Agricultural workers',   'The number of agricultural workers.'),
17746 (1, 't', 1499, 'Financial institutions', 'The number of financial institutions.'),
17747 (1, 't', 1500, 'Floors occupied', 'The number of floors occupied.'),
17748 (1, 't', 1501, 'Foreign related entities', 'The number of related entities located outside the country.'),
17749 (1, 't', 1502, 'Group employees',    'The number of employees within the group.'),
17750 (1, 't', 1503, 'Indirect employees', 'The number of employees not associated with direct production.'),
17751 (1, 't', 1504, 'Installers',    'The number of employees involved with the installation process.'),
17752 (1, 't', 1505, 'Invoices',      'The number of invoices.'),
17753 (1, 't', 1506, 'Issued shares', 'The number of shares actually issued.'),
17754 (1, 't', 1507, 'Labourers',     'The number of labourers.'),
17755 (1, 't', 1508, 'Manufactured units', 'The number of units manufactured.'),
17756 (1, 't', 1509, 'Maximum number of employees', 'The maximum number of people employed.'),
17757 (1, 't', 1510, 'Maximum number of employees at location', 'The maximum number of people employed at a location.'),
17758 (1, 't', 1511, 'Members in group', 'The number of members within a group.'),
17759 (1, 't', 1512, 'Minimum number of employees at location', 'The minimum number of people employed at a location.'),
17760 (1, 't', 1513, 'Minimum number of employees', 'The minimum number of people employed.'),
17761 (1, 't', 1514, 'Non-union employees', 'The number of employees not belonging to a labour union.'),
17762 (1, 't', 1515, 'Floors', 'The number of floors in a building.'),
17763 (1, 't', 1516, 'Nurses', 'The number of nurses.'),
17764 (1, 't', 1517, 'Office workers', 'The number of workers in an office.'),
17765 (1, 't', 1518, 'Other employees', 'The number of employees otherwise categorised.'),
17766 (1, 't', 1519, 'Part time employees', 'The number of employees working on a part time basis.'),
17767 (1, 't', 1520, 'Accounts payable average overdue days', 'The average number of days accounts payable are overdue.'),
17768 (1, 't', 1521, 'Pilots', 'The number of pilots.'),
17769 (1, 't', 1522, 'Plant workers', 'The number of workers within a plant.'),
17770 (1, 't', 1523, 'Previous number of accounts', 'The number of accounts which preceded the current count.'),
17771 (1, 't', 1524, 'Previous number of branch locations', 'The number of branch locations which preceded the current count.'),
17772 (1, 't', 1525, 'Principals included as employees', 'The number of principals which are included in the count of employees.'),
17773 (1, 't', 1526, 'Protested bills', 'The number of bills which are protested.'),
17774 (1, 't', 1527, 'Registered brands distributed', 'The number of registered brands which are being distributed.'),
17775 (1, 't', 1528, 'Registered brands manufactured', 'The number of registered brands which are being manufactured.'),
17776 (1, 't', 1529, 'Related business entities', 'The number of related business entities.'),
17777 (1, 't', 1530, 'Relatives employed', 'The number of relatives which are counted as employees.'),
17778 (1, 't', 1531, 'Rooms',        'The number of rooms.'),
17779 (1, 't', 1532, 'Salespersons', 'The number of salespersons.'),
17780 (1, 't', 1533, 'Seats',        'The number of seats.'),
17781 (1, 't', 1534, 'Shareholders', 'The number of shareholders.'),
17782 (1, 't', 1535, 'Shares of common stock', 'The number of shares of common stock.'),
17783 (1, 't', 1536, 'Shares of preferred stock', 'The number of shares of preferred stock.'),
17784 (1, 't', 1537, 'Silent partners', 'The number of silent partners.'),
17785 (1, 't', 1538, 'Subcontractors',  'The number of subcontractors.'),
17786 (1, 't', 1539, 'Subsidiaries',    'The number of subsidiaries.'),
17787 (1, 't', 1540, 'Law suits',       'The number of law suits.'),
17788 (1, 't', 1541, 'Suppliers',       'The number of suppliers.'),
17789 (1, 't', 1542, 'Teachers',        'The number of teachers.'),
17790 (1, 't', 1543, 'Technicians',     'The number of technicians.'),
17791 (1, 't', 1544, 'Trainees',        'The number of trainees.'),
17792 (1, 't', 1545, 'Union employees', 'The number of employees who are members of a labour union.'),
17793 (1, 't', 1546, 'Number of units', 'The quantity of units.'),
17794 (1, 't', 1547, 'Warehouse employees', 'The number of employees who work in a warehouse setting.'),
17795 (1, 't', 1548, 'Shareholders holding remainder of shares', 'Number of shareholders owning the remainder of shares.'),
17796 (1, 't', 1549, 'Payment orders filed', 'Number of payment orders filed.'),
17797 (1, 't', 1550, 'Uncovered cheques', 'Number of uncovered cheques.'),
17798 (1, 't', 1551, 'Auctions', 'Number of auctions.'),
17799 (1, 't', 1552, 'Units produced', 'The number of units produced.'),
17800 (1, 't', 1553, 'Added employees', 'Number of employees that were added to the workforce.'),
17801 (1, 't', 1554, 'Number of added locations', 'Number of locations that were added.'),
17802 (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.'),
17803 (1, 't', 1556, 'Number of closed locations', 'Number of locations that were closed.'),
17804 (1, 't', 1557, 'Counter clerks', 'The number of clerks that work behind a flat-topped fitment.'),
17805 (1, 't', 1558, 'Payment experiences in the last 3 months', 'The number of payment experiences received for an entity over the last 3 months.'),
17806 (1, 't', 1559, 'Payment experiences in the last 12 months', 'The number of payment experiences received for an entity over the last 12 months.'),
17807 (1, 't', 1560, 'Total number of subsidiaries not included in the financial', 'statement The total number of subsidiaries not included in the financial statement.'),
17808 (1, 't', 1561, 'Paid-in common shares', 'The number of paid-in common shares.'),
17809 (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.'),
17810 (1, 't', 1563, 'Total number of foreign subsidiaries included in financial statement', 'The total number of foreign subsidiaries included in the financial statement.'),
17811 (1, 't', 1564, 'Total number of domestic subsidiaries included in financial statement', 'The total number of domestic subsidiaries included in the financial statement.'),
17812 (1, 't', 1565, 'Total transactions', 'The total number of transactions.'),
17813 (1, 't', 1566, 'Paid-in preferred shares', 'The number of paid-in preferred shares.'),
17814 (1, 't', 1567, 'Employees', 'Code specifying the quantity of persons working for a company, whose services are used for pay.'),
17815 (1, 't', 1568, 'Active ingredient dose per unit, dispensed', 'The dosage of active ingredient per dispensed unit.'),
17816 (1, 't', 1569, 'Budget', 'Budget quantity.'),
17817 (1, 't', 1570, 'Budget, cumulative to date', 'Budget quantity, cumulative to date.'),
17818 (1, 't', 1571, 'Actual units', 'The number of actual units.'),
17819 (1, 't', 1572, 'Actual units, cumulative to date', 'The number of cumulative to date actual units.'),
17820 (1, 't', 1573, 'Earned value', 'Earned value quantity.'),
17821 (1, 't', 1574, 'Earned value, cumulative to date', 'Earned value quantity accumulated to date.'),
17822 (1, 't', 1575, 'At completion quantity, estimated', 'The estimated quantity when a project is complete.'),
17823 (1, 't', 1576, 'To complete quantity, estimated', 'The estimated quantity required to complete a project.'),
17824 (1, 't', 1577, 'Adjusted units', 'The number of adjusted units.'),
17825 (1, 't', 1578, 'Number of limited partnership shares', 'Number of shares held in a limited partnership.'),
17826 (1, 't', 1579, 'National business failure incidences', 'Number of firms in a country that discontinued with a loss to creditors.'),
17827 (1, 't', 1580, 'Industry business failure incidences', 'Number of firms in a specific industry that discontinued with a loss to creditors.'),
17828 (1, 't', 1581, 'Business class failure incidences', 'Number of firms in a specific class that discontinued with a loss to creditors.'),
17829 (1, 't', 1582, 'Mechanics', 'Number of mechanics.'),
17830 (1, 't', 1583, 'Messengers', 'Number of messengers.'),
17831 (1, 't', 1584, 'Primary managers', 'Number of primary managers.'),
17832 (1, 't', 1585, 'Secretaries', 'Number of secretaries.'),
17833 (1, 't', 1586, 'Detrimental legal filings', 'Number of detrimental legal filings.'),
17834 (1, 't', 1587, 'Branch office locations, estimated', 'Estimated number of branch office locations.'),
17835 (1, 't', 1588, 'Previous number of employees', 'The number of employees for a previous period.'),
17836 (1, 't', 1589, 'Asset seizers', 'Number of entities that seize assets of another entity.'),
17837 (1, 't', 1590, 'Out-turned quantity', 'The quantity discharged.'),
17838 (1, 't', 1591, 'Material on-board quantity, prior to loading', 'The material in vessel tanks, void spaces, and pipelines prior to loading.'),
17839 (1, 't', 1592, 'Supplier estimated previous meter reading', 'Previous meter reading estimated by the supplier.'),
17840 (1, 't', 1593, 'Supplier estimated latest meter reading',   'Latest meter reading estimated by the supplier.'),
17841 (1, 't', 1594, 'Customer estimated previous meter reading', 'Previous meter reading estimated by the customer.'),
17842 (1, 't', 1595, 'Customer estimated latest meter reading',   'Latest meter reading estimated by the customer.'),
17843 (1, 't', 1596, 'Supplier previous meter reading',           'Previous meter reading done by the supplier.'),
17844 (1, 't', 1597, 'Supplier latest meter reading',             'Latest meter reading recorded by the supplier.'),
17845 (1, 't', 1598, 'Maximum number of purchase orders allowed', 'Maximum number of purchase orders that are allowed.'),
17846 (1, 't', 1599, 'File size before compression', 'The size of a file before compression.'),
17847 (1, 't', 1600, 'File size after compression', 'The size of a file after compression.'),
17848 (1, 't', 1601, 'Securities shares', 'Number of shares of securities.'),
17849 (1, 't', 1602, 'Patients',         'Number of patients.'),
17850 (1, 't', 1603, 'Completed projects', 'Number of completed projects.'),
17851 (1, 't', 1604, 'Promoters',        'Number of entities who finance or organize an event or a production.'),
17852 (1, 't', 1605, 'Administrators',   'Number of administrators.'),
17853 (1, 't', 1606, 'Supervisors',      'Number of supervisors.'),
17854 (1, 't', 1607, 'Professionals',    'Number of professionals.'),
17855 (1, 't', 1608, 'Debt collectors',  'Number of debt collectors.'),
17856 (1, 't', 1609, 'Inspectors',       'Number of individuals who perform inspections.'),
17857 (1, 't', 1610, 'Operators',        'Number of operators.'),
17858 (1, 't', 1611, 'Trainers',         'Number of trainers.'),
17859 (1, 't', 1612, 'Active accounts',  'Number of accounts in a current or active status.'),
17860 (1, 't', 1613, 'Trademarks used',  'Number of trademarks used.'),
17861 (1, 't', 1614, 'Machines',         'Number of machines.'),
17862 (1, 't', 1615, 'Fuel pumps',       'Number of fuel pumps.'),
17863 (1, 't', 1616, 'Tables available', 'Number of tables available for use.'),
17864 (1, 't', 1617, 'Directors',        'Number of directors.'),
17865 (1, 't', 1618, 'Freelance debt collectors', 'Number of debt collectors who work on a freelance basis.'),
17866 (1, 't', 1619, 'Freelance salespersons',    'Number of salespersons who work on a freelance basis.'),
17867 (1, 't', 1620, 'Travelling employees',      'Number of travelling employees.'),
17868 (1, 't', 1621, 'Foremen', 'Number of workers with limited supervisory responsibilities.'),
17869 (1, 't', 1622, 'Production workers', 'Number of employees engaged in production.'),
17870 (1, 't', 1623, 'Employees not including owners', 'Number of employees excluding business owners.'),
17871 (1, 't', 1624, 'Beds', 'Number of beds.'),
17872 (1, 't', 1625, 'Resting quantity', 'A quantity of product that is at rest before it can be used.'),
17873 (1, 't', 1626, 'Production requirements', 'Quantity needed to meet production requirements.'),
17874 (1, 't', 1627, 'Corrected quantity', 'The quantity has been corrected.'),
17875 (1, 't', 1628, 'Operating divisions', 'Number of divisions operating.'),
17876 (1, 't', 1629, 'Quantitative incentive scheme base', 'Quantity constituting the base for the quantitative incentive scheme.'),
17877 (1, 't', 1630, 'Petitions filed', 'Number of petitions that have been filed.'),
17878 (1, 't', 1631, 'Bankruptcy petitions filed', 'Number of bankruptcy petitions that have been filed.'),
17879 (1, 't', 1632, 'Projects in process', 'Number of projects in process.'),
17880 (1, 't', 1633, 'Changes in capital structure', 'Number of modifications made to the capital structure of an entity.'),
17881 (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.'),
17882 (1, 't', 1635, 'Number of failed businesses of directors', 'The number of failed businesses with which the directors have been associated.'),
17883 (1, 't', 1636, 'Professor', 'The number of professors.'),
17884 (1, 't', 1637, 'Seller',    'The number of sellers.'),
17885 (1, 't', 1638, 'Skilled worker', 'The number of skilled workers.'),
17886 (1, 't', 1639, 'Trademark represented', 'The number of trademarks represented.'),
17887 (1, 't', 1640, 'Number of quantitative incentive scheme units', 'Number of units allocated to a quantitative incentive scheme.'),
17888 (1, 't', 1641, 'Quantity in manufacturing process', 'Quantity currently in the manufacturing process.'),
17889 (1, 't', 1642, 'Number of units in the width of a layer', 'Number of units which make up the width of a layer.'),
17890 (1, 't', 1643, 'Number of units in the depth of a layer', 'Number of units which make up the depth of a layer.'),
17891 (1, 't', 1644, 'Return to warehouse', 'A quantity of products sent back to the warehouse.'),
17892 (1, 't', 1645, 'Return to the manufacturer', 'A quantity of products sent back from the manufacturer.'),
17893 (1, 't', 1646, 'Delta quantity', 'An increment or decrement to a quantity.'),
17894 (1, 't', 1647, 'Quantity moved between outlets', 'A quantity of products moved between outlets.'),
17895 (1, 't', 1648, 'Pre-paid invoice annual consumption, estimated', 'The estimated annual consumption used for a prepayment invoice.'),
17896 (1, 't', 1649, 'Total quoted quantity', 'The sum of quoted quantities.'),
17897 (1, 't', 1650, 'Requests pertaining to entity in last 12 months', 'Number of requests received in last 12 months pertaining to the entity.'),
17898 (1, 't', 1651, 'Total inquiry matches', 'Number of instances which correspond with the inquiry.'),
17899 (1, 't', 1652, 'En route to warehouse quantity',   'A quantity of products that is en route to a warehouse.'),
17900 (1, 't', 1653, 'En route from warehouse quantity', 'A quantity of products that is en route from a warehouse.'),
17901 (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.'),
17902 (1, 't', 1655, 'Not yet ordered quantity', 'The quantity which has not yet been ordered.'),
17903 (1, 't', 1656, 'Net reserve power', 'The reserve power available for the net.'),
17904 (1, 't', 1657, 'Maximum number of units per shelf', 'Maximum number of units of a product that can be placed on a shelf.'),
17905 (1, 't', 1658, 'Stowaway', 'Number of stowaway(s) on a conveyance.'),
17906 (1, 't', 1659, 'Tug', 'The number of tugboat(s).'),
17907 (1, 't', 1660, 'Maximum quantity capability of the package', 'Maximum quantity of a product that can be contained in a package.'),
17908 (1, 't', 1661, 'Calculated', 'The calculated quantity.'),
17909 (1, 't', 1662, 'Monthly volume, estimated', 'Volume estimated for a month.'),
17910 (1, 't', 1663, 'Total number of persons', 'Quantity representing the total number of persons.'),
17911 (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.'),
17912 (1, 't', 1665, 'Deducted tariff quantity',   'Quantity deducted from tariff quantity to reckon duty/tax/fee assessment bases.'),
17913 (1, 't', 1666, 'Advised but not arrived',    'Goods are advised by the consignor or supplier, but have not yet arrived at the destination.'),
17914 (1, 't', 1667, 'Received but not available', 'Goods have been received in the arrival area but are not yet available.'),
17915 (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.'),
17916 (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.'),
17917 (1, 't', 1670, 'Chargeable number of trailers', 'The number of trailers on which charges are based.'),
17918 (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.'),
17919 (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.'),
17920 (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.'),
17921 (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.'),
17922 (1, 't', 1675, 'Agreed maximum buying quantity', 'The agreed maximum quantity of the trade item that may be purchased.'),
17923 (1, 't', 1676, 'Agreed minimum buying quantity', 'The agreed minimum quantity of the trade item that may be purchased.'),
17924 (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.'),
17925 (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.'),
17926 (1, 't', 1679, 'Marine Diesel Oil bunkers, loaded',                  'Number of Marine Diesel Oil (MDO) bunkers taken on in the port.'),
17927 (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.'),
17928 (1, 't', 1681, 'Intermediate Fuel Oil bunkers, loaded',              'Number of Intermediate Fuel Oil (IFO) bunkers taken on in the port.'),
17929 (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.'),
17930 (1, 't', 1683, 'Bunker C bunkers, loaded', 'Number of Bunker C, or Number 6 fuel oil bunkers, taken on in the port.'),
17931 (1, 't', 1684, 'Number of individual units within the smallest packaging', 'unit Total number of individual units contained within the smallest unit of packaging.'),
17932 (1, 't', 1685, 'Percentage of constituent element', 'The part of a product or material that is composed of the constituent element, as a percentage.'),
17933 (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).'),
17934 (1, 't', 1687, 'Regulated commodity count', 'The number of regulated items.'),
17935 (1, 't', 1688, 'Number of passengers, embarking', 'The number of passengers going aboard a conveyance.'),
17936 (1, 't', 1689, 'Number of passengers, disembarking', 'The number of passengers disembarking the conveyance.'),
17937 (1, 't', 1690, 'Constituent element or component quantity', 'The specific quantity of the identified constituent element.')
17938 ;
17939 -- ZZZ, 'Mutually defined', 'As agreed by the trading partners.'),
17940
17941 CREATE TABLE acq.serial_claim (
17942     id     SERIAL           PRIMARY KEY,
17943     type   INT              NOT NULL REFERENCES acq.claim_type
17944                                      DEFERRABLE INITIALLY DEFERRED,
17945     item    BIGINT          NOT NULL REFERENCES serial.item
17946                                      DEFERRABLE INITIALLY DEFERRED
17947 );
17948
17949 CREATE INDEX serial_claim_lid_idx ON acq.serial_claim( item );
17950
17951 CREATE TABLE acq.serial_claim_event (
17952     id             BIGSERIAL        PRIMARY KEY,
17953     type           INT              NOT NULL REFERENCES acq.claim_event_type
17954                                              DEFERRABLE INITIALLY DEFERRED,
17955     claim          SERIAL           NOT NULL REFERENCES acq.serial_claim
17956                                              DEFERRABLE INITIALLY DEFERRED,
17957     event_date     TIMESTAMPTZ      NOT NULL DEFAULT now(),
17958     creator        INT              NOT NULL REFERENCES actor.usr
17959                                              DEFERRABLE INITIALLY DEFERRED,
17960     note           TEXT
17961 );
17962
17963 CREATE INDEX serial_claim_event_claim_date_idx ON acq.serial_claim_event( claim, event_date );
17964
17965 ALTER TABLE asset.stat_cat ADD COLUMN required BOOL NOT NULL DEFAULT FALSE;
17966
17967 -- now what about the auditor.*_lifecycle views??
17968
17969 INSERT INTO config.metabib_field ( id, field_class, name, label, format, xpath ) VALUES
17970     (26, 'identifier', 'tcn', oils_i18n_gettext(26, 'Title Control Number', 'cmf', 'label'), 'marcxml', $$//marc:datafield[@tag='901']/marc:subfield[@code='a']$$ );
17971 INSERT INTO config.metabib_field ( id, field_class, name, label, format, xpath ) VALUES
17972     (27, 'identifier', 'bibid', oils_i18n_gettext(27, 'Internal ID', 'cmf', 'label'), 'marcxml', $$//marc:datafield[@tag='901']/marc:subfield[@code='c']$$ );
17973 INSERT INTO config.metabib_search_alias (alias,field_class,field) VALUES ('eg.tcn','identifier', 26);
17974 INSERT INTO config.metabib_search_alias (alias,field_class,field) VALUES ('eg.bibid','identifier', 27);
17975
17976 CREATE TABLE asset.call_number_class (
17977     id             bigserial     PRIMARY KEY,
17978     name           TEXT          NOT NULL,
17979     normalizer     TEXT          NOT NULL DEFAULT 'asset.normalize_generic',
17980     field          TEXT          NOT NULL DEFAULT '050ab,055ab,060ab,070ab,080ab,082ab,086ab,088ab,090,092,096,098,099'
17981 );
17982
17983 COMMENT ON TABLE asset.call_number_class IS $$
17984 Defines the call number normalization database functions in the "normalizer"
17985 column and the tag/subfield combinations to use to lookup the call number in
17986 the "field" column for a given classification scheme. Tag/subfield combinations
17987 are delimited by commas.
17988 $$;
17989
17990 INSERT INTO asset.call_number_class (name, normalizer) VALUES 
17991     ('Generic', 'asset.label_normalizer_generic'),
17992     ('Dewey (DDC)', 'asset.label_normalizer_dewey'),
17993     ('Library of Congress (LC)', 'asset.label_normalizer_lc')
17994 ;
17995
17996 -- Generic fields
17997 UPDATE asset.call_number_class
17998     SET field = '050ab,055ab,060ab,070ab,080ab,082ab,086ab,088ab,090,092,096,098,099'
17999     WHERE id = 1
18000 ;
18001
18002 -- Dewey fields
18003 UPDATE asset.call_number_class
18004     SET field = '080ab,082ab'
18005     WHERE id = 2
18006 ;
18007
18008 -- LC fields
18009 UPDATE asset.call_number_class
18010     SET field = '050ab,055ab'
18011     WHERE id = 3
18012 ;
18013  
18014 ALTER TABLE asset.call_number
18015         ADD COLUMN label_class BIGINT DEFAULT 1 NOT NULL
18016                 REFERENCES asset.call_number_class(id)
18017                 DEFERRABLE INITIALLY DEFERRED;
18018
18019 ALTER TABLE asset.call_number
18020         ADD COLUMN label_sortkey TEXT;
18021
18022 CREATE INDEX asset_call_number_label_sortkey
18023         ON asset.call_number(oils_text_as_bytea(label_sortkey));
18024
18025 ALTER TABLE auditor.asset_call_number_history
18026         ADD COLUMN label_class BIGINT;
18027
18028 ALTER TABLE auditor.asset_call_number_history
18029         ADD COLUMN label_sortkey TEXT;
18030
18031 -- Pick up the new columns in dependent views
18032
18033 DROP VIEW auditor.asset_call_number_lifecycle;
18034
18035 SELECT auditor.create_auditor_lifecycle( 'asset', 'call_number' );
18036
18037 DROP VIEW auditor.asset_call_number_lifecycle;
18038
18039 SELECT auditor.create_auditor_lifecycle( 'asset', 'call_number' );
18040
18041 DROP VIEW IF EXISTS stats.fleshed_call_number;
18042
18043 CREATE VIEW stats.fleshed_call_number AS
18044         SELECT  cn.*,
18045             CAST(cn.create_date AS DATE) AS create_date_day,
18046         CAST(cn.edit_date AS DATE) AS edit_date_day,
18047         DATE_TRUNC('hour', cn.create_date) AS create_date_hour,
18048         DATE_TRUNC('hour', cn.edit_date) AS edit_date_hour,
18049             rd.item_lang,
18050                 rd.item_type,
18051                 rd.item_form
18052         FROM    asset.call_number cn
18053                 JOIN metabib.rec_descriptor rd ON (rd.record = cn.record);
18054
18055 CREATE OR REPLACE FUNCTION asset.label_normalizer() RETURNS TRIGGER AS $func$
18056 DECLARE
18057     sortkey        TEXT := '';
18058 BEGIN
18059     sortkey := NEW.label_sortkey;
18060
18061     EXECUTE 'SELECT ' || acnc.normalizer || '(' || 
18062        quote_literal( NEW.label ) || ')'
18063        FROM asset.call_number_class acnc
18064        WHERE acnc.id = NEW.label_class
18065        INTO sortkey;
18066
18067     NEW.label_sortkey = sortkey;
18068
18069     RETURN NEW;
18070 END;
18071 $func$ LANGUAGE PLPGSQL;
18072
18073 CREATE OR REPLACE FUNCTION asset.label_normalizer_generic(TEXT) RETURNS TEXT AS $func$
18074     # Created after looking at the Koha C4::ClassSortRoutine::Generic module,
18075     # thus could probably be considered a derived work, although nothing was
18076     # directly copied - but to err on the safe side of providing attribution:
18077     # Copyright (C) 2007 LibLime
18078     # Licensed under the GPL v2 or later
18079
18080     use strict;
18081     use warnings;
18082
18083     # Converts the callnumber to uppercase
18084     # Strips spaces from start and end of the call number
18085     # Converts anything other than letters, digits, and periods into underscores
18086     # Collapses multiple underscores into a single underscore
18087     my $callnum = uc(shift);
18088     $callnum =~ s/^\s//g;
18089     $callnum =~ s/\s$//g;
18090     $callnum =~ s/[^A-Z0-9_.]/_/g;
18091     $callnum =~ s/_{2,}/_/g;
18092
18093     return $callnum;
18094 $func$ LANGUAGE PLPERLU;
18095
18096 CREATE OR REPLACE FUNCTION asset.label_normalizer_dewey(TEXT) RETURNS TEXT AS $func$
18097     # Derived from the Koha C4::ClassSortRoutine::Dewey module
18098     # Copyright (C) 2007 LibLime
18099     # Licensed under the GPL v2 or later
18100
18101     use strict;
18102     use warnings;
18103
18104     my $init = uc(shift);
18105     $init =~ s/^\s+//;
18106     $init =~ s/\s+$//;
18107     $init =~ s!/!!g;
18108     $init =~ s/^([\p{IsAlpha}]+)/$1 /;
18109     my @tokens = split /\.|\s+/, $init;
18110     my $digit_group_count = 0;
18111     for (my $i = 0; $i <= $#tokens; $i++) {
18112         if ($tokens[$i] =~ /^\d+$/) {
18113             $digit_group_count++;
18114             if (2 == $digit_group_count) {
18115                 $tokens[$i] = sprintf("%-15.15s", $tokens[$i]);
18116                 $tokens[$i] =~ tr/ /0/;
18117             }
18118         }
18119     }
18120     my $key = join("_", @tokens);
18121     $key =~ s/[^\p{IsAlnum}_]//g;
18122
18123     return $key;
18124
18125 $func$ LANGUAGE PLPERLU;
18126
18127 CREATE OR REPLACE FUNCTION asset.label_normalizer_lc(TEXT) RETURNS TEXT AS $func$
18128     use strict;
18129     use warnings;
18130
18131     # Library::CallNumber::LC is currently hosted at http://code.google.com/p/library-callnumber-lc/
18132     # The author hopes to upload it to CPAN some day, which would make our lives easier
18133     use Library::CallNumber::LC;
18134
18135     my $callnum = Library::CallNumber::LC->new(shift);
18136     return $callnum->normalize();
18137
18138 $func$ LANGUAGE PLPERLU;
18139
18140 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$
18141 DECLARE
18142     ans RECORD;
18143     trans INT;
18144 BEGIN
18145     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;
18146
18147     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
18148         RETURN QUERY
18149         SELECT  ans.depth,
18150                 ans.id,
18151                 COUNT( av.id ),
18152                 SUM( CASE WHEN cp.status IN (0,7,12) THEN 1 ELSE 0 END ),
18153                 COUNT( av.id ),
18154                 trans
18155           FROM
18156                 actor.org_unit_descendants(ans.id) d
18157                 JOIN asset.opac_visible_copies av ON (av.record = record AND av.circ_lib = d.id)
18158                 JOIN asset.copy cp ON (cp.id = av.id)
18159           GROUP BY 1,2,6;
18160
18161         IF NOT FOUND THEN
18162             RETURN QUERY SELECT ans.depth, ans.id, 0::BIGINT, 0::BIGINT, 0::BIGINT, trans;
18163         END IF;
18164
18165     END LOOP;
18166
18167     RETURN;
18168 END;
18169 $f$ LANGUAGE PLPGSQL;
18170
18171 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$
18172 DECLARE
18173     ans RECORD;
18174     trans INT;
18175 BEGIN
18176     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;
18177
18178     FOR ans IN SELECT u.org_unit AS id FROM actor.org_lasso_map AS u WHERE lasso = i_lasso LOOP
18179         RETURN QUERY
18180         SELECT  -1,
18181                 ans.id,
18182                 COUNT( av.id ),
18183                 SUM( CASE WHEN cp.status IN (0,7,12) THEN 1 ELSE 0 END ),
18184                 COUNT( av.id ),
18185                 trans
18186           FROM
18187                 actor.org_unit_descendants(ans.id) d
18188                 JOIN asset.opac_visible_copies av ON (av.record = record AND av.circ_lib = d.id)
18189                 JOIN asset.copy cp ON (cp.id = av.id)
18190           GROUP BY 1,2,6;
18191
18192         IF NOT FOUND THEN
18193             RETURN QUERY SELECT ans.depth, ans.id, 0::BIGINT, 0::BIGINT, 0::BIGINT, trans;
18194         END IF;
18195
18196     END LOOP;
18197
18198     RETURN;
18199 END;
18200 $f$ LANGUAGE PLPGSQL;
18201
18202 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$
18203 DECLARE
18204     ans RECORD;
18205     trans INT;
18206 BEGIN
18207     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;
18208
18209     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
18210         RETURN QUERY
18211         SELECT  ans.depth,
18212                 ans.id,
18213                 COUNT( cp.id ),
18214                 SUM( CASE WHEN cp.status IN (0,7,12) THEN 1 ELSE 0 END ),
18215                 COUNT( cp.id ),
18216                 trans
18217           FROM
18218                 actor.org_unit_descendants(ans.id) d
18219                 JOIN asset.copy cp ON (cp.circ_lib = d.id)
18220                 JOIN asset.call_number cn ON (cn.record = record AND cn.id = cp.call_number)
18221           GROUP BY 1,2,6;
18222
18223         IF NOT FOUND THEN
18224             RETURN QUERY SELECT ans.depth, ans.id, 0::BIGINT, 0::BIGINT, 0::BIGINT, trans;
18225         END IF;
18226
18227     END LOOP;
18228
18229     RETURN;
18230 END;
18231 $f$ LANGUAGE PLPGSQL;
18232
18233 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$
18234 DECLARE
18235     ans RECORD;
18236     trans INT;
18237 BEGIN
18238     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;
18239
18240     FOR ans IN SELECT u.org_unit AS id FROM actor.org_lasso_map AS u WHERE lasso = i_lasso LOOP
18241         RETURN QUERY
18242         SELECT  -1,
18243                 ans.id,
18244                 COUNT( cp.id ),
18245                 SUM( CASE WHEN cp.status IN (0,7,12) THEN 1 ELSE 0 END ),
18246                 COUNT( cp.id ),
18247                 trans
18248           FROM
18249                 actor.org_unit_descendants(ans.id) d
18250                 JOIN asset.copy cp ON (cp.circ_lib = d.id)
18251                 JOIN asset.call_number cn ON (cn.record = record AND cn.id = cp.call_number)
18252           GROUP BY 1,2,6;
18253
18254         IF NOT FOUND THEN
18255             RETURN QUERY SELECT ans.depth, ans.id, 0::BIGINT, 0::BIGINT, 0::BIGINT, trans;
18256         END IF;
18257
18258     END LOOP;
18259
18260     RETURN;
18261 END;
18262 $f$ LANGUAGE PLPGSQL;
18263
18264 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$
18265 BEGIN
18266     IF staff IS TRUE THEN
18267         IF place > 0 THEN
18268             RETURN QUERY SELECT * FROM asset.staff_ou_record_copy_count( place, record );
18269         ELSE
18270             RETURN QUERY SELECT * FROM asset.staff_lasso_record_copy_count( -place, record );
18271         END IF;
18272     ELSE
18273         IF place > 0 THEN
18274             RETURN QUERY SELECT * FROM asset.opac_ou_record_copy_count( place, record );
18275         ELSE
18276             RETURN QUERY SELECT * FROM asset.opac_lasso_record_copy_count( -place, record );
18277         END IF;
18278     END IF;
18279
18280     RETURN;
18281 END;
18282 $f$ LANGUAGE PLPGSQL;
18283
18284 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$
18285 DECLARE
18286     ans RECORD;
18287     trans INT;
18288 BEGIN
18289     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;
18290
18291     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
18292         RETURN QUERY
18293         SELECT  ans.depth,
18294                 ans.id,
18295                 COUNT( av.id ),
18296                 SUM( CASE WHEN cp.status IN (0,7,12) THEN 1 ELSE 0 END ),
18297                 COUNT( av.id ),
18298                 trans
18299           FROM
18300                 actor.org_unit_descendants(ans.id) d
18301                 JOIN asset.opac_visible_copies av ON (av.record = record AND av.circ_lib = d.id)
18302                 JOIN asset.copy cp ON (cp.id = av.id)
18303                 JOIN metabib.metarecord_source_map m ON (m.source = av.record)
18304           GROUP BY 1,2,6;
18305
18306         IF NOT FOUND THEN
18307             RETURN QUERY SELECT ans.depth, ans.id, 0::BIGINT, 0::BIGINT, 0::BIGINT, trans;
18308         END IF;
18309
18310     END LOOP;
18311
18312     RETURN;
18313 END;
18314 $f$ LANGUAGE PLPGSQL;
18315
18316 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$
18317 DECLARE
18318     ans RECORD;
18319     trans INT;
18320 BEGIN
18321     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;
18322
18323     FOR ans IN SELECT u.org_unit AS id FROM actor.org_lasso_map AS u WHERE lasso = i_lasso LOOP
18324         RETURN QUERY
18325         SELECT  -1,
18326                 ans.id,
18327                 COUNT( av.id ),
18328                 SUM( CASE WHEN cp.status IN (0,7,12) THEN 1 ELSE 0 END ),
18329                 COUNT( av.id ),
18330                 trans
18331           FROM
18332                 actor.org_unit_descendants(ans.id) d
18333                 JOIN asset.opac_visible_copies av ON (av.record = record AND av.circ_lib = d.id)
18334                 JOIN asset.copy cp ON (cp.id = av.id)
18335                 JOIN metabib.metarecord_source_map m ON (m.source = av.record)
18336           GROUP BY 1,2,6;
18337
18338         IF NOT FOUND THEN
18339             RETURN QUERY SELECT ans.depth, ans.id, 0::BIGINT, 0::BIGINT, 0::BIGINT, trans;
18340         END IF;
18341
18342     END LOOP;
18343
18344     RETURN;
18345 END;
18346 $f$ LANGUAGE PLPGSQL;
18347
18348 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$
18349 DECLARE
18350     ans RECORD;
18351     trans INT;
18352 BEGIN
18353     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;
18354
18355     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
18356         RETURN QUERY
18357         SELECT  ans.depth,
18358                 ans.id,
18359                 COUNT( cp.id ),
18360                 SUM( CASE WHEN cp.status IN (0,7,12) THEN 1 ELSE 0 END ),
18361                 COUNT( cp.id ),
18362                 trans
18363           FROM
18364                 actor.org_unit_descendants(ans.id) d
18365                 JOIN asset.copy cp ON (cp.circ_lib = d.id)
18366                 JOIN asset.call_number cn ON (cn.record = record AND cn.id = cp.call_number)
18367                 JOIN metabib.metarecord_source_map m ON (m.source = cn.record)
18368           GROUP BY 1,2,6;
18369
18370         IF NOT FOUND THEN
18371             RETURN QUERY SELECT ans.depth, ans.id, 0::BIGINT, 0::BIGINT, 0::BIGINT, trans;
18372         END IF;
18373
18374     END LOOP;
18375
18376     RETURN;
18377 END;
18378 $f$ LANGUAGE PLPGSQL;
18379
18380 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$
18381 DECLARE
18382     ans RECORD;
18383     trans INT;
18384 BEGIN
18385     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;
18386
18387     FOR ans IN SELECT u.org_unit AS id FROM actor.org_lasso_map AS u WHERE lasso = i_lasso LOOP
18388         RETURN QUERY
18389         SELECT  -1,
18390                 ans.id,
18391                 COUNT( cp.id ),
18392                 SUM( CASE WHEN cp.status IN (0,7,12) THEN 1 ELSE 0 END ),
18393                 COUNT( cp.id ),
18394                 trans
18395           FROM
18396                 actor.org_unit_descendants(ans.id) d
18397                 JOIN asset.copy cp ON (cp.circ_lib = d.id)
18398                 JOIN asset.call_number cn ON (cn.record = record AND cn.id = cp.call_number)
18399                 JOIN metabib.metarecord_source_map m ON (m.source = cn.record)
18400           GROUP BY 1,2,6;
18401
18402         IF NOT FOUND THEN
18403             RETURN QUERY SELECT ans.depth, ans.id, 0::BIGINT, 0::BIGINT, 0::BIGINT, trans;
18404         END IF;
18405
18406     END LOOP;
18407
18408     RETURN;
18409 END;
18410 $f$ LANGUAGE PLPGSQL;
18411
18412 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$
18413 BEGIN
18414     IF staff IS TRUE THEN
18415         IF place > 0 THEN
18416             RETURN QUERY SELECT * FROM asset.staff_ou_metarecord_copy_count( place, record );
18417         ELSE
18418             RETURN QUERY SELECT * FROM asset.staff_lasso_metarecord_copy_count( -place, record );
18419         END IF;
18420     ELSE
18421         IF place > 0 THEN
18422             RETURN QUERY SELECT * FROM asset.opac_ou_metarecord_copy_count( place, record );
18423         ELSE
18424             RETURN QUERY SELECT * FROM asset.opac_lasso_metarecord_copy_count( -place, record );
18425         END IF;
18426     END IF;
18427
18428     RETURN;
18429 END;
18430 $f$ LANGUAGE PLPGSQL;
18431
18432 -- No transaction is required
18433
18434 -- Triggers on the vandelay.queued_*_record tables delete entries from
18435 -- the associated vandelay.queued_*_record_attr tables based on the record's
18436 -- ID; create an index on that column to avoid sequential scans for each
18437 -- queued record that is deleted
18438 CREATE INDEX queued_bib_record_attr_record_idx ON vandelay.queued_bib_record_attr (record);
18439 CREATE INDEX queued_authority_record_attr_record_idx ON vandelay.queued_authority_record_attr (record);
18440
18441 -- Avoid sequential scans for queue retrieval operations by providing an
18442 -- index on the queue column
18443 CREATE INDEX queued_bib_record_queue_idx ON vandelay.queued_bib_record (queue);
18444 CREATE INDEX queued_authority_record_queue_idx ON vandelay.queued_authority_record (queue);
18445
18446 -- Start picking up call number label prefixes and suffixes
18447 -- from asset.copy_location
18448 ALTER TABLE asset.copy_location ADD COLUMN label_prefix TEXT;
18449 ALTER TABLE asset.copy_location ADD COLUMN label_suffix TEXT;
18450
18451 DROP VIEW auditor.asset_copy_lifecycle;
18452
18453 SELECT auditor.create_auditor_lifecycle( 'asset', 'copy' );
18454
18455 ALTER TABLE reporter.report RENAME COLUMN recurance TO recurrence;
18456
18457 -- Let's not break existing reports
18458 UPDATE reporter.template SET data = REGEXP_REPLACE(data, E'^(.*)recuring(.*)$', E'\\1recurring\\2') WHERE data LIKE '%recuring%';
18459 UPDATE reporter.template SET data = REGEXP_REPLACE(data, E'^(.*)recurance(.*)$', E'\\1recurrence\\2') WHERE data LIKE '%recurance%';
18460
18461 -- Need to recreate this view with DISTINCT calls to ARRAY_ACCUM, thus avoiding duplicated ISBN and ISSN values
18462 CREATE OR REPLACE VIEW reporter.old_super_simple_record AS
18463 SELECT  r.id,
18464     r.fingerprint,
18465     r.quality,
18466     r.tcn_source,
18467     r.tcn_value,
18468     FIRST(title.value) AS title,
18469     FIRST(author.value) AS author,
18470     ARRAY_TO_STRING(ARRAY_ACCUM( DISTINCT publisher.value), ', ') AS publisher,
18471     ARRAY_TO_STRING(ARRAY_ACCUM( DISTINCT SUBSTRING(pubdate.value FROM $$\d+$$) ), ', ') AS pubdate,
18472     ARRAY_ACCUM( DISTINCT SUBSTRING(isbn.value FROM $$^\S+$$) ) AS isbn,
18473     ARRAY_ACCUM( DISTINCT SUBSTRING(issn.value FROM $$^\S+$$) ) AS issn
18474   FROM  biblio.record_entry r
18475     LEFT JOIN metabib.full_rec title ON (r.id = title.record AND title.tag = '245' AND title.subfield = 'a')
18476     LEFT JOIN metabib.full_rec author ON (r.id = author.record AND author.tag IN ('100','110','111') AND author.subfield = 'a')
18477     LEFT JOIN metabib.full_rec publisher ON (r.id = publisher.record AND publisher.tag = '260' AND publisher.subfield = 'b')
18478     LEFT JOIN metabib.full_rec pubdate ON (r.id = pubdate.record AND pubdate.tag = '260' AND pubdate.subfield = 'c')
18479     LEFT JOIN metabib.full_rec isbn ON (r.id = isbn.record AND isbn.tag IN ('024', '020') AND isbn.subfield IN ('a','z'))
18480     LEFT JOIN metabib.full_rec issn ON (r.id = issn.record AND issn.tag = '022' AND issn.subfield = 'a')
18481   GROUP BY 1,2,3,4,5;
18482
18483 -- Correct the ISSN array definition for reporter.simple_record
18484
18485 CREATE OR REPLACE VIEW reporter.simple_record AS
18486 SELECT  r.id,
18487         s.metarecord,
18488         r.fingerprint,
18489         r.quality,
18490         r.tcn_source,
18491         r.tcn_value,
18492         title.value AS title,
18493         uniform_title.value AS uniform_title,
18494         author.value AS author,
18495         publisher.value AS publisher,
18496         SUBSTRING(pubdate.value FROM $$\d+$$) AS pubdate,
18497         series_title.value AS series_title,
18498         series_statement.value AS series_statement,
18499         summary.value AS summary,
18500         ARRAY_ACCUM( SUBSTRING(isbn.value FROM $$^\S+$$) ) AS isbn,
18501         ARRAY_ACCUM( REGEXP_REPLACE(issn.value, E'^\\S*(\\d{4})[-\\s](\\d{3,4}x?)', E'\\1 \\2') ) AS issn,
18502         ARRAY((SELECT DISTINCT value FROM metabib.full_rec WHERE tag = '650' AND subfield = 'a' AND record = r.id)) AS topic_subject,
18503         ARRAY((SELECT DISTINCT value FROM metabib.full_rec WHERE tag = '651' AND subfield = 'a' AND record = r.id)) AS geographic_subject,
18504         ARRAY((SELECT DISTINCT value FROM metabib.full_rec WHERE tag = '655' AND subfield = 'a' AND record = r.id)) AS genre,
18505         ARRAY((SELECT DISTINCT value FROM metabib.full_rec WHERE tag = '600' AND subfield = 'a' AND record = r.id)) AS name_subject,
18506         ARRAY((SELECT DISTINCT value FROM metabib.full_rec WHERE tag = '610' AND subfield = 'a' AND record = r.id)) AS corporate_subject,
18507         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
18508   FROM  biblio.record_entry r
18509         JOIN metabib.metarecord_source_map s ON (s.source = r.id)
18510         LEFT JOIN metabib.full_rec uniform_title ON (r.id = uniform_title.record AND uniform_title.tag = '240' AND uniform_title.subfield = 'a')
18511         LEFT JOIN metabib.full_rec title ON (r.id = title.record AND title.tag = '245' AND title.subfield = 'a')
18512         LEFT JOIN metabib.full_rec author ON (r.id = author.record AND author.tag = '100' AND author.subfield = 'a')
18513         LEFT JOIN metabib.full_rec publisher ON (r.id = publisher.record AND publisher.tag = '260' AND publisher.subfield = 'b')
18514         LEFT JOIN metabib.full_rec pubdate ON (r.id = pubdate.record AND pubdate.tag = '260' AND pubdate.subfield = 'c')
18515         LEFT JOIN metabib.full_rec isbn ON (r.id = isbn.record AND isbn.tag IN ('024', '020') AND isbn.subfield IN ('a','z'))
18516         LEFT JOIN metabib.full_rec issn ON (r.id = issn.record AND issn.tag = '022' AND issn.subfield = 'a')
18517         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')
18518         LEFT JOIN metabib.full_rec series_statement ON (r.id = series_statement.record AND series_statement.tag = '490' AND series_statement.subfield = 'a')
18519         LEFT JOIN metabib.full_rec summary ON (r.id = summary.record AND summary.tag = '520' AND summary.subfield = 'a')
18520   GROUP BY 1,2,3,4,5,6,7,8,9,10,11,12,13,14;
18521
18522 CREATE OR REPLACE FUNCTION reporter.disable_materialized_simple_record_trigger () RETURNS VOID AS $$
18523     DROP TRIGGER IF EXISTS zzz_update_materialized_simple_record_tgr ON metabib.real_full_rec;
18524 $$ LANGUAGE SQL;
18525
18526 CREATE OR REPLACE FUNCTION reporter.simple_rec_trigger () RETURNS TRIGGER AS $func$
18527 BEGIN
18528     IF TG_OP = 'DELETE' THEN
18529         PERFORM reporter.simple_rec_delete(NEW.id);
18530     ELSE
18531         PERFORM reporter.simple_rec_update(NEW.id);
18532     END IF;
18533
18534     RETURN NEW;
18535 END;
18536 $func$ LANGUAGE PLPGSQL;
18537
18538 CREATE TRIGGER bbb_simple_rec_trigger AFTER INSERT OR UPDATE ON biblio.record_entry FOR EACH ROW EXECUTE PROCEDURE reporter.simple_rec_trigger ();
18539
18540 ALTER TABLE extend_reporter.legacy_circ_count DROP CONSTRAINT legacy_circ_count_id_fkey;
18541
18542 CREATE INDEX asset_copy_note_owning_copy_idx ON asset.copy_note ( owning_copy );
18543
18544 UPDATE config.org_unit_setting_type
18545     SET view_perm = (SELECT id FROM permission.perm_list
18546         WHERE code = 'VIEW_CREDIT_CARD_PROCESSING' LIMIT 1)
18547     WHERE name LIKE 'credit.processor%' AND view_perm IS NULL;
18548
18549 UPDATE config.org_unit_setting_type
18550     SET update_perm = (SELECT id FROM permission.perm_list
18551         WHERE code = 'ADMIN_CREDIT_CARD_PROCESSING' LIMIT 1)
18552     WHERE name LIKE 'credit.processor%' AND update_perm IS NULL;
18553
18554 INSERT INTO config.org_unit_setting_type (name, label, description, datatype)
18555     VALUES (
18556         'opac.fully_compressed_serial_holdings',
18557         'OPAC: Use fully compressed serial holdings',
18558         'Show fully compressed serial holdings for all libraries at and below
18559         the current context unit',
18560         'bool'
18561     );
18562
18563 CREATE OR REPLACE FUNCTION authority.normalize_heading( TEXT ) RETURNS TEXT AS $func$
18564     use strict;
18565     use warnings;
18566
18567     use utf8;
18568     use MARC::Record;
18569     use MARC::File::XML (BinaryEncoding => 'UTF8');
18570     use UUID::Tiny ':std';
18571
18572     my $xml = shift() or return undef;
18573
18574     my $r;
18575
18576     # Prevent errors in XML parsing from blowing out ungracefully
18577     eval {
18578         $r = MARC::Record->new_from_xml( $xml );
18579         1;
18580     } or do {
18581        return 'BAD_MARCXML_' . create_uuid_as_string(UUID_MD5, $xml);
18582     };
18583
18584     if (!$r) {
18585        return 'BAD_MARCXML_' . create_uuid_as_string(UUID_MD5, $xml);
18586     }
18587
18588     # From http://www.loc.gov/standards/sourcelist/subject.html
18589     my $thes_code_map = {
18590         a => 'lcsh',
18591         b => 'lcshac',
18592         c => 'mesh',
18593         d => 'nal',
18594         k => 'cash',
18595         n => 'notapplicable',
18596         r => 'aat',
18597         s => 'sears',
18598         v => 'rvm',
18599     };
18600
18601     # Default to "No attempt to code" if the leader is horribly broken
18602     my $fixed_field = $r->field('008');
18603     my $thes_char = '|';
18604     if ($fixed_field) {
18605         $thes_char = substr($fixed_field->data(), 11, 1) || '|';
18606     }
18607
18608     my $thes_code = 'UNDEFINED';
18609
18610     if ($thes_char eq 'z') {
18611         # Grab the 040 $f per http://www.loc.gov/marc/authority/ad040.html
18612         $thes_code = $r->subfield('040', 'f') || 'UNDEFINED';
18613     } elsif ($thes_code_map->{$thes_char}) {
18614         $thes_code = $thes_code_map->{$thes_char};
18615     }
18616
18617     my $auth_txt = '';
18618     my $head = $r->field('1..');
18619     if ($head) {
18620         # Concatenate all of these subfields together, prefixed by their code
18621         # to prevent collisions along the lines of "Fiction, North Carolina"
18622         foreach my $sf ($head->subfields()) {
18623             $auth_txt .= '‡' . $sf->[0] . ' ' . $sf->[1];
18624         }
18625     }
18626
18627     # Perhaps better to parameterize the spi and pass as a parameter
18628     $auth_txt =~ s/'//go;
18629
18630     if ($auth_txt) {
18631         my $result = spi_exec_query("SELECT public.naco_normalize('$auth_txt') AS norm_text");
18632         my $norm_txt = $result->{rows}[0]->{norm_text};
18633         return $head->tag() . "_" . $thes_code . " " . $norm_txt;
18634     }
18635
18636     return 'NOHEADING_' . $thes_code . ' ' . create_uuid_as_string(UUID_MD5, $xml);
18637 $func$ LANGUAGE 'plperlu' IMMUTABLE;
18638
18639 COMMENT ON FUNCTION authority.normalize_heading( TEXT ) IS $$
18640 /**
18641 * Extract the authority heading, thesaurus, and NACO-normalized values
18642 * from an authority record. The primary purpose is to build a unique
18643 * index to defend against duplicated authority records from the same
18644 * thesaurus.
18645 */
18646 $$;
18647
18648 DROP INDEX authority.authority_record_unique_tcn;
18649 ALTER TABLE authority.record_entry DROP COLUMN arn_value;
18650 ALTER TABLE authority.record_entry DROP COLUMN arn_source;
18651
18652 ALTER TABLE acq.provider_contact
18653         ALTER COLUMN name SET NOT NULL;
18654
18655 ALTER TABLE actor.stat_cat
18656         ADD COLUMN usr_summary BOOL NOT NULL DEFAULT FALSE;
18657
18658 -- Recreate some foreign keys that were somehow dropped, probably
18659 -- by some kind of cascade from an inherited table:
18660
18661 ALTER TABLE action.reservation_transit_copy
18662         ADD CONSTRAINT artc_tc_fkey FOREIGN KEY (target_copy)
18663                 REFERENCES booking.resource(id)
18664                 ON DELETE CASCADE
18665                 DEFERRABLE INITIALLY DEFERRED,
18666         ADD CONSTRAINT reservation_transit_copy_reservation_fkey FOREIGN KEY (reservation)
18667                 REFERENCES booking.reservation(id)
18668                 ON DELETE SET NULL
18669                 DEFERRABLE INITIALLY DEFERRED;
18670
18671 CREATE INDEX user_bucket_item_target_user_idx
18672         ON container.user_bucket_item ( target_user );
18673
18674 CREATE INDEX m_c_t_collector_idx
18675         ON money.collections_tracker ( collector );
18676
18677 CREATE INDEX aud_actor_usr_address_hist_id_idx
18678         ON auditor.actor_usr_address_history ( id );
18679
18680 CREATE INDEX aud_actor_usr_hist_id_idx
18681         ON auditor.actor_usr_history ( id );
18682
18683 CREATE INDEX aud_asset_cn_hist_creator_idx
18684         ON auditor.asset_call_number_history ( creator );
18685
18686 CREATE INDEX aud_asset_cn_hist_editor_idx
18687         ON auditor.asset_call_number_history ( editor );
18688
18689 CREATE INDEX aud_asset_cp_hist_creator_idx
18690         ON auditor.asset_copy_history ( creator );
18691
18692 CREATE INDEX aud_asset_cp_hist_editor_idx
18693         ON auditor.asset_copy_history ( editor );
18694
18695 CREATE INDEX aud_bib_rec_entry_hist_creator_idx
18696         ON auditor.biblio_record_entry_history ( creator );
18697
18698 CREATE INDEX aud_bib_rec_entry_hist_editor_idx
18699         ON auditor.biblio_record_entry_history ( editor );
18700
18701 CREATE TABLE action.hold_request_note (
18702
18703     id     BIGSERIAL PRIMARY KEY,
18704     hold   BIGINT    NOT NULL REFERENCES action.hold_request (id)
18705                               ON DELETE CASCADE
18706                               DEFERRABLE INITIALLY DEFERRED,
18707     title  TEXT      NOT NULL,
18708     body   TEXT      NOT NULL,
18709     slip   BOOL      NOT NULL DEFAULT FALSE,
18710     pub    BOOL      NOT NULL DEFAULT FALSE,
18711     staff  BOOL      NOT NULL DEFAULT FALSE  -- created by staff
18712
18713 );
18714 CREATE INDEX ahrn_hold_idx ON action.hold_request_note (hold);
18715
18716 -- Tweak a constraint to add a CASCADE
18717
18718 ALTER TABLE action.hold_notification DROP CONSTRAINT hold_notification_hold_fkey;
18719
18720 ALTER TABLE action.hold_notification
18721         ADD CONSTRAINT hold_notification_hold_fkey
18722                 FOREIGN KEY (hold) REFERENCES action.hold_request (id)
18723                 ON DELETE CASCADE
18724                 DEFERRABLE INITIALLY DEFERRED;
18725
18726 CREATE TRIGGER asset_label_sortkey_trigger
18727     BEFORE UPDATE OR INSERT ON asset.call_number
18728     FOR EACH ROW EXECUTE PROCEDURE asset.label_normalizer();
18729
18730 CREATE OR REPLACE FUNCTION container.clear_all_expired_circ_history_items( )
18731 RETURNS VOID AS $$
18732 --
18733 -- Delete expired circulation bucket items for all users that have
18734 -- a setting for patron.max_reading_list_interval.
18735 --
18736 DECLARE
18737     today        TIMESTAMP WITH TIME ZONE;
18738     threshold    TIMESTAMP WITH TIME ZONE;
18739         usr_setting  RECORD;
18740 BEGIN
18741         SELECT date_trunc( 'day', now() ) INTO today;
18742         --
18743         FOR usr_setting in
18744                 SELECT
18745                         usr,
18746                         value
18747                 FROM
18748                         actor.usr_setting
18749                 WHERE
18750                         name = 'patron.max_reading_list_interval'
18751         LOOP
18752                 --
18753                 -- Make sure the setting is a valid interval
18754                 --
18755                 BEGIN
18756                         threshold := today - CAST( translate( usr_setting.value, '"', '' ) AS INTERVAL );
18757                 EXCEPTION
18758                         WHEN OTHERS THEN
18759                                 RAISE NOTICE 'Invalid setting patron.max_reading_list_interval for user %: ''%''',
18760                                         usr_setting.usr, usr_setting.value;
18761                                 CONTINUE;
18762                 END;
18763                 --
18764                 --RAISE NOTICE 'User % threshold %', usr_setting.usr, threshold;
18765                 --
18766         DELETE FROM container.copy_bucket_item
18767         WHERE
18768                 bucket IN
18769                 (
18770                     SELECT
18771                         id
18772                     FROM
18773                         container.copy_bucket
18774                     WHERE
18775                         owner = usr_setting.usr
18776                         AND btype = 'circ_history'
18777                 )
18778                 AND create_time < threshold;
18779         END LOOP;
18780         --
18781 END;
18782 $$ LANGUAGE plpgsql;
18783
18784 COMMENT ON FUNCTION container.clear_all_expired_circ_history_items( ) IS $$
18785 /*
18786  * Delete expired circulation bucket items for all users that have
18787  * a setting for patron.max_reading_list_interval.
18788 */
18789 $$;
18790
18791 CREATE OR REPLACE FUNCTION container.clear_expired_circ_history_items( 
18792          ac_usr IN INTEGER
18793 ) RETURNS VOID AS $$
18794 --
18795 -- Delete old circulation bucket items for a specified user.
18796 -- "Old" means older than the interval specified by a
18797 -- user-level setting, if it is so specified.
18798 --
18799 DECLARE
18800     threshold TIMESTAMP WITH TIME ZONE;
18801 BEGIN
18802         -- Sanity check
18803         IF ac_usr IS NULL THEN
18804                 RETURN;
18805         END IF;
18806         -- Determine the threshold date that defines "old".  Subtract the
18807         -- interval from the system date, then truncate to midnight.
18808         SELECT
18809                 date_trunc( 
18810                         'day',
18811                         now() - CAST( translate( value, '"', '' ) AS INTERVAL )
18812                 )
18813         INTO
18814                 threshold
18815         FROM
18816                 actor.usr_setting
18817         WHERE
18818                 usr = ac_usr
18819                 AND name = 'patron.max_reading_list_interval';
18820         --
18821         IF threshold is null THEN
18822                 -- No interval defined; don't delete anything
18823                 -- RAISE NOTICE 'No interval defined for user %', ac_usr;
18824                 return;
18825         END IF;
18826         --
18827         -- RAISE NOTICE 'Date threshold: %', threshold;
18828         --
18829         -- Threshold found; do the delete
18830         delete from container.copy_bucket_item
18831         where
18832                 bucket in
18833                 (
18834                         select
18835                                 id
18836                         from
18837                                 container.copy_bucket
18838                         where
18839                                 owner = ac_usr
18840                                 and btype = 'circ_history'
18841                 )
18842                 and create_time < threshold;
18843         --
18844         RETURN;
18845 END;
18846 $$ LANGUAGE plpgsql;
18847
18848 COMMENT ON FUNCTION container.clear_expired_circ_history_items( INTEGER ) IS $$
18849 /*
18850  * Delete old circulation bucket items for a specified user.
18851  * "Old" means older than the interval specified by a
18852  * user-level setting, if it is so specified.
18853 */
18854 $$;
18855
18856 CREATE OR REPLACE VIEW reporter.hold_request_record AS
18857 SELECT  id,
18858     target,
18859     hold_type,
18860     CASE
18861         WHEN hold_type = 'T'
18862             THEN target
18863         WHEN hold_type = 'I'
18864             THEN (SELECT ssub.record_entry FROM serial.subscription ssub JOIN serial.issuance si ON (si.subscription = ssub.id) WHERE si.id = ahr.target)
18865         WHEN hold_type = 'V'
18866             THEN (SELECT cn.record FROM asset.call_number cn WHERE cn.id = ahr.target)
18867         WHEN hold_type IN ('C','R','F')
18868             THEN (SELECT cn.record FROM asset.call_number cn JOIN asset.copy cp ON (cn.id = cp.call_number) WHERE cp.id = ahr.target)
18869         WHEN hold_type = 'M'
18870             THEN (SELECT mr.master_record FROM metabib.metarecord mr WHERE mr.id = ahr.target)
18871     END AS bib_record
18872   FROM  action.hold_request ahr;
18873
18874 UPDATE  metabib.rec_descriptor
18875   SET   date1=LPAD(NULLIF(REGEXP_REPLACE(NULLIF(date1, ''), E'\\D', '0', 'g')::INT,0)::TEXT,4,'0'),
18876         date2=LPAD(NULLIF(REGEXP_REPLACE(NULLIF(date2, ''), E'\\D', '9', 'g')::INT,9999)::TEXT,4,'0');
18877
18878 -- Change some ints to bigints:
18879
18880 ALTER TABLE container.biblio_record_entry_bucket_item
18881         ALTER COLUMN target_biblio_record_entry SET DATA TYPE bigint;
18882
18883 ALTER TABLE vandelay.queued_bib_record
18884         ALTER COLUMN imported_as SET DATA TYPE bigint;
18885
18886 ALTER TABLE action.hold_copy_map
18887         ALTER COLUMN id SET DATA TYPE bigint;
18888
18889 -- Make due times get pushed to 23:59:59 on insert OR update
18890 DROP TRIGGER IF EXISTS push_due_date_tgr ON action.circulation;
18891 CREATE TRIGGER push_due_date_tgr BEFORE INSERT OR UPDATE ON action.circulation FOR EACH ROW EXECUTE PROCEDURE action.push_circ_due_time();
18892
18893 INSERT INTO acq.lineitem_marc_attr_definition ( code, description, xpath, remove )
18894 SELECT 'upc', 'UPC', '//*[@tag="024" and @ind1="1"]/*[@code="a"]', $r$(?:-|\s.+$)$r$
18895 WHERE NOT EXISTS (
18896     SELECT 1 FROM acq.lineitem_marc_attr_definition WHERE code = 'upc'
18897 );  
18898
18899 COMMIT;
18900
18901 -- Some operations go outside of the transaction, because they may
18902 -- legitimately fail.
18903
18904 \qecho ALTERs of auditor.action_hold_request_history will fail if the table
18905 \qecho doesn't exist; ignore those errors if they occur.
18906
18907 ALTER TABLE auditor.action_hold_request_history ADD COLUMN cut_in_line BOOL;
18908
18909 ALTER TABLE auditor.action_hold_request_history
18910 ADD COLUMN mint_condition boolean NOT NULL DEFAULT TRUE;
18911
18912 ALTER TABLE auditor.action_hold_request_history
18913 ADD COLUMN shelf_expire_time TIMESTAMPTZ;
18914
18915 \qecho Outside of the transaction: adding indexes that may or may not exist.
18916 \qecho If any of these CREATE INDEX statements fails because the index already
18917 \qecho exists, ignore the failure.
18918
18919 CREATE INDEX acq_picklist_owner_idx   ON acq.picklist ( owner );
18920 CREATE INDEX acq_picklist_creator_idx ON acq.picklist ( creator );
18921 CREATE INDEX acq_picklist_editor_idx  ON acq.picklist ( editor );
18922 CREATE INDEX acq_po_note_creator_idx  ON acq.po_note ( creator );
18923 CREATE INDEX acq_po_note_editor_idx   ON acq.po_note ( editor );
18924 CREATE INDEX fund_alloc_allocator_idx ON acq.fund_allocation ( allocator );
18925 CREATE INDEX li_creator_idx   ON acq.lineitem ( creator );
18926 CREATE INDEX li_editor_idx    ON acq.lineitem ( editor );
18927 CREATE INDEX li_selector_idx  ON acq.lineitem ( selector );
18928 CREATE INDEX li_note_creator_idx  ON acq.lineitem_note ( creator );
18929 CREATE INDEX li_note_editor_idx   ON acq.lineitem_note ( editor );
18930 CREATE INDEX li_usr_attr_def_usr_idx  ON acq.lineitem_usr_attr_definition ( usr );
18931 CREATE INDEX po_editor_idx   ON acq.purchase_order ( editor );
18932 CREATE INDEX po_creator_idx  ON acq.purchase_order ( creator );
18933 CREATE INDEX acq_po_org_name_order_date_idx ON acq.purchase_order( ordering_agency, name, order_date );
18934 CREATE INDEX action_in_house_use_staff_idx  ON action.in_house_use ( staff );
18935 CREATE INDEX action_non_cat_circ_patron_idx ON action.non_cataloged_circulation ( patron );
18936 CREATE INDEX action_non_cat_circ_staff_idx  ON action.non_cataloged_circulation ( staff );
18937 CREATE INDEX action_survey_response_usr_idx ON action.survey_response ( usr );
18938 CREATE INDEX ahn_notify_staff_idx           ON action.hold_notification ( notify_staff );
18939 CREATE INDEX circ_all_usr_idx               ON action.circulation ( usr );
18940 CREATE INDEX circ_circ_staff_idx            ON action.circulation ( circ_staff );
18941 CREATE INDEX circ_checkin_staff_idx         ON action.circulation ( checkin_staff );
18942 CREATE INDEX hold_request_fulfillment_staff_idx ON action.hold_request ( fulfillment_staff );
18943 CREATE INDEX hold_request_requestor_idx     ON action.hold_request ( requestor );
18944 CREATE INDEX non_cat_in_house_use_staff_idx ON action.non_cat_in_house_use ( staff );
18945 CREATE INDEX actor_usr_note_creator_idx     ON actor.usr_note ( creator );
18946 CREATE INDEX actor_usr_standing_penalty_staff_idx ON actor.usr_standing_penalty ( staff );
18947 CREATE INDEX usr_org_unit_opt_in_staff_idx  ON actor.usr_org_unit_opt_in ( staff );
18948 CREATE INDEX asset_call_number_note_creator_idx ON asset.call_number_note ( creator );
18949 CREATE INDEX asset_copy_note_creator_idx    ON asset.copy_note ( creator );
18950 CREATE INDEX cp_creator_idx                 ON asset.copy ( creator );
18951 CREATE INDEX cp_editor_idx                  ON asset.copy ( editor );
18952
18953 CREATE INDEX actor_card_barcode_lower_idx ON actor.card (lower(barcode));
18954
18955 DROP INDEX IF EXISTS authority.unique_by_heading_and_thesaurus;
18956
18957 \qecho If the following CREATE INDEX fails, It will be necessary to do some
18958 \qecho data cleanup as described in the comments.
18959
18960 CREATE UNIQUE INDEX unique_by_heading_and_thesaurus
18961     ON authority.record_entry (authority.normalize_heading(marc))
18962         WHERE deleted IS FALSE or deleted = FALSE;
18963
18964 -- If the unique index fails, uncomment the following to create
18965 -- a regular index that will help find the duplicates in a hurry:
18966 --CREATE INDEX by_heading_and_thesaurus
18967 --    ON authority.record_entry (authority.normalize_heading(marc))
18968 --    WHERE deleted IS FALSE or deleted = FALSE
18969 --;
18970
18971 -- Then find the duplicates like so to get an idea of how much
18972 -- pain you're looking at to clean things up:
18973 --SELECT id, authority.normalize_heading(marc)
18974 --    FROM authority.record_entry
18975 --    WHERE authority.normalize_heading(marc) IN (
18976 --        SELECT authority.normalize_heading(marc)
18977 --        FROM authority.record_entry
18978 --        GROUP BY authority.normalize_heading(marc)
18979 --        HAVING COUNT(*) > 1
18980 --    )
18981 --;
18982
18983 -- Once you have removed the duplicates and the CREATE UNIQUE INDEX
18984 -- statement succeeds, drop the temporary index to avoid unnecessary
18985 -- duplication:
18986 -- DROP INDEX authority.by_heading_and_thesaurus;
18987
18988 -- 0448.data.trigger.circ.staff_age_to_lost.sql
18989
18990 INSERT INTO action_trigger.hook (key,core_type,description,passive) VALUES 
18991     (   'circ.staff_age_to_lost',
18992         'circ', 
18993         oils_i18n_gettext(
18994             'circ.staff_age_to_lost',
18995             'An overdue circulation should be aged to a Lost status.',
18996             'ath',
18997             'description'
18998         ), 
18999         TRUE
19000     )
19001 ;
19002
19003 INSERT INTO action_trigger.event_definition (
19004         id,
19005         active,
19006         owner,
19007         name,
19008         hook,
19009         validator,
19010         reactor,
19011         delay_field
19012     ) VALUES (
19013         36,
19014         FALSE,
19015         1,
19016         'circ.staff_age_to_lost',
19017         'circ.staff_age_to_lost',
19018         'CircIsOverdue',
19019         'MarkItemLost',
19020         'due_date'
19021     )
19022 ;
19023
19024
19025 \qecho Upgrade script completed.
19026