]> git.evergreen-ils.org Git - Evergreen.git/blob - Open-ILS/src/sql/Pg/1.6.1-2.0-upgrade-db.sql
Consolidated upgrade script for 1.6 -> 2.0.
[Evergreen.git] / Open-ILS / src / sql / Pg / 1.6.1-2.0-upgrade-db.sql
1 BEGIN;
2
3 -- Highest-numbered individual upgrade script
4 -- incorporated herein:
5
6 INSERT INTO config.upgrade_log (version) VALUES ('0387');
7
8 -- Add ON UPDATE CASCADE to some foreign keys so that, if we renumber the
9 -- permissions, the dependents will follow and stay in sync:
10
11 ALTER TABLE permission.grp_perm_map DROP CONSTRAINT grp_perm_map_perm_fkey;
12 ALTER TABLE permission.grp_perm_map ADD CONSTRAINT grp_perm_map_perm_fkey FOREIGN KEY (perm)
13     REFERENCES permission.perm_list (id) ON UPDATE CASCADE ON DELETE RESTRICT DEFERRABLE INITIALLY DEFERRED;
14
15 ALTER TABLE permission.usr_perm_map DROP CONSTRAINT usr_perm_map_perm_fkey;
16 ALTER TABLE permission.usr_perm_map ADD CONSTRAINT usr_perm_map_perm_fkey FOREIGN KEY (perm)
17     REFERENCES permission.perm_list (id) ON UPDATE CASCADE ON DELETE RESTRICT DEFERRABLE INITIALLY DEFERRED;
18
19 ALTER TABLE permission.usr_object_perm_map DROP CONSTRAINT usr_object_perm_map_perm_fkey;
20 ALTER TABLE permission.usr_object_perm_map ADD CONSTRAINT usr_object_perm_map_perm_fkey FOREIGN KEY (perm)
21     REFERENCES permission.perm_list (id) ON UPDATE CASCADE ON DELETE RESTRICT DEFERRABLE INITIALLY DEFERRED;
22
23 -- Create a reference table as parent to both
24 -- config.org_unit_setting_type and config_usr_setting_type
25
26 CREATE TABLE config.settings_group (
27     name    TEXT PRIMARY KEY,
28     label   TEXT UNIQUE NOT NULL -- I18N
29 );
30
31 -- org_unit setting types
32 CREATE TABLE config.org_unit_setting_type (
33     name            TEXT    PRIMARY KEY,
34     label           TEXT    UNIQUE NOT NULL,
35     grp             TEXT    REFERENCES config.settings_group (name),
36     description     TEXT,
37     datatype        TEXT    NOT NULL DEFAULT 'string',
38     fm_class        TEXT,
39     view_perm       INT,
40     update_perm     INT,
41     --
42     -- define valid datatypes
43     --
44     CONSTRAINT coust_valid_datatype CHECK ( datatype IN
45     ( 'bool', 'integer', 'float', 'currency', 'interval',
46       'date', 'string', 'object', 'array', 'link' ) ),
47     --
48     -- fm_class is meaningful only for 'link' datatype
49     --
50     CONSTRAINT coust_no_empty_link CHECK
51     ( ( datatype =  'link' AND fm_class IS NOT NULL ) OR
52       ( datatype <> 'link' AND fm_class IS NULL ) ),
53         CONSTRAINT view_perm_fkey FOREIGN KEY (view_perm) REFERENCES permission.perm_list (id)
54                 ON UPDATE CASCADE
55                 ON DELETE RESTRICT
56                 DEFERRABLE INITIALLY DEFERRED,
57         CONSTRAINT update_perm_fkey FOREIGN KEY (update_perm) REFERENCES permission.perm_list (id)
58                 ON UPDATE CASCADE
59                 DEFERRABLE INITIALLY DEFERRED
60 );
61
62 CREATE TABLE config.usr_setting_type (
63
64     name TEXT PRIMARY KEY,
65     opac_visible BOOL NOT NULL DEFAULT FALSE,
66     label TEXT UNIQUE NOT NULL,
67     description TEXT,
68     grp             TEXT    REFERENCES config.settings_group (name),
69     datatype TEXT NOT NULL DEFAULT 'string',
70     fm_class TEXT,
71
72     --
73     -- define valid datatypes
74     --
75     CONSTRAINT coust_valid_datatype CHECK ( datatype IN
76     ( 'bool', 'integer', 'float', 'currency', 'interval',
77         'date', 'string', 'object', 'array', 'link' ) ),
78
79     --
80     -- fm_class is meaningful only for 'link' datatype
81     --
82     CONSTRAINT coust_no_empty_link CHECK
83     ( ( datatype = 'link' AND fm_class IS NOT NULL ) OR
84         ( datatype <> 'link' AND fm_class IS NULL ) )
85
86 );
87
88 --------------------------------------
89 -- Seed data for org_unit_setting_type
90 --------------------------------------
91
92 INSERT into config.org_unit_setting_type
93 ( name, label, description, datatype ) VALUES
94
95 ( 'auth.opac_timeout',
96   'OPAC Inactivity Timeout (in seconds)',
97   null,
98   'integer' ),
99
100 ( 'auth.staff_timeout',
101   'Staff Login Inactivity Timeout (in seconds)',
102   null,
103   'integer' ),
104
105 ( 'circ.lost_materials_processing_fee',
106   'Lost Materials Processing Fee',
107   null,
108   'currency' ),
109
110 ( 'cat.default_item_price',
111   'Default Item Price',
112   null,
113   'currency' ),
114
115 ( 'org.bounced_emails',
116   'Sending email address for patron notices',
117   null,
118   'string' ),
119
120 ( 'circ.hold_expire_alert_interval',
121   'Holds: Expire Alert Interval',
122   'Amount of time before a hold expires at which point the patron should be alerted',
123   'interval' ),
124
125 ( 'circ.hold_expire_interval',
126   'Holds: Expire Interval',
127   'Amount of time after a hold is placed before the hold expires.  Example "100 days"',
128   'interval' ),
129
130 ( 'credit.payments.allow',
131   'Allow Credit Card Payments',
132   'If enabled, patrons will be able to pay fines accrued at this location via credit card',
133   'bool' ),
134
135 ( 'global.default_locale',
136   'Global Default Locale',
137   null,
138   'string' ),
139
140 ( 'circ.void_overdue_on_lost',
141   'Void overdue fines when items are marked lost',
142   null,
143   'bool' ),
144
145 ( 'circ.hold_stalling.soft',
146   'Holds: Soft stalling interval',
147   'How long to wait before allowing remote items to be opportunisticaly captured for a hold.  Example "5 days"',
148   'interval' ),
149
150 ( 'circ.hold_stalling_hard',
151   'Holds: Hard stalling interval',
152   '',
153   'interval' ),
154
155 ( 'circ.hold_boundary.hard',
156   'Holds: Hard boundary',
157   null,
158   'integer' ),
159
160 ( 'circ.hold_boundary.soft',
161   'Holds: Soft boundary',
162   null,
163   'integer' ),
164
165 ( 'opac.barcode_regex',
166   'Patron barcode format',
167   'Regular expression defining the patron barcode format',
168   'string' ),
169
170 ( 'global.password_regex',
171   'Password format',
172   'Regular expression defining the password format',
173   'string' ),
174
175 ( 'circ.item_checkout_history.max',
176   'Maximum previous checkouts displayed',
177   'This is maximum number of previous circulations the staff client will display when investigating item details',
178   'integer' ),
179
180 ( 'circ.reshelving_complete.interval',
181   'Change reshelving status interval',
182   'Amount of time to wait before changing an item from "reshelving" status to "available".  Examples "1 day", "6 hours"',
183   'interval' ),
184
185 ( 'circ.holds.default_estimated_wait_interval',
186   'Holds: Default Estimated Wait',
187   '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.',
188   'interval' ),
189
190 ( 'circ.holds.min_estimated_wait_interval',
191   'Holds: Minimum Estimated Wait',
192   '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.',
193   'interval' ),
194
195 ( 'circ.selfcheck.patron_login_timeout',
196   'Selfcheck: Patron Login Timeout (in seconds)',
197   'Number of seconds of inactivity before the patron is logged out of the selfcheck interfacer',
198   'integer' ),
199
200 ( 'circ.selfcheck.alert.popup',
201   'Selfcheck: Pop-up alert for errors',
202   'If true, checkout/renewal errors will cause a pop-up window in addition to the on-screen message',
203   'bool' ),
204
205 ( 'circ.selfcheck.require_patron_password',
206   'Selfcheck: Require patron password',
207   'If true, patrons will be required to enter their password in addition to their username/barcode to log into the selfcheck interface',
208   'bool' ),
209
210 ( 'global.juvenile_age_threshold',
211   'Juvenile Age Threshold',
212   'The age at which a user is no long considered a juvenile.  For example, "18 years".',
213   'interval' ),
214
215 ( 'cat.bib.keep_on_empty',
216   'Retain empty bib records',
217   'Retain a bib record even when all attached copies are deleted',
218   'bool' ),
219
220 ( 'cat.bib.alert_on_empty',
221   'Alert on empty bib records',
222   'Alert staff when the last copy for a record is being deleted',
223   'bool' ),
224
225 ( 'patron.password.use_phone',
226   'Patron: password from phone #',
227   'Use the last 4 digits of the patrons phone number as the default password when creating new users',
228   'bool' ),
229
230 ( 'circ.charge_on_damaged',
231   'Charge item price when marked damaged',
232   'Charge item price when marked damaged',
233   'bool' ),
234
235 ( 'circ.charge_lost_on_zero',
236   'Charge lost on zero',
237   '',
238   'bool' ),
239
240 ( 'circ.damaged_item_processing_fee',
241   'Charge processing fee for damaged items',
242   'Charge processing fee for damaged items',
243   'currency' ),
244
245 ( 'circ.void_lost_on_checkin',
246   'Circ: Void lost item billing when returned',
247   'Void lost item billing when returned',
248   'bool' ),
249
250 ( 'circ.max_accept_return_of_lost',
251   'Circ: Void lost max interval',
252   'Items that have been lost this long will not result in voided billings when returned.  E.g. ''6 months''',
253   'interval' ),
254
255 ( 'circ.void_lost_proc_fee_on_checkin',
256   'Circ: Void processing fee on lost item return',
257   'Void processing fee when lost item returned',
258   'bool' ),
259
260 ( 'circ.restore_overdue_on_lost_return',
261   'Circ: Restore overdues on lost item return',
262   'Restore overdue fines on lost item return',
263   'bool' ),
264
265 ( 'circ.lost_immediately_available',
266   'Circ: Lost items usable on checkin',
267   'Lost items are usable on checkin instead of going ''home'' first',
268   'bool' ),
269
270 ( 'circ.holds_fifo',
271   'Holds: FIFO',
272   'Force holds to a more strict First-In, First-Out capture',
273   'bool' ),
274
275 ( 'opac.allow_pending_address',
276   'OPAC: Allow pending addresses',
277   'If enabled, patrons can create and edit existing addresses.  Addresses are kept in a pending state until staff approves the changes',
278   'bool' ),
279
280 ( 'ui.circ.show_billing_tab_on_bills',
281   'Show billing tab first when bills are present',
282   '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',
283   'bool' ),
284
285 ( 'ui.general.idle_timeout',
286     'GUI: Idle timeout',
287     '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).',
288     'integer' ),
289
290 ( 'ui.circ.in_house_use.entry_cap',
291   'GUI: Record In-House Use: Maximum # of uses allowed per entry.',
292   'The # of uses entry in the Record In-House Use interface may not exceed the value of this setting.',
293   'integer' ),
294
295 ( 'ui.circ.in_house_use.entry_warn',
296   'GUI: Record In-House Use: # of uses threshold for Are You Sure? dialog.',
297   'In the Record In-House Use interface, a submission attempt will warn if the # of uses field exceeds the value of this setting.',
298   'integer' ),
299
300 ( 'acq.default_circ_modifier',
301   'Default circulation modifier',
302   null,
303   'string' ),
304
305 ( 'acq.tmp_barcode_prefix',
306   'Temporary barcode prefix',
307   null,
308   'string' ),
309
310 ( 'acq.tmp_callnumber_prefix',
311   'Temporary call number prefix',
312   null,
313   'string' ),
314
315 ( 'ui.circ.patron_summary.horizontal',
316   'Patron circulation summary is horizontal',
317   null,
318   'bool' ),
319
320 ( 'ui.staff.require_initials',
321   oils_i18n_gettext('ui.staff.require_initials', 'GUI: Require staff initials for entry/edit of item/patron/penalty notes/messages.', 'coust', 'label'),
322   oils_i18n_gettext('ui.staff.require_initials', 'Appends staff initials and edit date into note content.', 'coust', 'description'),
323   'bool' ),
324
325 ( 'ui.general.button_bar',
326   'Button bar',
327   null,
328   'bool' ),
329
330 ( 'circ.hold_shelf_status_delay',
331   'Hold Shelf Status Delay',
332   '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.',
333   'interval' ),
334
335 ( 'circ.patron_invalid_address_apply_penalty',
336   'Invalid patron address penalty',
337   'When set, if a patron address is set to invalid, a penalty is applied.',
338   'bool' ),
339
340 ( 'circ.checkout_fills_related_hold',
341   'Checkout Fills Related Hold',
342   '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',
343   'bool'),
344
345 ( 'circ.selfcheck.auto_override_checkout_events',
346   'Selfcheck override events list',
347   'List of checkout/renewal events that the selfcheck interface should automatically override instead instead of alerting and stopping the transaction',
348   'array' ),
349
350 ( 'circ.staff_client.do_not_auto_attempt_print',
351   'Disable Automatic Print Attempt Type List',
352   '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).',
353   'array' ),
354
355 ( 'ui.patron.default_inet_access_level',
356   'Default level of patrons'' internet access',
357   null,
358   'integer' ),
359
360 ( 'circ.max_patron_claim_return_count',
361     'Max Patron Claims Returned Count',
362     'When this count is exceeded, a staff override is required to mark the item as claims returned',
363     'integer' ),
364
365 ( 'circ.obscure_dob',
366     'Obscure the Date of Birth field',
367     '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.',
368     'bool' ),
369
370 ( 'circ.auto_hide_patron_summary',
371     'GUI: Toggle off the patron summary sidebar after first view.',
372     'When true, the patron summary sidebar will collapse after a new patron sub-interface is selected.',
373     'bool' ),
374
375 ( 'credit.processor.default',
376     'Credit card processing: Name default credit processor',
377     'This might be "AuthorizeNet", "PayPal", etc.',
378     'string' ),
379
380 ( 'credit.processor.authorizenet.enabled',
381     'Credit card processing: AuthorizeNet enabled',
382     '',
383     'bool' ),
384
385 ( 'credit.processor.authorizenet.login',
386     'Credit card processing: AuthorizeNet login',
387     '',
388     'string' ),
389
390 ( 'credit.processor.authorizenet.password',
391     'Credit card processing: AuthorizeNet password',
392     '',
393     'string' ),
394
395 ( 'credit.processor.authorizenet.server',
396     'Credit card processing: AuthorizeNet server',
397     'Required if using a developer/test account with AuthorizeNet',
398     'string' ),
399
400 ( 'credit.processor.authorizenet.testmode',
401     'Credit card processing: AuthorizeNet test mode',
402     '',
403     'bool' ),
404
405 ( 'credit.processor.paypal.enabled',
406     'Credit card processing: PayPal enabled',
407     '',
408     'bool' ),
409 ( 'credit.processor.paypal.login',
410     'Credit card processing: PayPal login',
411     '',
412     'string' ),
413 ( 'credit.processor.paypal.password',
414     'Credit card processing: PayPal password',
415     '',
416     'string' ),
417 ( 'credit.processor.paypal.signature',
418     'Credit card processing: PayPal signature',
419     '',
420     'string' ),
421 ( 'credit.processor.paypal.testmode',
422     'Credit card processing: PayPal test mode',
423     '',
424     'bool' ),
425
426 ( 'ui.admin.work_log.max_entries',
427     oils_i18n_gettext('ui.admin.work_log.max_entries', 'GUI: Work Log: Maximum Actions Logged', 'coust', 'label'),
428     oils_i18n_gettext('ui.admin.work_log.max_entries', 'Maximum entries for "Most Recent Staff Actions" section of the Work Log interface.', 'coust', 'description'),
429   'interval' ),
430
431 ( 'ui.admin.patron_log.max_entries',
432     oils_i18n_gettext('ui.admin.patron_log.max_entries', 'GUI: Work Log: Maximum Patrons Logged', 'coust', 'label'),
433     oils_i18n_gettext('ui.admin.patron_log.max_entries', 'Maximum entries for "Most Recently Affected Patrons..." section of the Work Log interface.', 'coust', 'description'),
434   'interval' ),
435
436 ( 'lib.courier_code',
437     oils_i18n_gettext('lib.courier_code', 'Courier Code', 'coust', 'label'),
438     oils_i18n_gettext('lib.courier_code', 'Courier Code for the library.  Available in transit slip templates as the %courier_code% macro.', 'coust', 'description'),
439     'string'),
440
441 ( 'circ.block_renews_for_holds',
442     oils_i18n_gettext('circ.block_renews_for_holds', 'Holds: Block Renewal of Items Needed for Holds', 'coust', 'label'),
443     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'),
444     'bool' ),
445
446 ( 'circ.password_reset_request_per_user_limit',
447     oils_i18n_gettext('circ.password_reset_request_per_user_limit', 'Circulation: Maximum concurrently active self-serve password reset requests per user', 'coust', 'label'),
448     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'),
449     'string'),
450
451 ( 'circ.password_reset_request_time_to_live',
452     oils_i18n_gettext('circ.password_reset_request_time_to_live', 'Circulation: Self-serve password reset request time-to-live', 'coust', 'label'),
453     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'),
454     'string'),
455
456 ( 'circ.password_reset_request_throttle',
457     oils_i18n_gettext('circ.password_reset_request_throttle', 'Circulation: Maximum concurrently active self-serve password reset requests', 'coust', 'label'),
458     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'),
459     'string')
460 ;
461
462 INSERT INTO config.org_unit_setting_type ( name, label, description, datatype ) VALUES (
463         'ui.circ.suppress_checkin_popups',
464         oils_i18n_gettext(
465             'ui.circ.suppress_checkin_popups', 
466             'Circ: Suppress popup-dialogs during check-in.', 
467             'coust', 
468             'label'),
469         oils_i18n_gettext(
470             'ui.circ.suppress_checkin_popups', 
471             'Circ: Suppress popup-dialogs during check-in.', 
472             'coust', 
473             'description'),
474         'bool'
475 );
476
477 INSERT INTO config.org_unit_setting_type ( name, label, description, datatype ) VALUES (
478         'format.date',
479         oils_i18n_gettext(
480             'format.date',
481             'GUI: Format Dates with this pattern.', 
482             'coust', 
483             'label'),
484         oils_i18n_gettext(
485             'format.date',
486             'GUI: Format Dates with this pattern (examples: "yyyy-MM-dd" for "2010-04-26", "MMM d, yyyy" for "Apr 26, 2010")', 
487             'coust', 
488             'description'),
489         'string'
490 ), (
491         'format.time',
492         oils_i18n_gettext(
493             'format.time',
494             'GUI: Format Times with this pattern.', 
495             'coust', 
496             'label'),
497         oils_i18n_gettext(
498             'format.time',
499             '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")', 
500             'coust', 
501             'description'),
502         'string'
503 );
504
505 INSERT INTO config.org_unit_setting_type ( name, label, description, datatype ) VALUES (
506         'cat.bib.delete_on_no_copy_via_acq_lineitem_cancel',
507         oils_i18n_gettext(
508             'cat.bib.delete_on_no_copy_via_acq_lineitem_cancel',
509             'CAT: Delete bib if all copies are deleted via Acquisitions lineitem cancellation.', 
510             'coust', 
511             'label'),
512         oils_i18n_gettext(
513             'cat.bib.delete_on_no_copy_via_acq_lineitem_cancel',
514             'CAT: Delete bib if all copies are deleted via Acquisitions lineitem cancellation.', 
515             'coust', 
516             'description'),
517         'bool'
518 );
519
520 INSERT INTO config.org_unit_setting_type ( name, label, description, datatype ) VALUES (
521         'url.remote_column_settings',
522         oils_i18n_gettext(
523             'url.remote_column_settings',
524             'GUI: URL for remote directory containing list column settings.', 
525             'coust', 
526             'label'),
527         oils_i18n_gettext(
528             'url.remote_column_settings',
529             '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.', 
530             'coust', 
531             'description'),
532         'string'
533 );
534
535 INSERT INTO config.org_unit_setting_type ( name, label, description, datatype ) VALUES (
536         'gui.disable_local_save_columns',
537         oils_i18n_gettext(
538             'gui.disable_local_save_columns',
539             'GUI: Disable the ability to save list column configurations locally.', 
540             'coust', 
541             'label'),
542         oils_i18n_gettext(
543             'gui.disable_local_save_columns',
544             '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.', 
545             'coust', 
546             'description'),
547         'bool'
548 );
549
550 INSERT INTO config.org_unit_setting_type ( name, label, description, datatype ) VALUES (
551         'circ.password_reset_request_requires_matching_email',
552         oils_i18n_gettext(
553             'circ.password_reset_request_requires_matching_email',
554             'Circulation: Require matching email address for password reset requests', 
555             'coust', 
556             'label'),
557         oils_i18n_gettext(
558             'circ.password_reset_request_requires_matching_email',
559             'Circulation: Require matching email address for password reset requests', 
560             'coust', 
561             'description'),
562         'bool'
563 );
564
565 INSERT INTO config.org_unit_setting_type ( name, label, description, datatype ) VALUES (
566         'circ.holds.expired_patron_block',
567         oils_i18n_gettext(
568             'circ.holds.expired_patron_block',
569             'Circulation: Block hold request if hold recipient privileges have expired', 
570             'coust', 
571             'label'),
572         oils_i18n_gettext(
573             'circ.holds.expired_patron_block',
574             'Circulation: Block hold request if hold recipient privileges have expired', 
575             'coust', 
576             'description'),
577         'bool'
578 );
579
580 INSERT INTO config.org_unit_setting_type
581     (name, label, description, datatype) VALUES (
582         'circ.booking_reservation.default_elbow_room',
583         oils_i18n_gettext(
584             'circ.booking_reservation.default_elbow_room',
585             'Booking: Elbow room',
586             'coust',
587             'label'
588         ),
589         oils_i18n_gettext(
590             'circ.booking_reservation.default_elbow_room',
591             '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.',
592             'coust',
593             'label'
594         ),
595         'interval'
596     );
597
598 -- Org_unit_setting_type(s) that need an fm_class:
599 INSERT into config.org_unit_setting_type
600 ( name, label, description, datatype, fm_class ) VALUES
601 ( 'acq.default_copy_location',
602   'Default copy location',
603   null,
604   'link',
605   'acpl' );
606
607 INSERT INTO config.org_unit_setting_type (name, label, description, datatype) VALUES (
608     'circ.holds.org_unit_target_weight',
609     'Holds: Org Unit Target Weight',
610     '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.',
611     'integer'
612 );
613
614 INSERT INTO config.org_unit_setting_type (name, label, description, datatype) VALUES (
615     'circ.holds.target_holds_by_org_unit_weight',
616     'Holds: Use weight-based hold targeting',
617     'Use library weight based hold targeting',
618     'bool'
619 );
620
621 INSERT INTO config.org_unit_setting_type (name, label, description, datatype) VALUES (
622     'circ.holds.max_org_unit_target_loops',
623     'Holds: Maximum library target attempts',
624     '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',
625     'integer'
626 );
627
628
629 -- Org setting for overriding the circ lib of a precat copy
630 INSERT INTO config.org_unit_setting_type (name, label, description, datatype) VALUES (
631     'circ.pre_cat_copy_circ_lib',
632     'Pre-cat Item Circ Lib',
633     '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',
634     'string'
635 );
636
637 -- Circ auto-renew interval setting
638 INSERT INTO config.org_unit_setting_type (name, label, description, datatype) VALUES (
639     'circ.checkout_auto_renew_age',
640     'Checkout auto renew age',
641     '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',
642     'interval'
643 );
644
645 -- Setting for behind the desk hold pickups
646 INSERT INTO config.org_unit_setting_type (name, label, description, datatype) VALUES (
647     'circ.holds.behind_desk_pickup_supported',
648     'Holds: Behind Desk Pickup Supported',
649     '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',
650     'bool'
651 );
652
653 INSERT INTO config.org_unit_setting_type ( name, label, description, datatype ) VALUES (
654         'acq.holds.allow_holds_from_purchase_request',
655         oils_i18n_gettext(
656             'acq.holds.allow_holds_from_purchase_request', 
657             'Allows patrons to create automatic holds from purchase requests.', 
658             'coust', 
659             'label'),
660         oils_i18n_gettext(
661             'acq.holds.allow_holds_from_purchase_request', 
662             'Allows patrons to create automatic holds from purchase requests.', 
663             'coust', 
664             'description'),
665         'bool'
666 );
667
668 INSERT INTO config.org_unit_setting_type (name, label, description, datatype) VALUES (
669     'circ.holds.target_skip_me',
670     'Skip For Hold Targeting',
671     'When true, don''t target any copies at this org unit for holds',
672     'bool'
673 );
674
675 -- claims returned mark item missing 
676 INSERT INTO
677     config.org_unit_setting_type ( name, label, description, datatype )
678     VALUES (
679         'circ.claim_return.mark_missing',
680         'Claim Return: Mark copy as missing', 
681         'When a circ is marked as claims-returned, also mark the copy as missing',
682         'bool'
683     );
684
685 -- claims never checked out mark item missing 
686 INSERT INTO
687     config.org_unit_setting_type ( name, label, description, datatype )
688     VALUES (
689         'circ.claim_never_checked_out.mark_missing',
690         'Claim Never Checked Out: Mark copy as missing', 
691         'When a circ is marked as claims-never-checked-out, mark the copy as missing',
692         'bool'
693     );
694
695 -- mark damaged void overdue setting
696 INSERT INTO
697     config.org_unit_setting_type ( name, label, description, datatype )
698     VALUES (
699         'circ.damaged.void_ovedue',
700         'Mark item damaged voids overdues',
701         'When an item is marked damaged, overdue fines on the most recent circulation are voided.',
702         'bool'
703     );
704
705 -- hold cancel display limits
706 INSERT INTO config.org_unit_setting_type ( name, label, description, datatype )
707     VALUES (
708         'circ.holds.canceled.display_count',
709         'Holds: Canceled holds display count',
710         'How many canceled holds to show in patron holds interfaces',
711         'integer'
712     );
713
714 INSERT INTO config.org_unit_setting_type ( name, label, description, datatype )
715     VALUES (
716         'circ.holds.canceled.display_age',
717         'Holds: Canceled holds display age',
718         'Show all canceled holds that were canceled within this amount of time',
719         'interval'
720     );
721
722 INSERT INTO config.org_unit_setting_type ( name, label, description, datatype )
723     VALUES (
724         'circ.holds.uncancel.reset_request_time',
725         'Holds: Reset request time on un-cancel',
726         'When a holds is uncanceled, reset the request time to push it to the end of the queue',
727         'bool'
728     );
729
730 INSERT INTO config.org_unit_setting_type (name, label, description, datatype)
731     VALUES (
732         'circ.holds.default_shelf_expire_interval',
733         'Default hold shelf expire interval',
734         '',
735         'interval'
736 );
737
738 INSERT INTO config.org_unit_setting_type (name, label, description, datatype, fm_class)
739     VALUES (
740         'circ.claim_return.copy_status', 
741         'Claim Return Copy Status', 
742         'Claims returned copies are put into this status.  Default is to leave the copy in the Checked Out status',
743         'link', 
744         'ccs' 
745     );
746
747 INSERT INTO config.org_unit_setting_type ( name, label, description, datatype ) 
748     VALUES ( 
749         'circ.max_fine.cap_at_price',
750         oils_i18n_gettext('circ.max_fine.cap_at_price', 'Circ: Cap Max Fine at Item Price', 'coust', 'label'),
751         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'),
752         'bool' 
753     );
754
755 INSERT INTO config.org_unit_setting_type ( name, label, description, datatype, fm_class ) 
756     VALUES ( 
757         'circ.holds.clear_shelf.copy_status',
758         oils_i18n_gettext('circ.holds.clear_shelf.copy_status', 'Holds: Clear shelf copy status', 'coust', 'label'),
759         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'),
760         'link',
761         'ccs'
762     );
763
764 INSERT INTO config.org_unit_setting_type ( name, label, description, datatype )
765     VALUES ( 
766         'circ.selfcheck.workstation_required',
767         oils_i18n_gettext('circ.selfcheck.workstation_required', 'Selfcheck: Workstation Required', 'coust', 'label'),
768         oils_i18n_gettext('circ.selfcheck.workstation_required', 'All selfcheck stations must use a workstation', 'coust', 'description'),
769         'bool'
770     ), (
771         'circ.selfcheck.patron_password_required',
772         oils_i18n_gettext('circ.selfcheck.patron_password_required', 'Selfcheck: Require Patron Password', 'coust', 'label'),
773         oils_i18n_gettext('circ.selfcheck.patron_password_required', 'Patron must log in with barcode and password at selfcheck station', 'coust', 'description'),
774         'bool'
775     );
776
777 INSERT INTO config.org_unit_setting_type ( name, label, description, datatype )
778     VALUES ( 
779         'circ.selfcheck.alert.sound',
780         oils_i18n_gettext('circ.selfcheck.alert.sound', 'Selfcheck: Audio Alerts', 'coust', 'label'),
781         oils_i18n_gettext('circ.selfcheck.alert.sound', 'Use audio alerts for selfcheck events', 'coust', 'description'),
782         'bool'
783     );
784
785 INSERT INTO
786     config.org_unit_setting_type (name, label, description, datatype)
787     VALUES (
788         'notice.telephony.callfile_lines',
789         'Telephony: Arbitrary line(s) to include in each notice callfile',
790         $$
791         This overrides lines from opensrf.xml.
792         Line(s) must be valid for your target server and platform
793         (e.g. Asterisk 1.4).
794         $$,
795         'string'
796     );
797
798 INSERT INTO config.org_unit_setting_type ( name, label, description, datatype )
799     VALUES ( 
800         'circ.offline.username_allowed',
801         oils_i18n_gettext('circ.offline.username_allowed', 'Offline: Patron Usernames Allowed', 'coust', 'label'),
802         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'),
803         'bool'
804     );
805
806 INSERT INTO config.org_unit_setting_type ( name, label, description, datatype )
807 VALUES (
808     'acq.fund.balance_limit.warn',
809     oils_i18n_gettext('acq.fund.balance_limit.warn', 'Fund Spending Limit for Warning', 'coust', 'label'),
810     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'),
811     'integer'
812 );
813
814 INSERT INTO config.org_unit_setting_type ( name, label, description, datatype )
815 VALUES (
816     'acq.fund.balance_limit.block',
817     oils_i18n_gettext('acq.fund.balance_limit.block', 'Fund Spending Limit for Block', 'coust', 'label'),
818     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'),
819     'integer'
820 );
821
822 INSERT INTO config.org_unit_setting_type ( name, label, description, datatype )
823     VALUES (
824         'circ.holds.hold_has_copy_at.alert',
825         oils_i18n_gettext('circ.holds.hold_has_copy_at.alert', 'Holds: Has Local Copy Alert', 'coust', 'label'),
826         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'),
827         'bool'
828     ),(
829         'circ.holds.hold_has_copy_at.block',
830         oils_i18n_gettext('circ.holds.hold_has_copy_at.block', 'Holds: Has Local Copy Block', 'coust', 'label'),
831         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'),
832         'bool'
833     );
834
835 INSERT INTO config.org_unit_setting_type ( name, label, description, datatype )
836 VALUES (
837     'auth.persistent_login_interval',
838     oils_i18n_gettext('auth.persistent_login_interval', 'Persistent Login Duration', 'coust', 'label'),
839     oils_i18n_gettext('auth.persistent_login_interval', 'How long a persistent login lasts.  E.g. ''2 weeks''', 'coust', 'description'),
840     'interval'
841 );
842
843 INSERT INTO config.org_unit_setting_type ( name, label, description, datatype ) VALUES (
844         'cat.marc_control_number_identifier',
845         oils_i18n_gettext(
846             'cat.marc_control_number_identifier', 
847             'Cat: Defines the control number identifier used in 003 and 035 fields.', 
848             'coust', 
849             'label'),
850         oils_i18n_gettext(
851             'cat.marc_control_number_identifier', 
852             'Cat: Defines the control number identifier used in 003 and 035 fields.', 
853             'coust', 
854             'description'),
855         'string'
856 );
857
858 INSERT INTO config.org_unit_setting_type (name, label, description, datatype) 
859     VALUES (
860         'circ.selfcheck.block_checkout_on_copy_status',
861         oils_i18n_gettext(
862             'circ.selfcheck.block_checkout_on_copy_status',
863             'Selfcheck: Block copy checkout status',
864             'coust',
865             'label'
866         ),
867         oils_i18n_gettext(
868             'circ.selfcheck.block_checkout_on_copy_status',
869             'List of copy status IDs that will block checkout even if the generic COPY_NOT_AVAILABLE event is overridden',
870             'coust',
871             'description'
872         ),
873         'array'
874     );
875
876 INSERT INTO config.org_unit_setting_type ( name, label, description, datatype, fm_class )
877 VALUES (
878     'serial.prev_issuance_copy_location',
879     oils_i18n_gettext(
880         'serial.prev_issuance_copy_location',
881         'Serials: Previous Issuance Copy Location',
882         'coust',
883         'label'
884     ),
885     oils_i18n_gettext(
886         'serial.prev_issuance_copy_location',
887         'When a serial issuance is received, copies (units) of the previous issuance will be automatically moved into the configured shelving location',
888         'coust',
889         'descripton'
890         ),
891     'link',
892     'acpl'
893 );
894
895 INSERT INTO config.org_unit_setting_type ( name, label, description, datatype, fm_class )
896 VALUES (
897     'cat.default_classification_scheme',
898     oils_i18n_gettext(
899         'cat.default_classification_scheme',
900         'Cataloging: Default Classification Scheme',
901         'coust',
902         'label'
903     ),
904     oils_i18n_gettext(
905         'cat.default_classification_scheme',
906         'Defines the default classification scheme for new call numbers: 1 = Generic; 2 = Dewey; 3 = LC',
907         'coust',
908         'descripton'
909         ),
910     'link',
911     'acnc'
912 );
913
914 INSERT INTO config.org_unit_setting_type ( name, label, description, datatype ) VALUES (
915         'opac.org_unit_hiding.depth',
916         oils_i18n_gettext(
917             'opac.org_unit_hiding.depth',
918             'OPAC: Org Unit Hiding Depth', 
919             'coust', 
920             'label'),
921         oils_i18n_gettext(
922             'opac.org_unit_hiding.depth',
923             '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.', 
924             'coust', 
925             'description'),
926         'integer'
927 );
928
929 INSERT INTO config.org_unit_setting_type (name, label, description, datatype)
930     VALUES 
931         ('circ.holds.alert_if_local_avail',
932          'Holds: Local available alert',
933          'If local copy is available, alert the person making the hold',
934          'bool'),
935
936         ('circ.holds.deny_if_local_avail',
937          'Holds: Local available block',
938          'If local copy is available, deny the creation of the hold',
939          'bool')
940     ;
941
942 INSERT INTO config.org_unit_setting_type ( name, label, description, datatype )
943     VALUES ( 
944         'circ.holds.clear_shelf.no_capture_holds',
945         oils_i18n_gettext('circ.holds.clear_shelf.no_capture_holds', 'Holds: Bypass hold capture during clear shelf process', 'coust', 'label'),
946         oils_i18n_gettext('circ.holds.clear_shelf.no_capture_holds', 'During the clear shelf process, avoid capturing new holds on cleared items.', 'coust', 'description'),
947         'bool'
948     );
949
950 INSERT INTO config.org_unit_setting_type (name, label, description, datatype) VALUES (
951     'circ.booking_reservation.stop_circ',
952     'Disallow circulation of items when they are on booking reserve and that reserve overlaps with the checkout period',
953     '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.',
954     'bool'
955 );
956
957 ---------------------------------
958 -- Seed data for usr_setting_type
959 ----------------------------------
960
961 INSERT INTO config.usr_setting_type (name,opac_visible,label,description,datatype)
962     VALUES ('opac.default_font', TRUE, 'OPAC Font Size', 'OPAC Font Size', 'string');
963
964 INSERT INTO config.usr_setting_type (name,opac_visible,label,description,datatype)
965     VALUES ('opac.default_search_depth', TRUE, 'OPAC Search Depth', 'OPAC Search Depth', 'integer');
966
967 INSERT INTO config.usr_setting_type (name,opac_visible,label,description,datatype)
968     VALUES ('opac.default_search_location', TRUE, 'OPAC Search Location', 'OPAC Search Location', 'integer');
969
970 INSERT INTO config.usr_setting_type (name,opac_visible,label,description,datatype)
971     VALUES ('opac.hits_per_page', TRUE, 'Hits per Page', 'Hits per Page', 'string');
972
973 INSERT INTO config.usr_setting_type (name,opac_visible,label,description,datatype)
974     VALUES ('opac.hold_notify', TRUE, 'Hold Notification Format', 'Hold Notification Format', 'string');
975
976 INSERT INTO config.usr_setting_type (name,opac_visible,label,description,datatype)
977     VALUES ('staff_client.catalog.record_view.default', TRUE, 'Default Record View', 'Default Record View', 'string');
978
979 INSERT INTO config.usr_setting_type (name,opac_visible,label,description,datatype)
980     VALUES ('staff_client.copy_editor.templates', TRUE, 'Copy Editor Template', 'Copy Editor Template', 'object');
981
982 INSERT INTO config.usr_setting_type (name,opac_visible,label,description,datatype)
983     VALUES ('circ.holds_behind_desk', FALSE, 'Hold is behind Circ Desk', 'Hold is behind Circ Desk', 'bool');
984
985 INSERT INTO config.usr_setting_type (name,opac_visible,label,description,datatype)
986     VALUES (
987         'history.circ.retention_age',
988         TRUE,
989         oils_i18n_gettext('history.circ.retention_age','Historical Circulation Retention Age','cust','label'),
990         oils_i18n_gettext('history.circ.retention_age','Historical Circulation Retention Age','cust','description'),
991         'interval'
992     ),(
993         'history.circ.retention_start',
994         FALSE,
995         oils_i18n_gettext('history.circ.retention_start','Historical Circulation Retention Start Date','cust','label'),
996         oils_i18n_gettext('history.circ.retention_start','Historical Circulation Retention Start Date','cust','description'),
997         'date'
998     );
999
1000 INSERT INTO config.usr_setting_type (name,opac_visible,label,description,datatype)
1001     VALUES (
1002         'history.hold.retention_age',
1003         TRUE,
1004         oils_i18n_gettext('history.hold.retention_age','Historical Hold Retention Age','cust','label'),
1005         oils_i18n_gettext('history.hold.retention_age','Historical Hold Retention Age','cust','description'),
1006         'interval'
1007     ),(
1008         'history.hold.retention_start',
1009         TRUE,
1010         oils_i18n_gettext('history.hold.retention_start','Historical Hold Retention Start Date','cust','label'),
1011         oils_i18n_gettext('history.hold.retention_start','Historical Hold Retention Start Date','cust','description'),
1012         'interval'
1013     ),(
1014         'history.hold.retention_count',
1015         TRUE,
1016         oils_i18n_gettext('history.hold.retention_count','Historical Hold Retention Count','cust','label'),
1017         oils_i18n_gettext('history.hold.retention_count','Historical Hold Retention Count','cust','description'),
1018         'integer'
1019     );
1020
1021 INSERT INTO config.usr_setting_type (name, opac_visible, label, description, datatype)
1022     VALUES (
1023         'opac.default_sort',
1024         TRUE,
1025         oils_i18n_gettext(
1026             'opac.default_sort',
1027             'OPAC Default Search Sort',
1028             'cust',
1029             'label'
1030         ),
1031         oils_i18n_gettext(
1032             'opac.default_sort',
1033             'OPAC Default Search Sort',
1034             'cust',
1035             'description'
1036         ),
1037         'string'
1038     );
1039
1040 INSERT INTO config.org_unit_setting_type ( name, label, description, datatype, fm_class ) VALUES (
1041         'circ.missing_pieces.copy_status',
1042         oils_i18n_gettext(
1043             'circ.missing_pieces.copy_status',
1044             'Circulation: Item Status for Missing Pieces',
1045             'coust',
1046             'label'),
1047         oils_i18n_gettext(
1048             'circ.missing_pieces.copy_status',
1049             'This is the Item Status to use for items that have been marked or scanned as having Missing Pieces.  In absense of this setting, the Damaged status is used.',
1050             'coust',
1051             'description'),
1052         'link',
1053         'ccs'
1054 );
1055
1056 INSERT INTO config.org_unit_setting_type ( name, label, description, datatype ) VALUES (
1057         'circ.do_not_tally_claims_returned',
1058         oils_i18n_gettext(
1059             'circ.do_not_tally_claims_returned',
1060             'Circulation: Do not include outstanding Claims Returned circulations in lump sum tallies in Patron Display.',
1061             'coust',
1062             'label'),
1063         oils_i18n_gettext(
1064             'circ.do_not_tally_claims_returned',
1065             '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.',
1066             'coust',
1067             'description'),
1068         'bool'
1069 );
1070
1071 INSERT INTO config.org_unit_setting_type (name, label, description, datatype)
1072     VALUES
1073         ('cat.label.font.size',
1074             oils_i18n_gettext('cat.label.font.size',
1075                 'Cataloging: Spine and pocket label font size', 'coust', 'label'),
1076             oils_i18n_gettext('cat.label.font.size',
1077                 'Set the default font size for spine and pocket labels', 'coust', 'description'),
1078             'integer'
1079         )
1080         ,('cat.label.font.family',
1081             oils_i18n_gettext('cat.label.font.family',
1082                 'Cataloging: Spine and pocket label font family', 'coust', 'label'),
1083             oils_i18n_gettext('cat.label.font.family',
1084                 '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".',
1085                 'coust', 'description'),
1086             'string'
1087         )
1088         ,('cat.spine.line.width',
1089             oils_i18n_gettext('cat.spine.line.width',
1090                 'Cataloging: Spine label line width', 'coust', 'label'),
1091             oils_i18n_gettext('cat.spine.line.width',
1092                 'Set the default line width for spine labels in number of characters. This specifies the boundary at which lines must be wrapped.',
1093                 'coust', 'description'),
1094             'integer'
1095         )
1096         ,('cat.spine.line.height',
1097             oils_i18n_gettext('cat.spine.line.height',
1098                 'Cataloging: Spine label maximum lines', 'coust', 'label'),
1099             oils_i18n_gettext('cat.spine.line.height',
1100                 'Set the default maximum number of lines for spine labels.',
1101                 'coust', 'description'),
1102             'integer'
1103         )
1104         ,('cat.spine.line.margin',
1105             oils_i18n_gettext('cat.spine.line.margin',
1106                 'Cataloging: Spine label left margin', 'coust', 'label'),
1107             oils_i18n_gettext('cat.spine.line.margin',
1108                 'Set the left margin for spine labels in number of characters.',
1109                 'coust', 'description'),
1110             'integer'
1111         )
1112 ;
1113
1114 INSERT INTO config.org_unit_setting_type (name, label, description, datatype)
1115     VALUES
1116         ('cat.label.font.weight',
1117             oils_i18n_gettext('cat.label.font.weight',
1118                 'Cataloging: Spine and pocket label font weight', 'coust', 'label'),
1119             oils_i18n_gettext('cat.label.font.weight',
1120                 'Set the preferred font weight for spine and pocket labels. You can specify "normal", "bold", "bolder", or "lighter".',
1121                 'coust', 'description'),
1122             'string'
1123         )
1124 ;
1125
1126 INSERT INTO config.org_unit_setting_type ( name, label, description, datatype ) VALUES (
1127         'circ.patron_edit.clone.copy_address',
1128         oils_i18n_gettext(
1129             'circ.patron_edit.clone.copy_address',
1130             'Patron Registration: Cloned patrons get address copy',
1131             'coust',
1132             'label'
1133         ),
1134         oils_i18n_gettext(
1135             'circ.patron_edit.clone.copy_address',
1136             'In the Patron editor, copy addresses from the cloned user instead of linking directly to the address',
1137             'coust',
1138             'description'
1139         ),
1140         'bool'
1141 );
1142
1143 -- Patch the name of an old selfcheck setting
1144 UPDATE actor.org_unit_setting
1145     SET name = 'circ.selfcheck.alert.popup'
1146     WHERE name = 'circ.selfcheck.alert_on_checkout_event';
1147
1148 -- Rename certain existing org_unit settings, if present,
1149 -- and make sure their values are JSON
1150 UPDATE actor.org_unit_setting SET
1151     name = 'circ.holds.default_estimated_wait_interval',
1152     --
1153     -- The value column should be JSON.  The old value should be a number,
1154     -- but it may or may not be quoted.  The following CASE behaves
1155     -- differently depending on whether value is quoted.  It is simplistic,
1156     -- and will be defeated by leading or trailing white space, or various
1157     -- malformations.
1158     --
1159     value = CASE WHEN SUBSTR( value, 1, 1 ) = '"'
1160                 THEN '"' || SUBSTR( value, 2, LENGTH(value) - 2 ) || ' days"'
1161                 ELSE '"' || value || ' days"'
1162             END
1163 WHERE name = 'circ.hold_estimate_wait_interval';
1164
1165 -- Create types for existing org unit settings
1166 -- not otherwise accounted for
1167
1168 INSERT INTO config.org_unit_setting_type(
1169  name,
1170  label,
1171  description
1172 )
1173 SELECT DISTINCT
1174         name,
1175         name,
1176         'FIXME'
1177 FROM
1178         actor.org_unit_setting
1179 WHERE
1180         name NOT IN (
1181                 SELECT name
1182                 FROM config.org_unit_setting_type
1183         );
1184
1185 -- Add foreign key to org_unit_setting
1186
1187 ALTER TABLE actor.org_unit_setting
1188         ADD FOREIGN KEY (name) REFERENCES config.org_unit_setting_type (name)
1189                 DEFERRABLE INITIALLY DEFERRED;
1190
1191 -- Create types for existing user settings
1192 -- not otherwise accounted for
1193
1194 INSERT INTO config.usr_setting_type (
1195         name,
1196         label,
1197         description
1198 )
1199 SELECT DISTINCT
1200         name,
1201         name,
1202         'FIXME'
1203 FROM
1204         actor.usr_setting
1205 WHERE
1206         name NOT IN (
1207                 SELECT name
1208                 FROM config.usr_setting_type
1209         );
1210
1211 -- Add foreign key to user_setting_type
1212
1213 ALTER TABLE actor.usr_setting
1214         ADD FOREIGN KEY (name) REFERENCES config.usr_setting_type (name)
1215                 ON DELETE CASCADE ON UPDATE CASCADE
1216                 DEFERRABLE INITIALLY DEFERRED;
1217
1218 INSERT INTO actor.org_unit_setting (org_unit, name, value) VALUES
1219     (1, 'cat.spine.line.margin', 0)
1220     ,(1, 'cat.spine.line.height', 9)
1221     ,(1, 'cat.spine.line.width', 8)
1222     ,(1, 'cat.label.font.family', '"monospace"')
1223     ,(1, 'cat.label.font.size', 10)
1224     ,(1, 'cat.label.font.weight', '"normal"')
1225 ;
1226
1227 ALTER TABLE action_trigger.event_definition ADD COLUMN granularity TEXT;
1228 ALTER TABLE action_trigger.event ADD COLUMN async_output BIGINT REFERENCES action_trigger.event_output (id);
1229 ALTER TABLE action_trigger.event_definition ADD COLUMN usr_field TEXT;
1230 ALTER TABLE action_trigger.event_definition ADD COLUMN opt_in_setting TEXT REFERENCES config.usr_setting_type (name) DEFERRABLE INITIALLY DEFERRED;
1231
1232 CREATE OR REPLACE FUNCTION is_json( TEXT ) RETURNS BOOL AS $f$
1233     use JSON::XS;
1234     my $json = shift();
1235     eval { JSON::XS->new->allow_nonref->decode( $json ) };
1236     return $@ ? 0 : 1;
1237 $f$ LANGUAGE PLPERLU;
1238
1239 ALTER TABLE action_trigger.event ADD COLUMN user_data TEXT CHECK (user_data IS NULL OR is_json( user_data ));
1240
1241 INSERT INTO action_trigger.hook (key,core_type,description) VALUES (
1242     'hold_request.cancel.expire_no_target',
1243     'ahr',
1244     'A hold is cancelled because no copies were found'
1245 );
1246
1247 INSERT INTO action_trigger.hook (key,core_type,description) VALUES (
1248     'hold_request.cancel.expire_holds_shelf',
1249     'ahr',
1250     'A hold is cancelled becuase it was on the holds shelf too long'
1251 );
1252
1253 INSERT INTO action_trigger.hook (key,core_type,description) VALUES (
1254     'hold_request.cancel.staff',
1255     'ahr',
1256     'A hold is cancelled becuase it was cancelled by staff'
1257 );
1258
1259 INSERT INTO action_trigger.hook (key,core_type,description) VALUES (
1260     'hold_request.cancel.patron',
1261     'ahr',
1262     'A hold is cancelled by the patron'
1263 );
1264
1265 -- Fix typos in descriptions
1266 UPDATE action_trigger.hook SET description = 'A hold is successfully placed' WHERE key = 'hold_request.success';
1267 UPDATE action_trigger.hook SET description = 'A hold is attempted but not successfully placed' WHERE key = 'hold_request.failure';
1268
1269 -- Add a hook for renewals
1270 INSERT INTO action_trigger.hook (key,core_type,description) VALUES ('renewal','circ','Item renewed to user');
1271
1272 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');
1273
1274 -- Sample Pre-due Notice --
1275
1276 INSERT INTO action_trigger.event_definition (id, active, owner, name, hook, validator, reactor, delay, delay_field, group_field, template) 
1277     VALUES (6, 'f', 1, '3 Day Courtesy Notice', 'checkout.due', 'CircIsOpen', 'SendEmail', '-3 days', 'due_date', 'usr', 
1278 $$
1279 [%- USE date -%]
1280 [%- user = target.0.usr -%]
1281 To: [%- params.recipient_email || user.email %]
1282 From: [%- params.sender_email || default_sender %]
1283 Subject: Courtesy Notice
1284
1285 Dear [% user.family_name %], [% user.first_given_name %]
1286 As a reminder, the following items are due in 3 days.
1287
1288 [% FOR circ IN target %]
1289     Title: [% circ.target_copy.call_number.record.simple_record.title %] 
1290     Barcode: [% circ.target_copy.barcode %] 
1291     Due: [% date.format(helpers.format_date(circ.due_date), '%Y-%m-%d') %]
1292     Item Cost: [% helpers.get_copy_price(circ.target_copy) %]
1293     Library: [% circ.circ_lib.name %]
1294     Library Phone: [% circ.circ_lib.phone %]
1295 [% END %]
1296
1297 $$);
1298
1299 INSERT INTO action_trigger.environment (event_def, path) VALUES 
1300     (6, 'target_copy.call_number.record.simple_record'),
1301     (6, 'usr'),
1302     (6, 'circ_lib.billing_address');
1303
1304 INSERT INTO action_trigger.event_params (event_def, param, value) VALUES
1305     (6, 'max_delay_age', '"1 day"');
1306
1307 -- also add the max delay age to the default overdue notice event def
1308 INSERT INTO action_trigger.event_params (event_def, param, value) VALUES
1309     (1, 'max_delay_age', '"1 day"');
1310   
1311 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');
1312
1313 INSERT INTO action_trigger.reactor (module,description) VALUES ('ApplyPatronPenalty','Applies the conifigured 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.');
1314
1315 INSERT INTO action_trigger.hook (
1316         key,
1317         core_type,
1318         description,
1319         passive
1320     ) VALUES (
1321         'hold_request.shelf_expires_soon',
1322         'ahr',
1323         'A hold on the shelf will expire there soon.',
1324         TRUE
1325     );
1326
1327 INSERT INTO action_trigger.event_definition (
1328         id,
1329         active,
1330         owner,
1331         name,
1332         hook,
1333         validator,
1334         reactor,
1335         delay,
1336         delay_field,
1337         group_field,
1338         template
1339     ) VALUES (
1340         7,
1341         FALSE,
1342         1,
1343         'Hold Expires from Shelf Soon',
1344         'hold_request.shelf_expires_soon',
1345         'HoldIsAvailable',
1346         'SendEmail',
1347         '- 1 DAY',
1348         'shelf_expire_time',
1349         'usr',
1350 $$
1351 [%- USE date -%]
1352 [%- user = target.0.usr -%]
1353 To: [%- params.recipient_email || user.email %]
1354 From: [%- params.sender_email || default_sender %]
1355 Subject: Hold Available Notification
1356
1357 Dear [% user.family_name %], [% user.first_given_name %]
1358 You requested holds on the following item(s), which are available for
1359 pickup, but these holds will soon expire.
1360
1361 [% FOR hold IN target %]
1362     [%- data = helpers.get_copy_bib_basics(hold.current_copy.id) -%]
1363     Title: [% data.title %]
1364     Author: [% data.author %]
1365     Library: [% hold.pickup_lib.name %]
1366 [% END %]
1367 $$
1368     );
1369
1370 INSERT INTO action_trigger.environment (
1371         event_def,
1372         path
1373     ) VALUES
1374     ( 7, 'current_copy'),
1375     ( 7, 'pickup_lib.billing_address'),
1376     ( 7, 'usr');
1377
1378 INSERT INTO action_trigger.hook (
1379         key,
1380         core_type,
1381         description,
1382         passive
1383     ) VALUES (
1384         'hold_request.long_wait',
1385         'ahr',
1386         'A patron has been waiting on a hold to be fulfilled for a long time.',
1387         TRUE
1388     );
1389
1390 INSERT INTO action_trigger.event_definition (
1391         id,
1392         active,
1393         owner,
1394         name,
1395         hook,
1396         validator,
1397         reactor,
1398         delay,
1399         delay_field,
1400         group_field,
1401         template
1402     ) VALUES (
1403         9,
1404         FALSE,
1405         1,
1406         'Hold waiting for pickup for long time',
1407         'hold_request.long_wait',
1408         'NOOP_True',
1409         'SendEmail',
1410         '6 MONTHS',
1411         'request_time',
1412         'usr',
1413 $$
1414 [%- USE date -%]
1415 [%- user = target.0.usr -%]
1416 To: [%- params.recipient_email || user.email %]
1417 From: [%- params.sender_email || default_sender %]
1418 Subject: Long Wait Hold Notification
1419
1420 Dear [% user.family_name %], [% user.first_given_name %]
1421
1422 You requested hold(s) on the following item(s), but unfortunately
1423 we have not been able to fulfill your request after a considerable
1424 length of time.  If you would still like to recieve these items,
1425 no action is required.
1426
1427 [% FOR hold IN target %]
1428     Title: [% hold.bib_rec.bib_record.simple_record.title %]
1429     Author: [% hold.bib_rec.bib_record.simple_record.author %]
1430 [% END %]
1431 $$
1432 );
1433
1434 INSERT INTO action_trigger.environment (
1435         event_def,
1436         path
1437     ) VALUES
1438     (9, 'pickup_lib'),
1439     (9, 'usr'),
1440     (9, 'bib_rec.bib_record.simple_record');
1441
1442 INSERT INTO action_trigger.hook (key, core_type, description, passive) 
1443     VALUES (
1444         'format.selfcheck.checkout',
1445         'circ',
1446         'Formats circ objects for self-checkout receipt',
1447         TRUE
1448     );
1449
1450 INSERT INTO action_trigger.event_definition (id, active, owner, name, hook, validator, reactor, group_field, granularity, template )
1451     VALUES (
1452         10,
1453         TRUE,
1454         1,
1455         'Self-Checkout Receipt',
1456         'format.selfcheck.checkout',
1457         'NOOP_True',
1458         'ProcessTemplate',
1459         'usr',
1460         'print-on-demand',
1461 $$
1462 [%- USE date -%]
1463 [%- SET user = target.0.usr -%]
1464 [%- SET lib = target.0.circ_lib -%]
1465 [%- SET lib_addr = target.0.circ_lib.billing_address -%]
1466 [%- SET hours = lib.hours_of_operation -%]
1467 <div>
1468     <style> li { padding: 8px; margin 5px; }</style>
1469     <div>[% date.format %]</div>
1470     <div>[% lib.name %]</div>
1471     <div>[% lib_addr.street1 %] [% lib_addr.street2 %]</div>
1472     <div>[% lib_addr.city %], [% lib_addr.state %] [% lb_addr.post_code %]</div>
1473     <div>[% lib.phone %]</div>
1474     <br/>
1475
1476     [% user.family_name %], [% user.first_given_name %]
1477     <ol>
1478     [% FOR circ IN target %]
1479         [%-
1480             SET idx = loop.count - 1;
1481             SET udata =  user_data.$idx
1482         -%]
1483         <li>
1484             <div>[% helpers.get_copy_bib_basics(circ.target_copy.id).title %]</div>
1485             <div>Barcode: [% circ.target_copy.barcode %]</div>
1486             [% IF udata.renewal_failure %]
1487                 <div style='color:red;'>Renewal Failed</div>
1488             [% ELSE %]
1489                 <div>Due Date: [% date.format(helpers.format_date(circ.due_date), '%Y-%m-%d') %]</div>
1490             [% END %]
1491         </li>
1492     [% END %]
1493     </ol>
1494     
1495     <div>
1496         Library Hours
1497         [%- BLOCK format_time; date.format(time _ ' 1/1/1000', format='%I:%M %p'); END -%]
1498         <div>
1499             Monday 
1500             [% PROCESS format_time time = hours.dow_0_open %] 
1501             [% PROCESS format_time time = hours.dow_0_close %] 
1502         </div>
1503         <div>
1504             Tuesday 
1505             [% PROCESS format_time time = hours.dow_1_open %] 
1506             [% PROCESS format_time time = hours.dow_1_close %] 
1507         </div>
1508         <div>
1509             Wednesday 
1510             [% PROCESS format_time time = hours.dow_2_open %] 
1511             [% PROCESS format_time time = hours.dow_2_close %] 
1512         </div>
1513         <div>
1514             Thursday
1515             [% PROCESS format_time time = hours.dow_3_open %] 
1516             [% PROCESS format_time time = hours.dow_3_close %] 
1517         </div>
1518         <div>
1519             Friday
1520             [% PROCESS format_time time = hours.dow_4_open %] 
1521             [% PROCESS format_time time = hours.dow_4_close %] 
1522         </div>
1523         <div>
1524             Saturday
1525             [% PROCESS format_time time = hours.dow_5_open %] 
1526             [% PROCESS format_time time = hours.dow_5_close %] 
1527         </div>
1528         <div>
1529             Sunday 
1530             [% PROCESS format_time time = hours.dow_6_open %] 
1531             [% PROCESS format_time time = hours.dow_6_close %] 
1532         </div>
1533     </div>
1534 </div>
1535 $$
1536 );
1537
1538 INSERT INTO action_trigger.environment ( event_def, path) VALUES
1539     ( 10, 'target_copy'),
1540     ( 10, 'circ_lib.billing_address'),
1541     ( 10, 'circ_lib.hours_of_operation'),
1542     ( 10, 'usr');
1543
1544 INSERT INTO action_trigger.hook (key, core_type, description, passive) 
1545     VALUES (
1546         'format.selfcheck.items_out',
1547         'circ',
1548         'Formats items out for self-checkout receipt',
1549         TRUE
1550     );
1551
1552 INSERT INTO action_trigger.event_definition (id, active, owner, name, hook, validator, reactor, group_field, granularity, template )
1553     VALUES (
1554         11,
1555         TRUE,
1556         1,
1557         'Self-Checkout Items Out Receipt',
1558         'format.selfcheck.items_out',
1559         'NOOP_True',
1560         'ProcessTemplate',
1561         'usr',
1562         'print-on-demand',
1563 $$
1564 [%- USE date -%]
1565 [%- SET user = target.0.usr -%]
1566 <div>
1567     <style> li { padding: 8px; margin 5px; }</style>
1568     <div>[% date.format %]</div>
1569     <br/>
1570
1571     [% user.family_name %], [% user.first_given_name %]
1572     <ol>
1573     [% FOR circ IN target %]
1574         <li>
1575             <div>[% helpers.get_copy_bib_basics(circ.target_copy.id).title %]</div>
1576             <div>Barcode: [% circ.target_copy.barcode %]</div>
1577             <div>Due Date: [% date.format(helpers.format_date(circ.due_date), '%Y-%m-%d') %]</div>
1578         </li>
1579     [% END %]
1580     </ol>
1581 </div>
1582 $$
1583 );
1584
1585
1586 INSERT INTO action_trigger.environment ( event_def, path) VALUES
1587     ( 11, 'target_copy'),
1588     ( 11, 'circ_lib.billing_address'),
1589     ( 11, 'circ_lib.hours_of_operation'),
1590     ( 11, 'usr');
1591
1592 INSERT INTO action_trigger.hook (key, core_type, description, passive) 
1593     VALUES (
1594         'format.selfcheck.holds',
1595         'ahr',
1596         'Formats holds for self-checkout receipt',
1597         TRUE
1598     );
1599
1600 INSERT INTO action_trigger.event_definition (id, active, owner, name, hook, validator, reactor, group_field, granularity, template )
1601     VALUES (
1602         12,
1603         TRUE,
1604         1,
1605         'Self-Checkout Holds Receipt',
1606         'format.selfcheck.holds',
1607         'NOOP_True',
1608         'ProcessTemplate',
1609         'usr',
1610         'print-on-demand',
1611 $$
1612 [%- USE date -%]
1613 [%- SET user = target.0.usr -%]
1614 <div>
1615     <style> li { padding: 8px; margin 5px; }</style>
1616     <div>[% date.format %]</div>
1617     <br/>
1618
1619     [% user.family_name %], [% user.first_given_name %]
1620     <ol>
1621     [% FOR hold IN target %]
1622         [%-
1623             SET idx = loop.count - 1;
1624             SET udata =  user_data.$idx
1625         -%]
1626         <li>
1627             <div>Title: [% hold.bib_rec.bib_record.simple_record.title %]</div>
1628             <div>Author: [% hold.bib_rec.bib_record.simple_record.author %]</div>
1629             <div>Pickup Location: [% hold.pickup_lib.name %]</div>
1630             <div>Status: 
1631                 [%- IF udata.ready -%]
1632                     Ready for pickup
1633                 [% ELSE %]
1634                     #[% udata.queue_position %] of [% udata.potential_copies %] copies.
1635                 [% END %]
1636             </div>
1637         </li>
1638     [% END %]
1639     </ol>
1640 </div>
1641 $$
1642 );
1643
1644
1645 INSERT INTO action_trigger.environment ( event_def, path) VALUES
1646     ( 12, 'bib_rec.bib_record.simple_record'),
1647     ( 12, 'pickup_lib'),
1648     ( 12, 'usr');
1649
1650 INSERT INTO action_trigger.hook (key, core_type, description, passive) 
1651     VALUES (
1652         'format.selfcheck.fines',
1653         'au',
1654         'Formats fines for self-checkout receipt',
1655         TRUE
1656     );
1657
1658 INSERT INTO action_trigger.event_definition (id, active, owner, name, hook, validator, reactor, granularity, template )
1659     VALUES (
1660         13,
1661         TRUE,
1662         1,
1663         'Self-Checkout Fines Receipt',
1664         'format.selfcheck.fines',
1665         'NOOP_True',
1666         'ProcessTemplate',
1667         'print-on-demand',
1668 $$
1669 [%- USE date -%]
1670 [%- SET user = target -%]
1671 <div>
1672     <style> li { padding: 8px; margin 5px; }</style>
1673     <div>[% date.format %]</div>
1674     <br/>
1675
1676     [% user.family_name %], [% user.first_given_name %]
1677     <ol>
1678     [% FOR xact IN user.open_billable_transactions_summary %]
1679         <li>
1680             <div>Details: 
1681                 [% IF xact.xact_type == 'circulation' %]
1682                     [%- helpers.get_copy_bib_basics(xact.circulation.target_copy).title -%]
1683                 [% ELSE %]
1684                     [%- xact.last_billing_type -%]
1685                 [% END %]
1686             </div>
1687             <div>Total Billed: [% xact.total_owed %]</div>
1688             <div>Total Paid: [% xact.total_paid %]</div>
1689             <div>Balance Owed : [% xact.balance_owed %]</div>
1690         </li>
1691     [% END %]
1692     </ol>
1693 </div>
1694 $$
1695 );
1696
1697 INSERT INTO action_trigger.environment ( event_def, path) VALUES
1698     ( 13, 'open_billable_transactions_summary.circulation' );
1699
1700 INSERT INTO action_trigger.reactor (module,description) VALUES
1701 (   'SendFile',
1702     oils_i18n_gettext(
1703         'SendFile',
1704         '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.',
1705         'atreact',
1706         'description'
1707     )
1708 );
1709
1710 INSERT INTO action_trigger.hook (key, core_type, description, passive) 
1711     VALUES (
1712         'format.acqli.html',
1713         'jub',
1714         'Formats lineitem worksheet for titles received',
1715         TRUE
1716     );
1717
1718 INSERT INTO action_trigger.event_definition (id, active, owner, name, hook, validator, reactor, granularity, template)
1719     VALUES (
1720         14,
1721         TRUE,
1722         1,
1723         'Lineitem Worksheet',
1724         'format.acqli.html',
1725         'NOOP_True',
1726         'ProcessTemplate',
1727         'print-on-demand',
1728 $$
1729 [%- USE date -%]
1730 [%- SET li = target; -%]
1731 <div class="wrapper">
1732     <div class="summary" style='font-size:110%; font-weight:bold;'>
1733
1734         <div>Title: [% helpers.get_li_attr("title", "", li.attributes) %]</div>
1735         <div>Author: [% helpers.get_li_attr("author", "", li.attributes) %]</div>
1736         <div class="count">Item Count: [% li.lineitem_details.size %]</div>
1737         <div class="lineid">Lineitem ID: [% li.id %]</div>
1738
1739         [% IF li.distribution_formulas.size > 0 %]
1740             [% SET forms = [] %]
1741             [% FOREACH form IN li.distribution_formulas; forms.push(form.formula.name); END %]
1742             <div>Distribution Formulas: [% forms.join(',') %]</div>
1743         [% END %]
1744
1745         [% IF li.lineitem_notes.size > 0 %]
1746             Lineitem Notes:
1747             <ul>
1748                 [%- FOR note IN li.lineitem_notes -%]
1749                     <li>
1750                     [% IF note.alert_text %]
1751                         [% note.alert_text.code -%] 
1752                         [% IF note.value -%]
1753                             : [% note.value %]
1754                         [% END %]
1755                     [% ELSE %]
1756                         [% note.value -%] 
1757                     [% END %]
1758                     </li>
1759                 [% END %]
1760             </ul>
1761         [% END %]
1762     </div>
1763     <br/>
1764     <table>
1765         <thead>
1766             <tr>
1767                 <th>Branch</th>
1768                 <th>Barcode</th>
1769                 <th>Call Number</th>
1770                 <th>Fund</th>
1771                 <th>Recd.</th>
1772                 <th>Notes</th>
1773             </tr>
1774         </thead>
1775         <tbody>
1776         [% FOREACH detail IN li.lineitem_details.sort('owning_lib') %]
1777             [% 
1778                 IF copy.eg_copy_id;
1779                     SET copy = copy.eg_copy_id;
1780                     SET cn_label = copy.call_number.label;
1781                 ELSE; 
1782                     SET copy = detail; 
1783                     SET cn_label = detail.cn_label;
1784                 END 
1785             %]
1786             <tr>
1787                 <!-- acq.lineitem_detail.id = [%- detail.id -%] -->
1788                 <td style='padding:5px;'>[% detail.owning_lib.shortname %]</td>
1789                 <td style='padding:5px;'>[% IF copy.barcode   %]<span class="barcode"  >[% detail.barcode   %]</span>[% END %]</td>
1790                 <td style='padding:5px;'>[% IF cn_label %]<span class="cn_label" >[% cn_label  %]</span>[% END %]</td>
1791                 <td style='padding:5px;'>[% IF detail.fund %]<span class="fund">[% detail.fund.code %] ([% detail.fund.year %])</span>[% END %]</td>
1792                 <td style='padding:5px;'>[% IF detail.recv_time %]<span class="recv_time">[% detail.recv_time %]</span>[% END %]</td>
1793                 <td style='padding:5px;'>[% detail.note %]</td>
1794             </tr>
1795         [% END %]
1796         </tbody>
1797     </table>
1798 </div>
1799 $$
1800 );
1801
1802
1803 INSERT INTO action_trigger.environment (event_def, path) VALUES
1804     ( 14, 'attributes' ),
1805     ( 14, 'lineitem_details' ),
1806     ( 14, 'lineitem_details.owning_lib' ),
1807     ( 14, 'lineitem_notes' )
1808 ;
1809
1810 INSERT INTO action_trigger.hook (key,core_type,description,passive) VALUES (
1811         'aur.ordered',
1812         'aur', 
1813         oils_i18n_gettext(
1814             'aur.ordered',
1815             'A patron acquisition request has been marked On-Order.',
1816             'ath',
1817             'description'
1818         ), 
1819         TRUE
1820     ), (
1821         'aur.received', 
1822         'aur', 
1823         oils_i18n_gettext(
1824             'aur.received', 
1825             'A patron acquisition request has been marked Received.',
1826             'ath',
1827             'description'
1828         ),
1829         TRUE
1830     ), (
1831         'aur.cancelled',
1832         'aur',
1833         oils_i18n_gettext(
1834             'aur.cancelled',
1835             'A patron acquisition request has been marked Cancelled.',
1836             'ath',
1837             'description'
1838         ),
1839         TRUE
1840     )
1841 ;
1842
1843 INSERT INTO action_trigger.validator (module,description) VALUES (
1844         'Acq::UserRequestOrdered',
1845         oils_i18n_gettext(
1846             'Acq::UserRequestOrdered',
1847             'Tests to see if the corresponding Line Item has a state of "on-order".',
1848             'atval',
1849             'description'
1850         )
1851     ), (
1852         'Acq::UserRequestReceived',
1853         oils_i18n_gettext(
1854             'Acq::UserRequestReceived',
1855             'Tests to see if the corresponding Line Item has a state of "received".',
1856             'atval',
1857             'description'
1858         )
1859     ), (
1860         'Acq::UserRequestCancelled',
1861         oils_i18n_gettext(
1862             'Acq::UserRequestCancelled',
1863             'Tests to see if the corresponding Line Item has a state of "cancelled".',
1864             'atval',
1865             'description'
1866         )
1867     )
1868 ;
1869
1870 -- What was event_definition #15 in v1.6.1 will be recreated as #20.  This
1871 -- renumbering requires some juggling:
1872 --
1873 -- 1. Update any child rows to point to #20.  These updates will temporarily
1874 -- violate foreign key constraints, but that's okay as long as we create
1875 -- #20 before committing.
1876 --
1877 -- 2. Delete the old #15.
1878 --
1879 -- 3. Insert the new #15.
1880 --
1881 -- 4. Insert #20.
1882 --
1883 -- We could combine steps 2 and 3 into a single update, but that would create
1884 -- additional opportunities for typos, since we already have the insert from
1885 -- an upgrade script.
1886
1887 UPDATE action_trigger.environment
1888 SET event_def = 20
1889 WHERE event_def = 15;
1890
1891 UPDATE action_trigger.event
1892 SET event_def = 20
1893 WHERE event_def = 15;
1894
1895 UPDATE action_trigger.event_params
1896 SET event_def = 20
1897 WHERE event_def = 15;
1898
1899 DELETE FROM action_trigger.event_definition
1900 WHERE id = 15;
1901
1902 INSERT INTO action_trigger.event_definition (
1903         id,
1904         active,
1905         owner,
1906         name,
1907         hook,
1908         validator,
1909         reactor,
1910         template
1911     ) VALUES (
1912         15,
1913         FALSE,
1914         1,
1915         'Email Notice: Patron Acquisition Request marked On-Order.',
1916         'aur.ordered',
1917         'Acq::UserRequestOrdered',
1918         'SendEmail',
1919 $$
1920 [%- USE date -%]
1921 [%- SET li = target.lineitem; -%]
1922 [%- SET user = target.usr -%]
1923 [%- SET title = helpers.get_li_attr("title", "", li.attributes) -%]
1924 [%- SET author = helpers.get_li_attr("author", "", li.attributes) -%]
1925 [%- SET edition = helpers.get_li_attr("edition", "", li.attributes) -%]
1926 [%- SET isbn = helpers.get_li_attr("isbn", "", li.attributes) -%]
1927 [%- SET publisher = helpers.get_li_attr("publisher", "", li.attributes) -%]
1928 [%- SET pubdate = helpers.get_li_attr("pubdate", "", li.attributes) -%]
1929
1930 To: [%- params.recipient_email || user.email %]
1931 From: [%- params.sender_email || default_sender %]
1932 Subject: Acquisition Request Notification
1933
1934 Dear [% user.family_name %], [% user.first_given_name %]
1935 Our records indicate the following acquisition request has been placed on order.
1936
1937 Title: [% title %]
1938 [% IF author %]Author: [% author %][% END %]
1939 [% IF edition %]Edition: [% edition %][% END %]
1940 [% IF isbn %]ISBN: [% isbn %][% END %]
1941 [% IF publisher %]Publisher: [% publisher %][% END %]
1942 [% IF pubdate %]Publication Date: [% pubdate %][% END %]
1943 Lineitem ID: [% li.id %]
1944 $$
1945     ), (
1946         16,
1947         FALSE,
1948         1,
1949         'Email Notice: Patron Acquisition Request marked Received.',
1950         'aur.received',
1951         'Acq::UserRequestReceived',
1952         'SendEmail',
1953 $$
1954 [%- USE date -%]
1955 [%- SET li = target.lineitem; -%]
1956 [%- SET user = target.usr -%]
1957 [%- SET title = helpers.get_li_attr("title", "", li.attributes) %]
1958 [%- SET author = helpers.get_li_attr("author", "", li.attributes) %]
1959 [%- SET edition = helpers.get_li_attr("edition", "", li.attributes) %]
1960 [%- SET isbn = helpers.get_li_attr("isbn", "", li.attributes) %]
1961 [%- SET publisher = helpers.get_li_attr("publisher", "", li.attributes) -%]
1962 [%- SET pubdate = helpers.get_li_attr("pubdate", "", li.attributes) -%]
1963
1964 To: [%- params.recipient_email || user.email %]
1965 From: [%- params.sender_email || default_sender %]
1966 Subject: Acquisition Request Notification
1967
1968 Dear [% user.family_name %], [% user.first_given_name %]
1969 Our records indicate the materials for the following acquisition request have been received.
1970
1971 Title: [% title %]
1972 [% IF author %]Author: [% author %][% END %]
1973 [% IF edition %]Edition: [% edition %][% END %]
1974 [% IF isbn %]ISBN: [% isbn %][% END %]
1975 [% IF publisher %]Publisher: [% publisher %][% END %]
1976 [% IF pubdate %]Publication Date: [% pubdate %][% END %]
1977 Lineitem ID: [% li.id %]
1978 $$
1979     ), (
1980         17,
1981         FALSE,
1982         1,
1983         'Email Notice: Patron Acquisition Request marked Cancelled.',
1984         'aur.cancelled',
1985         'Acq::UserRequestCancelled',
1986         'SendEmail',
1987 $$
1988 [%- USE date -%]
1989 [%- SET li = target.lineitem; -%]
1990 [%- SET user = target.usr -%]
1991 [%- SET title = helpers.get_li_attr("title", "", li.attributes) %]
1992 [%- SET author = helpers.get_li_attr("author", "", li.attributes) %]
1993 [%- SET edition = helpers.get_li_attr("edition", "", li.attributes) %]
1994 [%- SET isbn = helpers.get_li_attr("isbn", "", li.attributes) %]
1995 [%- SET publisher = helpers.get_li_attr("publisher", "", li.attributes) -%]
1996 [%- SET pubdate = helpers.get_li_attr("pubdate", "", li.attributes) -%]
1997
1998 To: [%- params.recipient_email || user.email %]
1999 From: [%- params.sender_email || default_sender %]
2000 Subject: Acquisition Request Notification
2001
2002 Dear [% user.family_name %], [% user.first_given_name %]
2003 Our records indicate the following acquisition request has been cancelled.
2004
2005 Title: [% title %]
2006 [% IF author %]Author: [% author %][% END %]
2007 [% IF edition %]Edition: [% edition %][% END %]
2008 [% IF isbn %]ISBN: [% isbn %][% END %]
2009 [% IF publisher %]Publisher: [% publisher %][% END %]
2010 [% IF pubdate %]Publication Date: [% pubdate %][% END %]
2011 Lineitem ID: [% li.id %]
2012 $$
2013     );
2014
2015 INSERT INTO action_trigger.environment (
2016         event_def,
2017         path
2018     ) VALUES 
2019         ( 15, 'lineitem' ),
2020         ( 15, 'lineitem.attributes' ),
2021         ( 15, 'usr' ),
2022
2023         ( 16, 'lineitem' ),
2024         ( 16, 'lineitem.attributes' ),
2025         ( 16, 'usr' ),
2026
2027         ( 17, 'lineitem' ),
2028         ( 17, 'lineitem.attributes' ),
2029         ( 17, 'usr' )
2030     ;
2031
2032 INSERT INTO action_trigger.event_definition
2033 (id, active, owner, name, hook, validator, reactor, cleanup_success, cleanup_failure, delay, delay_field, group_field, template) VALUES
2034 (23, true, 1, 'PO JEDI', 'acqpo.activated', 'Acq::PurchaseOrderEDIRequired', 'GeneratePurchaseOrderJEDI', NULL, NULL, '00:05:00', NULL, NULL,
2035 $$[%- USE date -%]
2036 [%# start JEDI document -%]
2037 [%- BLOCK big_block -%]
2038 {
2039    "recipient":"[% target.provider.san %]",
2040    "sender":"[% target.ordering_agency.mailing_address.san %]",
2041    "body": [{
2042      "ORDERS":[ "order", {
2043         "po_number":[% target.id %],
2044         "date":"[% date.format(date.now, '%Y%m%d') %]",
2045         "buyer":[{
2046             [%- IF target.provider.edi_default.vendcode -%]
2047                 "id":"[% target.ordering_agency.mailing_address.san _ ' ' _ target.provider.edi_default.vendcode %]", 
2048                 "id-qualifier": 91
2049             [%- ELSE -%]
2050                 "id":"[% target.ordering_agency.mailing_address.san %]"
2051             [%- END  -%]
2052         }],
2053         "vendor":[ 
2054             [%- # target.provider.name (target.provider.id) -%]
2055             "[% target.provider.san %]",
2056             {"id-qualifier": 92, "id":"[% target.provider.id %]"}
2057         ],
2058         "currency":"[% target.provider.currency_type %]",
2059         "items":[
2060         [% FOR li IN target.lineitems %]
2061         {
2062             "identifiers":[   [%-# li.isbns = helpers.get_li_isbns(li.attributes) %]
2063             [% FOR isbn IN helpers.get_li_isbns(li.attributes) -%]
2064                 [% IF isbn.length == 13 -%]
2065                 {"id-qualifier":"EN","id":"[% isbn %]"},
2066                 [% ELSE -%]
2067                 {"id-qualifier":"IB","id":"[% isbn %]"},
2068                 [%- END %]
2069             [% END %]
2070                 {"id-qualifier":"SA","id":"[% li.id %]"}
2071             ],
2072             "price":[% li.estimated_unit_price || '0.00' %],
2073             "desc":[
2074                 {"BTI":"[% helpers.get_li_attr('title',     '', li.attributes) %]"}, 
2075                 {"BPU":"[% helpers.get_li_attr('publisher', '', li.attributes) %]"},
2076                 {"BPD":"[% helpers.get_li_attr('pubdate',   '', li.attributes) %]"},
2077                 {"BPH":"[% helpers.get_li_attr('pagination','', li.attributes) %]"}
2078             ],
2079             "quantity":[% li.lineitem_details.size %]
2080         }[% UNLESS loop.last %],[% END %]
2081         [%-# TODO: lineitem details (later) -%]
2082         [% END %]
2083         ],
2084         "line_items":[% target.lineitems.size %]
2085      }]  [% # close ORDERS array %]
2086    }]    [% # close  body  array %]
2087 }
2088 [% END %]
2089 [% tempo = PROCESS big_block; helpers.escape_json(tempo) %]
2090 $$
2091 );
2092
2093 INSERT INTO action_trigger.environment (event_def, path) VALUES 
2094   (23, 'lineitems.attributes'), 
2095   (23, 'lineitems.lineitem_details'), 
2096   (23, 'lineitems.lineitem_notes'), 
2097   (23, 'ordering_agency.mailing_address'), 
2098   (23, 'provider');
2099
2100 UPDATE action_trigger.event_definition SET template = 
2101 $$
2102 [%- USE date -%]
2103 [%-
2104     # find a lineitem attribute by name and optional type
2105     BLOCK get_li_attr;
2106         FOR attr IN li.attributes;
2107             IF attr.attr_name == attr_name;
2108                 IF !attr_type OR attr_type == attr.attr_type;
2109                     attr.attr_value;
2110                     LAST;
2111                 END;
2112             END;
2113         END;
2114     END
2115 -%]
2116
2117 <h2>Purchase Order [% target.id %]</h2>
2118 <br/>
2119 date <b>[% date.format(date.now, '%Y%m%d') %]</b>
2120 <br/>
2121
2122 <style>
2123     table td { padding:5px; border:1px solid #aaa;}
2124     table { width:95%; border-collapse:collapse; }
2125     #vendor-notes { padding:5px; border:1px solid #aaa; }
2126 </style>
2127 <table id='vendor-table'>
2128   <tr>
2129     <td valign='top'>Vendor</td>
2130     <td>
2131       <div>[% target.provider.name %]</div>
2132       <div>[% target.provider.addresses.0.street1 %]</div>
2133       <div>[% target.provider.addresses.0.street2 %]</div>
2134       <div>[% target.provider.addresses.0.city %]</div>
2135       <div>[% target.provider.addresses.0.state %]</div>
2136       <div>[% target.provider.addresses.0.country %]</div>
2137       <div>[% target.provider.addresses.0.post_code %]</div>
2138     </td>
2139     <td valign='top'>Ship to / Bill to</td>
2140     <td>
2141       <div>[% target.ordering_agency.name %]</div>
2142       <div>[% target.ordering_agency.billing_address.street1 %]</div>
2143       <div>[% target.ordering_agency.billing_address.street2 %]</div>
2144       <div>[% target.ordering_agency.billing_address.city %]</div>
2145       <div>[% target.ordering_agency.billing_address.state %]</div>
2146       <div>[% target.ordering_agency.billing_address.country %]</div>
2147       <div>[% target.ordering_agency.billing_address.post_code %]</div>
2148     </td>
2149   </tr>
2150 </table>
2151
2152 <br/><br/>
2153 <fieldset id='vendor-notes'>
2154     <legend>Notes to the Vendor</legend>
2155     <ul>
2156     [% FOR note IN target.notes %]
2157         [% IF note.vendor_public == 't' %]
2158             <li>[% note.value %]</li>
2159         [% END %]
2160     [% END %]
2161     </ul>
2162 </fieldset>
2163 <br/><br/>
2164
2165 <table>
2166   <thead>
2167     <tr>
2168       <th>PO#</th>
2169       <th>ISBN or Item #</th>
2170       <th>Title</th>
2171       <th>Quantity</th>
2172       <th>Unit Price</th>
2173       <th>Line Total</th>
2174       <th>Notes</th>
2175     </tr>
2176   </thead>
2177   <tbody>
2178
2179   [% subtotal = 0 %]
2180   [% FOR li IN target.lineitems %]
2181
2182   <tr>
2183     [% count = li.lineitem_details.size %]
2184     [% price = li.estimated_unit_price %]
2185     [% litotal = (price * count) %]
2186     [% subtotal = subtotal + litotal %]
2187     [% isbn = PROCESS get_li_attr attr_name = 'isbn' %]
2188     [% ident = PROCESS get_li_attr attr_name = 'identifier' %]
2189
2190     <td>[% target.id %]</td>
2191     <td>[% isbn || ident %]</td>
2192     <td>[% PROCESS get_li_attr attr_name = 'title' %]</td>
2193     <td>[% count %]</td>
2194     <td>[% price %]</td>
2195     <td>[% litotal %]</td>
2196     <td>
2197         <ul>
2198         [% FOR note IN li.lineitem_notes %]
2199             [% IF note.vendor_public == 't' %]
2200                 <li>[% note.value %]</li>
2201             [% END %]
2202         [% END %]
2203         </ul>
2204     </td>
2205   </tr>
2206   [% END %]
2207   <tr>
2208     <td/><td/><td/><td/>
2209     <td>Sub Total</td>
2210     <td>[% subtotal %]</td>
2211   </tr>
2212   </tbody>
2213 </table>
2214
2215 <br/>
2216
2217 Total Line Item Count: [% target.lineitems.size %]
2218 $$
2219 WHERE id = 4;
2220
2221 INSERT INTO action_trigger.environment (event_def, path) VALUES 
2222     (4, 'lineitems.lineitem_notes'),
2223     (4, 'notes');
2224
2225 INSERT INTO action_trigger.environment (event_def, path) VALUES
2226     ( 14, 'lineitem_notes.alert_text' ),
2227     ( 14, 'distribution_formulas.formula' ),
2228     ( 14, 'lineitem_details.fund' ),
2229     ( 14, 'lineitem_details.eg_copy_id' ),
2230     ( 14, 'lineitem_details.eg_copy_id.call_number' )
2231 ;
2232
2233 INSERT INTO action_trigger.hook (key,core_type,description,passive) VALUES (
2234         'aur.created',
2235         'aur',
2236         oils_i18n_gettext(
2237             'aur.created',
2238             'A patron has made an acquisitions request.',
2239             'ath',
2240             'description'
2241         ),
2242         TRUE
2243     ), (
2244         'aur.rejected',
2245         'aur',
2246         oils_i18n_gettext(
2247             'aur.rejected',
2248             'A patron acquisition request has been rejected.',
2249             'ath',
2250             'description'
2251         ),
2252         TRUE
2253     )
2254 ;
2255
2256 INSERT INTO action_trigger.event_definition (
2257         id,
2258         active,
2259         owner,
2260         name,
2261         hook,
2262         validator,
2263         reactor,
2264         template
2265     ) VALUES (
2266         18,
2267         FALSE,
2268         1,
2269         'Email Notice: Acquisition Request created.',
2270         'aur.created',
2271         'NOOP_True',
2272         'SendEmail',
2273 $$
2274 [%- USE date -%]
2275 [%- SET user = target.usr -%]
2276 [%- SET title = target.title -%]
2277 [%- SET author = target.author -%]
2278 [%- SET isxn = target.isxn -%]
2279 [%- SET publisher = target.publisher -%]
2280 [%- SET pubdate = target.pubdate -%]
2281
2282 To: [%- params.recipient_email || user.email %]
2283 From: [%- params.sender_email || default_sender %]
2284 Subject: Acquisition Request Notification
2285
2286 Dear [% user.family_name %], [% user.first_given_name %]
2287 Our records indicate that you have made the following acquisition request:
2288
2289 Title: [% title %]
2290 [% IF author %]Author: [% author %][% END %]
2291 [% IF edition %]Edition: [% edition %][% END %]
2292 [% IF isbn %]ISXN: [% isxn %][% END %]
2293 [% IF publisher %]Publisher: [% publisher %][% END %]
2294 [% IF pubdate %]Publication Date: [% pubdate %][% END %]
2295 $$
2296     ), (
2297         19,
2298         FALSE,
2299         1,
2300         'Email Notice: Acquisition Request Rejected.',
2301         'aur.rejected',
2302         'NOOP_True',
2303         'SendEmail',
2304 $$
2305 [%- USE date -%]
2306 [%- SET user = target.usr -%]
2307 [%- SET title = target.title -%]
2308 [%- SET author = target.author -%]
2309 [%- SET isxn = target.isxn -%]
2310 [%- SET publisher = target.publisher -%]
2311 [%- SET pubdate = target.pubdate -%]
2312 [%- SET cancel_reason = target.cancel_reason.description -%]
2313
2314 To: [%- params.recipient_email || user.email %]
2315 From: [%- params.sender_email || default_sender %]
2316 Subject: Acquisition Request Notification
2317
2318 Dear [% user.family_name %], [% user.first_given_name %]
2319 Our records indicate the following acquisition request has been rejected for this reason: [% cancel_reason %]
2320
2321 Title: [% title %]
2322 [% IF author %]Author: [% author %][% END %]
2323 [% IF edition %]Edition: [% edition %][% END %]
2324 [% IF isbn %]ISBN: [% isbn %][% END %]
2325 [% IF publisher %]Publisher: [% publisher %][% END %]
2326 [% IF pubdate %]Publication Date: [% pubdate %][% END %]
2327 $$
2328     );
2329
2330 INSERT INTO action_trigger.environment (
2331         event_def,
2332         path
2333     ) VALUES 
2334         ( 18, 'usr' ),
2335         ( 19, 'usr' ),
2336         ( 19, 'cancel_reason' )
2337     ;
2338
2339 INSERT INTO action_trigger.event_definition (id, active, owner, name, hook, validator, reactor, delay, template) 
2340     VALUES (20, 'f', 1, 'Password reset request notification', 'password.reset_request', 'NOOP_True', 'SendEmail', '00:00:01',
2341 $$
2342 [%- USE date -%]
2343 [%- user = target.usr -%]
2344 To: [%- params.recipient_email || user.email %]
2345 From: [%- params.sender_email || user.home_ou.email || default_sender %]
2346 Subject: [% user.home_ou.name %]: library account password reset request
2347   
2348 You have received this message because you, or somebody else, requested a reset
2349 of your library system password. If you did not request a reset of your library
2350 system password, just ignore this message and your current password will
2351 continue to work.
2352
2353 If you did request a reset of your library system password, please perform
2354 the following steps to continue the process of resetting your password:
2355
2356 1. Open the following link in a web browser: https://[% params.hostname %]/opac/password/[% params.locale || 'en-US' %]/[% target.uuid %]
2357 The browser displays a password reset form.
2358
2359 2. Enter your new password in the password reset form in the browser. You must
2360 enter the password twice to ensure that you do not make a mistake. If the
2361 passwords match, you will then be able to log in to your library system account
2362 with the new password.
2363
2364 $$);
2365
2366 INSERT INTO action_trigger.hook (key, core_type, description, passive)
2367     VALUES (
2368         'format.acqcle.html',
2369         'acqcle',
2370         'Formats claim events into a voucher',
2371         TRUE
2372     );
2373
2374 INSERT INTO action_trigger.event_definition (
2375         id, active, owner, name, hook, group_field,
2376         validator, reactor, granularity, template
2377     ) VALUES (
2378         21,
2379         TRUE,
2380         1,
2381         'Claim Voucher',
2382         'format.acqcle.html',
2383         'claim',
2384         'NOOP_True',
2385         'ProcessTemplate',
2386         'print-on-demand',
2387 $$
2388 [%- USE date -%]
2389 [%- SET claim = target.0.claim -%]
2390 <!-- This will need refined/prettified. -->
2391 <div class="acq-claim-voucher">
2392     <h2>Claim: [% claim.id %] ([% claim.type.code %])</h2>
2393     <h3>Against: [%- helpers.get_li_attr("title", "", claim.lineitem_detail.lineitem.attributes) -%]</h3>
2394     <ul>
2395         [% FOR event IN target %]
2396         <li>
2397             Event type: [% event.type.code %]
2398             [% IF event.type.library_initiated %](Library initiated)[% END %]
2399             <br />
2400             Event date: [% event.event_date %]<br />
2401             Order date: [% event.claim.lineitem_detail.lineitem.purchase_order.order_date %]<br />
2402             Expected receive date: [% event.claim.lineitem_detail.lineitem.expected_recv_time %]<br />
2403             Initiated by: [% event.creator.family_name %], [% event.creator.first_given_name %] [% event.creator.second_given_name %]<br />
2404             Barcode: [% event.claim.lineitem_detail.barcode %]; Fund:
2405             [% event.claim.lineitem_detail.fund.code %]
2406             ([% event.claim.lineitem_detail.fund.year %])
2407         </li>
2408         [% END %]
2409     </ul>
2410 </div>
2411 $$
2412 );
2413
2414 INSERT INTO action_trigger.environment (event_def, path) VALUES
2415     (21, 'claim'),
2416     (21, 'claim.type'),
2417     (21, 'claim.lineitem_detail'),
2418     (21, 'claim.lineitem_detail.fund'),
2419     (21, 'claim.lineitem_detail.lineitem.attributes'),
2420     (21, 'claim.lineitem_detail.lineitem.purchase_order'),
2421     (21, 'creator'),
2422     (21, 'type')
2423 ;
2424
2425 INSERT INTO action_trigger.hook (key, core_type, description, passive)
2426     VALUES (
2427         'format.acqinv.html',
2428         'acqinv',
2429         'Formats invoices into a voucher',
2430         TRUE
2431     );
2432
2433 INSERT INTO action_trigger.event_definition (
2434         id, active, owner, name, hook,
2435         validator, reactor, granularity, template
2436     ) VALUES (
2437         22,
2438         TRUE,
2439         1,
2440         'Invoice',
2441         'format.acqinv.html',
2442         'NOOP_True',
2443         'ProcessTemplate',
2444         'print-on-demand',
2445 $$
2446 [% FILTER collapse %]
2447 [%- SET invoice = target -%]
2448 <!-- This lacks totals, info about funds (for invoice entries,
2449     funds are per-LID!), and general refinement -->
2450 <div class="acq-invoice-voucher">
2451     <h1>Invoice</h1>
2452     <div>
2453         <strong>No.</strong> [% invoice.inv_ident %]
2454         [% IF invoice.inv_type %]
2455             / <strong>Type:</strong>[% invoice.inv_type %]
2456         [% END %]
2457     </div>
2458     <div>
2459         <dl>
2460             [% BLOCK ent_with_address %]
2461             <dt>[% ent_label %]: [% ent.name %] ([% ent.code %])</dt>
2462             <dd>
2463                 [% IF ent.addresses.0 %]
2464                     [% SET addr = ent.addresses.0 %]
2465                     [% addr.street1 %]<br />
2466                     [% IF addr.street2 %][% addr.street2 %]<br />[% END %]
2467                     [% addr.city %],
2468                     [% IF addr.county %] [% addr.county %], [% END %]
2469                     [% IF addr.state %] [% addr.state %] [% END %]
2470                     [% IF addr.post_code %][% addr.post_code %][% END %]<br />
2471                     [% IF addr.country %] [% addr.country %] [% END %]
2472                 [% END %]
2473                 <p>
2474                     [% IF ent.phone %] Phone: [% ent.phone %]<br />[% END %]
2475                     [% IF ent.fax_phone %] Fax: [% ent.fax_phone %]<br />[% END %]
2476                     [% IF ent.url %] URL: [% ent.url %]<br />[% END %]
2477                     [% IF ent.email %] E-mail: [% ent.email %] [% END %]
2478                 </p>
2479             </dd>
2480             [% END %]
2481             [% INCLUDE ent_with_address
2482                 ent = invoice.provider
2483                 ent_label = "Provider" %]
2484             [% INCLUDE ent_with_address
2485                 ent = invoice.shipper
2486                 ent_label = "Shipper" %]
2487             <dt>Receiver</dt>
2488             <dd>
2489                 [% invoice.receiver.name %] ([% invoice.receiver.shortname %])
2490             </dd>
2491             <dt>Received</dt>
2492             <dd>
2493                 [% helpers.format_date(invoice.recv_date) %] by
2494                 [% invoice.recv_method %]
2495             </dd>
2496             [% IF invoice.note %]
2497                 <dt>Note</dt>
2498                 <dd>
2499                     [% invoice.note %]
2500                 </dd>
2501             [% END %]
2502         </dl>
2503     </div>
2504     <ul>
2505         [% FOR entry IN invoice.entries %]
2506             <li>
2507                 [% IF entry.lineitem %]
2508                     Title: [% helpers.get_li_attr(
2509                         "title", "", entry.lineitem.attributes
2510                     ) %]<br />
2511                     Author: [% helpers.get_li_attr(
2512                         "author", "", entry.lineitem.attributes
2513                     ) %]
2514                 [% END %]
2515                 [% IF entry.purchase_order %]
2516                     (PO: [% entry.purchase_order.name %])
2517                 [% END %]<br />
2518                 Invoice item count: [% entry.inv_item_count %]
2519                 [% IF entry.phys_item_count %]
2520                     / Physical item count: [% entry.phys_item_count %]
2521                 [% END %]
2522                 <br />
2523                 [% IF entry.cost_billed %]
2524                     Cost billed: [% entry.cost_billed %]
2525                     [% IF entry.billed_per_item %](per item)[% END %]
2526                     <br />
2527                 [% END %]
2528                 [% IF entry.actual_cost %]
2529                     Actual cost: [% entry.actual_cost %]<br />
2530                 [% END %]
2531                 [% IF entry.amount_paid %]
2532                     Amount paid: [% entry.amount_paid %]<br />
2533                 [% END %]
2534                 [% IF entry.note %]Note: [% entry.note %][% END %]
2535             </li>
2536         [% END %]
2537         [% FOR item IN invoice.items %]
2538             <li>
2539                 [% IF item.inv_item_type %]
2540                     Item Type: [% item.inv_item_type %]<br />
2541                 [% END %]
2542                 [% IF item.title %]Title/Description:
2543                     [% item.title %]<br />
2544                 [% END %]
2545                 [% IF item.author %]Author: [% item.author %]<br />[% END %]
2546                 [% IF item.purchase_order %]PO: [% item.purchase_order %]<br />[% END %]
2547                 [% IF item.note %]Note: [% item.note %]<br />[% END %]
2548                 [% IF item.cost_billed %]
2549                     Cost billed: [% item.cost_billed %]<br />
2550                 [% END %]
2551                 [% IF item.actual_cost %]
2552                     Actual cost: [% item.actual_cost %]<br />
2553                 [% END %]
2554                 [% IF item.amount_paid %]
2555                     Amount paid: [% item.amount_paid %]<br />
2556                 [% END %]
2557             </li>
2558         [% END %]
2559     </ul>
2560 </div>
2561 [% END %]
2562 $$
2563 );
2564
2565 INSERT INTO action_trigger.environment (event_def, path) VALUES
2566     (22, 'provider'),
2567     (22, 'provider.addresses'),
2568     (22, 'shipper'),
2569     (22, 'shipper.addresses'),
2570     (22, 'receiver'),
2571     (22, 'entries'),
2572     (22, 'entries.purchase_order'),
2573     (22, 'entries.lineitem'),
2574     (22, 'entries.lineitem.attributes'),
2575     (22, 'items')
2576 ;
2577
2578 INSERT INTO action_trigger.environment (event_def, path) VALUES 
2579   (23, 'provider.edi_default');
2580
2581 INSERT INTO action_trigger.validator (module, description) 
2582     VALUES (
2583         'Acq::PurchaseOrderEDIRequired',
2584         oils_i18n_gettext(
2585             'Acq::PurchaseOrderEDIRequired',
2586             'Purchase order is delivered via EDI',
2587             'atval',
2588             'description'
2589         )
2590     );
2591
2592 INSERT INTO action_trigger.reactor (module, description)
2593     VALUES (
2594         'GeneratePurchaseOrderJEDI',
2595         oils_i18n_gettext(
2596             'GeneratePurchaseOrderJEDI',
2597             'Creates purchase order JEDI (JSON EDI) for subsequent EDI processing',
2598             'atreact',
2599             'description'
2600         )
2601     );
2602
2603 UPDATE action_trigger.hook 
2604     SET 
2605         key = 'acqpo.activated', 
2606         passive = FALSE,
2607         description = oils_i18n_gettext(
2608             'acqpo.activated',
2609             'Purchase order was activated',
2610             'ath',
2611             'description'
2612         )
2613     WHERE key = 'format.po.jedi';
2614
2615 -- We just changed a key in action_trigger.hook.  Now redirect any
2616 -- child rows to point to the new key.  (There probably aren't any;
2617 -- this is just a precaution against possible local modifications.)
2618
2619 UPDATE action_trigger.event_definition
2620 SET hook = 'acqpo.activated'
2621 WHERE hook = 'format.po.jedi';
2622
2623 INSERT INTO action_trigger.reactor (module, description) VALUES (
2624     'AstCall', 'Possibly place a phone call with Asterisk'
2625 );
2626
2627 INSERT INTO
2628     action_trigger.event_definition (
2629         id, active, owner, name, hook, validator, reactor,
2630         cleanup_success, cleanup_failure, delay, delay_field, group_field,
2631         max_delay, granularity, usr_field, opt_in_setting, template
2632     ) VALUES (
2633         24,
2634         FALSE,
2635         1,
2636         'Telephone Overdue Notice',
2637         'checkout.due', 'NOOP_True', 'AstCall',
2638         DEFAULT, DEFAULT, '5 seconds', 'due_date', 'usr',
2639         DEFAULT, DEFAULT, DEFAULT, DEFAULT,
2640         $$
2641 [% phone = target.0.usr.day_phone | replace('[\s\-\(\)]', '') -%]
2642 [% IF phone.match('^[2-9]') %][% country = 1 %][% ELSE %][% country = '' %][% END -%]
2643 Channel: [% channel_prefix %]/[% country %][% phone %]
2644 Context: overdue-test
2645 MaxRetries: 1
2646 RetryTime: 60
2647 WaitTime: 30
2648 Extension: 10
2649 Archive: 1
2650 Set: eg_user_id=[% target.0.usr.id %]
2651 Set: items=[% target.size %]
2652 Set: titlestring=[% titles = [] %][% FOR circ IN target %][% titles.push(circ.target_copy.call_number.record.simple_record.title) %][% END %][% titles.join(". ") %]
2653 $$
2654     );
2655
2656 INSERT INTO
2657     action_trigger.environment (id, event_def, path)
2658     VALUES
2659         (DEFAULT, 24, 'target_copy.call_number.record.simple_record'),
2660         (DEFAULT, 24, 'usr')
2661     ;
2662
2663 INSERT INTO action_trigger.hook (key,core_type,description,passive) VALUES (
2664         'circ.format.history.email',
2665         'circ', 
2666         oils_i18n_gettext(
2667             'circ.format.history.email',
2668             'An email has been requested for a circ history.',
2669             'ath',
2670             'description'
2671         ), 
2672         FALSE
2673     )
2674     ,(
2675         'circ.format.history.print',
2676         'circ', 
2677         oils_i18n_gettext(
2678             'circ.format.history.print',
2679             'A circ history needs to be formatted for printing.',
2680             'ath',
2681             'description'
2682         ), 
2683         FALSE
2684     )
2685     ,(
2686         'ahr.format.history.email',
2687         'ahr', 
2688         oils_i18n_gettext(
2689             'ahr.format.history.email',
2690             'An email has been requested for a hold request history.',
2691             'ath',
2692             'description'
2693         ), 
2694         FALSE
2695     )
2696     ,(
2697         'ahr.format.history.print',
2698         'ahr', 
2699         oils_i18n_gettext(
2700             'ahr.format.history.print',
2701             'A hold request history needs to be formatted for printing.',
2702             'ath',
2703             'description'
2704         ), 
2705         FALSE
2706     )
2707
2708 ;
2709
2710 INSERT INTO action_trigger.event_definition (
2711         id,
2712         active,
2713         owner,
2714         name,
2715         hook,
2716         validator,
2717         reactor,
2718         group_field,
2719         granularity,
2720         template
2721     ) VALUES (
2722         25,
2723         TRUE,
2724         1,
2725         'circ.history.email',
2726         'circ.format.history.email',
2727         'NOOP_True',
2728         'SendEmail',
2729         'usr',
2730         NULL,
2731 $$
2732 [%- USE date -%]
2733 [%- SET user = target.0.usr -%]
2734 To: [%- params.recipient_email || user.email %]
2735 From: [%- params.sender_email || default_sender %]
2736 Subject: Circulation History
2737
2738     [% FOR circ IN target %]
2739             [% helpers.get_copy_bib_basics(circ.target_copy.id).title %]
2740             Barcode: [% circ.target_copy.barcode %]
2741             Checked Out: [% date.format(helpers.format_date(circ.xact_start), '%Y-%m-%d') %]
2742             Due Date: [% date.format(helpers.format_date(circ.due_date), '%Y-%m-%d') %]
2743             Returned: [% date.format(helpers.format_date(circ.checkin_time), '%Y-%m-%d') %]
2744     [% END %]
2745 $$
2746     )
2747     ,(
2748         26,
2749         TRUE,
2750         1,
2751         'circ.history.print',
2752         'circ.format.history.print',
2753         'NOOP_True',
2754         'ProcessTemplate',
2755         'usr',
2756         'print-on-demand',
2757 $$
2758 [%- USE date -%]
2759 <div>
2760     <style> li { padding: 8px; margin 5px; }</style>
2761     <div>[% date.format %]</div>
2762     <br/>
2763
2764     [% user.family_name %], [% user.first_given_name %]
2765     <ol>
2766     [% FOR circ IN target %]
2767         <li>
2768             <div>[% helpers.get_copy_bib_basics(circ.target_copy.id).title %]</div>
2769             <div>Barcode: [% circ.target_copy.barcode %]</div>
2770             <div>Checked Out: [% date.format(helpers.format_date(circ.xact_start), '%Y-%m-%d') %]</div>
2771             <div>Due Date: [% date.format(helpers.format_date(circ.due_date), '%Y-%m-%d') %]</div>
2772             <div>Returned: [% date.format(helpers.format_date(circ.checkin_time), '%Y-%m-%d') %]</div>
2773         </li>
2774     [% END %]
2775     </ol>
2776 </div>
2777 $$
2778     )
2779     ,(
2780         27,
2781         TRUE,
2782         1,
2783         'ahr.history.email',
2784         'ahr.format.history.email',
2785         'NOOP_True',
2786         'SendEmail',
2787         'usr',
2788         NULL,
2789 $$
2790 [%- USE date -%]
2791 [%- SET user = target.0.usr -%]
2792 To: [%- params.recipient_email || user.email %]
2793 From: [%- params.sender_email || default_sender %]
2794 Subject: Hold Request History
2795
2796     [% FOR hold IN target %]
2797             [% helpers.get_copy_bib_basics(hold.current_copy.id).title %]
2798             Requested: [% date.format(helpers.format_date(hold.request_time), '%Y-%m-%d') %]
2799             [% IF hold.fulfillment_time %]Fulfilled: [% date.format(helpers.format_date(hold.fulfillment_time), '%Y-%m-%d') %][% END %]
2800     [% END %]
2801 $$
2802     )
2803     ,(
2804         28,
2805         TRUE,
2806         1,
2807         'ahr.history.print',
2808         'ahr.format.history.print',
2809         'NOOP_True',
2810         'ProcessTemplate',
2811         'usr',
2812         'print-on-demand',
2813 $$
2814 [%- USE date -%]
2815 <div>
2816     <style> li { padding: 8px; margin 5px; }</style>
2817     <div>[% date.format %]</div>
2818     <br/>
2819
2820     [% user.family_name %], [% user.first_given_name %]
2821     <ol>
2822     [% FOR hold IN target %]
2823         <li>
2824             <div>[% helpers.get_copy_bib_basics(hold.current_copy.id).title %]</div>
2825             <div>Requested: [% date.format(helpers.format_date(hold.request_time), '%Y-%m-%d') %]</div>
2826             [% IF hold.fulfillment_time %]<div>Fulfilled: [% date.format(helpers.format_date(hold.fulfillment_time), '%Y-%m-%d') %]</div>[% END %]
2827         </li>
2828     [% END %]
2829     </ol>
2830 </div>
2831 $$
2832     )
2833
2834 ;
2835
2836 INSERT INTO action_trigger.environment (
2837         event_def,
2838         path
2839     ) VALUES 
2840          ( 25, 'target_copy')
2841         ,( 25, 'usr' )
2842         ,( 26, 'target_copy' )
2843         ,( 26, 'usr' )
2844         ,( 27, 'current_copy' )
2845         ,( 27, 'usr' )
2846         ,( 28, 'current_copy' )
2847         ,( 28, 'usr' )
2848 ;
2849
2850 INSERT INTO action_trigger.hook (key,core_type,description,passive) VALUES (
2851         'money.format.payment_receipt.email',
2852         'mp', 
2853         oils_i18n_gettext(
2854             'money.format.payment_receipt.email',
2855             'An email has been requested for a payment receipt.',
2856             'ath',
2857             'description'
2858         ), 
2859         FALSE
2860     )
2861     ,(
2862         'money.format.payment_receipt.print',
2863         'mp', 
2864         oils_i18n_gettext(
2865             'money.format.payment_receipt.print',
2866             'A payment receipt needs to be formatted for printing.',
2867             'ath',
2868             'description'
2869         ), 
2870         FALSE
2871     )
2872 ;
2873
2874 INSERT INTO action_trigger.event_definition (
2875         id,
2876         active,
2877         owner,
2878         name,
2879         hook,
2880         validator,
2881         reactor,
2882         group_field,
2883         granularity,
2884         template
2885     ) VALUES (
2886         29,
2887         TRUE,
2888         1,
2889         'money.payment_receipt.email',
2890         'money.format.payment_receipt.email',
2891         'NOOP_True',
2892         'SendEmail',
2893         'xact.usr',
2894         NULL,
2895 $$
2896 [%- USE date -%]
2897 [%- SET user = target.0.xact.usr -%]
2898 To: [%- params.recipient_email || user.email %]
2899 From: [%- params.sender_email || default_sender %]
2900 Subject: Payment Receipt
2901
2902 [% date.format -%]
2903 [%- SET xact_mp_hash = {} -%]
2904 [%- FOR mp IN target %][%# Template is hooked around payments, but let us make the receipt focused on transactions -%]
2905     [%- SET xact_id = mp.xact.id -%]
2906     [%- IF ! xact_mp_hash.defined( xact_id ) -%][%- xact_mp_hash.$xact_id = { 'xact' => mp.xact, 'payments' => [] } -%][%- END -%]
2907     [%- xact_mp_hash.$xact_id.payments.push(mp) -%]
2908 [%- END -%]
2909 [%- FOR xact_id IN xact_mp_hash.keys.sort -%]
2910     [%- SET xact = xact_mp_hash.$xact_id.xact %]
2911 Transaction ID: [% xact_id %]
2912     [% IF xact.circulation %][% helpers.get_copy_bib_basics(xact.circulation.target_copy).title %]
2913     [% ELSE %]Miscellaneous
2914     [% END %]
2915     Line item billings:
2916         [%- SET mb_type_hash = {} -%]
2917         [%- FOR mb IN xact.billings %][%# Group billings by their btype -%]
2918             [%- IF mb.voided == 'f' -%]
2919                 [%- SET mb_type = mb.btype.id -%]
2920                 [%- IF ! mb_type_hash.defined( mb_type ) -%][%- mb_type_hash.$mb_type = { 'sum' => 0.00, 'billings' => [] } -%][%- END -%]
2921                 [%- IF ! mb_type_hash.$mb_type.defined( 'first_ts' ) -%][%- mb_type_hash.$mb_type.first_ts = mb.billing_ts -%][%- END -%]
2922                 [%- mb_type_hash.$mb_type.last_ts = mb.billing_ts -%]
2923                 [%- mb_type_hash.$mb_type.sum = mb_type_hash.$mb_type.sum + mb.amount -%]
2924                 [%- mb_type_hash.$mb_type.billings.push( mb ) -%]
2925             [%- END -%]
2926         [%- END -%]
2927         [%- FOR mb_type IN mb_type_hash.keys.sort -%]
2928             [%- IF mb_type == 1 %][%-# Consolidated view of overdue billings -%]
2929                 $[% mb_type_hash.$mb_type.sum %] for [% mb_type_hash.$mb_type.billings.0.btype.name %] 
2930                     on [% mb_type_hash.$mb_type.first_ts %] through [% mb_type_hash.$mb_type.last_ts %]
2931             [%- ELSE -%][%# all other billings show individually %]
2932                 [% FOR mb IN mb_type_hash.$mb_type.billings %]
2933                     $[% mb.amount %] for [% mb.btype.name %] on [% mb.billing_ts %] [% mb.note %]
2934                 [% END %]
2935             [% END %]
2936         [% END %]
2937     Line item payments:
2938         [% FOR mp IN xact_mp_hash.$xact_id.payments %]
2939             Payment ID: [% mp.id %]
2940                 Paid [% mp.amount %] via [% SWITCH mp.payment_type -%]
2941                     [% CASE "cash_payment" %]cash
2942                     [% CASE "check_payment" %]check
2943                     [% CASE "credit_card_payment" %]credit card (
2944                         [%- SET cc_chunks = mp.credit_card_payment.cc_number.replace(' ','').chunk(4); -%]
2945                         [%- cc_chunks.slice(0, -1+cc_chunks.max).join.replace('\S','X') -%] 
2946                         [% cc_chunks.last -%]
2947                         exp [% mp.credit_card_payment.expire_month %]/[% mp.credit_card_payment.expire_year -%]
2948                     )
2949                     [% CASE "credit_payment" %]credit
2950                     [% CASE "forgive_payment" %]forgiveness
2951                     [% CASE "goods_payment" %]goods
2952                     [% CASE "work_payment" %]work
2953                 [%- END %] on [% mp.payment_ts %] [% mp.note %]
2954         [% END %]
2955 [% END %]
2956 $$
2957     )
2958     ,(
2959         30,
2960         TRUE,
2961         1,
2962         'money.payment_receipt.print',
2963         'money.format.payment_receipt.print',
2964         'NOOP_True',
2965         'ProcessTemplate',
2966         'xact.usr',
2967         'print-on-demand',
2968 $$
2969 [%- USE date -%][%- SET user = target.0.xact.usr -%]
2970 <div style="li { padding: 8px; margin 5px; }">
2971     <div>[% date.format %]</div><br/>
2972     <ol>
2973     [% SET xact_mp_hash = {} %]
2974     [% FOR mp IN target %][%# Template is hooked around payments, but let us make the receipt focused on transactions %]
2975         [% SET xact_id = mp.xact.id %]
2976         [% IF ! xact_mp_hash.defined( xact_id ) %][% xact_mp_hash.$xact_id = { 'xact' => mp.xact, 'payments' => [] } %][% END %]
2977         [% xact_mp_hash.$xact_id.payments.push(mp) %]
2978     [% END %]
2979     [% FOR xact_id IN xact_mp_hash.keys.sort %]
2980         [% SET xact = xact_mp_hash.$xact_id.xact %]
2981         <li>Transaction ID: [% xact_id %]
2982             [% IF xact.circulation %][% helpers.get_copy_bib_basics(xact.circulation.target_copy).title %]
2983             [% ELSE %]Miscellaneous
2984             [% END %]
2985             Line item billings:<ol>
2986                 [% SET mb_type_hash = {} %]
2987                 [% FOR mb IN xact.billings %][%# Group billings by their btype %]
2988                     [% IF mb.voided == 'f' %]
2989                         [% SET mb_type = mb.btype.id %]
2990                         [% IF ! mb_type_hash.defined( mb_type ) %][% mb_type_hash.$mb_type = { 'sum' => 0.00, 'billings' => [] } %][% END %]
2991                         [% IF ! mb_type_hash.$mb_type.defined( 'first_ts' ) %][% mb_type_hash.$mb_type.first_ts = mb.billing_ts %][% END %]
2992                         [% mb_type_hash.$mb_type.last_ts = mb.billing_ts %]
2993                         [% mb_type_hash.$mb_type.sum = mb_type_hash.$mb_type.sum + mb.amount %]
2994                         [% mb_type_hash.$mb_type.billings.push( mb ) %]
2995                     [% END %]
2996                 [% END %]
2997                 [% FOR mb_type IN mb_type_hash.keys.sort %]
2998                     <li>[% IF mb_type == 1 %][%# Consolidated view of overdue billings %]
2999                         $[% mb_type_hash.$mb_type.sum %] for [% mb_type_hash.$mb_type.billings.0.btype.name %] 
3000                             on [% mb_type_hash.$mb_type.first_ts %] through [% mb_type_hash.$mb_type.last_ts %]
3001                     [% ELSE %][%# all other billings show individually %]
3002                         [% FOR mb IN mb_type_hash.$mb_type.billings %]
3003                             $[% mb.amount %] for [% mb.btype.name %] on [% mb.billing_ts %] [% mb.note %]
3004                         [% END %]
3005                     [% END %]</li>
3006                 [% END %]
3007             </ol>
3008             Line item payments:<ol>
3009                 [% FOR mp IN xact_mp_hash.$xact_id.payments %]
3010                     <li>Payment ID: [% mp.id %]
3011                         Paid [% mp.amount %] via [% SWITCH mp.payment_type -%]
3012                             [% CASE "cash_payment" %]cash
3013                             [% CASE "check_payment" %]check
3014                             [% CASE "credit_card_payment" %]credit card (
3015                                 [%- SET cc_chunks = mp.credit_card_payment.cc_number.replace(' ','').chunk(4); -%]
3016                                 [%- cc_chunks.slice(0, -1+cc_chunks.max).join.replace('\S','X') -%] 
3017                                 [% cc_chunks.last -%]
3018                                 exp [% mp.credit_card_payment.expire_month %]/[% mp.credit_card_payment.expire_year -%]
3019                             )
3020                             [% CASE "credit_payment" %]credit
3021                             [% CASE "forgive_payment" %]forgiveness
3022                             [% CASE "goods_payment" %]goods
3023                             [% CASE "work_payment" %]work
3024                         [%- END %] on [% mp.payment_ts %] [% mp.note %]
3025                     </li>
3026                 [% END %]
3027             </ol>
3028         </li>
3029     [% END %]
3030     </ol>
3031 </div>
3032 $$
3033     )
3034 ;
3035
3036 INSERT INTO action_trigger.environment (
3037         event_def,
3038         path
3039     ) VALUES -- for fleshing mp objects
3040          ( 29, 'xact')
3041         ,( 29, 'xact.usr')
3042         ,( 29, 'xact.grocery' )
3043         ,( 29, 'xact.circulation' )
3044         ,( 29, 'xact.summary' )
3045         ,( 30, 'xact')
3046         ,( 30, 'xact.usr')
3047         ,( 30, 'xact.grocery' )
3048         ,( 30, 'xact.circulation' )
3049         ,( 30, 'xact.summary' )
3050 ;
3051
3052 INSERT INTO action_trigger.cleanup ( module, description ) VALUES (
3053     'DeleteTempBiblioBucket',
3054     oils_i18n_gettext(
3055         'DeleteTempBiblioBucket',
3056         'Deletes a cbreb object used as a target if it has a btype of "temp"',
3057         'atclean',
3058         'description'
3059     )
3060 );
3061
3062 INSERT INTO action_trigger.hook (key,core_type,description,passive) VALUES (
3063         'biblio.format.record_entry.email',
3064         'cbreb', 
3065         oils_i18n_gettext(
3066             'biblio.format.record_entry.email',
3067             'An email has been requested for one or more biblio record entries.',
3068             'ath',
3069             'description'
3070         ), 
3071         FALSE
3072     )
3073     ,(
3074         'biblio.format.record_entry.print',
3075         'cbreb', 
3076         oils_i18n_gettext(
3077             'biblio.format.record_entry.print',
3078             'One or more biblio record entries need to be formatted for printing.',
3079             'ath',
3080             'description'
3081         ), 
3082         FALSE
3083     )
3084 ;
3085
3086 INSERT INTO action_trigger.event_definition (
3087         id,
3088         active,
3089         owner,
3090         name,
3091         hook,
3092         validator,
3093         reactor,
3094         cleanup_success,
3095         cleanup_failure,
3096         group_field,
3097         granularity,
3098         template
3099     ) VALUES (
3100         31,
3101         TRUE,
3102         1,
3103         'biblio.record_entry.email',
3104         'biblio.format.record_entry.email',
3105         'NOOP_True',
3106         'SendEmail',
3107         'DeleteTempBiblioBucket',
3108         'DeleteTempBiblioBucket',
3109         'owner',
3110         NULL,
3111 $$
3112 [%- USE date -%]
3113 [%- SET user = target.0.owner -%]
3114 To: [%- params.recipient_email || user.email %]
3115 From: [%- params.sender_email || default_sender %]
3116 Subject: Bibliographic Records
3117
3118     [% FOR cbreb IN target %]
3119     [% FOR cbrebi IN cbreb.items %]
3120         Bib ID# [% cbrebi.target_biblio_record_entry.id %] ISBN: [% crebi.target_biblio_record_entry.simple_record.isbn %]
3121         Title: [% cbrebi.target_biblio_record_entry.simple_record.title %]
3122         Author: [% cbrebi.target_biblio_record_entry.simple_record.author %]
3123         Publication Year: [% cbrebi.target_biblio_record_entry.simple_record.pubdate %]
3124
3125     [% END %]
3126     [% END %]
3127 $$
3128     )
3129     ,(
3130         32,
3131         TRUE,
3132         1,
3133         'biblio.record_entry.print',
3134         'biblio.format.record_entry.print',
3135         'NOOP_True',
3136         'ProcessTemplate',
3137         'DeleteTempBiblioBucket',
3138         'DeleteTempBiblioBucket',
3139         'owner',
3140         'print-on-demand',
3141 $$
3142 [%- USE date -%]
3143 <div>
3144     <style> li { padding: 8px; margin 5px; }</style>
3145     <ol>
3146     [% FOR cbreb IN target %]
3147     [% FOR cbrebi IN cbreb.items %]
3148         <li>Bib ID# [% cbrebi.target_biblio_record_entry.id %] ISBN: [% crebi.target_biblio_record_entry.simple_record.isbn %]<br />
3149             Title: [% cbrebi.target_biblio_record_entry.simple_record.title %]<br />
3150             Author: [% cbrebi.target_biblio_record_entry.simple_record.author %]<br />
3151             Publication Year: [% cbrebi.target_biblio_record_entry.simple_record.pubdate %]
3152         </li>
3153     [% END %]
3154     [% END %]
3155     </ol>
3156 </div>
3157 $$
3158     )
3159 ;
3160
3161 INSERT INTO action_trigger.environment (
3162         event_def,
3163         path
3164     ) VALUES -- for fleshing cbreb objects
3165          ( 31, 'owner' )
3166         ,( 31, 'items' )
3167         ,( 31, 'items.target_biblio_record_entry' )
3168         ,( 31, 'items.target_biblio_record_entry.simple_record' )
3169         ,( 31, 'items.target_biblio_record_entry.call_numbers' )
3170         ,( 31, 'items.target_biblio_record_entry.fixed_fields' )
3171         ,( 31, 'items.target_biblio_record_entry.notes' )
3172         ,( 31, 'items.target_biblio_record_entry.full_record_entries' )
3173         ,( 32, 'owner' )
3174         ,( 32, 'items' )
3175         ,( 32, 'items.target_biblio_record_entry' )
3176         ,( 32, 'items.target_biblio_record_entry.simple_record' )
3177         ,( 32, 'items.target_biblio_record_entry.call_numbers' )
3178         ,( 32, 'items.target_biblio_record_entry.fixed_fields' )
3179         ,( 32, 'items.target_biblio_record_entry.notes' )
3180         ,( 32, 'items.target_biblio_record_entry.full_record_entries' )
3181 ;
3182
3183 INSERT INTO action_trigger.environment (
3184         event_def,
3185         path
3186     ) VALUES -- for fleshing mp objects
3187          ( 29, 'credit_card_payment')
3188         ,( 29, 'xact.billings')
3189         ,( 29, 'xact.billings.btype')
3190         ,( 30, 'credit_card_payment')
3191         ,( 30, 'xact.billings')
3192         ,( 30, 'xact.billings.btype')
3193 ;
3194
3195 INSERT INTO action_trigger.hook (key,core_type,description,passive) VALUES 
3196     (   'circ.format.missing_pieces.slip.print',
3197         'circ', 
3198         oils_i18n_gettext(
3199             'circ.format.missing_pieces.slip.print',
3200             'A missing pieces slip needs to be formatted for printing.',
3201             'ath',
3202             'description'
3203         ), 
3204         FALSE
3205     )
3206     ,(  'circ.format.missing_pieces.letter.print',
3207         'circ', 
3208         oils_i18n_gettext(
3209             'circ.format.missing_pieces.letter.print',
3210             'A missing pieces patron letter needs to be formatted for printing.',
3211             'ath',
3212             'description'
3213         ), 
3214         FALSE
3215     )
3216 ;
3217
3218 INSERT INTO action_trigger.event_definition (
3219         id,
3220         active,
3221         owner,
3222         name,
3223         hook,
3224         validator,
3225         reactor,
3226         group_field,
3227         granularity,
3228         template
3229     ) VALUES (
3230         33,
3231         TRUE,
3232         1,
3233         'circ.missing_pieces.slip.print',
3234         'circ.format.missing_pieces.slip.print',
3235         'NOOP_True',
3236         'ProcessTemplate',
3237         'usr',
3238         'print-on-demand',
3239 $$
3240 [%- USE date -%]
3241 [%- SET user = target.0.usr -%]
3242 <div style="li { padding: 8px; margin 5px; }">
3243     <div>[% date.format %]</div><br/>
3244     Missing pieces for:
3245     <ol>
3246     [% FOR circ IN target %]
3247         <li>Barcode: [% circ.target_copy.barcode %] Transaction ID: [% circ.id %] Due: [% circ.due_date.format %]<br />
3248             [% helpers.get_copy_bib_basics(circ.target_copy.id).title %]
3249         </li>
3250     [% END %]
3251     </ol>
3252 </div>
3253 $$
3254     )
3255     ,(
3256         34,
3257         TRUE,
3258         1,
3259         'circ.missing_pieces.letter.print',
3260         'circ.format.missing_pieces.letter.print',
3261         'NOOP_True',
3262         'ProcessTemplate',
3263         'usr',
3264         'print-on-demand',
3265 $$
3266 [%- USE date -%]
3267 [%- SET user = target.0.usr -%]
3268 [% date.format %]
3269 Dear [% user.prefix %] [% user.first_given_name %] [% user.family_name %],
3270
3271 We are missing pieces for the following returned items:
3272 [% FOR circ IN target %]
3273 Barcode: [% circ.target_copy.barcode %] Transaction ID: [% circ.id %] Due: [% circ.due_date.format %]
3274 [% helpers.get_copy_bib_basics(circ.target_copy.id).title %]
3275 [% END %]
3276
3277 Please return these pieces as soon as possible.
3278
3279 Thanks!
3280
3281 Library Staff
3282 $$
3283     )
3284 ;
3285
3286 INSERT INTO action_trigger.environment (
3287         event_def,
3288         path
3289     ) VALUES -- for fleshing circ objects
3290          ( 33, 'usr')
3291         ,( 33, 'target_copy')
3292         ,( 33, 'target_copy.circ_lib')
3293         ,( 33, 'target_copy.circ_lib.mailing_address')
3294         ,( 33, 'target_copy.circ_lib.billing_address')
3295         ,( 33, 'target_copy.call_number')
3296         ,( 33, 'target_copy.call_number.owning_lib')
3297         ,( 33, 'target_copy.call_number.owning_lib.mailing_address')
3298         ,( 33, 'target_copy.call_number.owning_lib.billing_address')
3299         ,( 33, 'circ_lib')
3300         ,( 33, 'circ_lib.mailing_address')
3301         ,( 33, 'circ_lib.billing_address')
3302         ,( 34, 'usr')
3303         ,( 34, 'target_copy')
3304         ,( 34, 'target_copy.circ_lib')
3305         ,( 34, 'target_copy.circ_lib.mailing_address')
3306         ,( 34, 'target_copy.circ_lib.billing_address')
3307         ,( 34, 'target_copy.call_number')
3308         ,( 34, 'target_copy.call_number.owning_lib')
3309         ,( 34, 'target_copy.call_number.owning_lib.mailing_address')
3310         ,( 34, 'target_copy.call_number.owning_lib.billing_address')
3311         ,( 34, 'circ_lib')
3312         ,( 34, 'circ_lib.mailing_address')
3313         ,( 34, 'circ_lib.billing_address')
3314 ;
3315
3316 INSERT INTO action_trigger.hook (key,core_type,description,passive) 
3317     VALUES (   
3318         'ahr.format.pull_list',
3319         'ahr', 
3320         oils_i18n_gettext(
3321             'ahr.format.pull_list',
3322             'Format holds pull list for printing',
3323             'ath',
3324             'description'
3325         ), 
3326         FALSE
3327     );
3328
3329 INSERT INTO action_trigger.event_definition (
3330         id,
3331         active,
3332         owner,
3333         name,
3334         hook,
3335         validator,
3336         reactor,
3337         group_field,
3338         granularity,
3339         template
3340     ) VALUES (
3341         35,
3342         TRUE,
3343         1,
3344         'Holds Pull List',
3345         'ahr.format.pull_list',
3346         'NOOP_True',
3347         'ProcessTemplate',
3348         'pickup_lib',
3349         'print-on-demand',
3350 $$
3351 [%- USE date -%]
3352 <style>
3353     table { border-collapse: collapse; } 
3354     td { padding: 5px; border-bottom: 1px solid #888; } 
3355     th { font-weight: bold; }
3356 </style>
3357 [% 
3358     # Sort the holds into copy-location buckets
3359     # In the main print loop, sort each bucket by callnumber before printing
3360     SET holds_list = [];
3361     SET loc_data = [];
3362     SET current_location = target.0.current_copy.location.id;
3363     FOR hold IN target;
3364         IF current_location != hold.current_copy.location.id;
3365             SET current_location = hold.current_copy.location.id;
3366             holds_list.push(loc_data);
3367             SET loc_data = [];
3368         END;
3369         SET hold_data = {
3370             'hold' => hold,
3371             'callnumber' => hold.current_copy.call_number.label
3372         };
3373         loc_data.push(hold_data);
3374     END;
3375     holds_list.push(loc_data)
3376 %]
3377 <table>
3378     <thead>
3379         <tr>
3380             <th>Title</th>
3381             <th>Author</th>
3382             <th>Shelving Location</th>
3383             <th>Call Number</th>
3384             <th>Barcode</th>
3385             <th>Patron</th>
3386         </tr>
3387     </thead>
3388     <tbody>
3389     [% FOR loc_data IN holds_list  %]
3390         [% FOR hold_data IN loc_data.sort('callnumber') %]
3391             [% 
3392                 SET hold = hold_data.hold;
3393                 SET copy_data = helpers.get_copy_bib_basics(hold.current_copy.id);
3394             %]
3395             <tr>
3396                 <td>[% copy_data.title | truncate %]</td>
3397                 <td>[% copy_data.author | truncate %]</td>
3398                 <td>[% hold.current_copy.location.name %]</td>
3399                 <td>[% hold.current_copy.call_number.label %]</td>
3400                 <td>[% hold.current_copy.barcode %]</td>
3401                 <td>[% hold.usr.card.barcode %]</td>
3402             </tr>
3403         [% END %]
3404     [% END %]
3405     <tbody>
3406 </table>
3407 $$
3408 );
3409
3410 INSERT INTO action_trigger.environment (
3411         event_def,
3412         path
3413     ) VALUES
3414         (35, 'current_copy.location'),
3415         (35, 'current_copy.call_number'),
3416         (35, 'usr.card'),
3417         (35, 'pickup_lib')
3418 ;
3419
3420 -- Create the query schema, and the tables and views therein
3421
3422 DROP SCHEMA IF EXISTS sql CASCADE;
3423 DROP SCHEMA IF EXISTS query CASCADE;
3424
3425 CREATE SCHEMA query;
3426
3427 CREATE TABLE query.datatype (
3428         id              SERIAL            PRIMARY KEY,
3429         datatype_name   TEXT              NOT NULL UNIQUE,
3430         is_numeric      BOOL              NOT NULL DEFAULT FALSE,
3431         is_composite    BOOL              NOT NULL DEFAULT FALSE,
3432         CONSTRAINT qdt_comp_not_num CHECK
3433         ( is_numeric IS FALSE OR is_composite IS FALSE )
3434 );
3435
3436 -- Define the most common datatypes in query.datatype.  Note that none of
3437 -- these stock datatypes specifies a width or precision.
3438
3439 -- Also: set the sequence for query.datatype to 1000, leaving plenty of
3440 -- room for more stock datatypes if we ever want to add them.
3441
3442 SELECT setval( 'query.datatype_id_seq', 1000 );
3443
3444 INSERT INTO query.datatype (id, datatype_name, is_numeric )
3445   VALUES (1, 'SMALLINT', true);
3446  
3447 INSERT INTO query.datatype (id, datatype_name, is_numeric )
3448   VALUES (2, 'INTEGER', true);
3449  
3450 INSERT INTO query.datatype (id, datatype_name, is_numeric )
3451   VALUES (3, 'BIGINT', true);
3452  
3453 INSERT INTO query.datatype (id, datatype_name, is_numeric )
3454   VALUES (4, 'DECIMAL', true);
3455  
3456 INSERT INTO query.datatype (id, datatype_name, is_numeric )
3457   VALUES (5, 'NUMERIC', true);
3458  
3459 INSERT INTO query.datatype (id, datatype_name, is_numeric )
3460   VALUES (6, 'REAL', true);
3461  
3462 INSERT INTO query.datatype (id, datatype_name, is_numeric )
3463   VALUES (7, 'DOUBLE PRECISION', true);
3464  
3465 INSERT INTO query.datatype (id, datatype_name, is_numeric )
3466   VALUES (8, 'SERIAL', true);
3467  
3468 INSERT INTO query.datatype (id, datatype_name, is_numeric )
3469   VALUES (9, 'BIGSERIAL', true);
3470  
3471 INSERT INTO query.datatype (id, datatype_name, is_numeric )
3472   VALUES (10, 'MONEY', false);
3473  
3474 INSERT INTO query.datatype (id, datatype_name, is_numeric )
3475   VALUES (11, 'VARCHAR', false);
3476  
3477 INSERT INTO query.datatype (id, datatype_name, is_numeric )
3478   VALUES (12, 'CHAR', false);
3479  
3480 INSERT INTO query.datatype (id, datatype_name, is_numeric )
3481   VALUES (13, 'TEXT', false);
3482  
3483 INSERT INTO query.datatype (id, datatype_name, is_numeric )
3484   VALUES (14, '"char"', false);
3485  
3486 INSERT INTO query.datatype (id, datatype_name, is_numeric )
3487   VALUES (15, 'NAME', false);
3488  
3489 INSERT INTO query.datatype (id, datatype_name, is_numeric )
3490   VALUES (16, 'BYTEA', false);
3491  
3492 INSERT INTO query.datatype (id, datatype_name, is_numeric )
3493   VALUES (17, 'TIMESTAMP WITHOUT TIME ZONE', false);
3494  
3495 INSERT INTO query.datatype (id, datatype_name, is_numeric )
3496   VALUES (18, 'TIMESTAMP WITH TIME ZONE', false);
3497  
3498 INSERT INTO query.datatype (id, datatype_name, is_numeric )
3499   VALUES (19, 'DATE', false);
3500  
3501 INSERT INTO query.datatype (id, datatype_name, is_numeric )
3502   VALUES (20, 'TIME WITHOUT TIME ZONE', false);
3503  
3504 INSERT INTO query.datatype (id, datatype_name, is_numeric )
3505   VALUES (21, 'TIME WITH TIME ZONE', false);
3506  
3507 INSERT INTO query.datatype (id, datatype_name, is_numeric )
3508   VALUES (22, 'INTERVAL', false);
3509  
3510 INSERT INTO query.datatype (id, datatype_name, is_numeric )
3511   VALUES (23, 'BOOLEAN', false);
3512  
3513 CREATE TABLE query.subfield (
3514         id              SERIAL            PRIMARY KEY,
3515         composite_type  INT               NOT NULL
3516                                           REFERENCES query.datatype(id)
3517                                           ON DELETE CASCADE
3518                                           DEFERRABLE INITIALLY DEFERRED,
3519         seq_no          INT               NOT NULL
3520                                           CONSTRAINT qsf_pos_seq_no
3521                                           CHECK( seq_no > 0 ),
3522         subfield_type   INT               NOT NULL
3523                                           REFERENCES query.datatype(id)
3524                                           DEFERRABLE INITIALLY DEFERRED,
3525         CONSTRAINT qsf_datatype_seq_no UNIQUE (composite_type, seq_no)
3526 );
3527
3528 CREATE TABLE query.function_sig (
3529         id              SERIAL            PRIMARY KEY,
3530         function_name   TEXT              NOT NULL,
3531         return_type     INT               REFERENCES query.datatype(id)
3532                                           DEFERRABLE INITIALLY DEFERRED,
3533         is_aggregate    BOOL              NOT NULL DEFAULT FALSE,
3534         CONSTRAINT qfd_rtn_or_aggr CHECK
3535         ( return_type IS NULL OR is_aggregate = FALSE )
3536 );
3537
3538 CREATE INDEX query_function_sig_name_idx 
3539         ON query.function_sig (function_name);
3540
3541 CREATE TABLE query.function_param_def (
3542         id              SERIAL            PRIMARY KEY,
3543         function_id     INT               NOT NULL
3544                                           REFERENCES query.function_sig( id )
3545                                           ON DELETE CASCADE
3546                                           DEFERRABLE INITIALLY DEFERRED,
3547         seq_no          INT               NOT NULL
3548                                           CONSTRAINT qfpd_pos_seq_no CHECK
3549                                           ( seq_no > 0 ),
3550         datatype        INT               NOT NULL
3551                                           REFERENCES query.datatype( id )
3552                                           DEFERRABLE INITIALLY DEFERRED,
3553         CONSTRAINT qfpd_function_param_seq UNIQUE (function_id, seq_no)
3554 );
3555
3556 CREATE TABLE  query.stored_query (
3557         id            SERIAL         PRIMARY KEY,
3558         type          TEXT           NOT NULL CONSTRAINT query_type CHECK
3559                                      ( type IN ( 'SELECT', 'UNION', 'INTERSECT', 'EXCEPT' ) ),
3560         use_all       BOOLEAN        NOT NULL DEFAULT FALSE,
3561         use_distinct  BOOLEAN        NOT NULL DEFAULT FALSE,
3562         from_clause   INT            , --REFERENCES query.from_clause
3563                                      --DEFERRABLE INITIALLY DEFERRED,
3564         where_clause  INT            , --REFERENCES query.expression
3565                                      --DEFERRABLE INITIALLY DEFERRED,
3566         having_clause INT            , --REFERENCES query.expression
3567                                      --DEFERRABLE INITIALLY DEFERRED
3568         limit_count   INT            , --REFERENCES query.expression( id )
3569                                      --DEFERRABLE INITIALLY DEFERRED,
3570         offset_count  INT            --REFERENCES query.expression( id )
3571                                      --DEFERRABLE INITIALLY DEFERRED
3572 );
3573
3574 -- (Foreign keys to be defined later after other tables are created)
3575
3576 CREATE TABLE query.query_sequence (
3577         id              SERIAL            PRIMARY KEY,
3578         parent_query    INT               NOT NULL
3579                                           REFERENCES query.stored_query
3580                                                                           ON DELETE CASCADE
3581                                                                           DEFERRABLE INITIALLY DEFERRED,
3582         seq_no          INT               NOT NULL,
3583         child_query     INT               NOT NULL
3584                                           REFERENCES query.stored_query
3585                                                                           ON DELETE CASCADE
3586                                                                           DEFERRABLE INITIALLY DEFERRED,
3587         CONSTRAINT query_query_seq UNIQUE( parent_query, seq_no )
3588 );
3589
3590 CREATE TABLE query.bind_variable (
3591         name          TEXT             PRIMARY KEY,
3592         type          TEXT             NOT NULL
3593                                            CONSTRAINT bind_variable_type CHECK
3594                                            ( type in ( 'string', 'number', 'string_list', 'number_list' )),
3595         description   TEXT             NOT NULL,
3596         default_value TEXT,            -- to be encoded in JSON
3597         label         TEXT             NOT NULL
3598 );
3599
3600 CREATE TABLE query.expression (
3601         id            SERIAL        PRIMARY KEY,
3602         type          TEXT          NOT NULL CONSTRAINT expression_type CHECK
3603                                     ( type IN (
3604                                     'xbet',    -- between
3605                                     'xbind',   -- bind variable
3606                                     'xbool',   -- boolean
3607                                     'xcase',   -- case
3608                                     'xcast',   -- cast
3609                                     'xcol',    -- column
3610                                     'xex',     -- exists
3611                                     'xfunc',   -- function
3612                                     'xin',     -- in
3613                                     'xisnull', -- is null
3614                                     'xnull',   -- null
3615                                     'xnum',    -- number
3616                                     'xop',     -- operator
3617                                     'xser',    -- series
3618                                     'xstr',    -- string
3619                                     'xsubq'    -- subquery
3620                                                                 ) ),
3621         parenthesize  BOOL          NOT NULL DEFAULT FALSE,
3622         parent_expr   INT           REFERENCES query.expression
3623                                     ON DELETE CASCADE
3624                                     DEFERRABLE INITIALLY DEFERRED,
3625         seq_no        INT           NOT NULL DEFAULT 1,
3626         literal       TEXT,
3627         table_alias   TEXT,
3628         column_name   TEXT,
3629         left_operand  INT           REFERENCES query.expression
3630                                     DEFERRABLE INITIALLY DEFERRED,
3631         operator      TEXT,
3632         right_operand INT           REFERENCES query.expression
3633                                     DEFERRABLE INITIALLY DEFERRED,
3634         function_id   INT           REFERENCES query.function_sig
3635                                     DEFERRABLE INITIALLY DEFERRED,
3636         subquery      INT           REFERENCES query.stored_query
3637                                     DEFERRABLE INITIALLY DEFERRED,
3638         cast_type     INT           REFERENCES query.datatype
3639                                     DEFERRABLE INITIALLY DEFERRED,
3640         negate        BOOL          NOT NULL DEFAULT FALSE,
3641         bind_variable TEXT          REFERENCES query.bind_variable
3642                                         DEFERRABLE INITIALLY DEFERRED
3643 );
3644
3645 CREATE UNIQUE INDEX query_expr_parent_seq
3646         ON query.expression( parent_expr, seq_no )
3647         WHERE parent_expr IS NOT NULL;
3648
3649 -- Due to some circular references, the following foreign key definitions
3650 -- had to be deferred until query.expression existed:
3651
3652 ALTER TABLE query.stored_query
3653         ADD FOREIGN KEY ( where_clause )
3654         REFERENCES query.expression( id )
3655         DEFERRABLE INITIALLY DEFERRED;
3656
3657 ALTER TABLE query.stored_query
3658         ADD FOREIGN KEY ( having_clause )
3659         REFERENCES query.expression( id )
3660         DEFERRABLE INITIALLY DEFERRED;
3661
3662 ALTER TABLE query.stored_query
3663     ADD FOREIGN KEY ( limit_count )
3664     REFERENCES query.expression( id )
3665     DEFERRABLE INITIALLY DEFERRED;
3666
3667 ALTER TABLE query.stored_query
3668     ADD FOREIGN KEY ( offset_count )
3669     REFERENCES query.expression( id )
3670     DEFERRABLE INITIALLY DEFERRED;
3671
3672 CREATE TABLE query.case_branch (
3673         id            SERIAL        PRIMARY KEY,
3674         parent_expr   INT           NOT NULL REFERENCES query.expression
3675                                     ON DELETE CASCADE
3676                                     DEFERRABLE INITIALLY DEFERRED,
3677         seq_no        INT           NOT NULL,
3678         condition     INT           REFERENCES query.expression
3679                                     DEFERRABLE INITIALLY DEFERRED,
3680         result        INT           NOT NULL REFERENCES query.expression
3681                                     DEFERRABLE INITIALLY DEFERRED,
3682         CONSTRAINT case_branch_parent_seq UNIQUE (parent_expr, seq_no)
3683 );
3684
3685 CREATE TABLE query.from_relation (
3686         id               SERIAL        PRIMARY KEY,
3687         type             TEXT          NOT NULL CONSTRAINT relation_type CHECK (
3688                                            type IN ( 'RELATION', 'SUBQUERY', 'FUNCTION' ) ),
3689         table_name       TEXT,
3690         class_name       TEXT,
3691         subquery         INT           REFERENCES query.stored_query,
3692         function_call    INT           REFERENCES query.expression,
3693         table_alias      TEXT,
3694         parent_relation  INT           REFERENCES query.from_relation
3695                                        ON DELETE CASCADE
3696                                        DEFERRABLE INITIALLY DEFERRED,
3697         seq_no           INT           NOT NULL DEFAULT 1,
3698         join_type        TEXT          CONSTRAINT good_join_type CHECK (
3699                                            join_type IS NULL OR join_type IN
3700                                            ( 'INNER', 'LEFT', 'RIGHT', 'FULL' )
3701                                        ),
3702         on_clause        INT           REFERENCES query.expression
3703                                        DEFERRABLE INITIALLY DEFERRED,
3704         CONSTRAINT join_or_core CHECK (
3705         ( parent_relation IS NULL AND join_type IS NULL
3706           AND on_clause IS NULL )
3707         OR
3708         ( parent_relation IS NOT NULL AND join_type IS NOT NULL
3709           AND on_clause IS NOT NULL )
3710         )
3711 );
3712
3713 CREATE UNIQUE INDEX from_parent_seq
3714         ON query.from_relation( parent_relation, seq_no )
3715         WHERE parent_relation IS NOT NULL;
3716
3717 -- The following foreign key had to be deferred until
3718 -- query.from_relation existed
3719
3720 ALTER TABLE query.stored_query
3721         ADD FOREIGN KEY (from_clause)
3722         REFERENCES query.from_relation
3723         DEFERRABLE INITIALLY DEFERRED;
3724
3725 CREATE TABLE query.record_column (
3726         id            SERIAL            PRIMARY KEY,
3727         from_relation INT               NOT NULL REFERENCES query.from_relation
3728                                         ON DELETE CASCADE
3729                                         DEFERRABLE INITIALLY DEFERRED,
3730         seq_no        INT               NOT NULL,
3731         column_name   TEXT              NOT NULL,
3732         column_type   INT               NOT NULL REFERENCES query.datatype
3733                                         ON DELETE CASCADE
3734                                                                         DEFERRABLE INITIALLY DEFERRED,
3735         CONSTRAINT column_sequence UNIQUE (from_relation, seq_no)
3736 );
3737
3738 CREATE TABLE query.select_item (
3739         id               SERIAL         PRIMARY KEY,
3740         stored_query     INT            NOT NULL REFERENCES query.stored_query
3741                                         ON DELETE CASCADE
3742                                         DEFERRABLE INITIALLY DEFERRED,
3743         seq_no           INT            NOT NULL,
3744         expression       INT            NOT NULL REFERENCES query.expression
3745                                         DEFERRABLE INITIALLY DEFERRED,
3746         column_alias     TEXT,
3747         grouped_by       BOOL           NOT NULL DEFAULT FALSE,
3748         CONSTRAINT select_sequence UNIQUE( stored_query, seq_no )
3749 );
3750
3751 CREATE TABLE query.order_by_item (
3752         id               SERIAL         PRIMARY KEY,
3753         stored_query     INT            NOT NULL REFERENCES query.stored_query
3754                                         ON DELETE CASCADE
3755                                         DEFERRABLE INITIALLY DEFERRED,
3756         seq_no           INT            NOT NULL,
3757         expression       INT            NOT NULL REFERENCES query.expression
3758                                         ON DELETE CASCADE
3759                                         DEFERRABLE INITIALLY DEFERRED,
3760         CONSTRAINT order_by_sequence UNIQUE( stored_query, seq_no )
3761 );
3762
3763 ------------------------------------------------------------
3764 -- Create updatable views for different kinds of expressions
3765 ------------------------------------------------------------
3766
3767 -- Create updatable view for BETWEEN expressions
3768
3769 CREATE OR REPLACE VIEW query.expr_xbet AS
3770     SELECT
3771                 id,
3772                 parenthesize,
3773                 parent_expr,
3774                 seq_no,
3775                 left_operand,
3776                 negate
3777     FROM
3778         query.expression
3779     WHERE
3780         type = 'xbet';
3781
3782 CREATE OR REPLACE RULE query_expr_xbet_insert_rule AS
3783     ON INSERT TO query.expr_xbet
3784     DO INSTEAD
3785     INSERT INTO query.expression (
3786                 id,
3787                 type,
3788                 parenthesize,
3789                 parent_expr,
3790                 seq_no,
3791                 left_operand,
3792                 negate
3793     ) VALUES (
3794         COALESCE(NEW.id, NEXTVAL('query.expression_id_seq'::REGCLASS)),
3795         'xbet',
3796         COALESCE(NEW.parenthesize, FALSE),
3797         NEW.parent_expr,
3798         COALESCE(NEW.seq_no, 1),
3799                 NEW.left_operand,
3800                 COALESCE(NEW.negate, false)
3801     );
3802
3803 CREATE OR REPLACE RULE query_expr_xbet_update_rule AS
3804     ON UPDATE TO query.expr_xbet
3805     DO INSTEAD
3806     UPDATE query.expression SET
3807         id = NEW.id,
3808         parenthesize = NEW.parenthesize,
3809         parent_expr = NEW.parent_expr,
3810         seq_no = NEW.seq_no,
3811                 left_operand = NEW.left_operand,
3812                 negate = NEW.negate
3813     WHERE
3814         id = OLD.id;
3815
3816 CREATE OR REPLACE RULE query_expr_xbet_delete_rule AS
3817     ON DELETE TO query.expr_xbet
3818     DO INSTEAD
3819     DELETE FROM query.expression WHERE id = OLD.id;
3820
3821 -- Create updatable view for bind variable expressions
3822
3823 CREATE OR REPLACE VIEW query.expr_xbind AS
3824     SELECT
3825                 id,
3826                 parenthesize,
3827                 parent_expr,
3828                 seq_no,
3829                 bind_variable
3830     FROM
3831         query.expression
3832     WHERE
3833         type = 'xbind';
3834
3835 CREATE OR REPLACE RULE query_expr_xbind_insert_rule AS
3836     ON INSERT TO query.expr_xbind
3837     DO INSTEAD
3838     INSERT INTO query.expression (
3839                 id,
3840                 type,
3841                 parenthesize,
3842                 parent_expr,
3843                 seq_no,
3844                 bind_variable
3845     ) VALUES (
3846         COALESCE(NEW.id, NEXTVAL('query.expression_id_seq'::REGCLASS)),
3847         'xbind',
3848         COALESCE(NEW.parenthesize, FALSE),
3849         NEW.parent_expr,
3850         COALESCE(NEW.seq_no, 1),
3851                 NEW.bind_variable
3852     );
3853
3854 CREATE OR REPLACE RULE query_expr_xbind_update_rule AS
3855     ON UPDATE TO query.expr_xbind
3856     DO INSTEAD
3857     UPDATE query.expression SET
3858         id = NEW.id,
3859         parenthesize = NEW.parenthesize,
3860         parent_expr = NEW.parent_expr,
3861         seq_no = NEW.seq_no,
3862                 bind_variable = NEW.bind_variable
3863     WHERE
3864         id = OLD.id;
3865
3866 CREATE OR REPLACE RULE query_expr_xbind_delete_rule AS
3867     ON DELETE TO query.expr_xbind
3868     DO INSTEAD
3869     DELETE FROM query.expression WHERE id = OLD.id;
3870
3871 -- Create updatable view for boolean expressions
3872
3873 CREATE OR REPLACE VIEW query.expr_xbool AS
3874     SELECT
3875                 id,
3876                 parenthesize,
3877                 parent_expr,
3878                 seq_no,
3879                 literal,
3880                 negate
3881     FROM
3882         query.expression
3883     WHERE
3884         type = 'xbool';
3885
3886 CREATE OR REPLACE RULE query_expr_xbool_insert_rule AS
3887     ON INSERT TO query.expr_xbool
3888     DO INSTEAD
3889     INSERT INTO query.expression (
3890                 id,
3891                 type,
3892                 parenthesize,
3893                 parent_expr,
3894                 seq_no,
3895                 literal,
3896                 negate
3897     ) VALUES (
3898         COALESCE(NEW.id, NEXTVAL('query.expression_id_seq'::REGCLASS)),
3899         'xbool',
3900         COALESCE(NEW.parenthesize, FALSE),
3901         NEW.parent_expr,
3902         COALESCE(NEW.seq_no, 1),
3903         NEW.literal,
3904                 COALESCE(NEW.negate, false)
3905     );
3906
3907 CREATE OR REPLACE RULE query_expr_xbool_update_rule AS
3908     ON UPDATE TO query.expr_xbool
3909     DO INSTEAD
3910     UPDATE query.expression SET
3911         id = NEW.id,
3912         parenthesize = NEW.parenthesize,
3913         parent_expr = NEW.parent_expr,
3914         seq_no = NEW.seq_no,
3915         literal = NEW.literal,
3916                 negate = NEW.negate
3917     WHERE
3918         id = OLD.id;
3919
3920 CREATE OR REPLACE RULE query_expr_xbool_delete_rule AS
3921     ON DELETE TO query.expr_xbool
3922     DO INSTEAD
3923     DELETE FROM query.expression WHERE id = OLD.id;
3924
3925 -- Create updatable view for CASE expressions
3926
3927 CREATE OR REPLACE VIEW query.expr_xcase AS
3928     SELECT
3929                 id,
3930                 parenthesize,
3931                 parent_expr,
3932                 seq_no,
3933                 left_operand,
3934                 negate
3935     FROM
3936         query.expression
3937     WHERE
3938         type = 'xcase';
3939
3940 CREATE OR REPLACE RULE query_expr_xcase_insert_rule AS
3941     ON INSERT TO query.expr_xcase
3942     DO INSTEAD
3943     INSERT INTO query.expression (
3944                 id,
3945                 type,
3946                 parenthesize,
3947                 parent_expr,
3948                 seq_no,
3949                 left_operand,
3950                 negate
3951     ) VALUES (
3952         COALESCE(NEW.id, NEXTVAL('query.expression_id_seq'::REGCLASS)),
3953         'xcase',
3954         COALESCE(NEW.parenthesize, FALSE),
3955         NEW.parent_expr,
3956         COALESCE(NEW.seq_no, 1),
3957                 NEW.left_operand,
3958                 COALESCE(NEW.negate, false)
3959     );
3960
3961 CREATE OR REPLACE RULE query_expr_xcase_update_rule AS
3962     ON UPDATE TO query.expr_xcase
3963     DO INSTEAD
3964     UPDATE query.expression SET
3965         id = NEW.id,
3966         parenthesize = NEW.parenthesize,
3967         parent_expr = NEW.parent_expr,
3968         seq_no = NEW.seq_no,
3969                 left_operand = NEW.left_operand,
3970                 negate = NEW.negate
3971     WHERE
3972         id = OLD.id;
3973
3974 CREATE OR REPLACE RULE query_expr_xcase_delete_rule AS
3975     ON DELETE TO query.expr_xcase
3976     DO INSTEAD
3977     DELETE FROM query.expression WHERE id = OLD.id;
3978
3979 -- Create updatable view for cast expressions
3980
3981 CREATE OR REPLACE VIEW query.expr_xcast AS
3982     SELECT
3983                 id,
3984                 parenthesize,
3985                 parent_expr,
3986                 seq_no,
3987                 left_operand,
3988                 cast_type,
3989                 negate
3990     FROM
3991         query.expression
3992     WHERE
3993         type = 'xcast';
3994
3995 CREATE OR REPLACE RULE query_expr_xcast_insert_rule AS
3996     ON INSERT TO query.expr_xcast
3997     DO INSTEAD
3998     INSERT INTO query.expression (
3999         id,
4000         type,
4001         parenthesize,
4002         parent_expr,
4003         seq_no,
4004         left_operand,
4005         cast_type,
4006         negate
4007     ) VALUES (
4008         COALESCE(NEW.id, NEXTVAL('query.expression_id_seq'::REGCLASS)),
4009         'xcast',
4010         COALESCE(NEW.parenthesize, FALSE),
4011         NEW.parent_expr,
4012         COALESCE(NEW.seq_no, 1),
4013         NEW.left_operand,
4014         NEW.cast_type,
4015         COALESCE(NEW.negate, false)
4016     );
4017
4018 CREATE OR REPLACE RULE query_expr_xcast_update_rule AS
4019     ON UPDATE TO query.expr_xcast
4020     DO INSTEAD
4021     UPDATE query.expression SET
4022         id = NEW.id,
4023         parenthesize = NEW.parenthesize,
4024         parent_expr = NEW.parent_expr,
4025         seq_no = NEW.seq_no,
4026                 left_operand = NEW.left_operand,
4027                 cast_type = NEW.cast_type,
4028                 negate = NEW.negate
4029     WHERE
4030         id = OLD.id;
4031
4032 CREATE OR REPLACE RULE query_expr_xcast_delete_rule AS
4033     ON DELETE TO query.expr_xcast
4034     DO INSTEAD
4035     DELETE FROM query.expression WHERE id = OLD.id;
4036
4037 -- Create updatable view for column expressions
4038
4039 CREATE OR REPLACE VIEW query.expr_xcol AS
4040     SELECT
4041                 id,
4042                 parenthesize,
4043                 parent_expr,
4044                 seq_no,
4045                 table_alias,
4046                 column_name,
4047                 negate
4048     FROM
4049         query.expression
4050     WHERE
4051         type = 'xcol';
4052
4053 CREATE OR REPLACE RULE query_expr_xcol_insert_rule AS
4054     ON INSERT TO query.expr_xcol
4055     DO INSTEAD
4056     INSERT INTO query.expression (
4057                 id,
4058                 type,
4059                 parenthesize,
4060                 parent_expr,
4061                 seq_no,
4062                 table_alias,
4063                 column_name,
4064                 negate
4065     ) VALUES (
4066         COALESCE(NEW.id, NEXTVAL('query.expression_id_seq'::REGCLASS)),
4067         'xcol',
4068         COALESCE(NEW.parenthesize, FALSE),
4069         NEW.parent_expr,
4070         COALESCE(NEW.seq_no, 1),
4071                 NEW.table_alias,
4072                 NEW.column_name,
4073                 COALESCE(NEW.negate, false)
4074     );
4075
4076 CREATE OR REPLACE RULE query_expr_xcol_update_rule AS
4077     ON UPDATE TO query.expr_xcol
4078     DO INSTEAD
4079     UPDATE query.expression SET
4080         id = NEW.id,
4081         parenthesize = NEW.parenthesize,
4082         parent_expr = NEW.parent_expr,
4083         seq_no = NEW.seq_no,
4084                 table_alias = NEW.table_alias,
4085                 column_name = NEW.column_name,
4086                 negate = NEW.negate
4087     WHERE
4088         id = OLD.id;
4089
4090 CREATE OR REPLACE RULE query_expr_xcol_delete_rule AS
4091     ON DELETE TO query.expr_xcol
4092     DO INSTEAD
4093     DELETE FROM query.expression WHERE id = OLD.id;
4094
4095 -- Create updatable view for EXISTS expressions
4096
4097 CREATE OR REPLACE VIEW query.expr_xex AS
4098     SELECT
4099                 id,
4100                 parenthesize,
4101                 parent_expr,
4102                 seq_no,
4103                 subquery,
4104                 negate
4105     FROM
4106         query.expression
4107     WHERE
4108         type = 'xex';
4109
4110 CREATE OR REPLACE RULE query_expr_xex_insert_rule AS
4111     ON INSERT TO query.expr_xex
4112     DO INSTEAD
4113     INSERT INTO query.expression (
4114                 id,
4115                 type,
4116                 parenthesize,
4117                 parent_expr,
4118                 seq_no,
4119                 subquery,
4120                 negate
4121     ) VALUES (
4122         COALESCE(NEW.id, NEXTVAL('query.expression_id_seq'::REGCLASS)),
4123         'xex',
4124         COALESCE(NEW.parenthesize, FALSE),
4125         NEW.parent_expr,
4126         COALESCE(NEW.seq_no, 1),
4127                 NEW.subquery,
4128                 COALESCE(NEW.negate, false)
4129     );
4130
4131 CREATE OR REPLACE RULE query_expr_xex_update_rule AS
4132     ON UPDATE TO query.expr_xex
4133     DO INSTEAD
4134     UPDATE query.expression SET
4135         id = NEW.id,
4136         parenthesize = NEW.parenthesize,
4137         parent_expr = NEW.parent_expr,
4138         seq_no = NEW.seq_no,
4139                 subquery = NEW.subquery,
4140                 negate = NEW.negate
4141     WHERE
4142         id = OLD.id;
4143
4144 CREATE OR REPLACE RULE query_expr_xex_delete_rule AS
4145     ON DELETE TO query.expr_xex
4146     DO INSTEAD
4147     DELETE FROM query.expression WHERE id = OLD.id;
4148
4149 -- Create updatable view for function call expressions
4150
4151 CREATE OR REPLACE VIEW query.expr_xfunc AS
4152     SELECT
4153         id,
4154         parenthesize,
4155         parent_expr,
4156         seq_no,
4157         column_name,
4158         function_id,
4159         negate
4160     FROM
4161         query.expression
4162     WHERE
4163         type = 'xfunc';
4164
4165 CREATE OR REPLACE RULE query_expr_xfunc_insert_rule AS
4166     ON INSERT TO query.expr_xfunc
4167     DO INSTEAD
4168     INSERT INTO query.expression (
4169         id,
4170         type,
4171         parenthesize,
4172         parent_expr,
4173         seq_no,
4174         column_name,
4175         function_id,
4176         negate
4177     ) VALUES (
4178         COALESCE(NEW.id, NEXTVAL('query.expression_id_seq'::REGCLASS)),
4179         'xfunc',
4180         COALESCE(NEW.parenthesize, FALSE),
4181         NEW.parent_expr,
4182         COALESCE(NEW.seq_no, 1),
4183         NEW.column_name,
4184         NEW.function_id,
4185         COALESCE(NEW.negate, false)
4186     );
4187
4188 CREATE OR REPLACE RULE query_expr_xfunc_update_rule AS
4189     ON UPDATE TO query.expr_xfunc
4190     DO INSTEAD
4191     UPDATE query.expression SET
4192         id = NEW.id,
4193         parenthesize = NEW.parenthesize,
4194         parent_expr = NEW.parent_expr,
4195         seq_no = NEW.seq_no,
4196         column_name = NEW.column_name,
4197         function_id = NEW.function_id,
4198         negate = NEW.negate
4199     WHERE
4200         id = OLD.id;
4201
4202 CREATE OR REPLACE RULE query_expr_xfunc_delete_rule AS
4203     ON DELETE TO query.expr_xfunc
4204     DO INSTEAD
4205     DELETE FROM query.expression WHERE id = OLD.id;
4206
4207 -- Create updatable view for IN expressions
4208
4209 CREATE OR REPLACE VIEW query.expr_xin AS
4210     SELECT
4211                 id,
4212                 parenthesize,
4213                 parent_expr,
4214                 seq_no,
4215                 left_operand,
4216                 subquery,
4217                 negate
4218     FROM
4219         query.expression
4220     WHERE
4221         type = 'xin';
4222
4223 CREATE OR REPLACE RULE query_expr_xin_insert_rule AS
4224     ON INSERT TO query.expr_xin
4225     DO INSTEAD
4226     INSERT INTO query.expression (
4227                 id,
4228                 type,
4229                 parenthesize,
4230                 parent_expr,
4231                 seq_no,
4232                 left_operand,
4233                 subquery,
4234                 negate
4235     ) VALUES (
4236         COALESCE(NEW.id, NEXTVAL('query.expression_id_seq'::REGCLASS)),
4237         'xin',
4238         COALESCE(NEW.parenthesize, FALSE),
4239         NEW.parent_expr,
4240         COALESCE(NEW.seq_no, 1),
4241                 NEW.left_operand,
4242                 NEW.subquery,
4243                 COALESCE(NEW.negate, false)
4244     );
4245
4246 CREATE OR REPLACE RULE query_expr_xin_update_rule AS
4247     ON UPDATE TO query.expr_xin
4248     DO INSTEAD
4249     UPDATE query.expression SET
4250         id = NEW.id,
4251         parenthesize = NEW.parenthesize,
4252         parent_expr = NEW.parent_expr,
4253         seq_no = NEW.seq_no,
4254                 left_operand = NEW.left_operand,
4255                 subquery = NEW.subquery,
4256                 negate = NEW.negate
4257     WHERE
4258         id = OLD.id;
4259
4260 CREATE OR REPLACE RULE query_expr_xin_delete_rule AS
4261     ON DELETE TO query.expr_xin
4262     DO INSTEAD
4263     DELETE FROM query.expression WHERE id = OLD.id;
4264
4265 -- Create updatable view for IS NULL expressions
4266
4267 CREATE OR REPLACE VIEW query.expr_xisnull AS
4268     SELECT
4269                 id,
4270                 parenthesize,
4271                 parent_expr,
4272                 seq_no,
4273                 left_operand,
4274                 negate
4275     FROM
4276         query.expression
4277     WHERE
4278         type = 'xisnull';
4279
4280 CREATE OR REPLACE RULE query_expr_xisnull_insert_rule AS
4281     ON INSERT TO query.expr_xisnull
4282     DO INSTEAD
4283     INSERT INTO query.expression (
4284                 id,
4285                 type,
4286                 parenthesize,
4287                 parent_expr,
4288                 seq_no,
4289                 left_operand,
4290                 negate
4291     ) VALUES (
4292         COALESCE(NEW.id, NEXTVAL('query.expression_id_seq'::REGCLASS)),
4293         'xisnull',
4294         COALESCE(NEW.parenthesize, FALSE),
4295         NEW.parent_expr,
4296         COALESCE(NEW.seq_no, 1),
4297                 NEW.left_operand,
4298                 COALESCE(NEW.negate, false)
4299     );
4300
4301 CREATE OR REPLACE RULE query_expr_xisnull_update_rule AS
4302     ON UPDATE TO query.expr_xisnull
4303     DO INSTEAD
4304     UPDATE query.expression SET
4305         id = NEW.id,
4306         parenthesize = NEW.parenthesize,
4307         parent_expr = NEW.parent_expr,
4308         seq_no = NEW.seq_no,
4309                 left_operand = NEW.left_operand,
4310                 negate = NEW.negate
4311     WHERE
4312         id = OLD.id;
4313
4314 CREATE OR REPLACE RULE query_expr_xisnull_delete_rule AS
4315     ON DELETE TO query.expr_xisnull
4316     DO INSTEAD
4317     DELETE FROM query.expression WHERE id = OLD.id;
4318
4319 -- Create updatable view for NULL expressions
4320
4321 CREATE OR REPLACE VIEW query.expr_xnull AS
4322     SELECT
4323                 id,
4324                 parenthesize,
4325                 parent_expr,
4326                 seq_no,
4327                 negate
4328     FROM
4329         query.expression
4330     WHERE
4331         type = 'xnull';
4332
4333 CREATE OR REPLACE RULE query_expr_xnull_insert_rule AS
4334     ON INSERT TO query.expr_xnull
4335     DO INSTEAD
4336     INSERT INTO query.expression (
4337                 id,
4338                 type,
4339                 parenthesize,
4340                 parent_expr,
4341                 seq_no,
4342                 negate
4343     ) VALUES (
4344         COALESCE(NEW.id, NEXTVAL('query.expression_id_seq'::REGCLASS)),
4345         'xnull',
4346         COALESCE(NEW.parenthesize, FALSE),
4347         NEW.parent_expr,
4348         COALESCE(NEW.seq_no, 1),
4349                 COALESCE(NEW.negate, false)
4350     );
4351
4352 CREATE OR REPLACE RULE query_expr_xnull_update_rule AS
4353     ON UPDATE TO query.expr_xnull
4354     DO INSTEAD
4355     UPDATE query.expression SET
4356         id = NEW.id,
4357         parenthesize = NEW.parenthesize,
4358         parent_expr = NEW.parent_expr,
4359         seq_no = NEW.seq_no,
4360                 negate = NEW.negate
4361     WHERE
4362         id = OLD.id;
4363
4364 CREATE OR REPLACE RULE query_expr_xnull_delete_rule AS
4365     ON DELETE TO query.expr_xnull
4366     DO INSTEAD
4367     DELETE FROM query.expression WHERE id = OLD.id;
4368
4369 -- Create updatable view for numeric literal expressions
4370
4371 CREATE OR REPLACE VIEW query.expr_xnum AS
4372     SELECT
4373                 id,
4374                 parenthesize,
4375                 parent_expr,
4376                 seq_no,
4377                 literal
4378     FROM
4379         query.expression
4380     WHERE
4381         type = 'xnum';
4382
4383 CREATE OR REPLACE RULE query_expr_xnum_insert_rule AS
4384     ON INSERT TO query.expr_xnum
4385     DO INSTEAD
4386     INSERT INTO query.expression (
4387                 id,
4388                 type,
4389                 parenthesize,
4390                 parent_expr,
4391                 seq_no,
4392                 literal
4393     ) VALUES (
4394         COALESCE(NEW.id, NEXTVAL('query.expression_id_seq'::REGCLASS)),
4395         'xnum',
4396         COALESCE(NEW.parenthesize, FALSE),
4397         NEW.parent_expr,
4398         COALESCE(NEW.seq_no, 1),
4399         NEW.literal
4400     );
4401
4402 CREATE OR REPLACE RULE query_expr_xnum_update_rule AS
4403     ON UPDATE TO query.expr_xnum
4404     DO INSTEAD
4405     UPDATE query.expression SET
4406         id = NEW.id,
4407         parenthesize = NEW.parenthesize,
4408         parent_expr = NEW.parent_expr,
4409         seq_no = NEW.seq_no,
4410         literal = NEW.literal
4411     WHERE
4412         id = OLD.id;
4413
4414 CREATE OR REPLACE RULE query_expr_xnum_delete_rule AS
4415     ON DELETE TO query.expr_xnum
4416     DO INSTEAD
4417     DELETE FROM query.expression WHERE id = OLD.id;
4418
4419 -- Create updatable view for operator expressions
4420
4421 CREATE OR REPLACE VIEW query.expr_xop AS
4422     SELECT
4423                 id,
4424                 parenthesize,
4425                 parent_expr,
4426                 seq_no,
4427                 left_operand,
4428                 operator,
4429                 right_operand,
4430                 negate
4431     FROM
4432         query.expression
4433     WHERE
4434         type = 'xop';
4435
4436 CREATE OR REPLACE RULE query_expr_xop_insert_rule AS
4437     ON INSERT TO query.expr_xop
4438     DO INSTEAD
4439     INSERT INTO query.expression (
4440                 id,
4441                 type,
4442                 parenthesize,
4443                 parent_expr,
4444                 seq_no,
4445                 left_operand,
4446                 operator,
4447                 right_operand,
4448                 negate
4449     ) VALUES (
4450         COALESCE(NEW.id, NEXTVAL('query.expression_id_seq'::REGCLASS)),
4451         'xop',
4452         COALESCE(NEW.parenthesize, FALSE),
4453         NEW.parent_expr,
4454         COALESCE(NEW.seq_no, 1),
4455                 NEW.left_operand,
4456                 NEW.operator,
4457                 NEW.right_operand,
4458                 COALESCE(NEW.negate, false)
4459     );
4460
4461 CREATE OR REPLACE RULE query_expr_xop_update_rule AS
4462     ON UPDATE TO query.expr_xop
4463     DO INSTEAD
4464     UPDATE query.expression SET
4465         id = NEW.id,
4466         parenthesize = NEW.parenthesize,
4467         parent_expr = NEW.parent_expr,
4468         seq_no = NEW.seq_no,
4469                 left_operand = NEW.left_operand,
4470                 operator = NEW.operator,
4471                 right_operand = NEW.right_operand,
4472                 negate = NEW.negate
4473     WHERE
4474         id = OLD.id;
4475
4476 CREATE OR REPLACE RULE query_expr_xop_delete_rule AS
4477     ON DELETE TO query.expr_xop
4478     DO INSTEAD
4479     DELETE FROM query.expression WHERE id = OLD.id;
4480
4481 -- Create updatable view for series expressions
4482 -- i.e. series of expressions separated by operators
4483
4484 CREATE OR REPLACE VIEW query.expr_xser AS
4485     SELECT
4486                 id,
4487                 parenthesize,
4488                 parent_expr,
4489                 seq_no,
4490                 operator,
4491                 negate
4492     FROM
4493         query.expression
4494     WHERE
4495         type = 'xser';
4496
4497 CREATE OR REPLACE RULE query_expr_xser_insert_rule AS
4498     ON INSERT TO query.expr_xser
4499     DO INSTEAD
4500     INSERT INTO query.expression (
4501                 id,
4502                 type,
4503                 parenthesize,
4504                 parent_expr,
4505                 seq_no,
4506                 operator,
4507                 negate
4508     ) VALUES (
4509         COALESCE(NEW.id, NEXTVAL('query.expression_id_seq'::REGCLASS)),
4510         'xser',
4511         COALESCE(NEW.parenthesize, FALSE),
4512         NEW.parent_expr,
4513         COALESCE(NEW.seq_no, 1),
4514                 NEW.operator,
4515                 COALESCE(NEW.negate, false)
4516     );
4517
4518 CREATE OR REPLACE RULE query_expr_xser_update_rule AS
4519     ON UPDATE TO query.expr_xser
4520     DO INSTEAD
4521     UPDATE query.expression SET
4522         id = NEW.id,
4523         parenthesize = NEW.parenthesize,
4524         parent_expr = NEW.parent_expr,
4525         seq_no = NEW.seq_no,
4526                 operator = NEW.operator,
4527                 negate = NEW.negate
4528     WHERE
4529         id = OLD.id;
4530
4531 CREATE OR REPLACE RULE query_expr_xser_delete_rule AS
4532     ON DELETE TO query.expr_xser
4533     DO INSTEAD
4534     DELETE FROM query.expression WHERE id = OLD.id;
4535
4536 -- Create updatable view for string literal expressions
4537
4538 CREATE OR REPLACE VIEW query.expr_xstr AS
4539     SELECT
4540         id,
4541         parenthesize,
4542         parent_expr,
4543         seq_no,
4544         literal
4545     FROM
4546         query.expression
4547     WHERE
4548         type = 'xstr';
4549
4550 CREATE OR REPLACE RULE query_expr_string_insert_rule AS
4551     ON INSERT TO query.expr_xstr
4552     DO INSTEAD
4553     INSERT INTO query.expression (
4554         id,
4555         type,
4556         parenthesize,
4557         parent_expr,
4558         seq_no,
4559         literal
4560     ) VALUES (
4561         COALESCE(NEW.id, NEXTVAL('query.expression_id_seq'::REGCLASS)),
4562         'xstr',
4563         COALESCE(NEW.parenthesize, FALSE),
4564         NEW.parent_expr,
4565         COALESCE(NEW.seq_no, 1),
4566         NEW.literal
4567     );
4568
4569 CREATE OR REPLACE RULE query_expr_string_update_rule AS
4570     ON UPDATE TO query.expr_xstr
4571     DO INSTEAD
4572     UPDATE query.expression SET
4573         id = NEW.id,
4574         parenthesize = NEW.parenthesize,
4575         parent_expr = NEW.parent_expr,
4576         seq_no = NEW.seq_no,
4577         literal = NEW.literal
4578     WHERE
4579         id = OLD.id;
4580
4581 CREATE OR REPLACE RULE query_expr_string_delete_rule AS
4582     ON DELETE TO query.expr_xstr
4583     DO INSTEAD
4584     DELETE FROM query.expression WHERE id = OLD.id;
4585
4586 -- Create updatable view for subquery expressions
4587
4588 CREATE OR REPLACE VIEW query.expr_xsubq AS
4589     SELECT
4590                 id,
4591                 parenthesize,
4592                 parent_expr,
4593                 seq_no,
4594                 subquery,
4595                 negate
4596     FROM
4597         query.expression
4598     WHERE
4599         type = 'xsubq';
4600
4601 CREATE OR REPLACE RULE query_expr_xsubq_insert_rule AS
4602     ON INSERT TO query.expr_xsubq
4603     DO INSTEAD
4604     INSERT INTO query.expression (
4605                 id,
4606                 type,
4607                 parenthesize,
4608                 parent_expr,
4609                 seq_no,
4610                 subquery,
4611                 negate
4612     ) VALUES (
4613         COALESCE(NEW.id, NEXTVAL('query.expression_id_seq'::REGCLASS)),
4614         'xsubq',
4615         COALESCE(NEW.parenthesize, FALSE),
4616         NEW.parent_expr,
4617         COALESCE(NEW.seq_no, 1),
4618                 NEW.subquery,
4619                 COALESCE(NEW.negate, false)
4620     );
4621
4622 CREATE OR REPLACE RULE query_expr_xsubq_update_rule AS
4623     ON UPDATE TO query.expr_xsubq
4624     DO INSTEAD
4625     UPDATE query.expression SET
4626         id = NEW.id,
4627         parenthesize = NEW.parenthesize,
4628         parent_expr = NEW.parent_expr,
4629         seq_no = NEW.seq_no,
4630                 subquery = NEW.subquery,
4631                 negate = NEW.negate
4632     WHERE
4633         id = OLD.id;
4634
4635 CREATE OR REPLACE RULE query_expr_xsubq_delete_rule AS
4636     ON DELETE TO query.expr_xsubq
4637     DO INSTEAD
4638     DELETE FROM query.expression WHERE id = OLD.id;
4639
4640 CREATE TABLE action.fieldset (
4641     id              SERIAL          PRIMARY KEY,
4642     owner           INT             NOT NULL REFERENCES actor.usr (id)
4643                                     DEFERRABLE INITIALLY DEFERRED,
4644     owning_lib      INT             NOT NULL REFERENCES actor.org_unit (id)
4645                                     DEFERRABLE INITIALLY DEFERRED,
4646     status          TEXT            NOT NULL
4647                                     CONSTRAINT valid_status CHECK ( status in
4648                                     ( 'PENDING', 'APPLIED', 'ERROR' )),
4649     creation_time   TIMESTAMPTZ     NOT NULL DEFAULT NOW(),
4650     scheduled_time  TIMESTAMPTZ,
4651     applied_time    TIMESTAMPTZ,
4652     classname       TEXT            NOT NULL, -- an IDL class name
4653     name            TEXT            NOT NULL,
4654     stored_query    INT             REFERENCES query.stored_query (id)
4655                                     DEFERRABLE INITIALLY DEFERRED,
4656     pkey_value      TEXT,
4657     CONSTRAINT lib_name_unique UNIQUE (owning_lib, name),
4658     CONSTRAINT fieldset_one_or_the_other CHECK (
4659         (stored_query IS NOT NULL AND pkey_value IS NULL) OR
4660         (pkey_value IS NOT NULL AND stored_query IS NULL)
4661     )
4662     -- the CHECK constraint means we can update the fields for a single
4663     -- row without all the extra overhead involved in a query
4664 );
4665
4666 CREATE INDEX action_fieldset_sched_time_idx ON action.fieldset( scheduled_time );
4667 CREATE INDEX action_owner_idx               ON action.fieldset( owner );
4668
4669 CREATE TABLE action.fieldset_col_val (
4670     id              SERIAL  PRIMARY KEY,
4671     fieldset        INT     NOT NULL REFERENCES action.fieldset
4672                                          ON DELETE CASCADE
4673                                          DEFERRABLE INITIALLY DEFERRED,
4674     col             TEXT    NOT NULL,  -- "field" from the idl ... the column on the table
4675     val             TEXT,              -- value for the column ... NULL means, well, NULL
4676     CONSTRAINT fieldset_col_once_per_set UNIQUE (fieldset, col)
4677 );
4678
4679 CREATE OR REPLACE FUNCTION action.apply_fieldset(
4680         fieldset_id IN INT,        -- id from action.fieldset
4681         table_name  IN TEXT,       -- table to be updated
4682         pkey_name   IN TEXT,       -- name of primary key column in that table
4683         query       IN TEXT        -- query constructed by qstore (for query-based
4684                                    --    fieldsets only; otherwise null
4685 )
4686 RETURNS TEXT AS $$
4687 DECLARE
4688         statement TEXT;
4689         fs_status TEXT;
4690         fs_pkey_value TEXT;
4691         fs_query TEXT;
4692         sep CHAR;
4693         status_code TEXT;
4694         msg TEXT;
4695         update_count INT;
4696         cv RECORD;
4697 BEGIN
4698         -- Sanity checks
4699         IF fieldset_id IS NULL THEN
4700                 RETURN 'Fieldset ID parameter is NULL';
4701         END IF;
4702         IF table_name IS NULL THEN
4703                 RETURN 'Table name parameter is NULL';
4704         END IF;
4705         IF pkey_name IS NULL THEN
4706                 RETURN 'Primary key name parameter is NULL';
4707         END IF;
4708         --
4709         statement := 'UPDATE ' || table_name || ' SET';
4710         --
4711         SELECT
4712                 status,
4713                 quote_literal( pkey_value )
4714         INTO
4715                 fs_status,
4716                 fs_pkey_value
4717         FROM
4718                 action.fieldset
4719         WHERE
4720                 id = fieldset_id;
4721         --
4722         IF fs_status IS NULL THEN
4723                 RETURN 'No fieldset found for id = ' || fieldset_id;
4724         ELSIF fs_status = 'APPLIED' THEN
4725                 RETURN 'Fieldset ' || fieldset_id || ' has already been applied';
4726         END IF;
4727         --
4728         sep := '';
4729         FOR cv IN
4730                 SELECT  col,
4731                                 val
4732                 FROM    action.fieldset_col_val
4733                 WHERE   fieldset = fieldset_id
4734         LOOP
4735                 statement := statement || sep || ' ' || cv.col
4736                                          || ' = ' || coalesce( quote_literal( cv.val ), 'NULL' );
4737                 sep := ',';
4738         END LOOP;
4739         --
4740         IF sep = '' THEN
4741                 RETURN 'Fieldset ' || fieldset_id || ' has no column values defined';
4742         END IF;
4743         --
4744         -- Add the WHERE clause.  This differs according to whether it's a
4745         -- single-row fieldset or a query-based fieldset.
4746         --
4747         IF query IS NULL        AND fs_pkey_value IS NULL THEN
4748                 RETURN 'Incomplete fieldset: neither a primary key nor a query available';
4749         ELSIF query IS NOT NULL AND fs_pkey_value IS NULL THEN
4750             fs_query := rtrim( query, ';' );
4751             statement := statement || ' WHERE ' || pkey_name || ' IN ( '
4752                          || fs_query || ' );';
4753         ELSIF query IS NULL     AND fs_pkey_value IS NOT NULL THEN
4754                 statement := statement || ' WHERE ' || pkey_name || ' = '
4755                                      || fs_pkey_value || ';';
4756         ELSE  -- both are not null
4757                 RETURN 'Ambiguous fieldset: both a primary key and a query provided';
4758         END IF;
4759         --
4760         -- Execute the update
4761         --
4762         BEGIN
4763                 EXECUTE statement;
4764                 GET DIAGNOSTICS update_count = ROW_COUNT;
4765                 --
4766                 IF UPDATE_COUNT > 0 THEN
4767                         status_code := 'APPLIED';
4768                         msg := NULL;
4769                 ELSE
4770                         status_code := 'ERROR';
4771                         msg := 'No eligible rows found for fieldset ' || fieldset_id;
4772         END IF;
4773         EXCEPTION WHEN OTHERS THEN
4774                 status_code := 'ERROR';
4775                 msg := 'Unable to apply fieldset ' || fieldset_id
4776                            || ': ' || sqlerrm;
4777         END;
4778         --
4779         -- Update fieldset status
4780         --
4781         UPDATE action.fieldset
4782         SET status       = status_code,
4783             applied_time = now()
4784         WHERE id = fieldset_id;
4785         --
4786         RETURN msg;
4787 END;
4788 $$ LANGUAGE plpgsql;
4789
4790 COMMENT ON FUNCTION action.apply_fieldset( INT, TEXT, TEXT, TEXT ) IS $$
4791 /**
4792  * Applies a specified fieldset, using a supplied table name and primary
4793  * key name.  The query parameter should be non-null only for
4794  * query-based fieldsets.
4795  *
4796  * Returns NULL if successful, or an error message if not.
4797  */
4798 $$;
4799
4800 -- Removed:
4801 -- stuff pertaining to settings and setting types
4802 -- almost everything pertaining to permission.perm_list
4803 -- everything pertaining to the action_trigger schema
4804 -- the query schema
4805 -- almost everything pertaining to fieldsets
4806
4807 CREATE INDEX uhr_hold_idx ON action.unfulfilled_hold_list (hold);
4808
4809 CREATE OR REPLACE VIEW action.unfulfilled_hold_loops AS
4810     SELECT  u.hold,
4811             c.circ_lib,
4812             count(*)
4813       FROM  action.unfulfilled_hold_list u
4814             JOIN asset.copy c ON (c.id = u.current_copy)
4815       GROUP BY 1,2;
4816
4817 CREATE OR REPLACE VIEW action.unfulfilled_hold_min_loop AS
4818     SELECT  hold,
4819             min(count)
4820       FROM  action.unfulfilled_hold_loops
4821       GROUP BY 1;
4822
4823 CREATE OR REPLACE VIEW action.unfulfilled_hold_innermost_loop AS
4824     SELECT  DISTINCT l.*
4825       FROM  action.unfulfilled_hold_loops l
4826             JOIN action.unfulfilled_hold_min_loop m USING (hold)
4827       WHERE l.count = m.min;
4828
4829 ALTER TABLE asset.copy
4830 ADD COLUMN dummy_isbn TEXT;
4831
4832 ALTER TABLE auditor.asset_copy_history
4833 ADD COLUMN dummy_isbn TEXT;
4834
4835 -- Add new column status_changed_date to asset.copy, with trigger to maintain it
4836 -- Add corresponding new column to auditor.asset_copy_history
4837
4838 ALTER TABLE asset.copy
4839         ADD COLUMN status_changed_time TIMESTAMPTZ;
4840
4841 ALTER TABLE auditor.asset_copy_history
4842         ADD COLUMN status_changed_time TIMESTAMPTZ;
4843
4844 CREATE OR REPLACE FUNCTION asset.acp_status_changed()
4845 RETURNS TRIGGER AS $$
4846 BEGIN
4847         IF NEW.status <> OLD.status THEN
4848                 NEW.status_changed_time := now();
4849         END IF;
4850         RETURN NEW;
4851 END;
4852 $$ LANGUAGE plpgsql;
4853
4854 CREATE TRIGGER acp_status_changed_trig
4855         BEFORE UPDATE ON asset.copy
4856         FOR EACH ROW EXECUTE PROCEDURE asset.acp_status_changed();
4857
4858 ALTER TABLE asset.copy
4859 ADD COLUMN mint_condition boolean NOT NULL DEFAULT TRUE;
4860
4861 ALTER TABLE auditor.asset_copy_history
4862 ADD COLUMN mint_condition boolean NOT NULL DEFAULT TRUE;
4863
4864 ALTER TABLE asset.copy ADD COLUMN floating BOOL NOT NULL DEFAULT FALSE;
4865 ALTER TABLE auditor.asset_copy_history ADD COLUMN floating BOOL;
4866
4867 DROP INDEX IF EXISTS asset.copy_barcode_key;
4868 CREATE UNIQUE INDEX copy_barcode_key ON asset.copy (barcode) WHERE deleted = FALSE OR deleted IS FALSE;
4869
4870 -- Note: later we create a trigger a_opac_vis_mat_view_tgr
4871 -- AFTER INSERT OR UPDATE ON asset.copy
4872
4873 ALTER TABLE asset.copy ADD COLUMN cost NUMERIC(8,2);
4874 ALTER TABLE auditor.asset_copy_history ADD COLUMN cost NUMERIC(8,2);
4875
4876 -- Moke mostly parallel changes to action.circulation
4877 -- and action.aged_circulation
4878
4879 ALTER TABLE action.circulation
4880 ADD COLUMN workstation INT
4881     REFERENCES actor.workstation
4882         ON DELETE SET NULL
4883         DEFERRABLE INITIALLY DEFERRED;
4884
4885 ALTER TABLE action.aged_circulation
4886 ADD COLUMN workstation INT;
4887
4888 ALTER TABLE action.circulation
4889 ADD COLUMN parent_circ BIGINT
4890         REFERENCES action.circulation(id)
4891         DEFERRABLE INITIALLY DEFERRED;
4892
4893 CREATE UNIQUE INDEX circ_parent_idx
4894 ON action.circulation( parent_circ )
4895 WHERE parent_circ IS NOT NULL;
4896
4897 ALTER TABLE action.aged_circulation
4898 ADD COLUMN parent_circ BIGINT;
4899
4900 ALTER TABLE action.circulation
4901 ADD COLUMN checkin_workstation INT
4902         REFERENCES actor.workstation(id)
4903         ON DELETE SET NULL
4904         DEFERRABLE INITIALLY DEFERRED;
4905
4906 ALTER TABLE action.aged_circulation
4907 ADD COLUMN checkin_workstation INT;
4908
4909 ALTER TABLE action.circulation
4910 ADD COLUMN checkin_scan_time TIMESTAMPTZ;
4911
4912 ALTER TABLE action.aged_circulation
4913 ADD COLUMN checkin_scan_time TIMESTAMPTZ;
4914
4915 CREATE INDEX action_circulation_target_copy_idx
4916 ON action.circulation (target_copy);
4917
4918 CREATE INDEX action_aged_circulation_target_copy_idx
4919 ON action.aged_circulation (target_copy);
4920
4921 ALTER TABLE action.circulation
4922 DROP CONSTRAINT circulation_stop_fines_check;
4923
4924 ALTER TABLE action.circulation
4925         ADD CONSTRAINT circulation_stop_fines_check
4926         CHECK (stop_fines IN (
4927         'CHECKIN','CLAIMSRETURNED','LOST','MAXFINES','RENEW','LONGOVERDUE','CLAIMSNEVERCHECKEDOUT'));
4928
4929 -- Correct some long-standing misspellings involving variations of "recur"
4930
4931 ALTER TABLE action.circulation RENAME COLUMN recuring_fine TO recurring_fine;
4932 ALTER TABLE action.circulation RENAME COLUMN recuring_fine_rule TO recurring_fine_rule;
4933
4934 ALTER TABLE action.aged_circulation RENAME COLUMN recuring_fine TO recurring_fine;
4935 ALTER TABLE action.aged_circulation RENAME COLUMN recuring_fine_rule TO recurring_fine_rule;
4936
4937 ALTER TABLE config.rule_recuring_fine RENAME TO rule_recurring_fine;
4938 ALTER TABLE config.rule_recuring_fine_id_seq RENAME TO rule_recurring_fine_id_seq;
4939
4940 ALTER TABLE config.rule_recurring_fine RENAME COLUMN recurance_interval TO recurrence_interval;
4941
4942 -- Might as well keep the comment in sync as well
4943 COMMENT ON TABLE config.rule_recurring_fine IS $$
4944 /*
4945  * Copyright (C) 2005  Georgia Public Library Service 
4946  * Mike Rylander <mrylander@gmail.com>
4947  *
4948  * Circulation Recurring Fine rules
4949  *
4950  * Each circulation is given a recurring fine amount based on one of
4951  * these rules.  The recurrence_interval should not be any shorter
4952  * than the interval between runs of the fine_processor.pl script
4953  * (which is run from CRON), or you could miss fines.
4954  * 
4955  *
4956  * ****
4957  *
4958  * This program is free software; you can redistribute it and/or
4959  * modify it under the terms of the GNU General Public License
4960  * as published by the Free Software Foundation; either version 2
4961  * of the License, or (at your option) any later version.
4962  *
4963  * This program is distributed in the hope that it will be useful,
4964  * but WITHOUT ANY WARRANTY; without even the implied warranty of
4965  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
4966  * GNU General Public License for more details.
4967  */
4968 $$;
4969
4970 ALTER TABLE reporter.report RENAME COLUMN recurance TO recurrence;
4971
4972 -- Extend the name change to some related views:
4973
4974 -- Must drop a dependent view temporarily:
4975 DROP VIEW IF EXISTS extend_reporter.full_circ_count;
4976
4977 -- You would think that CREATE OR REPLACE would be enough, but in testing
4978 -- PostgreSQL complained about renaming the columns in the view. So we
4979 -- drop the view first.
4980 DROP VIEW IF EXISTS action.all_circulation;
4981
4982 CREATE OR REPLACE VIEW action.all_circulation AS
4983     SELECT  id,usr_post_code, usr_home_ou, usr_profile, usr_birth_year, copy_call_number, copy_location,
4984         copy_owning_lib, copy_circ_lib, copy_bib_record, xact_start, xact_finish, target_copy,
4985         circ_lib, circ_staff, checkin_staff, checkin_lib, renewal_remaining, due_date,
4986         stop_fines_time, checkin_time, create_time, duration, fine_interval, recurring_fine,
4987         max_fine, phone_renewal, desk_renewal, opac_renewal, duration_rule, recurring_fine_rule,
4988         max_fine_rule, stop_fines, workstation, checkin_workstation, checkin_scan_time, parent_circ
4989       FROM  action.aged_circulation
4990             UNION ALL
4991     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,
4992         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,
4993         cn.record AS copy_bib_record, circ.xact_start, circ.xact_finish, circ.target_copy, circ.circ_lib, circ.circ_staff, circ.checkin_staff,
4994         circ.checkin_lib, circ.renewal_remaining, circ.due_date, circ.stop_fines_time, circ.checkin_time, circ.create_time, circ.duration,
4995         circ.fine_interval, circ.recurring_fine, circ.max_fine, circ.phone_renewal, circ.desk_renewal, circ.opac_renewal, circ.duration_rule,
4996         circ.recurring_fine_rule, circ.max_fine_rule, circ.stop_fines, circ.workstation, circ.checkin_workstation, circ.checkin_scan_time,
4997         circ.parent_circ
4998       FROM  action.circulation circ
4999         JOIN asset.copy cp ON (circ.target_copy = cp.id)
5000         JOIN asset.call_number cn ON (cp.call_number = cn.id)
5001         JOIN actor.usr p ON (circ.usr = p.id)
5002         LEFT JOIN actor.usr_address a ON (p.mailing_address = a.id)
5003         LEFT JOIN actor.usr_address b ON (p.billing_address = a.id);
5004
5005 -- Recreate the temporarily dropped view, with a revised view action.all_circulation:
5006
5007 CREATE OR REPLACE VIEW extend_reporter.full_circ_count AS
5008  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
5009    FROM asset."copy" cp
5010    LEFT JOIN extend_reporter.legacy_circ_count c USING (id)
5011    LEFT JOIN "action".circulation circ ON circ.target_copy = cp.id
5012    LEFT JOIN "action".aged_circulation acirc ON acirc.target_copy = cp.id
5013   GROUP BY cp.id;
5014
5015 -- Let's not break existing reports
5016 UPDATE reporter.template SET data = REGEXP_REPLACE(data, E'^(.*)recuring(.*)$', E'\\1recurring\\2') WHERE data LIKE '%recuring%';
5017 UPDATE reporter.template SET data = REGEXP_REPLACE(data, E'^(.*)recurance(.*)$', E'\\1recurrence\\2') WHERE data LIKE '%recurance%';
5018
5019 CREATE UNIQUE INDEX only_one_concurrent_checkout_per_copy ON action.circulation(target_copy) WHERE checkin_time IS NULL;
5020
5021 ALTER TABLE action.circulation DROP CONSTRAINT action_circulation_target_copy_fkey;
5022
5023 CREATE OR REPLACE FUNCTION action.age_circ_on_delete () RETURNS TRIGGER AS $$
5024 DECLARE
5025 found char := 'N';
5026 BEGIN
5027
5028     -- If there are any renewals for this circulation, don't archive or delete
5029     -- it yet.   We'll do so later, when we archive and delete the renewals.
5030
5031     SELECT 'Y' INTO found
5032     FROM action.circulation
5033     WHERE parent_circ = OLD.id
5034     LIMIT 1;
5035
5036     IF found = 'Y' THEN
5037         RETURN NULL;  -- don't delete
5038         END IF;
5039
5040     -- Archive a copy of the old row to action.aged_circulation
5041
5042     INSERT INTO action.aged_circulation
5043         (id,usr_post_code, usr_home_ou, usr_profile, usr_birth_year, copy_call_number, copy_location,
5044         copy_owning_lib, copy_circ_lib, copy_bib_record, xact_start, xact_finish, target_copy,
5045         circ_lib, circ_staff, checkin_staff, checkin_lib, renewal_remaining, due_date,
5046         stop_fines_time, checkin_time, create_time, duration, fine_interval, recurring_fine,
5047         max_fine, phone_renewal, desk_renewal, opac_renewal, duration_rule, recurring_fine_rule,
5048         max_fine_rule, stop_fines, workstation, checkin_workstation, checkin_scan_time, parent_circ)
5049       SELECT
5050         id,usr_post_code, usr_home_ou, usr_profile, usr_birth_year, copy_call_number, copy_location,
5051         copy_owning_lib, copy_circ_lib, copy_bib_record, xact_start, xact_finish, target_copy,
5052         circ_lib, circ_staff, checkin_staff, checkin_lib, renewal_remaining, due_date,
5053         stop_fines_time, checkin_time, create_time, duration, fine_interval, recurring_fine,
5054         max_fine, phone_renewal, desk_renewal, opac_renewal, duration_rule, recurring_fine_rule,
5055         max_fine_rule, stop_fines, workstation, checkin_workstation, checkin_scan_time, parent_circ
5056         FROM action.all_circulation WHERE id = OLD.id;
5057
5058     RETURN OLD;
5059 END;
5060 $$ LANGUAGE 'plpgsql';
5061
5062 UPDATE config.z3950_attr SET truncation = 1 WHERE source = 'biblios' AND name = 'title';
5063
5064 UPDATE config.z3950_attr SET truncation = 1 WHERE source = 'biblios' AND truncation = 0;
5065
5066 -- Adding circ.holds.target_skip_me OU setting logic to the pre-matchpoint tests
5067
5068 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$
5069 DECLARE
5070     matchpoint_id        INT;
5071     user_object        actor.usr%ROWTYPE;
5072     age_protect_object    config.rule_age_hold_protect%ROWTYPE;
5073     standing_penalty    config.standing_penalty%ROWTYPE;
5074     transit_range_ou_type    actor.org_unit_type%ROWTYPE;
5075     transit_source        actor.org_unit%ROWTYPE;
5076     item_object        asset.copy%ROWTYPE;
5077     ou_skip              actor.org_unit_setting%ROWTYPE;
5078     result            action.matrix_test_result;
5079     hold_test        config.hold_matrix_matchpoint%ROWTYPE;
5080     hold_count        INT;
5081     hold_transit_prox    INT;
5082     frozen_hold_count    INT;
5083     context_org_list    INT[];
5084     done            BOOL := FALSE;
5085 BEGIN
5086     SELECT INTO user_object * FROM actor.usr WHERE id = match_user;
5087     SELECT INTO context_org_list ARRAY_ACCUM(id) FROM actor.org_unit_full_path( pickup_ou );
5088
5089     result.success := TRUE;
5090
5091     -- Fail if we couldn't find a user
5092     IF user_object.id IS NULL THEN
5093         result.fail_part := 'no_user';
5094         result.success := FALSE;
5095         done := TRUE;
5096         RETURN NEXT result;
5097         RETURN;
5098     END IF;
5099
5100     SELECT INTO item_object * FROM asset.copy WHERE id = match_item;
5101
5102     -- Fail if we couldn't find a copy
5103     IF item_object.id IS NULL THEN
5104         result.fail_part := 'no_item';
5105         result.success := FALSE;
5106         done := TRUE;
5107         RETURN NEXT result;
5108         RETURN;
5109     END IF;
5110
5111     SELECT INTO matchpoint_id action.find_hold_matrix_matchpoint(pickup_ou, request_ou, match_item, match_user, match_requestor);
5112     result.matchpoint := matchpoint_id;
5113
5114     SELECT INTO ou_skip * FROM actor.org_unit_setting WHERE name = 'circ.holds.target_skip_me' AND org_unit = item_object.circ_lib;
5115
5116     -- Fail if the circ_lib for the item has circ.holds.target_skip_me set to true
5117     IF ou_skip.id IS NOT NULL AND ou_skip.value = 'true' THEN
5118         result.fail_part := 'circ.holds.target_skip_me';
5119         result.success := FALSE;
5120         done := TRUE;
5121         RETURN NEXT result;
5122         RETURN;
5123     END IF;
5124
5125     -- Fail if user is barred
5126     IF user_object.barred IS TRUE THEN
5127         result.fail_part := 'actor.usr.barred';
5128         result.success := FALSE;
5129         done := TRUE;
5130         RETURN NEXT result;
5131         RETURN;
5132     END IF;
5133
5134     -- Fail if we couldn't find any matchpoint (requires a default)
5135     IF matchpoint_id IS NULL THEN
5136         result.fail_part := 'no_matchpoint';
5137         result.success := FALSE;
5138         done := TRUE;
5139         RETURN NEXT result;
5140         RETURN;
5141     END IF;
5142
5143     SELECT INTO hold_test * FROM config.hold_matrix_matchpoint WHERE id = matchpoint_id;
5144
5145     IF hold_test.holdable IS FALSE THEN
5146         result.fail_part := 'config.hold_matrix_test.holdable';
5147         result.success := FALSE;
5148         done := TRUE;
5149         RETURN NEXT result;
5150     END IF;
5151
5152     IF hold_test.transit_range IS NOT NULL THEN
5153         SELECT INTO transit_range_ou_type * FROM actor.org_unit_type WHERE id = hold_test.transit_range;
5154         IF hold_test.distance_is_from_owner THEN
5155             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;
5156         ELSE
5157             SELECT INTO transit_source * FROM actor.org_unit WHERE id = item_object.circ_lib;
5158         END IF;
5159
5160         PERFORM * FROM actor.org_unit_descendants( transit_source.id, transit_range_ou_type.depth ) WHERE id = pickup_ou;
5161
5162         IF NOT FOUND THEN
5163             result.fail_part := 'transit_range';
5164             result.success := FALSE;
5165             done := TRUE;
5166             RETURN NEXT result;
5167         END IF;
5168     END IF;
5169  
5170     FOR standing_penalty IN
5171         SELECT  DISTINCT csp.*
5172           FROM  actor.usr_standing_penalty usp
5173                 JOIN config.standing_penalty csp ON (csp.id = usp.standing_penalty)
5174           WHERE usr = match_user
5175                 AND usp.org_unit IN ( SELECT * FROM explode_array(context_org_list) )
5176                 AND (usp.stop_date IS NULL or usp.stop_date > NOW())
5177                 AND csp.block_list LIKE '%HOLD%' LOOP
5178
5179         result.fail_part := standing_penalty.name;
5180         result.success := FALSE;
5181         done := TRUE;
5182         RETURN NEXT result;
5183     END LOOP;
5184
5185     IF hold_test.stop_blocked_user IS TRUE THEN
5186         FOR standing_penalty IN
5187             SELECT  DISTINCT csp.*
5188               FROM  actor.usr_standing_penalty usp
5189                     JOIN config.standing_penalty csp ON (csp.id = usp.standing_penalty)
5190               WHERE usr = match_user
5191                     AND usp.org_unit IN ( SELECT * FROM explode_array(context_org_list) )
5192                     AND (usp.stop_date IS NULL or usp.stop_date > NOW())
5193                     AND csp.block_list LIKE '%CIRC%' LOOP
5194     
5195             result.fail_part := standing_penalty.name;
5196             result.success := FALSE;
5197             done := TRUE;
5198             RETURN NEXT result;
5199         END LOOP;
5200     END IF;
5201
5202     IF hold_test.max_holds IS NOT NULL THEN
5203         SELECT    INTO hold_count COUNT(*)
5204           FROM    action.hold_request
5205           WHERE    usr = match_user
5206             AND fulfillment_time IS NULL
5207             AND cancel_time IS NULL
5208             AND CASE WHEN hold_test.include_frozen_holds THEN TRUE ELSE frozen IS FALSE END;
5209
5210         IF hold_count >= hold_test.max_holds THEN
5211             result.fail_part := 'config.hold_matrix_test.max_holds';
5212             result.success := FALSE;
5213             done := TRUE;
5214             RETURN NEXT result;
5215         END IF;
5216     END IF;
5217
5218     IF item_object.age_protect IS NOT NULL THEN
5219         SELECT INTO age_protect_object * FROM config.rule_age_hold_protect WHERE id = item_object.age_protect;
5220
5221         IF item_object.create_date + age_protect_object.age > NOW() THEN
5222             IF hold_test.distance_is_from_owner THEN
5223                 SELECT INTO hold_transit_prox prox FROM actor.org_unit_proximity WHERE from_org = item_cn_object.owning_lib AND to_org = pickup_ou;
5224             ELSE
5225                 SELECT INTO hold_transit_prox prox FROM actor.org_unit_proximity WHERE from_org = item_object.circ_lib AND to_org = pickup_ou;
5226             END IF;
5227
5228             IF hold_transit_prox > age_protect_object.prox THEN
5229                 result.fail_part := 'config.rule_age_hold_protect.prox';
5230                 result.success := FALSE;
5231                 done := TRUE;
5232                 RETURN NEXT result;
5233             END IF;
5234         END IF;
5235     END IF;
5236
5237     IF NOT done THEN
5238         RETURN NEXT result;
5239     END IF;
5240
5241     RETURN;
5242 END;
5243 $func$ LANGUAGE plpgsql;
5244
5245 -- New post-delete trigger to propagate deletions to parent(s)
5246
5247 CREATE OR REPLACE FUNCTION action.age_parent_circ_on_delete () RETURNS TRIGGER AS $$
5248 BEGIN
5249
5250     -- Having deleted a renewal, we can delete the original circulation (or a previous
5251     -- renewal, if that's what parent_circ is pointing to).  That deletion will trigger
5252     -- deletion of any prior parents, etc. recursively.
5253
5254     IF OLD.parent_circ IS NOT NULL THEN
5255         DELETE FROM action.circulation
5256         WHERE id = OLD.parent_circ;
5257     END IF;
5258
5259     RETURN OLD;
5260 END;
5261 $$ LANGUAGE 'plpgsql';
5262
5263 CREATE TRIGGER age_parent_circ AFTER DELETE ON action.circulation
5264 FOR EACH ROW EXECUTE PROCEDURE action.age_parent_circ_on_delete ();
5265
5266 -- Need to recreate this view with DISTINCT calls to ARRAY_ACCUM, thus avoiding duplicated ISBN and ISSN values
5267 CREATE OR REPLACE VIEW reporter.old_super_simple_record AS
5268 SELECT  r.id,
5269     r.fingerprint,
5270     r.quality,
5271     r.tcn_source,
5272     r.tcn_value,
5273     FIRST(title.value) AS title,
5274     FIRST(author.value) AS author,
5275     ARRAY_TO_STRING(ARRAY_ACCUM( DISTINCT publisher.value), ', ') AS publisher,
5276     ARRAY_TO_STRING(ARRAY_ACCUM( DISTINCT SUBSTRING(pubdate.value FROM $$\d+$$) ), ', ') AS pubdate,
5277     ARRAY_ACCUM( DISTINCT SUBSTRING(isbn.value FROM $$^\S+$$) ) AS isbn,
5278     ARRAY_ACCUM( DISTINCT SUBSTRING(issn.value FROM $$^\S+$$) ) AS issn
5279   FROM  biblio.record_entry r
5280     LEFT JOIN metabib.full_rec title ON (r.id = title.record AND title.tag = '245' AND title.subfield = 'a')
5281     LEFT JOIN metabib.full_rec author ON (r.id = author.record AND author.tag IN ('100','110','111') AND author.subfield = 'a')
5282     LEFT JOIN metabib.full_rec publisher ON (r.id = publisher.record AND publisher.tag = '260' AND publisher.subfield = 'b')
5283     LEFT JOIN metabib.full_rec pubdate ON (r.id = pubdate.record AND pubdate.tag = '260' AND pubdate.subfield = 'c')
5284     LEFT JOIN metabib.full_rec isbn ON (r.id = isbn.record AND isbn.tag IN ('024', '020') AND isbn.subfield IN ('a','z'))
5285     LEFT JOIN metabib.full_rec issn ON (r.id = issn.record AND issn.tag = '022' AND issn.subfield = 'a')
5286   GROUP BY 1,2,3,4,5;
5287
5288 -- This only gets inserted if there are no other id > 100 billing types
5289 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;
5290 SELECT SETVAL('config.billing_type_id_seq'::TEXT, 101) FROM config.billing_type_id_seq WHERE last_value < 101;
5291
5292 -- Populate xact_type column in the materialized version of billable_xact_summary
5293
5294 CREATE OR REPLACE FUNCTION money.mat_summary_create () RETURNS TRIGGER AS $$
5295 BEGIN
5296         INSERT INTO money.materialized_billable_xact_summary (id, usr, xact_start, xact_finish, total_paid, total_owed, balance_owed, xact_type)
5297                 VALUES ( NEW.id, NEW.usr, NEW.xact_start, NEW.xact_finish, 0.0, 0.0, 0.0, TG_ARGV[0]);
5298         RETURN NEW;
5299 END;
5300 $$ LANGUAGE PLPGSQL;
5301  
5302 DROP TRIGGER IF EXISTS mat_summary_create_tgr ON action.circulation;
5303 CREATE TRIGGER mat_summary_create_tgr AFTER INSERT ON action.circulation FOR EACH ROW EXECUTE PROCEDURE money.mat_summary_create ('circulation');
5304  
5305 DROP TRIGGER IF EXISTS mat_summary_create_tgr ON money.grocery;
5306 CREATE TRIGGER mat_summary_create_tgr AFTER INSERT ON money.grocery FOR EACH ROW EXECUTE PROCEDURE money.mat_summary_create ('grocery');
5307
5308 CREATE RULE money_payment_view_update AS ON UPDATE TO money.payment_view DO INSTEAD 
5309     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;
5310
5311 -- Generate the equivalent of compound subject entries from the existing rows
5312 -- so that we don't have to laboriously reindex them
5313
5314 --INSERT INTO config.metabib_field (field_class, name, format, xpath ) VALUES
5315 --    ( 'subject', 'complete', 'mods32', $$//mods32:mods/mods32:subject//text()$$ );
5316 --
5317 --CREATE INDEX metabib_subject_field_entry_source_idx ON metabib.subject_field_entry (source);
5318 --
5319 --INSERT INTO metabib.subject_field_entry (source, field, value)
5320 --    SELECT source, (
5321 --            SELECT id 
5322 --            FROM config.metabib_field
5323 --            WHERE field_class = 'subject' AND name = 'complete'
5324 --        ), 
5325 --        ARRAY_TO_STRING ( 
5326 --            ARRAY (
5327 --                SELECT value 
5328 --                FROM metabib.subject_field_entry msfe
5329 --                WHERE msfe.source = groupee.source
5330 --                ORDER BY source 
5331 --            ), ' ' 
5332 --        ) AS grouped
5333 --    FROM ( 
5334 --        SELECT source
5335 --        FROM metabib.subject_field_entry
5336 --        GROUP BY source
5337 --    ) AS groupee;
5338
5339 CREATE OR REPLACE FUNCTION money.materialized_summary_billing_del () RETURNS TRIGGER AS $$
5340 DECLARE
5341         prev_billing    money.billing%ROWTYPE;
5342         old_billing     money.billing%ROWTYPE;
5343 BEGIN
5344         SELECT * INTO prev_billing FROM money.billing WHERE xact = OLD.xact AND NOT voided ORDER BY billing_ts DESC LIMIT 1 OFFSET 1;
5345         SELECT * INTO old_billing FROM money.billing WHERE xact = OLD.xact AND NOT voided ORDER BY billing_ts DESC LIMIT 1;
5346
5347         IF OLD.id = old_billing.id THEN
5348                 UPDATE  money.materialized_billable_xact_summary
5349                   SET   last_billing_ts = prev_billing.billing_ts,
5350                         last_billing_note = prev_billing.note,
5351                         last_billing_type = prev_billing.billing_type
5352                   WHERE id = OLD.xact;
5353         END IF;
5354
5355         IF NOT OLD.voided THEN
5356                 UPDATE  money.materialized_billable_xact_summary
5357                   SET   total_owed = total_owed - OLD.amount,
5358                         balance_owed = balance_owed + OLD.amount
5359                   WHERE id = OLD.xact;
5360         END IF;
5361
5362         RETURN OLD;
5363 END;
5364 $$ LANGUAGE PLPGSQL;
5365
5366 -- ARG! need to rid ourselves of the broken table definition ... this mechanism is not ideal, sorry.
5367 DROP TABLE IF EXISTS config.index_normalizer CASCADE;
5368
5369 CREATE OR REPLACE FUNCTION public.naco_normalize( TEXT, TEXT ) RETURNS TEXT AS $func$
5370         use Unicode::Normalize;
5371         use Encode;
5372
5373         # When working with Unicode data, the first step is to decode it to
5374         # a byte string; after that, lowercasing is safe
5375         my $txt = lc(decode_utf8(shift));
5376         my $sf = shift;
5377
5378         $txt = NFD($txt);
5379         $txt =~ s/\pM+//go;     # Remove diacritics
5380
5381         $txt =~ s/\xE6/AE/go;   # Convert ae digraph
5382         $txt =~ s/\x{153}/OE/go;# Convert oe digraph
5383         $txt =~ s/\xFE/TH/go;   # Convert Icelandic thorn
5384
5385         $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
5386         $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
5387
5388         $txt =~ tr/\x{0251}\x{03B1}\x{03B2}\x{0262}\x{03B3}/AABGG/;             # Convert Latin and Greek
5389         $txt =~ tr/\x{2113}\xF0\!\"\(\)\-\{\}\<\>\;\:\.\?\xA1\xBF\/\\\@\*\%\=\xB1\+\xAE\xA9\x{2117}\$\xA3\x{FFE1}\xB0\^\_\~\`/LD /;     # Convert Misc
5390         $txt =~ tr/\'\[\]\|//d;                                                 # Remove Misc
5391
5392         if ($sf && $sf =~ /^a/o) {
5393                 my $commapos = index($txt,',');
5394                 if ($commapos > -1) {
5395                         if ($commapos != length($txt) - 1) {
5396                                 my @list = split /,/, $txt;
5397                                 my $first = shift @list;
5398                                 $txt = $first . ',' . join(' ', @list);
5399                         } else {
5400                                 $txt =~ s/,/ /go;
5401                         }
5402                 }
5403         } else {
5404                 $txt =~ s/,/ /go;
5405         }
5406
5407         $txt =~ s/\s+/ /go;     # Compress multiple spaces
5408         $txt =~ s/^\s+//o;      # Remove leading space
5409         $txt =~ s/\s+$//o;      # Remove trailing space
5410
5411         # Encoding the outgoing string is good practice, but not strictly
5412         # necessary in this case because we've stripped everything from it
5413         return encode_utf8($txt);
5414 $func$ LANGUAGE 'plperlu' STRICT IMMUTABLE;
5415
5416 -- Some handy functions, based on existing ones, to provide optional ingest normalization
5417
5418 CREATE OR REPLACE FUNCTION public.left_trunc( TEXT, INT ) RETURNS TEXT AS $func$
5419         SELECT SUBSTRING($1,$2);
5420 $func$ LANGUAGE SQL STRICT IMMUTABLE;
5421
5422 CREATE OR REPLACE FUNCTION public.right_trunc( TEXT, INT ) RETURNS TEXT AS $func$
5423         SELECT SUBSTRING($1,1,$2);
5424 $func$ LANGUAGE SQL STRICT IMMUTABLE;
5425
5426 CREATE OR REPLACE FUNCTION public.naco_normalize_keep_comma( TEXT ) RETURNS TEXT AS $func$
5427         SELECT public.naco_normalize($1,'a');
5428 $func$ LANGUAGE SQL STRICT IMMUTABLE;
5429
5430 CREATE OR REPLACE FUNCTION public.split_date_range( TEXT ) RETURNS TEXT AS $func$
5431         SELECT REGEXP_REPLACE( $1, E'(\\d{4})-(\\d{4})', E'\\1 \\2', 'g' );
5432 $func$ LANGUAGE SQL STRICT IMMUTABLE;
5433
5434 -- And ... a table in which to register them
5435
5436 CREATE TABLE config.index_normalizer (
5437         id              SERIAL  PRIMARY KEY,
5438         name            TEXT    UNIQUE NOT NULL,
5439         description     TEXT    UNIQUE NOT NULL,
5440         func            TEXT    NOT NULL,
5441         param_count     INT     NOT NULL DEFAULT 0
5442 );
5443
5444 CREATE TABLE config.metabib_field_index_norm_map (
5445         id      SERIAL  PRIMARY KEY,
5446         field   INT     NOT NULL REFERENCES config.metabib_field (id) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED,
5447         norm    INT     NOT NULL REFERENCES config.index_normalizer (id) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED,
5448         params  TEXT,
5449         pos     INT     NOT NULL DEFAULT 0
5450 );
5451
5452 INSERT INTO config.index_normalizer (name, description, func, param_count) VALUES (
5453         'NACO Normalize',
5454         'Apply NACO normalization rules to the extracted text.  See http://www.loc.gov/catdir/pcc/naco/normrule-2.html for details.',
5455         'naco_normalize',
5456         0
5457 );
5458
5459 INSERT INTO config.index_normalizer (name, description, func, param_count) VALUES (
5460         'Normalize date range',
5461         'Split date ranges in the form of "XXXX-YYYY" into "XXXX YYYY" for proper index.',
5462         'split_date_range',
5463         1
5464 );
5465
5466 INSERT INTO config.index_normalizer (name, description, func, param_count) VALUES (
5467         'NACO Normalize -- retain first comma',
5468         '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.',
5469         'naco_normalize_keep_comma',
5470         0
5471 );
5472
5473 INSERT INTO config.index_normalizer (name, description, func, param_count) VALUES (
5474         'Strip Diacritics',
5475         'Convert text to NFD form and remove non-spacing combining marks.',
5476         'remove_diacritics',
5477         0
5478 );
5479
5480 INSERT INTO config.index_normalizer (name, description, func, param_count) VALUES (
5481         'Up-case',
5482         'Convert text upper case.',
5483         'uppercase',
5484         0
5485 );
5486
5487 INSERT INTO config.index_normalizer (name, description, func, param_count) VALUES (
5488         'Down-case',
5489         'Convert text lower case.',
5490         'lowercase',
5491         0
5492 );
5493
5494 INSERT INTO config.index_normalizer (name, description, func, param_count) VALUES (
5495         'Extract Dewey-like number',
5496         'Extract a string of numeric characters ther resembles a DDC number.',
5497         'call_number_dewey',
5498         0
5499 );
5500
5501 INSERT INTO config.index_normalizer (name, description, func, param_count) VALUES (
5502         'Left truncation',
5503         'Discard the specified number of characters from the left side of the string.',
5504         'left_trunc',
5505         1
5506 );
5507
5508 INSERT INTO config.index_normalizer (name, description, func, param_count) VALUES (
5509         'Right truncation',
5510         'Include only the specified number of characters from the left side of the string.',
5511         'right_trunc',
5512         1
5513 );
5514
5515 INSERT INTO config.index_normalizer (name, description, func, param_count) VALUES (
5516         'First word',
5517         'Include only the first space-separated word of a string.',
5518         'first_word',
5519         0
5520 );
5521
5522 INSERT INTO config.metabib_field_index_norm_map (field,norm)
5523         SELECT  m.id,
5524                 i.id
5525           FROM  config.metabib_field m,
5526                 config.index_normalizer i
5527           WHERE i.func IN ('naco_normalize','split_date_range');
5528
5529 CREATE OR REPLACE FUNCTION oils_tsearch2 () RETURNS TRIGGER AS $$
5530 DECLARE
5531     normalizer      RECORD;
5532     value           TEXT := '';
5533 BEGIN
5534
5535     value := NEW.value;
5536
5537     IF TG_TABLE_NAME::TEXT ~ 'field_entry$' THEN
5538         FOR normalizer IN
5539             SELECT  n.func AS func,
5540                     n.param_count AS param_count,
5541                     m.params AS params
5542               FROM  config.index_normalizer n
5543                     JOIN config.metabib_field_index_norm_map m ON (m.norm = n.id)
5544               WHERE field = NEW.field AND m.pos < 0
5545               ORDER BY m.pos LOOP
5546                 EXECUTE 'SELECT ' || normalizer.func || '(' ||
5547                     quote_literal( value ) ||
5548                     CASE
5549                         WHEN normalizer.param_count > 0
5550                             THEN ',' || REPLACE(REPLACE(BTRIM(normalizer.params,'[]'),E'\'',E'\\\''),E'"',E'\'')
5551                             ELSE ''
5552                         END ||
5553                     ')' INTO value;
5554
5555         END LOOP;
5556
5557         NEW.value := value;
5558     END IF;
5559
5560     IF NEW.index_vector = ''::tsvector THEN
5561         RETURN NEW;
5562     END IF;
5563
5564     IF TG_TABLE_NAME::TEXT ~ 'field_entry$' THEN
5565         FOR normalizer IN
5566             SELECT  n.func AS func,
5567                     n.param_count AS param_count,
5568                     m.params AS params
5569               FROM  config.index_normalizer n
5570                     JOIN config.metabib_field_index_norm_map m ON (m.norm = n.id)
5571               WHERE field = NEW.field AND m.pos >= 0
5572               ORDER BY m.pos LOOP
5573                 EXECUTE 'SELECT ' || normalizer.func || '(' ||
5574                     quote_literal( value ) ||
5575                     CASE
5576                         WHEN normalizer.param_count > 0
5577                             THEN ',' || REPLACE(REPLACE(BTRIM(normalizer.params,'[]'),E'\'',E'\\\''),E'"',E'\'')
5578                             ELSE ''
5579                         END ||
5580                     ')' INTO value;
5581
5582         END LOOP;
5583     END IF;
5584
5585     IF REGEXP_REPLACE(VERSION(),E'^.+?(\\d+\\.\\d+).*?$',E'\\1')::FLOAT > 8.2 THEN
5586         NEW.index_vector = to_tsvector((TG_ARGV[0])::regconfig, value);
5587     ELSE
5588         NEW.index_vector = to_tsvector(TG_ARGV[0], value);
5589     END IF;
5590
5591     RETURN NEW;
5592 END;
5593 $$ LANGUAGE PLPGSQL;
5594
5595 CREATE OR REPLACE FUNCTION oils_xpath ( TEXT, TEXT, ANYARRAY ) RETURNS TEXT[] AS 'SELECT XPATH( $1, $2::XML, $3 )::TEXT[];' LANGUAGE SQL IMMUTABLE;
5596
5597 CREATE OR REPLACE FUNCTION oils_xpath ( TEXT, TEXT ) RETURNS TEXT[] AS 'SELECT XPATH( $1, $2::XML )::TEXT[];' LANGUAGE SQL IMMUTABLE;
5598
5599 CREATE OR REPLACE FUNCTION oils_xpath_string ( TEXT, TEXT, TEXT, ANYARRAY ) RETURNS TEXT AS $func$
5600     SELECT  ARRAY_TO_STRING(
5601                 oils_xpath(
5602                     $1 ||
5603                         CASE WHEN $1 ~ $re$/[^/[]*@[^]]+$$re$ OR $1 ~ $re$text\(\)$$re$ THEN '' ELSE '//text()' END,
5604                     $2,
5605                     $4
5606                 ),
5607                 $3
5608             );
5609 $func$ LANGUAGE SQL IMMUTABLE;
5610
5611 CREATE OR REPLACE FUNCTION oils_xpath_string ( TEXT, TEXT, TEXT ) RETURNS TEXT AS $func$
5612     SELECT oils_xpath_string( $1, $2, $3, '{}'::TEXT[] );
5613 $func$ LANGUAGE SQL IMMUTABLE;
5614
5615 CREATE OR REPLACE FUNCTION oils_xpath_string ( TEXT, TEXT, ANYARRAY ) RETURNS TEXT AS $func$
5616     SELECT oils_xpath_string( $1, $2, '', $3 );
5617 $func$ LANGUAGE SQL IMMUTABLE;
5618
5619 CREATE OR REPLACE FUNCTION oils_xpath_string ( TEXT, TEXT ) RETURNS TEXT AS $func$
5620     SELECT oils_xpath_string( $1, $2, '{}'::TEXT[] );
5621 $func$ LANGUAGE SQL IMMUTABLE;
5622
5623 CREATE TYPE metabib.field_entry_template AS (
5624         field_class     TEXT,
5625         field           INT,
5626         source          BIGINT,
5627         value           TEXT
5628 );
5629
5630 CREATE OR REPLACE FUNCTION oils_xslt_process(TEXT, TEXT) RETURNS TEXT AS $func$
5631   use strict;
5632
5633   use XML::LibXSLT;
5634   use XML::LibXML;
5635
5636   my $doc = shift;
5637   my $xslt = shift;
5638
5639   # The following approach uses the older XML::LibXML 1.69 / XML::LibXSLT 1.68
5640   # methods of parsing XML documents and stylesheets, in the hopes of broader
5641   # compatibility with distributions
5642   my $parser = $_SHARED{'_xslt_process'}{parsers}{xml} || XML::LibXML->new();
5643
5644   # Cache the XML parser, if we do not already have one
5645   $_SHARED{'_xslt_process'}{parsers}{xml} = $parser
5646     unless ($_SHARED{'_xslt_process'}{parsers}{xml});
5647
5648   my $xslt_parser = $_SHARED{'_xslt_process'}{parsers}{xslt} || XML::LibXSLT->new();
5649
5650   # Cache the XSLT processor, if we do not already have one
5651   $_SHARED{'_xslt_process'}{parsers}{xslt} = $xslt_parser
5652     unless ($_SHARED{'_xslt_process'}{parsers}{xslt});
5653
5654   my $stylesheet = $_SHARED{'_xslt_process'}{stylesheets}{$xslt} ||
5655     $xslt_parser->parse_stylesheet( $parser->parse_string($xslt) );
5656
5657   $_SHARED{'_xslt_process'}{stylesheets}{$xslt} = $stylesheet
5658     unless ($_SHARED{'_xslt_process'}{stylesheets}{$xslt});
5659
5660   return $stylesheet->output_string(
5661     $stylesheet->transform(
5662       $parser->parse_string($doc)
5663     )
5664   );
5665
5666 $func$ LANGUAGE 'plperlu' STRICT IMMUTABLE;
5667
5668 -- Add two columns so that the following function will compile.
5669 -- Eventually the label column will be NOT NULL, but not yet.
5670 ALTER TABLE config.metabib_field ADD COLUMN label TEXT;
5671 ALTER TABLE config.metabib_field ADD COLUMN facet_xpath TEXT;
5672
5673 CREATE OR REPLACE FUNCTION biblio.extract_metabib_field_entry ( rid BIGINT, default_joiner TEXT ) RETURNS SETOF metabib.field_entry_template AS $func$
5674 DECLARE
5675     bib     biblio.record_entry%ROWTYPE;
5676     idx     config.metabib_field%ROWTYPE;
5677     xfrm        config.xml_transform%ROWTYPE;
5678     prev_xfrm   TEXT;
5679     transformed_xml TEXT;
5680     xml_node    TEXT;
5681     xml_node_list   TEXT[];
5682     facet_text  TEXT;
5683     raw_text    TEXT;
5684     curr_text   TEXT;
5685     joiner      TEXT := default_joiner; -- XXX will index defs supply a joiner?
5686     output_row  metabib.field_entry_template%ROWTYPE;
5687 BEGIN
5688
5689     -- Get the record
5690     SELECT INTO bib * FROM biblio.record_entry WHERE id = rid;
5691
5692     -- Loop over the indexing entries
5693     FOR idx IN SELECT * FROM config.metabib_field ORDER BY format LOOP
5694
5695         SELECT INTO xfrm * from config.xml_transform WHERE name = idx.format;
5696
5697         -- See if we can skip the XSLT ... it's expensive
5698         IF prev_xfrm IS NULL OR prev_xfrm <> xfrm.name THEN
5699             -- Can't skip the transform
5700             IF xfrm.xslt <> '---' THEN
5701                 transformed_xml := oils_xslt_process(bib.marc,xfrm.xslt);
5702             ELSE
5703                 transformed_xml := bib.marc;
5704             END IF;
5705
5706             prev_xfrm := xfrm.name;
5707         END IF;
5708
5709         xml_node_list := oils_xpath( idx.xpath, transformed_xml, ARRAY[ARRAY[xfrm.prefix, xfrm.namespace_uri]] );
5710
5711         raw_text := NULL;
5712         FOR xml_node IN SELECT x FROM explode_array(xml_node_list) AS x LOOP
5713             CONTINUE WHEN xml_node !~ E'^\\s*<';
5714
5715             curr_text := ARRAY_TO_STRING(
5716                 oils_xpath( '//text()',
5717                     REGEXP_REPLACE( -- This escapes all &s not followed by "amp;".  Data ise returned from oils_xpath (above) in UTF-8, not entity encoded
5718                         REGEXP_REPLACE( -- This escapes embeded <s
5719                             xml_node,
5720                             $re$(>[^<]+)(<)([^>]+<)$re$,
5721                             E'\\1&lt;\\3',
5722                             'g'
5723                         ),
5724                         '&(?!amp;)',
5725                         '&amp;',
5726                         'g'
5727                     )
5728                 ),
5729                 ' '
5730             );
5731
5732             CONTINUE WHEN curr_text IS NULL OR curr_text = '';
5733
5734             IF raw_text IS NOT NULL THEN
5735                 raw_text := raw_text || joiner;
5736             END IF;
5737
5738             raw_text := COALESCE(raw_text,'') || curr_text;
5739
5740             -- insert raw node text for faceting
5741             IF idx.facet_field THEN
5742
5743                 IF idx.facet_xpath IS NOT NULL AND idx.facet_xpath <> '' THEN
5744                     facet_text := oils_xpath_string( idx.facet_xpath, xml_node, joiner, ARRAY[ARRAY[xfrm.prefix, xfrm.namespace_uri]] );
5745                 ELSE
5746                     facet_text := curr_text;
5747                 END IF;
5748
5749                 output_row.field_class = idx.field_class;
5750                 output_row.field = -1 * idx.id;
5751                 output_row.source = rid;
5752                 output_row.value = BTRIM(REGEXP_REPLACE(facet_text, E'\\s+', ' ', 'g'));
5753
5754                 RETURN NEXT output_row;
5755             END IF;
5756
5757         END LOOP;
5758
5759         CONTINUE WHEN raw_text IS NULL OR raw_text = '';
5760
5761         -- insert combined node text for searching
5762         IF idx.search_field THEN
5763             output_row.field_class = idx.field_class;
5764             output_row.field = idx.id;
5765             output_row.source = rid;
5766             output_row.value = BTRIM(REGEXP_REPLACE(raw_text, E'\\s+', ' ', 'g'));
5767
5768             RETURN NEXT output_row;
5769         END IF;
5770
5771     END LOOP;
5772
5773 END;
5774 $func$ LANGUAGE PLPGSQL;
5775
5776 -- default to a space joiner
5777 CREATE OR REPLACE FUNCTION biblio.extract_metabib_field_entry ( BIGINT ) RETURNS SETOF metabib.field_entry_template AS $func$
5778         SELECT * FROM biblio.extract_metabib_field_entry($1, ' ');
5779 $func$ LANGUAGE SQL;
5780
5781 CREATE OR REPLACE FUNCTION biblio.flatten_marc ( TEXT ) RETURNS SETOF metabib.full_rec AS $func$
5782
5783 use MARC::Record;
5784 use MARC::File::XML (BinaryEncoding => 'UTF-8');
5785
5786 my $xml = shift;
5787 my $r = MARC::Record->new_from_xml( $xml );
5788
5789 return_next( { tag => 'LDR', value => $r->leader } );
5790
5791 for my $f ( $r->fields ) {
5792     if ($f->is_control_field) {
5793         return_next({ tag => $f->tag, value => $f->data });
5794     } else {
5795         for my $s ($f->subfields) {
5796             return_next({
5797                 tag      => $f->tag,
5798                 ind1     => $f->indicator(1),
5799                 ind2     => $f->indicator(2),
5800                 subfield => $s->[0],
5801                 value    => $s->[1]
5802             });
5803
5804             if ( $f->tag eq '245' and $s->[0] eq 'a' ) {
5805                 my $trim = $f->indicator(2) || 0;
5806                 return_next({
5807                     tag      => 'tnf',
5808                     ind1     => $f->indicator(1),
5809                     ind2     => $f->indicator(2),
5810                     subfield => 'a',
5811                     value    => substr( $s->[1], $trim )
5812                 });
5813             }
5814         }
5815     }
5816 }
5817
5818 return undef;
5819
5820 $func$ LANGUAGE PLPERLU;
5821
5822 CREATE OR REPLACE FUNCTION biblio.flatten_marc ( rid BIGINT ) RETURNS SETOF metabib.full_rec AS $func$
5823 DECLARE
5824     bib biblio.record_entry%ROWTYPE;
5825     output  metabib.full_rec%ROWTYPE;
5826     field   RECORD;
5827 BEGIN
5828     SELECT INTO bib * FROM biblio.record_entry WHERE id = rid;
5829
5830     FOR field IN SELECT * FROM biblio.flatten_marc( bib.marc ) LOOP
5831         output.record := rid;
5832         output.ind1 := field.ind1;
5833         output.ind2 := field.ind2;
5834         output.tag := field.tag;
5835         output.subfield := field.subfield;
5836         IF field.subfield IS NOT NULL AND field.tag NOT IN ('020','022','024') THEN -- exclude standard numbers and control fields
5837             output.value := naco_normalize(field.value, field.subfield);
5838         ELSE
5839             output.value := field.value;
5840         END IF;
5841
5842         CONTINUE WHEN output.value IS NULL;
5843
5844         RETURN NEXT output;
5845     END LOOP;
5846 END;
5847 $func$ LANGUAGE PLPGSQL;
5848
5849 ALTER TABLE action.hold_request ADD COLUMN cut_in_line BOOL;
5850
5851 ALTER TABLE action.hold_request
5852 ADD COLUMN mint_condition boolean NOT NULL DEFAULT TRUE;
5853
5854 ALTER TABLE action.hold_request
5855 ADD COLUMN shelf_expire_time TIMESTAMPTZ;
5856
5857 ALTER TABLE action.hold_request DROP CONSTRAINT hold_request_current_copy_fkey;
5858
5859 ALTER TABLE action.hold_request DROP CONSTRAINT hold_request_hold_type_check;
5860
5861 -- If the following ALTERs die because the table doesn't exist, don't
5862 -- worry about it.  Some installations have it and some don't.
5863
5864 -- ALTER TABLE auditor.action_hold_request_history ADD COLUMN cut_in_line BOOL;
5865
5866 -- ALTER TABLE auditor.action_hold_request_history
5867 -- ADD COLUMN mint_condition boolean NOT NULL DEFAULT TRUE;
5868
5869 -- ALTER TABLE auditor.action_hold_request_history
5870 -- ADD COLUMN shelf_expire_time TIMESTAMPTZ;
5871
5872 UPDATE config.index_normalizer SET param_count = 0 WHERE func = 'split_date_range';
5873
5874 ALTER TABLE actor.usr ADD COLUMN
5875         claims_never_checked_out_count  INT         NOT NULL DEFAULT 0;
5876
5877 ALTER TABLE AUDITOR.actor_usr_history ADD COLUMN 
5878         claims_never_checked_out_count INT NOT NULL DEFAULT 0;
5879
5880 CREATE OR REPLACE FUNCTION action.circulation_claims_returned () RETURNS TRIGGER AS $$
5881 BEGIN
5882         IF OLD.stop_fines IS NULL OR OLD.stop_fines <> NEW.stop_fines THEN
5883                 IF NEW.stop_fines = 'CLAIMSRETURNED' THEN
5884                         UPDATE actor.usr SET claims_returned_count = claims_returned_count + 1 WHERE id = NEW.usr;
5885                 END IF;
5886                 IF NEW.stop_fines = 'CLAIMSNEVERCHECKEDOUT' THEN
5887                         UPDATE actor.usr SET claims_never_checked_out_count = claims_never_checked_out_count + 1 WHERE id = NEW.usr;
5888                 END IF;
5889                 IF NEW.stop_fines = 'LOST' THEN
5890                         UPDATE asset.copy SET status = 3 WHERE id = NEW.target_copy;
5891                 END IF;
5892         END IF;
5893         RETURN NEW;
5894 END;
5895 $$ LANGUAGE 'plpgsql';
5896
5897 -- Create new table acq.fund_allocation_percent
5898 -- Populate it from acq.fund_allocation
5899 -- Convert all percentages to amounts in acq.fund_allocation
5900
5901 CREATE TABLE acq.fund_allocation_percent
5902 (
5903     id                   SERIAL            PRIMARY KEY,
5904     funding_source       INT               NOT NULL REFERENCES acq.funding_source
5905                                                DEFERRABLE INITIALLY DEFERRED,
5906     org                  INT               NOT NULL REFERENCES actor.org_unit
5907                                                DEFERRABLE INITIALLY DEFERRED,
5908     fund_code            TEXT,
5909     percent              NUMERIC           NOT NULL,
5910     allocator            INTEGER           NOT NULL REFERENCES actor.usr
5911                                                DEFERRABLE INITIALLY DEFERRED,
5912     note                 TEXT,
5913     create_time          TIMESTAMPTZ       NOT NULL DEFAULT now(),
5914     CONSTRAINT logical_key UNIQUE( funding_source, org, fund_code ),
5915     CONSTRAINT percentage_range CHECK( percent >= 0 AND percent <= 100 )
5916 );
5917
5918 -- Trigger function to validate combination of org_unit and fund_code
5919
5920 CREATE OR REPLACE FUNCTION acq.fund_alloc_percent_val()
5921 RETURNS TRIGGER AS $$
5922 --
5923 DECLARE
5924 --
5925 dummy int := 0;
5926 --
5927 BEGIN
5928     SELECT
5929         1
5930     INTO
5931         dummy
5932     FROM
5933         acq.fund
5934     WHERE
5935         org = NEW.org
5936         AND code = NEW.fund_code
5937         LIMIT 1;
5938     --
5939     IF dummy = 1 then
5940         RETURN NEW;
5941     ELSE
5942         RAISE EXCEPTION 'No fund exists for org % and code %', NEW.org, NEW.fund_code;
5943     END IF;
5944 END;
5945 $$ LANGUAGE plpgsql;
5946
5947 CREATE TRIGGER acq_fund_alloc_percent_val_trig
5948     BEFORE INSERT OR UPDATE ON acq.fund_allocation_percent
5949     FOR EACH ROW EXECUTE PROCEDURE acq.fund_alloc_percent_val();
5950
5951 CREATE OR REPLACE FUNCTION acq.fap_limit_100()
5952 RETURNS TRIGGER AS $$
5953 DECLARE
5954 --
5955 total_percent numeric;
5956 --
5957 BEGIN
5958     SELECT
5959         sum( percent )
5960     INTO
5961         total_percent
5962     FROM
5963         acq.fund_allocation_percent AS fap
5964     WHERE
5965         fap.funding_source = NEW.funding_source;
5966     --
5967     IF total_percent > 100 THEN
5968         RAISE EXCEPTION 'Total percentages exceed 100 for funding_source %',
5969             NEW.funding_source;
5970     ELSE
5971         RETURN NEW;
5972     END IF;
5973 END;
5974 $$ LANGUAGE plpgsql;
5975
5976 CREATE TRIGGER acqfap_limit_100_trig
5977     AFTER INSERT OR UPDATE ON acq.fund_allocation_percent
5978     FOR EACH ROW EXECUTE PROCEDURE acq.fap_limit_100();
5979
5980 -- Populate new table from acq.fund_allocation
5981
5982 INSERT INTO acq.fund_allocation_percent
5983 (
5984     funding_source,
5985     org,
5986     fund_code,
5987     percent,
5988     allocator,
5989     note,
5990     create_time
5991 )
5992     SELECT
5993         fa.funding_source,
5994         fund.org,
5995         fund.code,
5996         fa.percent,
5997         fa.allocator,
5998         fa.note,
5999         fa.create_time
6000     FROM
6001         acq.fund_allocation AS fa
6002             INNER JOIN acq.fund AS fund
6003                 ON ( fa.fund = fund.id )
6004     WHERE
6005         fa.percent is not null
6006     ORDER BY
6007         fund.org;
6008
6009 -- Temporary function to convert percentages to amounts in acq.fund_allocation
6010
6011 -- Algorithm to apply to each funding source:
6012
6013 -- 1. Add up the credits.
6014 -- 2. Add up the percentages.
6015 -- 3. Multiply the sum of the percentages times the sum of the credits.  Drop any
6016 --    fractional cents from the result.  This is the total amount to be allocated.
6017 -- 4. For each allocation: multiply the percentage by the total allocation.  Drop any
6018 --    fractional cents to get a preliminary amount.
6019 -- 5. Add up the preliminary amounts for all the allocations.
6020 -- 6. Subtract the results of step 5 from the result of step 3.  The difference is the
6021 --    number of residual cents (resulting from having dropped fractional cents) that
6022 --    must be distributed across the funds in order to make the total of the amounts
6023 --    match the total allocation.
6024 -- 7. Make a second pass through the allocations, in decreasing order of the fractional
6025 --    cents that were dropped from their amounts in step 4.  Add one cent to the amount
6026 --    for each successive fund, until all the residual cents have been exhausted.
6027
6028 -- Result: the sum of the individual allocations now equals the total to be allocated,
6029 -- to the penny.  The individual amounts match the percentages as closely as possible,
6030 -- given the constraint that the total must match.
6031
6032 CREATE OR REPLACE FUNCTION acq.apply_percents()
6033 RETURNS VOID AS $$
6034 declare
6035 --
6036 tot              RECORD;
6037 fund             RECORD;
6038 tot_cents        INTEGER;
6039 src              INTEGER;
6040 id               INTEGER[];
6041 curr_id          INTEGER;
6042 pennies          NUMERIC[];
6043 curr_amount      NUMERIC;
6044 i                INTEGER;
6045 total_of_floors  INTEGER;
6046 total_percent    NUMERIC;
6047 total_allocation INTEGER;
6048 residue          INTEGER;
6049 --
6050 begin
6051         RAISE NOTICE 'Applying percents';
6052         FOR tot IN
6053                 SELECT
6054                         fsrc.funding_source,
6055                         sum( fsrc.amount ) AS total
6056                 FROM
6057                         acq.funding_source_credit AS fsrc
6058                 WHERE fsrc.funding_source IN
6059                         ( SELECT DISTINCT fa.funding_source
6060                           FROM acq.fund_allocation AS fa
6061                           WHERE fa.percent IS NOT NULL )
6062                 GROUP BY
6063                         fsrc.funding_source
6064         LOOP
6065                 tot_cents = floor( tot.total * 100 );
6066                 src = tot.funding_source;
6067                 RAISE NOTICE 'Funding source % total %',
6068                         src, tot_cents;
6069                 i := 0;
6070                 total_of_floors := 0;
6071                 total_percent := 0;
6072                 --
6073                 FOR fund in
6074                         SELECT
6075                                 fa.id,
6076                                 fa.percent,
6077                                 floor( fa.percent * tot_cents / 100 ) as floor_pennies
6078                         FROM
6079                                 acq.fund_allocation AS fa
6080                         WHERE
6081                                 fa.funding_source = src
6082                                 AND fa.percent IS NOT NULL
6083                         ORDER BY
6084                                 mod( fa.percent * tot_cents / 100, 1 ),
6085                                 fa.fund,
6086                                 fa.id
6087                 LOOP
6088                         RAISE NOTICE '   %: %',
6089                                 fund.id,
6090                                 fund.floor_pennies;
6091                         i := i + 1;
6092                         id[i] = fund.id;
6093                         pennies[i] = fund.floor_pennies;
6094                         total_percent := total_percent + fund.percent;
6095                         total_of_floors := total_of_floors + pennies[i];
6096                 END LOOP;
6097                 total_allocation := floor( total_percent * tot_cents /100 );
6098                 RAISE NOTICE 'Total before distributing residue: %', total_of_floors;
6099                 residue := total_allocation - total_of_floors;
6100                 RAISE NOTICE 'Residue: %', residue;
6101                 --
6102                 -- Post the calculated amounts, revising as needed to
6103                 -- distribute the rounding error
6104                 --
6105                 WHILE i > 0 LOOP
6106                         IF residue > 0 THEN
6107                                 pennies[i] = pennies[i] + 1;
6108                                 residue := residue - 1;
6109                         END IF;
6110                         --
6111                         -- Post amount
6112                         --
6113                         curr_id     := id[i];
6114                         curr_amount := trunc( pennies[i] / 100, 2 );
6115                         --
6116                         UPDATE
6117                                 acq.fund_allocation AS fa
6118                         SET
6119                                 amount = curr_amount,
6120                                 percent = NULL
6121                         WHERE
6122                                 fa.id = curr_id;
6123                         --
6124                         RAISE NOTICE '   ID % and amount %',
6125                                 curr_id,
6126                                 curr_amount;
6127                         i = i - 1;
6128                 END LOOP;
6129         END LOOP;
6130 end;
6131 $$ LANGUAGE 'plpgsql';
6132
6133 -- Run the temporary function
6134
6135 select * from acq.apply_percents();
6136
6137 -- Drop the temporary function now that we're done with it
6138
6139 DROP FUNCTION IF EXISTS acq.apply_percents();
6140
6141 -- Eliminate acq.fund_allocation.percent, which has been moved to the acq.fund_allocation_percent table.
6142
6143 -- If the following step fails, it's probably because there are still some non-null percent values in
6144 -- acq.fund_allocation.  They should have all been converted to amounts, and then set to null, by a
6145 -- previous upgrade step based on 0049.schema.acq_funding_allocation_percent.sql.  If there are any
6146 -- non-null values, then either that step didn't run, or it didn't work, or some non-null values
6147 -- slipped in afterwards.
6148
6149 -- To convert any remaining percents to amounts: create, run, and then drop the temporary stored
6150 -- procedure acq.apply_percents() as defined above.
6151
6152 ALTER TABLE acq.fund_allocation
6153 ALTER COLUMN amount SET NOT NULL;
6154
6155 CREATE OR REPLACE VIEW acq.fund_allocation_total AS
6156     SELECT  fund,
6157             SUM(a.amount * acq.exchange_ratio(s.currency_type, f.currency_type))::NUMERIC(100,2) AS amount
6158     FROM acq.fund_allocation a
6159          JOIN acq.fund f ON (a.fund = f.id)
6160          JOIN acq.funding_source s ON (a.funding_source = s.id)
6161     GROUP BY 1;
6162
6163 CREATE OR REPLACE VIEW acq.funding_source_allocation_total AS
6164     SELECT  funding_source,
6165             SUM(a.amount)::NUMERIC(100,2) AS amount
6166     FROM  acq.fund_allocation a
6167     GROUP BY 1;
6168
6169 ALTER TABLE acq.fund_allocation
6170 DROP COLUMN percent;
6171
6172 CREATE TABLE asset.copy_location_order
6173 (
6174         id              SERIAL           PRIMARY KEY,
6175         location        INT              NOT NULL
6176                                              REFERENCES asset.copy_location
6177                                              ON DELETE CASCADE
6178                                              DEFERRABLE INITIALLY DEFERRED,
6179         org             INT              NOT NULL
6180                                              REFERENCES actor.org_unit
6181                                              ON DELETE CASCADE
6182                                              DEFERRABLE INITIALLY DEFERRED,
6183         position        INT              NOT NULL DEFAULT 0,
6184         CONSTRAINT acplo_once_per_org UNIQUE ( location, org )
6185 );
6186
6187 ALTER TABLE money.credit_card_payment ADD COLUMN cc_processor TEXT;
6188
6189 -- If you ran this before its most recent incarnation:
6190 -- delete from config.upgrade_log where version = '0328';
6191 -- alter table money.credit_card_payment drop column cc_name;
6192
6193 ALTER TABLE money.credit_card_payment ADD COLUMN cc_first_name TEXT;
6194 ALTER TABLE money.credit_card_payment ADD COLUMN cc_last_name TEXT;
6195
6196 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$
6197 DECLARE
6198     current_group    permission.grp_tree%ROWTYPE;
6199     user_object    actor.usr%ROWTYPE;
6200     item_object    asset.copy%ROWTYPE;
6201     cn_object    asset.call_number%ROWTYPE;
6202     rec_descriptor    metabib.rec_descriptor%ROWTYPE;
6203     current_mp    config.circ_matrix_matchpoint%ROWTYPE;
6204     matchpoint    config.circ_matrix_matchpoint%ROWTYPE;
6205 BEGIN
6206     SELECT INTO user_object * FROM actor.usr WHERE id = match_user;
6207     SELECT INTO item_object * FROM asset.copy WHERE id = match_item;
6208     SELECT INTO cn_object * FROM asset.call_number WHERE id = item_object.call_number;
6209     SELECT INTO rec_descriptor r.* FROM metabib.rec_descriptor r JOIN asset.call_number c USING (record) WHERE c.id = item_object.call_number;
6210     SELECT INTO current_group * FROM permission.grp_tree WHERE id = user_object.profile;
6211
6212     LOOP
6213         -- for each potential matchpoint for this ou and group ...
6214         FOR current_mp IN
6215             SELECT  m.*
6216               FROM  config.circ_matrix_matchpoint m
6217                     JOIN actor.org_unit_ancestors( context_ou ) d ON (m.org_unit = d.id)
6218                     LEFT JOIN actor.org_unit_proximity p ON (p.from_org = context_ou AND p.to_org = d.id)
6219               WHERE m.grp = current_group.id
6220                     AND m.active
6221                     AND (m.copy_owning_lib IS NULL OR cn_object.owning_lib IN ( SELECT id FROM actor.org_unit_descendants(m.copy_owning_lib) ))
6222                     AND (m.copy_circ_lib   IS NULL OR item_object.circ_lib IN ( SELECT id FROM actor.org_unit_descendants(m.copy_circ_lib)   ))
6223               ORDER BY    CASE WHEN p.prox        IS NULL THEN 999 ELSE p.prox END,
6224                     CASE WHEN m.copy_owning_lib IS NOT NULL
6225                         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 )
6226                         ELSE 0
6227                     END +
6228                     CASE WHEN m.copy_circ_lib IS NOT NULL
6229                         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 )
6230                         ELSE 0
6231                     END +
6232                     CASE WHEN m.is_renewal = renewal        THEN 128 ELSE 0 END +
6233                     CASE WHEN m.juvenile_flag    IS NOT NULL THEN 64 ELSE 0 END +
6234                     CASE WHEN m.circ_modifier    IS NOT NULL THEN 32 ELSE 0 END +
6235                     CASE WHEN m.marc_type        IS NOT NULL THEN 16 ELSE 0 END +
6236                     CASE WHEN m.marc_form        IS NOT NULL THEN 8 ELSE 0 END +
6237                     CASE WHEN m.marc_vr_format    IS NOT NULL THEN 4 ELSE 0 END +
6238                     CASE WHEN m.ref_flag        IS NOT NULL THEN 2 ELSE 0 END +
6239                     CASE WHEN m.usr_age_lower_bound    IS NOT NULL THEN 0.5 ELSE 0 END +
6240                     CASE WHEN m.usr_age_upper_bound    IS NOT NULL THEN 0.5 ELSE 0 END DESC LOOP
6241
6242             IF current_mp.circ_modifier IS NOT NULL THEN
6243                 CONTINUE WHEN current_mp.circ_modifier <> item_object.circ_modifier OR item_object.circ_modifier IS NULL;
6244             END IF;
6245
6246             IF current_mp.marc_type IS NOT NULL THEN
6247                 IF item_object.circ_as_type IS NOT NULL THEN
6248                     CONTINUE WHEN current_mp.marc_type <> item_object.circ_as_type;
6249                 ELSE
6250                     CONTINUE WHEN current_mp.marc_type <> rec_descriptor.item_type;
6251                 END IF;
6252             END IF;
6253
6254             IF current_mp.marc_form IS NOT NULL THEN
6255                 CONTINUE WHEN current_mp.marc_form <> rec_descriptor.item_form;
6256             END IF;
6257
6258             IF current_mp.marc_vr_format IS NOT NULL THEN
6259                 CONTINUE WHEN current_mp.marc_vr_format <> rec_descriptor.vr_format;
6260             END IF;
6261
6262             IF current_mp.ref_flag IS NOT NULL THEN
6263                 CONTINUE WHEN current_mp.ref_flag <> item_object.ref;
6264             END IF;
6265
6266             IF current_mp.juvenile_flag IS NOT NULL THEN
6267                 CONTINUE WHEN current_mp.juvenile_flag <> user_object.juvenile;
6268             END IF;
6269
6270             IF current_mp.usr_age_lower_bound IS NOT NULL THEN
6271                 CONTINUE WHEN user_object.dob IS NULL OR current_mp.usr_age_lower_bound < age(user_object.dob);
6272             END IF;
6273
6274             IF current_mp.usr_age_upper_bound IS NOT NULL THEN
6275                 CONTINUE WHEN user_object.dob IS NULL OR current_mp.usr_age_upper_bound > age(user_object.dob);
6276             END IF;
6277
6278
6279             -- everything was undefined or matched
6280             matchpoint = current_mp;
6281
6282             EXIT WHEN matchpoint.id IS NOT NULL;
6283         END LOOP;
6284
6285         EXIT WHEN current_group.parent IS NULL OR matchpoint.id IS NOT NULL;
6286
6287         SELECT INTO current_group * FROM permission.grp_tree WHERE id = current_group.parent;
6288     END LOOP;
6289
6290     RETURN matchpoint;
6291 END;
6292 $func$ LANGUAGE plpgsql;
6293
6294 CREATE TYPE action.hold_stats AS (
6295     hold_count              INT,
6296     copy_count              INT,
6297     available_count         INT,
6298     total_copy_ratio        FLOAT,
6299     available_copy_ratio    FLOAT
6300 );
6301
6302 CREATE OR REPLACE FUNCTION action.copy_related_hold_stats (copy_id INT) RETURNS action.hold_stats AS $func$
6303 DECLARE
6304     output          action.hold_stats%ROWTYPE;
6305     hold_count      INT := 0;
6306     copy_count      INT := 0;
6307     available_count INT := 0;
6308     hold_map_data   RECORD;
6309 BEGIN
6310
6311     output.hold_count := 0;
6312     output.copy_count := 0;
6313     output.available_count := 0;
6314
6315     SELECT  COUNT( DISTINCT m.hold ) INTO hold_count
6316       FROM  action.hold_copy_map m
6317             JOIN action.hold_request h ON (m.hold = h.id)
6318       WHERE m.target_copy = copy_id
6319             AND NOT h.frozen;
6320
6321     output.hold_count := hold_count;
6322
6323     IF output.hold_count > 0 THEN
6324         FOR hold_map_data IN
6325             SELECT  DISTINCT m.target_copy,
6326                     acp.status
6327               FROM  action.hold_copy_map m
6328                     JOIN asset.copy acp ON (m.target_copy = acp.id)
6329                     JOIN action.hold_request h ON (m.hold = h.id)
6330               WHERE m.hold IN ( SELECT DISTINCT hold FROM action.hold_copy_map WHERE target_copy = copy_id ) AND NOT h.frozen
6331         LOOP
6332             output.copy_count := output.copy_count + 1;
6333             IF hold_map_data.status IN (0,7,12) THEN
6334                 output.available_count := output.available_count + 1;
6335             END IF;
6336         END LOOP;
6337         output.total_copy_ratio = output.copy_count::FLOAT / output.hold_count::FLOAT;
6338         output.available_copy_ratio = output.available_count::FLOAT / output.hold_count::FLOAT;
6339
6340     END IF;
6341
6342     RETURN output;
6343
6344 END;
6345 $func$ LANGUAGE PLPGSQL;
6346
6347 ALTER TABLE config.circ_matrix_matchpoint ADD COLUMN total_copy_hold_ratio FLOAT;
6348 ALTER TABLE config.circ_matrix_matchpoint ADD COLUMN available_copy_hold_ratio FLOAT;
6349
6350 ALTER TABLE config.circ_matrix_matchpoint DROP CONSTRAINT ep_once_per_grp_loc_mod_marc;
6351
6352 ALTER TABLE config.circ_matrix_matchpoint ADD COLUMN copy_circ_lib   INT REFERENCES actor.org_unit (id) DEFERRABLE INITIALLY DEFERRED;
6353 ALTER TABLE config.circ_matrix_matchpoint ADD COLUMN copy_owning_lib INT REFERENCES actor.org_unit (id) DEFERRABLE INITIALLY DEFERRED;
6354
6355 ALTER TABLE config.circ_matrix_matchpoint ADD CONSTRAINT ep_once_per_grp_loc_mod_marc UNIQUE (
6356     grp, org_unit, circ_modifier, marc_type, marc_form, marc_vr_format, ref_flag,
6357     juvenile_flag, usr_age_lower_bound, usr_age_upper_bound, is_renewal, copy_circ_lib,
6358     copy_owning_lib
6359 );
6360
6361 -- Return the correct fail_part when the item can't be found
6362 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$
6363 DECLARE
6364     user_object        actor.usr%ROWTYPE;
6365     standing_penalty    config.standing_penalty%ROWTYPE;
6366     item_object        asset.copy%ROWTYPE;
6367     item_status_object    config.copy_status%ROWTYPE;
6368     item_location_object    asset.copy_location%ROWTYPE;
6369     result            action.matrix_test_result;
6370     circ_test        config.circ_matrix_matchpoint%ROWTYPE;
6371     out_by_circ_mod        config.circ_matrix_circ_mod_test%ROWTYPE;
6372     circ_mod_map        config.circ_matrix_circ_mod_test_map%ROWTYPE;
6373     hold_ratio          action.hold_stats%ROWTYPE;
6374     penalty_type         TEXT;
6375     tmp_grp         INT;
6376     items_out        INT;
6377     context_org_list        INT[];
6378     done            BOOL := FALSE;
6379 BEGIN
6380     result.success := TRUE;
6381
6382     -- Fail if the user is BARRED
6383     SELECT INTO user_object * FROM actor.usr WHERE id = match_user;
6384
6385     -- Fail if we couldn't find the user 
6386     IF user_object.id IS NULL THEN
6387         result.fail_part := 'no_user';
6388         result.success := FALSE;
6389         done := TRUE;
6390         RETURN NEXT result;
6391         RETURN;
6392     END IF;
6393
6394     SELECT INTO item_object * FROM asset.copy WHERE id = match_item;
6395
6396     -- Fail if we couldn't find the item 
6397     IF item_object.id IS NULL THEN
6398         result.fail_part := 'no_item';
6399         result.success := FALSE;
6400         done := TRUE;
6401         RETURN NEXT result;
6402         RETURN;
6403     END IF;
6404
6405     SELECT INTO circ_test * FROM action.find_circ_matrix_matchpoint(circ_ou, match_item, match_user, renewal);
6406     result.matchpoint := circ_test.id;
6407
6408     -- Fail if we couldn't find a matchpoint
6409     IF result.matchpoint IS NULL THEN
6410         result.fail_part := 'no_matchpoint';
6411         result.success := FALSE;
6412         done := TRUE;
6413         RETURN NEXT result;
6414     END IF;
6415
6416     IF user_object.barred IS TRUE THEN
6417         result.fail_part := 'actor.usr.barred';
6418         result.success := FALSE;
6419         done := TRUE;
6420         RETURN NEXT result;
6421     END IF;
6422
6423     -- Fail if the item can't circulate
6424     IF item_object.circulate IS FALSE THEN
6425         result.fail_part := 'asset.copy.circulate';
6426         result.success := FALSE;
6427         done := TRUE;
6428         RETURN NEXT result;
6429     END IF;
6430
6431     -- Fail if the item isn't in a circulateable status on a non-renewal
6432     IF NOT renewal AND item_object.status NOT IN ( 0, 7, 8 ) THEN
6433         result.fail_part := 'asset.copy.status';
6434         result.success := FALSE;
6435         done := TRUE;
6436         RETURN NEXT result;
6437     ELSIF renewal AND item_object.status <> 1 THEN
6438         result.fail_part := 'asset.copy.status';
6439         result.success := FALSE;
6440         done := TRUE;
6441         RETURN NEXT result;
6442     END IF;
6443
6444     -- Fail if the item can't circulate because of the shelving location
6445     SELECT INTO item_location_object * FROM asset.copy_location WHERE id = item_object.location;
6446     IF item_location_object.circulate IS FALSE THEN
6447         result.fail_part := 'asset.copy_location.circulate';
6448         result.success := FALSE;
6449         done := TRUE;
6450         RETURN NEXT result;
6451     END IF;
6452
6453     SELECT INTO context_org_list ARRAY_ACCUM(id) FROM actor.org_unit_full_path( circ_test.org_unit );
6454
6455     -- Fail if the test is set to hard non-circulating
6456     IF circ_test.circulate IS FALSE THEN
6457         result.fail_part := 'config.circ_matrix_test.circulate';
6458         result.success := FALSE;
6459         done := TRUE;
6460         RETURN NEXT result;
6461     END IF;
6462
6463     -- Fail if the total copy-hold ratio is too low
6464     IF circ_test.total_copy_hold_ratio IS NOT NULL THEN
6465         SELECT INTO hold_ratio * FROM action.copy_related_hold_stats(match_item);
6466         IF hold_ratio.total_copy_ratio IS NOT NULL AND hold_ratio.total_copy_ratio < circ_test.total_copy_hold_ratio THEN
6467             result.fail_part := 'config.circ_matrix_test.total_copy_hold_ratio';
6468             result.success := FALSE;
6469             done := TRUE;
6470             RETURN NEXT result;
6471         END IF;
6472     END IF;
6473
6474     -- Fail if the available copy-hold ratio is too low
6475     IF circ_test.available_copy_hold_ratio IS NOT NULL THEN
6476         SELECT INTO hold_ratio * FROM action.copy_related_hold_stats(match_item);
6477         IF hold_ratio.available_copy_ratio IS NOT NULL AND hold_ratio.available_copy_ratio < circ_test.available_copy_hold_ratio THEN
6478             result.fail_part := 'config.circ_matrix_test.available_copy_hold_ratio';
6479             result.success := FALSE;
6480             done := TRUE;
6481             RETURN NEXT result;
6482         END IF;
6483     END IF;
6484
6485     IF renewal THEN
6486         penalty_type = '%RENEW%';
6487     ELSE
6488         penalty_type = '%CIRC%';
6489     END IF;
6490
6491     FOR standing_penalty IN
6492         SELECT  DISTINCT csp.*
6493           FROM  actor.usr_standing_penalty usp
6494                 JOIN config.standing_penalty csp ON (csp.id = usp.standing_penalty)
6495           WHERE usr = match_user
6496                 AND usp.org_unit IN ( SELECT * FROM explode_array(context_org_list) )
6497                 AND (usp.stop_date IS NULL or usp.stop_date > NOW())
6498                 AND csp.block_list LIKE penalty_type LOOP
6499
6500         result.fail_part := standing_penalty.name;
6501         result.success := FALSE;
6502         done := TRUE;
6503         RETURN NEXT result;
6504     END LOOP;
6505
6506     -- Fail if the user has too many items with specific circ_modifiers checked out
6507     FOR out_by_circ_mod IN SELECT * FROM config.circ_matrix_circ_mod_test WHERE matchpoint = circ_test.id LOOP
6508         SELECT  INTO items_out COUNT(*)
6509           FROM  action.circulation circ
6510             JOIN asset.copy cp ON (cp.id = circ.target_copy)
6511           WHERE circ.usr = match_user
6512                AND circ.circ_lib IN ( SELECT * FROM explode_array(context_org_list) )
6513             AND circ.checkin_time IS NULL
6514             AND (circ.stop_fines IN ('MAXFINES','LONGOVERDUE') OR circ.stop_fines IS NULL)
6515             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);
6516         IF items_out >= out_by_circ_mod.items_out THEN
6517             result.fail_part := 'config.circ_matrix_circ_mod_test';
6518             result.success := FALSE;
6519             done := TRUE;
6520             RETURN NEXT result;
6521         END IF;
6522     END LOOP;
6523
6524     -- If we passed everything, return the successful matchpoint id
6525     IF NOT done THEN
6526         RETURN NEXT result;
6527     END IF;
6528
6529     RETURN;
6530 END;
6531 $func$ LANGUAGE plpgsql;
6532
6533 CREATE TABLE config.remote_account (
6534     id          SERIAL  PRIMARY KEY,
6535     label       TEXT    NOT NULL,
6536     host        TEXT    NOT NULL,   -- name or IP, :port optional
6537     username    TEXT,               -- optional, since we could default to $USER
6538     password    TEXT,               -- optional, since we could use SSH keys, or anonymous login.
6539     account     TEXT,               -- aka profile or FTP "account" command
6540     path        TEXT,               -- aka directory
6541     owner       INT     NOT NULL REFERENCES actor.org_unit (id) DEFERRABLE INITIALLY DEFERRED,
6542     last_activity TIMESTAMP WITH TIME ZONE
6543 );
6544
6545 CREATE TABLE acq.edi_account (      -- similar tables can extend remote_account for other parts of EG
6546     provider    INT     NOT NULL REFERENCES acq.provider          (id) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED,
6547     in_dir      TEXT,   -- incoming messages dir (probably different than config.remote_account.path, the outgoing dir)
6548         vendcode    TEXT,
6549         vendacct    TEXT
6550
6551 ) INHERITS (config.remote_account);
6552
6553 ALTER TABLE acq.edi_account ADD PRIMARY KEY (id);
6554
6555 CREATE TABLE acq.claim_type (
6556         id             SERIAL           PRIMARY KEY,
6557         org_unit       INT              NOT NULL REFERENCES actor.org_unit(id)
6558                                                  DEFERRABLE INITIALLY DEFERRED,
6559         code           TEXT             NOT NULL,
6560         description    TEXT             NOT NULL,
6561         CONSTRAINT claim_type_once_per_org UNIQUE ( org_unit, code )
6562 );
6563
6564 CREATE TABLE acq.claim (
6565         id             SERIAL           PRIMARY KEY,
6566         type           INT              NOT NULL REFERENCES acq.claim_type
6567                                                  DEFERRABLE INITIALLY DEFERRED,
6568         lineitem_detail BIGINT          NOT NULL REFERENCES acq.lineitem_detail
6569                                                  DEFERRABLE INITIALLY DEFERRED
6570 );
6571
6572 CREATE TABLE acq.claim_policy (
6573         id              SERIAL       PRIMARY KEY,
6574         org_unit        INT          NOT NULL REFERENCES actor.org_unit
6575                                      DEFERRABLE INITIALLY DEFERRED,
6576         name            TEXT         NOT NULL,
6577         description     TEXT         NOT NULL,
6578         CONSTRAINT name_once_per_org UNIQUE (org_unit, name)
6579 );
6580
6581 -- Add a san column for EDI. 
6582 -- See: http://isbn.org/standards/home/isbn/us/san/san-qa.asp
6583
6584 ALTER TABLE acq.provider ADD COLUMN san INT;
6585
6586 ALTER TABLE acq.provider ALTER COLUMN san TYPE TEXT USING lpad(text(san), 7, '0');
6587
6588 -- null edi_default is OK... it has to be, since we have no values in acq.edi_account yet
6589 ALTER TABLE acq.provider ADD COLUMN edi_default INT REFERENCES acq.edi_account (id) DEFERRABLE INITIALLY DEFERRED;
6590
6591 ALTER TABLE acq.provider
6592         ADD COLUMN active BOOL NOT NULL DEFAULT TRUE;
6593
6594 ALTER TABLE acq.provider
6595         ADD COLUMN prepayment_required BOOLEAN NOT NULL DEFAULT FALSE;
6596
6597 ALTER TABLE acq.provider
6598         ADD COLUMN url TEXT;
6599
6600 ALTER TABLE acq.provider
6601         ADD COLUMN email TEXT;
6602
6603 ALTER TABLE acq.provider
6604         ADD COLUMN phone TEXT;
6605
6606 ALTER TABLE acq.provider
6607         ADD COLUMN fax_phone TEXT;
6608
6609 ALTER TABLE acq.provider
6610         ADD COLUMN default_claim_policy INT
6611                 REFERENCES acq.claim_policy
6612                 DEFERRABLE INITIALLY DEFERRED;
6613
6614 ALTER TABLE action.transit_copy
6615 ADD COLUMN prev_dest INTEGER REFERENCES actor.org_unit( id )
6616                                                          DEFERRABLE INITIALLY DEFERRED;
6617
6618 -- Correct the ISSN array definition for reporter.simple_record
6619
6620 CREATE OR REPLACE VIEW reporter.simple_record AS
6621 SELECT  r.id,
6622         s.metarecord,
6623         r.fingerprint,
6624         r.quality,
6625         r.tcn_source,
6626         r.tcn_value,
6627         title.value AS title,
6628         uniform_title.value AS uniform_title,
6629         author.value AS author,
6630         publisher.value AS publisher,
6631         SUBSTRING(pubdate.value FROM $$\d+$$) AS pubdate,
6632         series_title.value AS series_title,
6633         series_statement.value AS series_statement,
6634         summary.value AS summary,
6635         ARRAY_ACCUM( SUBSTRING(isbn.value FROM $$^\S+$$) ) AS isbn,
6636         ARRAY_ACCUM( REGEXP_REPLACE(issn.value, E'^\\S*(\\d{4})[-\\s](\\d{3,4}x?)', E'\\1 \\2') ) AS issn,
6637         ARRAY((SELECT DISTINCT value FROM metabib.full_rec WHERE tag = '650' AND subfield = 'a' AND record = r.id)) AS topic_subject,
6638         ARRAY((SELECT DISTINCT value FROM metabib.full_rec WHERE tag = '651' AND subfield = 'a' AND record = r.id)) AS geographic_subject,
6639         ARRAY((SELECT DISTINCT value FROM metabib.full_rec WHERE tag = '655' AND subfield = 'a' AND record = r.id)) AS genre,
6640         ARRAY((SELECT DISTINCT value FROM metabib.full_rec WHERE tag = '600' AND subfield = 'a' AND record = r.id)) AS name_subject,
6641         ARRAY((SELECT DISTINCT value FROM metabib.full_rec WHERE tag = '610' AND subfield = 'a' AND record = r.id)) AS corporate_subject,
6642         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
6643   FROM  biblio.record_entry r
6644         JOIN metabib.metarecord_source_map s ON (s.source = r.id)
6645         LEFT JOIN metabib.full_rec uniform_title ON (r.id = uniform_title.record AND uniform_title.tag = '240' AND uniform_title.subfield = 'a')
6646         LEFT JOIN metabib.full_rec title ON (r.id = title.record AND title.tag = '245' AND title.subfield = 'a')
6647         LEFT JOIN metabib.full_rec author ON (r.id = author.record AND author.tag = '100' AND author.subfield = 'a')
6648         LEFT JOIN metabib.full_rec publisher ON (r.id = publisher.record AND publisher.tag = '260' AND publisher.subfield = 'b')
6649         LEFT JOIN metabib.full_rec pubdate ON (r.id = pubdate.record AND pubdate.tag = '260' AND pubdate.subfield = 'c')
6650         LEFT JOIN metabib.full_rec isbn ON (r.id = isbn.record AND isbn.tag IN ('024', '020') AND isbn.subfield IN ('a','z'))
6651         LEFT JOIN metabib.full_rec issn ON (r.id = issn.record AND issn.tag = '022' AND issn.subfield = 'a')
6652         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')
6653         LEFT JOIN metabib.full_rec series_statement ON (r.id = series_statement.record AND series_statement.tag = '490' AND series_statement.subfield = 'a')
6654         LEFT JOIN metabib.full_rec summary ON (r.id = summary.record AND summary.tag = '520' AND summary.subfield = 'a')
6655   GROUP BY 1,2,3,4,5,6,7,8,9,10,11,12,13,14;
6656
6657 DROP SCHEMA IF EXISTS booking CASCADE;
6658
6659 CREATE SCHEMA booking;
6660
6661 CREATE TABLE booking.resource_type (
6662         id             SERIAL          PRIMARY KEY,
6663         name           TEXT            NOT NULL,
6664         fine_interval  INTERVAL,
6665         fine_amount    DECIMAL(8,2)    NOT NULL DEFAULT 0,
6666         owner          INT             NOT NULL
6667                                        REFERENCES actor.org_unit( id )
6668                                        DEFERRABLE INITIALLY DEFERRED,
6669         catalog_item   BOOLEAN         NOT NULL DEFAULT FALSE,
6670         transferable   BOOLEAN         NOT NULL DEFAULT FALSE,
6671     record         BIGINT          REFERENCES biblio.record_entry (id)
6672                                        DEFERRABLE INITIALLY DEFERRED,
6673     max_fine       NUMERIC(8,2),
6674     elbow_room     INTERVAL,
6675     CONSTRAINT brt_name_or_record_once_per_owner UNIQUE(owner, name, record)
6676 );
6677
6678 CREATE TABLE booking.resource (
6679         id             SERIAL           PRIMARY KEY,
6680         owner          INT              NOT NULL
6681                                         REFERENCES actor.org_unit(id)
6682                                         DEFERRABLE INITIALLY DEFERRED,
6683         type           INT              NOT NULL
6684                                         REFERENCES booking.resource_type(id)
6685                                         DEFERRABLE INITIALLY DEFERRED,
6686         overbook       BOOLEAN          NOT NULL DEFAULT FALSE,
6687         barcode        TEXT             NOT NULL,
6688         deposit        BOOLEAN          NOT NULL DEFAULT FALSE,
6689         deposit_amount DECIMAL(8,2)     NOT NULL DEFAULT 0.00,
6690         user_fee       DECIMAL(8,2)     NOT NULL DEFAULT 0.00,
6691         CONSTRAINT br_unique UNIQUE (owner, barcode)
6692 );
6693
6694 -- For non-catalog items: hijack barcode for name/description
6695
6696 CREATE TABLE booking.resource_attr (
6697         id              SERIAL          PRIMARY KEY,
6698         owner           INT             NOT NULL
6699                                         REFERENCES actor.org_unit(id)
6700                                         DEFERRABLE INITIALLY DEFERRED,
6701         name            TEXT            NOT NULL,
6702         resource_type   INT             NOT NULL
6703                                         REFERENCES booking.resource_type(id)
6704                                         ON DELETE CASCADE
6705                                         DEFERRABLE INITIALLY DEFERRED,
6706         required        BOOLEAN         NOT NULL DEFAULT FALSE,
6707         CONSTRAINT bra_name_once_per_type UNIQUE(resource_type, name)
6708 );
6709
6710 CREATE TABLE booking.resource_attr_value (
6711         id               SERIAL         PRIMARY KEY,
6712         owner            INT            NOT NULL
6713                                         REFERENCES actor.org_unit(id)
6714                                         DEFERRABLE INITIALLY DEFERRED,
6715         attr             INT            NOT NULL
6716                                         REFERENCES booking.resource_attr(id)
6717                                         DEFERRABLE INITIALLY DEFERRED,
6718         valid_value      TEXT           NOT NULL,
6719         CONSTRAINT brav_logical_key UNIQUE(owner, attr, valid_value)
6720 );
6721
6722 CREATE TABLE booking.resource_attr_map (
6723         id               SERIAL         PRIMARY KEY,
6724         resource         INT            NOT NULL
6725                                         REFERENCES booking.resource(id)
6726                                         ON DELETE CASCADE
6727                                         DEFERRABLE INITIALLY DEFERRED,
6728         resource_attr    INT            NOT NULL
6729                                         REFERENCES booking.resource_attr(id)
6730                                         ON DELETE CASCADE
6731                                         DEFERRABLE INITIALLY DEFERRED,
6732         value            INT            NOT NULL
6733                                         REFERENCES booking.resource_attr_value(id)
6734                                         DEFERRABLE INITIALLY DEFERRED,
6735         CONSTRAINT bram_one_value_per_attr UNIQUE(resource, resource_attr)
6736 );
6737
6738 CREATE TABLE booking.reservation (
6739         request_time     TIMESTAMPTZ   NOT NULL DEFAULT now(),
6740         start_time       TIMESTAMPTZ,
6741         end_time         TIMESTAMPTZ,
6742         capture_time     TIMESTAMPTZ,
6743         cancel_time      TIMESTAMPTZ,
6744         pickup_time      TIMESTAMPTZ,
6745         return_time      TIMESTAMPTZ,
6746         booking_interval INTERVAL,
6747         fine_interval    INTERVAL,
6748         fine_amount      DECIMAL(8,2),
6749         target_resource_type  INT       NOT NULL
6750                                         REFERENCES booking.resource_type(id)
6751                                         ON DELETE CASCADE
6752                                         DEFERRABLE INITIALLY DEFERRED,
6753         target_resource  INT            REFERENCES booking.resource(id)
6754                                         ON DELETE CASCADE
6755                                         DEFERRABLE INITIALLY DEFERRED,
6756         current_resource INT            REFERENCES booking.resource(id)
6757                                         ON DELETE CASCADE
6758                                         DEFERRABLE INITIALLY DEFERRED,
6759         request_lib      INT            NOT NULL
6760                                         REFERENCES actor.org_unit(id)
6761                                         DEFERRABLE INITIALLY DEFERRED,
6762         pickup_lib       INT            REFERENCES actor.org_unit(id)
6763                                         DEFERRABLE INITIALLY DEFERRED,
6764         capture_staff    INT            REFERENCES actor.usr(id)
6765                                         DEFERRABLE INITIALLY DEFERRED,
6766     max_fine         NUMERIC(8,2)
6767 ) INHERITS (money.billable_xact);
6768
6769 ALTER TABLE booking.reservation ADD PRIMARY KEY (id);
6770
6771 ALTER TABLE booking.reservation
6772         ADD CONSTRAINT booking_reservation_usr_fkey
6773         FOREIGN KEY (usr) REFERENCES actor.usr (id)
6774         DEFERRABLE INITIALLY DEFERRED;
6775
6776 CREATE TABLE booking.reservation_attr_value_map (
6777         id               SERIAL         PRIMARY KEY,
6778         reservation      INT            NOT NULL
6779                                         REFERENCES booking.reservation(id)
6780                                         ON DELETE CASCADE
6781                                         DEFERRABLE INITIALLY DEFERRED,
6782         attr_value       INT            NOT NULL
6783                                         REFERENCES booking.resource_attr_value(id)
6784                                         ON DELETE CASCADE
6785                                         DEFERRABLE INITIALLY DEFERRED,
6786         CONSTRAINT bravm_logical_key UNIQUE(reservation, attr_value)
6787 );
6788
6789 CREATE FUNCTION auditor.create_auditor_seq     ( sch TEXT, tbl TEXT ) RETURNS BOOL AS $creator$
6790 BEGIN
6791     EXECUTE $$
6792         CREATE SEQUENCE auditor.$$ || sch || $$_$$ || tbl || $$_pkey_seq;
6793     $$;
6794         RETURN TRUE;
6795 END;
6796 $creator$ LANGUAGE 'plpgsql';
6797
6798 CREATE FUNCTION auditor.create_auditor_history ( sch TEXT, tbl TEXT ) RETURNS BOOL AS $creator$
6799 BEGIN
6800     EXECUTE $$
6801         CREATE TABLE auditor.$$ || sch || $$_$$ || tbl || $$_history (
6802             audit_id    BIGINT                          PRIMARY KEY,
6803             audit_time  TIMESTAMP WITH TIME ZONE        NOT NULL,
6804             audit_action        TEXT                            NOT NULL,
6805             LIKE $$ || sch || $$.$$ || tbl || $$
6806         );
6807     $$;
6808         RETURN TRUE;
6809 END;
6810 $creator$ LANGUAGE 'plpgsql';
6811
6812 CREATE FUNCTION auditor.create_auditor_func    ( sch TEXT, tbl TEXT ) RETURNS BOOL AS $creator$
6813 BEGIN
6814     EXECUTE $$
6815         CREATE FUNCTION auditor.audit_$$ || sch || $$_$$ || tbl || $$_func ()
6816         RETURNS TRIGGER AS $func$
6817         BEGIN
6818             INSERT INTO auditor.$$ || sch || $$_$$ || tbl || $$_history
6819                 SELECT  nextval('auditor.$$ || sch || $$_$$ || tbl || $$_pkey_seq'),
6820                     now(),
6821                     SUBSTR(TG_OP,1,1),
6822                     OLD.*;
6823             RETURN NULL;
6824         END;
6825         $func$ LANGUAGE 'plpgsql';
6826     $$;
6827     RETURN TRUE;
6828 END;
6829 $creator$ LANGUAGE 'plpgsql';
6830
6831 CREATE FUNCTION auditor.create_auditor_update_trigger ( sch TEXT, tbl TEXT ) RETURNS BOOL AS $creator$
6832 BEGIN
6833     EXECUTE $$
6834         CREATE TRIGGER audit_$$ || sch || $$_$$ || tbl || $$_update_trigger
6835             AFTER UPDATE OR DELETE ON $$ || sch || $$.$$ || tbl || $$ FOR EACH ROW
6836             EXECUTE PROCEDURE auditor.audit_$$ || sch || $$_$$ || tbl || $$_func ();
6837     $$;
6838         RETURN TRUE;
6839 END;
6840 $creator$ LANGUAGE 'plpgsql';
6841
6842 CREATE FUNCTION auditor.create_auditor_lifecycle     ( sch TEXT, tbl TEXT ) RETURNS BOOL AS $creator$
6843 BEGIN
6844     EXECUTE $$
6845         CREATE VIEW auditor.$$ || sch || $$_$$ || tbl || $$_lifecycle AS
6846             SELECT      -1, now() as audit_time, '-' as audit_action, *
6847               FROM      $$ || sch || $$.$$ || tbl || $$
6848                 UNION ALL
6849             SELECT      *
6850               FROM      auditor.$$ || sch || $$_$$ || tbl || $$_history;
6851     $$;
6852         RETURN TRUE;
6853 END;
6854 $creator$ LANGUAGE 'plpgsql';
6855
6856 DROP FUNCTION IF EXISTS auditor.create_auditor (TEXT, TEXT);
6857
6858 -- The main event
6859
6860 CREATE FUNCTION auditor.create_auditor ( sch TEXT, tbl TEXT ) RETURNS BOOL AS $creator$
6861 BEGIN
6862     PERFORM auditor.create_auditor_seq(sch, tbl);
6863     PERFORM auditor.create_auditor_history(sch, tbl);
6864     PERFORM auditor.create_auditor_func(sch, tbl);
6865     PERFORM auditor.create_auditor_update_trigger(sch, tbl);
6866     PERFORM auditor.create_auditor_lifecycle(sch, tbl);
6867         RETURN TRUE;
6868 END;
6869 $creator$ LANGUAGE 'plpgsql';
6870
6871 -- Take advantage of the "IF EXISTS" option that has existed since
6872 -- PostgreSQL 8.2 to avoid SQL errors 
6873 CREATE OR REPLACE FUNCTION reporter.disable_materialized_simple_record_trigger () RETURNS VOID AS $$
6874     DROP TRIGGER IF EXISTS zzz_update_materialized_simple_record_tgr ON metabib.real_full_rec;
6875 $$ LANGUAGE SQL;
6876
6877 -- represents a circ chain summary
6878 CREATE TYPE action.circ_chain_summary AS (
6879     num_circs INTEGER,
6880     start_time TIMESTAMP WITH TIME ZONE,
6881     checkout_workstation TEXT,
6882     last_renewal_time TIMESTAMP WITH TIME ZONE, -- NULL if no renewals
6883     last_stop_fines TEXT,
6884     last_stop_fines_time TIMESTAMP WITH TIME ZONE,
6885     last_renewal_workstation TEXT, -- NULL if no renewals
6886     last_checkin_workstation TEXT,
6887     last_checkin_time TIMESTAMP WITH TIME ZONE,
6888     last_checkin_scan_time TIMESTAMP WITH TIME ZONE
6889 );
6890
6891 CREATE OR REPLACE FUNCTION action.circ_chain ( ctx_circ_id INTEGER ) RETURNS SETOF action.circulation AS $$
6892 DECLARE
6893     tmp_circ action.circulation%ROWTYPE;
6894     circ_0 action.circulation%ROWTYPE;
6895 BEGIN
6896
6897     SELECT INTO tmp_circ * FROM action.circulation WHERE id = ctx_circ_id;
6898
6899     IF tmp_circ IS NULL THEN
6900         RETURN NEXT tmp_circ;
6901     END IF;
6902     circ_0 := tmp_circ;
6903
6904     -- find the front of the chain
6905     WHILE TRUE LOOP
6906         SELECT INTO tmp_circ * FROM action.circulation WHERE id = tmp_circ.parent_circ;
6907         IF tmp_circ IS NULL THEN
6908             EXIT;
6909         END IF;
6910         circ_0 := tmp_circ;
6911     END LOOP;
6912
6913     -- now send the circs to the caller, oldest to newest
6914     tmp_circ := circ_0;
6915     WHILE TRUE LOOP
6916         IF tmp_circ IS NULL THEN
6917             EXIT;
6918         END IF;
6919         RETURN NEXT tmp_circ;
6920         SELECT INTO tmp_circ * FROM action.circulation WHERE parent_circ = tmp_circ.id;
6921     END LOOP;
6922
6923 END;
6924 $$ LANGUAGE 'plpgsql';
6925
6926 CREATE OR REPLACE FUNCTION action.summarize_circ_chain ( ctx_circ_id INTEGER ) RETURNS action.circ_chain_summary AS $$
6927
6928 DECLARE
6929
6930     -- first circ in the chain
6931     circ_0 action.circulation%ROWTYPE;
6932
6933     -- last circ in the chain
6934     circ_n action.circulation%ROWTYPE;
6935
6936     -- circ chain under construction
6937     chain action.circ_chain_summary;
6938     tmp_circ action.circulation%ROWTYPE;
6939
6940 BEGIN
6941     
6942     chain.num_circs := 0;
6943     FOR tmp_circ IN SELECT * FROM action.circ_chain(ctx_circ_id) LOOP
6944
6945         IF chain.num_circs = 0 THEN
6946             circ_0 := tmp_circ;
6947         END IF;
6948
6949         chain.num_circs := chain.num_circs + 1;
6950         circ_n := tmp_circ;
6951     END LOOP;
6952
6953     chain.start_time := circ_0.xact_start;
6954     chain.last_stop_fines := circ_n.stop_fines;
6955     chain.last_stop_fines_time := circ_n.stop_fines_time;
6956     chain.last_checkin_time := circ_n.checkin_time;
6957     chain.last_checkin_scan_time := circ_n.checkin_scan_time;
6958     SELECT INTO chain.checkout_workstation name FROM actor.workstation WHERE id = circ_0.workstation;
6959     SELECT INTO chain.last_checkin_workstation name FROM actor.workstation WHERE id = circ_n.checkin_workstation;
6960
6961     IF chain.num_circs > 1 THEN
6962         chain.last_renewal_time := circ_n.xact_start;
6963         SELECT INTO chain.last_renewal_workstation name FROM actor.workstation WHERE id = circ_n.workstation;
6964     END IF;
6965
6966     RETURN chain;
6967
6968 END;
6969 $$ LANGUAGE 'plpgsql';
6970
6971 CREATE TRIGGER mat_summary_create_tgr AFTER INSERT ON booking.reservation FOR EACH ROW EXECUTE PROCEDURE money.mat_summary_create ('reservation');
6972 CREATE TRIGGER mat_summary_change_tgr AFTER UPDATE ON booking.reservation FOR EACH ROW EXECUTE PROCEDURE money.mat_summary_update ();
6973 CREATE TRIGGER mat_summary_remove_tgr AFTER DELETE ON booking.reservation FOR EACH ROW EXECUTE PROCEDURE money.mat_summary_delete ();
6974
6975 ALTER TABLE config.standing_penalty
6976         ADD COLUMN org_depth   INTEGER;
6977
6978 CREATE OR REPLACE FUNCTION actor.calculate_system_penalties( match_user INT, context_org INT ) RETURNS SETOF actor.usr_standing_penalty AS $func$
6979 DECLARE
6980     user_object         actor.usr%ROWTYPE;
6981     new_sp_row          actor.usr_standing_penalty%ROWTYPE;
6982     existing_sp_row     actor.usr_standing_penalty%ROWTYPE;
6983     collections_fines   permission.grp_penalty_threshold%ROWTYPE;
6984     max_fines           permission.grp_penalty_threshold%ROWTYPE;
6985     max_overdue         permission.grp_penalty_threshold%ROWTYPE;
6986     max_items_out       permission.grp_penalty_threshold%ROWTYPE;
6987     tmp_grp             INT;
6988     items_overdue       INT;
6989     items_out           INT;
6990     context_org_list    INT[];
6991     current_fines        NUMERIC(8,2) := 0.0;
6992     tmp_fines            NUMERIC(8,2);
6993     tmp_groc            RECORD;
6994     tmp_circ            RECORD;
6995     tmp_org             actor.org_unit%ROWTYPE;
6996     tmp_penalty         config.standing_penalty%ROWTYPE;
6997     tmp_depth           INTEGER;
6998 BEGIN
6999     SELECT INTO user_object * FROM actor.usr WHERE id = match_user;
7000
7001     -- Max fines
7002     SELECT INTO tmp_org * FROM actor.org_unit WHERE id = context_org;
7003
7004     -- Fail if the user has a high fine balance
7005     LOOP
7006         tmp_grp := user_object.profile;
7007         LOOP
7008             SELECT * INTO max_fines FROM permission.grp_penalty_threshold WHERE grp = tmp_grp AND penalty = 1 AND org_unit = tmp_org.id;
7009
7010             IF max_fines.threshold IS NULL THEN
7011                 SELECT parent INTO tmp_grp FROM permission.grp_tree WHERE id = tmp_grp;
7012             ELSE
7013                 EXIT;
7014             END IF;
7015
7016             IF tmp_grp IS NULL THEN
7017                 EXIT;
7018             END IF;
7019         END LOOP;
7020
7021         IF max_fines.threshold IS NOT NULL OR tmp_org.parent_ou IS NULL THEN
7022             EXIT;
7023         END IF;
7024
7025         SELECT * INTO tmp_org FROM actor.org_unit WHERE id = tmp_org.parent_ou;
7026
7027     END LOOP;
7028
7029     IF max_fines.threshold IS NOT NULL THEN
7030
7031         FOR existing_sp_row IN
7032                 SELECT  *
7033                   FROM  actor.usr_standing_penalty
7034                   WHERE usr = match_user
7035                         AND org_unit = max_fines.org_unit
7036                         AND (stop_date IS NULL or stop_date > NOW())
7037                         AND standing_penalty = 1
7038                 LOOP
7039             RETURN NEXT existing_sp_row;
7040         END LOOP;
7041
7042         SELECT  SUM(f.balance_owed) INTO current_fines
7043           FROM  money.materialized_billable_xact_summary f
7044                 JOIN (
7045                     SELECT  r.id
7046                       FROM  booking.reservation r
7047                             JOIN  actor.org_unit_full_path( max_fines.org_unit ) fp ON (r.pickup_lib = fp.id)
7048                       WHERE usr = match_user
7049                             AND xact_finish IS NULL
7050                                 UNION ALL
7051                     SELECT  g.id
7052                       FROM  money.grocery g
7053                             JOIN  actor.org_unit_full_path( max_fines.org_unit ) fp ON (g.billing_location = fp.id)
7054                       WHERE usr = match_user
7055                             AND xact_finish IS NULL
7056                                 UNION ALL
7057                     SELECT  circ.id
7058                       FROM  action.circulation circ
7059                             JOIN  actor.org_unit_full_path( max_fines.org_unit ) fp ON (circ.circ_lib = fp.id)
7060                       WHERE usr = match_user
7061                             AND xact_finish IS NULL ) l USING (id);
7062
7063         IF current_fines >= max_fines.threshold THEN
7064             new_sp_row.usr := match_user;
7065             new_sp_row.org_unit := max_fines.org_unit;
7066             new_sp_row.standing_penalty := 1;
7067             RETURN NEXT new_sp_row;
7068         END IF;
7069     END IF;
7070
7071     -- Start over for max overdue
7072     SELECT INTO tmp_org * FROM actor.org_unit WHERE id = context_org;
7073
7074     -- Fail if the user has too many overdue items
7075     LOOP
7076         tmp_grp := user_object.profile;
7077         LOOP
7078
7079             SELECT * INTO max_overdue FROM permission.grp_penalty_threshold WHERE grp = tmp_grp AND penalty = 2 AND org_unit = tmp_org.id;
7080
7081             IF max_overdue.threshold IS NULL THEN
7082                 SELECT parent INTO tmp_grp FROM permission.grp_tree WHERE id = tmp_grp;
7083             ELSE
7084                 EXIT;
7085             END IF;
7086
7087             IF tmp_grp IS NULL THEN
7088                 EXIT;
7089             END IF;
7090         END LOOP;
7091
7092         IF max_overdue.threshold IS NOT NULL OR tmp_org.parent_ou IS NULL THEN
7093             EXIT;
7094         END IF;
7095
7096         SELECT INTO tmp_org * FROM actor.org_unit WHERE id = tmp_org.parent_ou;
7097
7098     END LOOP;
7099
7100     IF max_overdue.threshold IS NOT NULL THEN
7101
7102         FOR existing_sp_row IN
7103                 SELECT  *
7104                   FROM  actor.usr_standing_penalty
7105                   WHERE usr = match_user
7106                         AND org_unit = max_overdue.org_unit
7107                         AND (stop_date IS NULL or stop_date > NOW())
7108                         AND standing_penalty = 2
7109                 LOOP
7110             RETURN NEXT existing_sp_row;
7111         END LOOP;
7112
7113         SELECT  INTO items_overdue COUNT(*)
7114           FROM  action.circulation circ
7115                 JOIN  actor.org_unit_full_path( max_overdue.org_unit ) fp ON (circ.circ_lib = fp.id)
7116           WHERE circ.usr = match_user
7117             AND circ.checkin_time IS NULL
7118             AND circ.due_date < NOW()
7119             AND (circ.stop_fines = 'MAXFINES' OR circ.stop_fines IS NULL);
7120
7121         IF items_overdue >= max_overdue.threshold::INT THEN
7122             new_sp_row.usr := match_user;
7123             new_sp_row.org_unit := max_overdue.org_unit;
7124             new_sp_row.standing_penalty := 2;
7125             RETURN NEXT new_sp_row;
7126         END IF;
7127     END IF;
7128
7129     -- Start over for max out
7130     SELECT INTO tmp_org * FROM actor.org_unit WHERE id = context_org;
7131
7132     -- Fail if the user has too many checked out items
7133     LOOP
7134         tmp_grp := user_object.profile;
7135         LOOP
7136             SELECT * INTO max_items_out FROM permission.grp_penalty_threshold WHERE grp = tmp_grp AND penalty = 3 AND org_unit = tmp_org.id;
7137
7138             IF max_items_out.threshold IS NULL THEN
7139                 SELECT parent INTO tmp_grp FROM permission.grp_tree WHERE id = tmp_grp;
7140             ELSE
7141                 EXIT;
7142             END IF;
7143
7144             IF tmp_grp IS NULL THEN
7145                 EXIT;
7146             END IF;
7147         END LOOP;
7148
7149         IF max_items_out.threshold IS NOT NULL OR tmp_org.parent_ou IS NULL THEN
7150             EXIT;
7151         END IF;
7152
7153         SELECT INTO tmp_org * FROM actor.org_unit WHERE id = tmp_org.parent_ou;
7154
7155     END LOOP;
7156
7157
7158     -- Fail if the user has too many items checked out
7159     IF max_items_out.threshold IS NOT NULL THEN
7160
7161         FOR existing_sp_row IN
7162                 SELECT  *
7163                   FROM  actor.usr_standing_penalty
7164                   WHERE usr = match_user
7165                         AND org_unit = max_items_out.org_unit
7166                         AND (stop_date IS NULL or stop_date > NOW())
7167                         AND standing_penalty = 3
7168                 LOOP
7169             RETURN NEXT existing_sp_row;
7170         END LOOP;
7171
7172         SELECT  INTO items_out COUNT(*)
7173           FROM  action.circulation circ
7174                 JOIN  actor.org_unit_full_path( max_items_out.org_unit ) fp ON (circ.circ_lib = fp.id)
7175           WHERE circ.usr = match_user
7176                 AND circ.checkin_time IS NULL
7177                 AND (circ.stop_fines IN ('MAXFINES','LONGOVERDUE') OR circ.stop_fines IS NULL);
7178
7179            IF items_out >= max_items_out.threshold::INT THEN
7180             new_sp_row.usr := match_user;
7181             new_sp_row.org_unit := max_items_out.org_unit;
7182             new_sp_row.standing_penalty := 3;
7183             RETURN NEXT new_sp_row;
7184            END IF;
7185     END IF;
7186
7187     -- Start over for collections warning
7188     SELECT INTO tmp_org * FROM actor.org_unit WHERE id = context_org;
7189
7190     -- Fail if the user has a collections-level fine balance
7191     LOOP
7192         tmp_grp := user_object.profile;
7193         LOOP
7194             SELECT * INTO max_fines FROM permission.grp_penalty_threshold WHERE grp = tmp_grp AND penalty = 4 AND org_unit = tmp_org.id;
7195
7196             IF max_fines.threshold IS NULL THEN
7197                 SELECT parent INTO tmp_grp FROM permission.grp_tree WHERE id = tmp_grp;
7198             ELSE
7199                 EXIT;
7200             END IF;
7201
7202             IF tmp_grp IS NULL THEN
7203                 EXIT;
7204             END IF;
7205         END LOOP;
7206
7207         IF max_fines.threshold IS NOT NULL OR tmp_org.parent_ou IS NULL THEN
7208             EXIT;
7209         END IF;
7210
7211         SELECT * INTO tmp_org FROM actor.org_unit WHERE id = tmp_org.parent_ou;
7212
7213     END LOOP;
7214
7215     IF max_fines.threshold IS NOT NULL THEN
7216
7217         FOR existing_sp_row IN
7218                 SELECT  *
7219                   FROM  actor.usr_standing_penalty
7220                   WHERE usr = match_user
7221                         AND org_unit = max_fines.org_unit
7222                         AND (stop_date IS NULL or stop_date > NOW())
7223                         AND standing_penalty = 4
7224                 LOOP
7225             RETURN NEXT existing_sp_row;
7226         END LOOP;
7227
7228         SELECT  SUM(f.balance_owed) INTO current_fines
7229           FROM  money.materialized_billable_xact_summary f
7230                 JOIN (
7231                     SELECT  r.id
7232                       FROM  booking.reservation r
7233                             JOIN  actor.org_unit_full_path( max_fines.org_unit ) fp ON (r.pickup_lib = fp.id)
7234                       WHERE usr = match_user
7235                             AND xact_finish IS NULL
7236                                 UNION ALL
7237                     SELECT  g.id
7238                       FROM  money.grocery g
7239                             JOIN  actor.org_unit_full_path( max_fines.org_unit ) fp ON (g.billing_location = fp.id)
7240                       WHERE usr = match_user
7241                             AND xact_finish IS NULL
7242                                 UNION ALL
7243                     SELECT  circ.id
7244                       FROM  action.circulation circ
7245                             JOIN  actor.org_unit_full_path( max_fines.org_unit ) fp ON (circ.circ_lib = fp.id)
7246                       WHERE usr = match_user
7247                             AND xact_finish IS NULL ) l USING (id);
7248
7249         IF current_fines >= max_fines.threshold THEN
7250             new_sp_row.usr := match_user;
7251             new_sp_row.org_unit := max_fines.org_unit;
7252             new_sp_row.standing_penalty := 4;
7253             RETURN NEXT new_sp_row;
7254         END IF;
7255     END IF;
7256
7257     -- Start over for in collections
7258     SELECT INTO tmp_org * FROM actor.org_unit WHERE id = context_org;
7259
7260     -- Remove the in-collections penalty if the user has paid down enough
7261     -- This penalty is different, because this code is not responsible for creating 
7262     -- new in-collections penalties, only for removing them
7263     LOOP
7264         tmp_grp := user_object.profile;
7265         LOOP
7266             SELECT * INTO max_fines FROM permission.grp_penalty_threshold WHERE grp = tmp_grp AND penalty = 30 AND org_unit = tmp_org.id;
7267
7268             IF max_fines.threshold IS NULL THEN
7269                 SELECT parent INTO tmp_grp FROM permission.grp_tree WHERE id = tmp_grp;
7270             ELSE
7271                 EXIT;
7272             END IF;
7273
7274             IF tmp_grp IS NULL THEN
7275                 EXIT;
7276             END IF;
7277         END LOOP;
7278
7279         IF max_fines.threshold IS NOT NULL OR tmp_org.parent_ou IS NULL THEN
7280             EXIT;
7281         END IF;
7282
7283         SELECT * INTO tmp_org FROM actor.org_unit WHERE id = tmp_org.parent_ou;
7284
7285     END LOOP;
7286
7287     IF max_fines.threshold IS NOT NULL THEN
7288
7289         -- first, see if the user had paid down to the threshold
7290         SELECT  SUM(f.balance_owed) INTO current_fines
7291           FROM  money.materialized_billable_xact_summary f
7292                 JOIN (
7293                     SELECT  r.id
7294                       FROM  booking.reservation r
7295                             JOIN  actor.org_unit_full_path( max_fines.org_unit ) fp ON (r.pickup_lib = fp.id)
7296                       WHERE usr = match_user
7297                             AND xact_finish IS NULL
7298                                 UNION ALL
7299                     SELECT  g.id
7300                       FROM  money.grocery g
7301                             JOIN  actor.org_unit_full_path( max_fines.org_unit ) fp ON (g.billing_location = fp.id)
7302                       WHERE usr = match_user
7303                             AND xact_finish IS NULL
7304                                 UNION ALL
7305                     SELECT  circ.id
7306                       FROM  action.circulation circ
7307                             JOIN  actor.org_unit_full_path( max_fines.org_unit ) fp ON (circ.circ_lib = fp.id)
7308                       WHERE usr = match_user
7309                             AND xact_finish IS NULL ) l USING (id);
7310
7311         IF current_fines IS NULL OR current_fines <= max_fines.threshold THEN
7312             -- patron has paid down enough
7313
7314             SELECT INTO tmp_penalty * FROM config.standing_penalty WHERE id = 30;
7315
7316             IF tmp_penalty.org_depth IS NOT NULL THEN
7317
7318                 -- since this code is not responsible for applying the penalty, it can't 
7319                 -- guarantee the current context org will match the org at which the penalty 
7320                 --- was applied.  search up the org tree until we hit the configured penalty depth
7321                 SELECT INTO tmp_org * FROM actor.org_unit WHERE id = context_org;
7322                 SELECT INTO tmp_depth depth FROM actor.org_unit_type WHERE id = tmp_org.ou_type;
7323
7324                 WHILE tmp_depth >= tmp_penalty.org_depth LOOP
7325
7326                     FOR existing_sp_row IN
7327                             SELECT  *
7328                             FROM  actor.usr_standing_penalty
7329                             WHERE usr = match_user
7330                                     AND org_unit = tmp_org.id
7331                                     AND (stop_date IS NULL or stop_date > NOW())
7332                                     AND standing_penalty = 30 
7333                             LOOP
7334
7335                         -- Penalty exists, return it for removal
7336                         RETURN NEXT existing_sp_row;
7337                     END LOOP;
7338
7339                     IF tmp_org.parent_ou IS NULL THEN
7340                         EXIT;
7341                     END IF;
7342
7343                     SELECT * INTO tmp_org FROM actor.org_unit WHERE id = tmp_org.parent_ou;
7344                     SELECT INTO tmp_depth depth FROM actor.org_unit_type WHERE id = tmp_org.ou_type;
7345                 END LOOP;
7346
7347             ELSE
7348
7349                 -- no penalty depth is defined, look for exact matches
7350
7351                 FOR existing_sp_row IN
7352                         SELECT  *
7353                         FROM  actor.usr_standing_penalty
7354                         WHERE usr = match_user
7355                                 AND org_unit = max_fines.org_unit
7356                                 AND (stop_date IS NULL or stop_date > NOW())
7357                                 AND standing_penalty = 30 
7358                         LOOP
7359                     -- Penalty exists, return it for removal
7360                     RETURN NEXT existing_sp_row;
7361                 END LOOP;
7362             END IF;
7363     
7364         END IF;
7365
7366     END IF;
7367
7368     RETURN;
7369 END;
7370 $func$ LANGUAGE plpgsql;
7371
7372 -- Create a default row in acq.fiscal_calendar
7373 -- Add a column in actor.org_unit to point to it
7374
7375 INSERT INTO acq.fiscal_calendar ( id, name ) VALUES ( 1, 'Default' );
7376
7377 ALTER TABLE actor.org_unit
7378 ADD COLUMN fiscal_calendar INT NOT NULL
7379         REFERENCES acq.fiscal_calendar( id )
7380         DEFERRABLE INITIALLY DEFERRED
7381         DEFAULT 1;
7382
7383 ALTER TABLE auditor.actor_org_unit_history
7384         ADD COLUMN fiscal_calendar INT;
7385
7386 ALTER TABLE acq.funding_source_credit
7387 ADD COLUMN deadline_date TIMESTAMPTZ;
7388
7389 ALTER TABLE acq.funding_source_credit
7390 ADD COLUMN effective_date TIMESTAMPTZ NOT NULL DEFAULT now();
7391
7392 INSERT INTO config.standing_penalty (id,name,label) VALUES (30,'PATRON_IN_COLLECTIONS','Patron has been referred to a collections agency');
7393
7394 CREATE TABLE acq.fund_transfer (
7395         id               SERIAL         PRIMARY KEY,
7396         src_fund         INT            NOT NULL REFERENCES acq.fund( id )
7397                                         DEFERRABLE INITIALLY DEFERRED,
7398         src_amount       NUMERIC        NOT NULL,
7399         dest_fund        INT            REFERENCES acq.fund( id )
7400                                         DEFERRABLE INITIALLY DEFERRED,
7401         dest_amount      NUMERIC,
7402         transfer_time    TIMESTAMPTZ    NOT NULL DEFAULT now(),
7403         transfer_user    INT            NOT NULL REFERENCES actor.usr( id )
7404                                         DEFERRABLE INITIALLY DEFERRED,
7405         note             TEXT,
7406     funding_source_credit INTEGER   NOT NULL
7407                                         REFERENCES acq.funding_source_credit(id)
7408                                         DEFERRABLE INITIALLY DEFERRED
7409 );
7410
7411 CREATE INDEX acqftr_usr_idx
7412 ON acq.fund_transfer( transfer_user );
7413
7414 COMMENT ON TABLE acq.fund_transfer IS $$
7415 /*
7416  * Copyright (C) 2009  Georgia Public Library Service
7417  * Scott McKellar <scott@esilibrary.com>
7418  *
7419  * Fund Transfer
7420  *
7421  * Each row represents the transfer of money from a source fund
7422  * to a destination fund.  There should be corresponding entries
7423  * in acq.fund_allocation.  The purpose of acq.fund_transfer is
7424  * to record how much money moved from which fund to which other
7425  * fund.
7426  * 
7427  * The presence of two amount fields, rather than one, reflects
7428  * the possibility that the two funds are denominated in different
7429  * currencies.  If they use the same currency type, the two
7430  * amounts should be the same.
7431  *
7432  * ****
7433  *
7434  * This program is free software; you can redistribute it and/or
7435  * modify it under the terms of the GNU General Public License
7436  * as published by the Free Software Foundation; either version 2
7437  * of the License, or (at your option) any later version.
7438  *
7439  * This program is distributed in the hope that it will be useful,
7440  * but WITHOUT ANY WARRANTY; without even the implied warranty of
7441  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
7442  * GNU General Public License for more details.
7443  */
7444 $$;
7445
7446 CREATE TABLE acq.claim_event_type (
7447         id             SERIAL           PRIMARY KEY,
7448         org_unit       INT              NOT NULL REFERENCES actor.org_unit(id)
7449                                                  DEFERRABLE INITIALLY DEFERRED,
7450         code           TEXT             NOT NULL,
7451         description    TEXT             NOT NULL,
7452         library_initiated BOOL          NOT NULL DEFAULT FALSE,
7453         CONSTRAINT event_type_once_per_org UNIQUE ( org_unit, code )
7454 );
7455
7456 CREATE TABLE acq.claim_event (
7457         id             BIGSERIAL        PRIMARY KEY,
7458         type           INT              NOT NULL REFERENCES acq.claim_event_type
7459                                                  DEFERRABLE INITIALLY DEFERRED,
7460         claim          SERIAL           NOT NULL REFERENCES acq.claim
7461                                                  DEFERRABLE INITIALLY DEFERRED,
7462         event_date     TIMESTAMPTZ      NOT NULL DEFAULT now(),
7463         creator        INT              NOT NULL REFERENCES actor.usr
7464                                                  DEFERRABLE INITIALLY DEFERRED,
7465         note           TEXT
7466 );
7467
7468 CREATE INDEX claim_event_claim_date_idx ON acq.claim_event( claim, event_date );
7469
7470 CREATE OR REPLACE FUNCTION actor.usr_purge_data(
7471         src_usr  IN INTEGER,
7472         dest_usr IN INTEGER
7473 ) RETURNS VOID AS $$
7474 DECLARE
7475         suffix TEXT;
7476         renamable_row RECORD;
7477 BEGIN
7478
7479         UPDATE actor.usr SET
7480                 active = FALSE,
7481                 card = NULL,
7482                 mailing_address = NULL,
7483                 billing_address = NULL
7484         WHERE id = src_usr;
7485
7486         -- acq.*
7487         UPDATE acq.fund_allocation SET allocator = dest_usr WHERE allocator = src_usr;
7488         UPDATE acq.lineitem SET creator = dest_usr WHERE creator = src_usr;
7489         UPDATE acq.lineitem SET editor = dest_usr WHERE editor = src_usr;
7490         UPDATE acq.lineitem SET selector = dest_usr WHERE selector = src_usr;
7491         UPDATE acq.lineitem_note SET creator = dest_usr WHERE creator = src_usr;
7492         UPDATE acq.lineitem_note SET editor = dest_usr WHERE editor = src_usr;
7493         DELETE FROM acq.lineitem_usr_attr_definition WHERE usr = src_usr;
7494
7495         -- Update with a rename to avoid collisions
7496         FOR renamable_row in
7497                 SELECT id, name
7498                 FROM   acq.picklist
7499                 WHERE  owner = src_usr
7500         LOOP
7501                 suffix := ' (' || src_usr || ')';
7502                 LOOP
7503                         BEGIN
7504                                 UPDATE  acq.picklist
7505                                 SET     owner = dest_usr, name = name || suffix
7506                                 WHERE   id = renamable_row.id;
7507                         EXCEPTION WHEN unique_violation THEN
7508                                 suffix := suffix || ' ';
7509                                 CONTINUE;
7510                         END;
7511                         EXIT;
7512                 END LOOP;
7513         END LOOP;
7514
7515         UPDATE acq.picklist SET creator = dest_usr WHERE creator = src_usr;
7516         UPDATE acq.picklist SET editor = dest_usr WHERE editor = src_usr;
7517         UPDATE acq.po_note SET creator = dest_usr WHERE creator = src_usr;
7518         UPDATE acq.po_note SET editor = dest_usr WHERE editor = src_usr;
7519         UPDATE acq.purchase_order SET owner = dest_usr WHERE owner = src_usr;
7520         UPDATE acq.purchase_order SET creator = dest_usr WHERE creator = src_usr;
7521         UPDATE acq.purchase_order SET editor = dest_usr WHERE editor = src_usr;
7522         UPDATE acq.claim_event SET creator = dest_usr WHERE creator = src_usr;
7523
7524         -- action.*
7525         DELETE FROM action.circulation WHERE usr = src_usr;
7526         UPDATE action.circulation SET circ_staff = dest_usr WHERE circ_staff = src_usr;
7527         UPDATE action.circulation SET checkin_staff = dest_usr WHERE checkin_staff = src_usr;
7528         UPDATE action.hold_notification SET notify_staff = dest_usr WHERE notify_staff = src_usr;
7529         UPDATE action.hold_request SET fulfillment_staff = dest_usr WHERE fulfillment_staff = src_usr;
7530         UPDATE action.hold_request SET requestor = dest_usr WHERE requestor = src_usr;
7531         DELETE FROM action.hold_request WHERE usr = src_usr;
7532         UPDATE action.in_house_use SET staff = dest_usr WHERE staff = src_usr;
7533         UPDATE action.non_cat_in_house_use SET staff = dest_usr WHERE staff = src_usr;
7534         DELETE FROM action.non_cataloged_circulation WHERE patron = src_usr;
7535         UPDATE action.non_cataloged_circulation SET staff = dest_usr WHERE staff = src_usr;
7536         DELETE FROM action.survey_response WHERE usr = src_usr;
7537         UPDATE action.fieldset SET owner = dest_usr WHERE owner = src_usr;
7538
7539         -- actor.*
7540         DELETE FROM actor.card WHERE usr = src_usr;
7541         DELETE FROM actor.stat_cat_entry_usr_map WHERE target_usr = src_usr;
7542
7543         -- The following update is intended to avoid transient violations of a foreign
7544         -- key constraint, whereby actor.usr_address references itself.  It may not be
7545         -- necessary, but it does no harm.
7546         UPDATE actor.usr_address SET replaces = NULL
7547                 WHERE usr = src_usr AND replaces IS NOT NULL;
7548         DELETE FROM actor.usr_address WHERE usr = src_usr;
7549         DELETE FROM actor.usr_note WHERE usr = src_usr;
7550         UPDATE actor.usr_note SET creator = dest_usr WHERE creator = src_usr;
7551         DELETE FROM actor.usr_org_unit_opt_in WHERE usr = src_usr;
7552         UPDATE actor.usr_org_unit_opt_in SET staff = dest_usr WHERE staff = src_usr;
7553         DELETE FROM actor.usr_setting WHERE usr = src_usr;
7554         DELETE FROM actor.usr_standing_penalty WHERE usr = src_usr;
7555         UPDATE actor.usr_standing_penalty SET staff = dest_usr WHERE staff = src_usr;
7556
7557         -- asset.*
7558         UPDATE asset.call_number SET creator = dest_usr WHERE creator = src_usr;
7559         UPDATE asset.call_number SET editor = dest_usr WHERE editor = src_usr;
7560         UPDATE asset.call_number_note SET creator = dest_usr WHERE creator = src_usr;
7561         UPDATE asset.copy SET creator = dest_usr WHERE creator = src_usr;
7562         UPDATE asset.copy SET editor = dest_usr WHERE editor = src_usr;
7563         UPDATE asset.copy_note SET creator = dest_usr WHERE creator = src_usr;
7564
7565         -- auditor.*
7566         DELETE FROM auditor.actor_usr_address_history WHERE id = src_usr;
7567         DELETE FROM auditor.actor_usr_history WHERE id = src_usr;
7568         UPDATE auditor.asset_call_number_history SET creator = dest_usr WHERE creator = src_usr;
7569         UPDATE auditor.asset_call_number_history SET editor  = dest_usr WHERE editor  = src_usr;
7570         UPDATE auditor.asset_copy_history SET creator = dest_usr WHERE creator = src_usr;
7571         UPDATE auditor.asset_copy_history SET editor  = dest_usr WHERE editor  = src_usr;
7572         UPDATE auditor.biblio_record_entry_history SET creator = dest_usr WHERE creator = src_usr;
7573         UPDATE auditor.biblio_record_entry_history SET editor  = dest_usr WHERE editor  = src_usr;
7574
7575         -- biblio.*
7576         UPDATE biblio.record_entry SET creator = dest_usr WHERE creator = src_usr;
7577         UPDATE biblio.record_entry SET editor = dest_usr WHERE editor = src_usr;
7578         UPDATE biblio.record_note SET creator = dest_usr WHERE creator = src_usr;
7579         UPDATE biblio.record_note SET editor = dest_usr WHERE editor = src_usr;
7580
7581         -- container.*
7582         -- Update buckets with a rename to avoid collisions
7583         FOR renamable_row in
7584                 SELECT id, name
7585                 FROM   container.biblio_record_entry_bucket
7586                 WHERE  owner = src_usr
7587         LOOP
7588                 suffix := ' (' || src_usr || ')';
7589                 LOOP
7590                         BEGIN
7591                                 UPDATE  container.biblio_record_entry_bucket
7592                                 SET     owner = dest_usr, name = name || suffix
7593                                 WHERE   id = renamable_row.id;
7594                         EXCEPTION WHEN unique_violation THEN
7595                                 suffix := suffix || ' ';
7596                                 CONTINUE;
7597                         END;
7598                         EXIT;
7599                 END LOOP;
7600         END LOOP;
7601
7602         FOR renamable_row in
7603                 SELECT id, name
7604                 FROM   container.call_number_bucket
7605                 WHERE  owner = src_usr
7606         LOOP
7607                 suffix := ' (' || src_usr || ')';
7608                 LOOP
7609                         BEGIN
7610                                 UPDATE  container.call_number_bucket
7611                                 SET     owner = dest_usr, name = name || suffix
7612                                 WHERE   id = renamable_row.id;
7613                         EXCEPTION WHEN unique_violation THEN
7614                                 suffix := suffix || ' ';
7615                                 CONTINUE;
7616                         END;
7617                         EXIT;
7618                 END LOOP;
7619         END LOOP;
7620
7621         FOR renamable_row in
7622                 SELECT id, name
7623                 FROM   container.copy_bucket
7624                 WHERE  owner = src_usr
7625         LOOP
7626                 suffix := ' (' || src_usr || ')';
7627                 LOOP
7628                         BEGIN
7629                                 UPDATE  container.copy_bucket
7630                                 SET     owner = dest_usr, name = name || suffix
7631                                 WHERE   id = renamable_row.id;
7632                         EXCEPTION WHEN unique_violation THEN
7633                                 suffix := suffix || ' ';
7634                                 CONTINUE;
7635                         END;
7636                         EXIT;
7637                 END LOOP;
7638         END LOOP;
7639
7640         FOR renamable_row in
7641                 SELECT id, name
7642                 FROM   container.user_bucket
7643                 WHERE  owner = src_usr
7644         LOOP
7645                 suffix := ' (' || src_usr || ')';
7646                 LOOP
7647                         BEGIN
7648                                 UPDATE  container.user_bucket
7649                                 SET     owner = dest_usr, name = name || suffix
7650                                 WHERE   id = renamable_row.id;
7651                         EXCEPTION WHEN unique_violation THEN
7652                                 suffix := suffix || ' ';
7653                                 CONTINUE;
7654                         END;
7655                         EXIT;
7656                 END LOOP;
7657         END LOOP;
7658
7659         DELETE FROM container.user_bucket_item WHERE target_user = src_usr;
7660
7661         -- money.*
7662         DELETE FROM money.billable_xact WHERE usr = src_usr;
7663         DELETE FROM money.collections_tracker WHERE usr = src_usr;
7664         UPDATE money.collections_tracker SET collector = dest_usr WHERE collector = src_usr;
7665
7666         -- permission.*
7667         DELETE FROM permission.usr_grp_map WHERE usr = src_usr;
7668         DELETE FROM permission.usr_object_perm_map WHERE usr = src_usr;
7669         DELETE FROM permission.usr_perm_map WHERE usr = src_usr;
7670         DELETE FROM permission.usr_work_ou_map WHERE usr = src_usr;
7671
7672         -- reporter.*
7673         -- Update with a rename to avoid collisions
7674         BEGIN
7675                 FOR renamable_row in
7676                         SELECT id, name
7677                         FROM   reporter.output_folder
7678                         WHERE  owner = src_usr
7679                 LOOP
7680                         suffix := ' (' || src_usr || ')';
7681                         LOOP
7682                                 BEGIN
7683                                         UPDATE  reporter.output_folder
7684                                         SET     owner = dest_usr, name = name || suffix
7685                                         WHERE   id = renamable_row.id;
7686                                 EXCEPTION WHEN unique_violation THEN
7687                                         suffix := suffix || ' ';
7688                                         CONTINUE;
7689                                 END;
7690                                 EXIT;
7691                         END LOOP;
7692                 END LOOP;
7693         EXCEPTION WHEN undefined_table THEN
7694                 -- do nothing
7695         END;
7696
7697         BEGIN
7698                 UPDATE reporter.report SET owner = dest_usr WHERE owner = src_usr;
7699         EXCEPTION WHEN undefined_table THEN
7700                 -- do nothing
7701         END;
7702
7703         -- Update with a rename to avoid collisions
7704         BEGIN
7705                 FOR renamable_row in
7706                         SELECT id, name
7707                         FROM   reporter.report_folder
7708                         WHERE  owner = src_usr
7709                 LOOP
7710                         suffix := ' (' || src_usr || ')';
7711                         LOOP
7712                                 BEGIN
7713                                         UPDATE  reporter.report_folder
7714                                         SET     owner = dest_usr, name = name || suffix
7715                                         WHERE   id = renamable_row.id;
7716                                 EXCEPTION WHEN unique_violation THEN
7717                                         suffix := suffix || ' ';
7718                                         CONTINUE;
7719                                 END;
7720                                 EXIT;
7721                         END LOOP;
7722                 END LOOP;
7723         EXCEPTION WHEN undefined_table THEN
7724                 -- do nothing
7725         END;
7726
7727         BEGIN
7728                 UPDATE reporter.schedule SET runner = dest_usr WHERE runner = src_usr;
7729         EXCEPTION WHEN undefined_table THEN
7730                 -- do nothing
7731         END;
7732
7733         BEGIN
7734                 UPDATE reporter.template SET owner = dest_usr WHERE owner = src_usr;
7735         EXCEPTION WHEN undefined_table THEN
7736                 -- do nothing
7737         END;
7738
7739         -- Update with a rename to avoid collisions
7740         BEGIN
7741                 FOR renamable_row in
7742                         SELECT id, name
7743                         FROM   reporter.template_folder
7744                         WHERE  owner = src_usr
7745                 LOOP
7746                         suffix := ' (' || src_usr || ')';
7747                         LOOP
7748                                 BEGIN
7749                                         UPDATE  reporter.template_folder
7750                                         SET     owner = dest_usr, name = name || suffix
7751                                         WHERE   id = renamable_row.id;
7752                                 EXCEPTION WHEN unique_violation THEN
7753                                         suffix := suffix || ' ';
7754                                         CONTINUE;
7755                                 END;
7756                                 EXIT;
7757                         END LOOP;
7758                 END LOOP;
7759         EXCEPTION WHEN undefined_table THEN
7760         -- do nothing
7761         END;
7762
7763         -- vandelay.*
7764         -- Update with a rename to avoid collisions
7765         FOR renamable_row in
7766                 SELECT id, name
7767                 FROM   vandelay.queue
7768                 WHERE  owner = src_usr
7769         LOOP
7770                 suffix := ' (' || src_usr || ')';
7771                 LOOP
7772                         BEGIN
7773                                 UPDATE  vandelay.queue
7774                                 SET     owner = dest_usr, name = name || suffix
7775                                 WHERE   id = renamable_row.id;
7776                         EXCEPTION WHEN unique_violation THEN
7777                                 suffix := suffix || ' ';
7778                                 CONTINUE;
7779                         END;
7780                         EXIT;
7781                 END LOOP;
7782         END LOOP;
7783
7784 END;
7785 $$ LANGUAGE plpgsql;
7786
7787 -- INSERT INTO config.copy_status (id,name) VALUES (15,oils_i18n_gettext(15, 'On reservation shelf', 'ccs', 'name'));
7788
7789 ALTER TABLE acq.fund
7790 ADD COLUMN rollover BOOL NOT NULL DEFAULT FALSE;
7791
7792 ALTER TABLE acq.fund
7793         ADD COLUMN propagate BOOLEAN NOT NULL DEFAULT TRUE;
7794
7795 -- A fund can't roll over if it doesn't propagate from one year to the next
7796
7797 ALTER TABLE acq.fund
7798         ADD CONSTRAINT acq_fund_rollover_implies_propagate CHECK
7799         ( propagate OR NOT rollover );
7800
7801 ALTER TABLE acq.fund
7802         ADD COLUMN active BOOL NOT NULL DEFAULT TRUE;
7803
7804 ALTER TABLE acq.fund
7805     ADD COLUMN balance_warning_percent INT
7806     CONSTRAINT balance_warning_percent_limit
7807         CHECK( balance_warning_percent <= 100 );
7808
7809 ALTER TABLE acq.fund
7810     ADD COLUMN balance_stop_percent INT
7811     CONSTRAINT balance_stop_percent_limit
7812         CHECK( balance_stop_percent <= 100 );
7813
7814 CREATE VIEW acq.ordered_funding_source_credit AS
7815         SELECT
7816                 CASE WHEN deadline_date IS NULL THEN
7817                         2
7818                 ELSE
7819                         1
7820                 END AS sort_priority,
7821                 CASE WHEN deadline_date IS NULL THEN
7822                         effective_date
7823                 ELSE
7824                         deadline_date
7825                 END AS sort_date,
7826                 id,
7827                 funding_source,
7828                 amount,
7829                 note
7830         FROM
7831                 acq.funding_source_credit;
7832
7833 COMMENT ON VIEW acq.ordered_funding_source_credit IS $$
7834 /*
7835  * Copyright (C) 2009  Georgia Public Library Service
7836  * Scott McKellar <scott@gmail.com>
7837  *
7838  * The acq.ordered_funding_source_credit view is a prioritized
7839  * ordering of funding source credits.  When ordered by the first
7840  * three columns, this view defines the order in which the various
7841  * credits are to be tapped for spending, subject to the allocations
7842  * in the acq.fund_allocation table.
7843  *
7844  * The first column reflects the principle that we should spend
7845  * money with deadlines before spending money without deadlines.
7846  *
7847  * The second column reflects the principle that we should spend the
7848  * oldest money first.  For money with deadlines, that means that we
7849  * spend first from the credit with the earliest deadline.  For
7850  * money without deadlines, we spend first from the credit with the
7851  * earliest effective date.  
7852  *
7853  * The third column is a tie breaker to ensure a consistent
7854  * ordering.
7855  *
7856  * ****
7857  *
7858  * This program is free software; you can redistribute it and/or
7859  * modify it under the terms of the GNU General Public License
7860  * as published by the Free Software Foundation; either version 2
7861  * of the License, or (at your option) any later version.
7862  *
7863  * This program is distributed in the hope that it will be useful,
7864  * but WITHOUT ANY WARRANTY; without even the implied warranty of
7865  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
7866  * GNU General Public License for more details.
7867  */
7868 $$;
7869
7870 CREATE OR REPLACE VIEW money.billable_xact_summary_location_view AS
7871     SELECT  m.*, COALESCE(c.circ_lib, g.billing_location, r.pickup_lib) AS billing_location
7872       FROM  money.materialized_billable_xact_summary m
7873             LEFT JOIN action.circulation c ON (c.id = m.id)
7874             LEFT JOIN money.grocery g ON (g.id = m.id)
7875             LEFT JOIN booking.reservation r ON (r.id = m.id);
7876
7877 CREATE TABLE config.marc21_rec_type_map (
7878     code        TEXT    PRIMARY KEY,
7879     type_val    TEXT    NOT NULL,
7880     blvl_val    TEXT    NOT NULL
7881 );
7882
7883 CREATE TABLE config.marc21_ff_pos_map (
7884     id          SERIAL  PRIMARY KEY,
7885     fixed_field TEXT    NOT NULL,
7886     tag         TEXT    NOT NULL,
7887     rec_type    TEXT    NOT NULL,
7888     start_pos   INT     NOT NULL,
7889     length      INT     NOT NULL,
7890     default_val TEXT    NOT NULL DEFAULT ' '
7891 );
7892
7893 CREATE TABLE config.marc21_physical_characteristic_type_map (
7894     ptype_key   TEXT    PRIMARY KEY,
7895     label       TEXT    NOT NULL -- I18N
7896 );
7897
7898 CREATE TABLE config.marc21_physical_characteristic_subfield_map (
7899     id          SERIAL  PRIMARY KEY,
7900     ptype_key   TEXT    NOT NULL REFERENCES config.marc21_physical_characteristic_type_map (ptype_key) ON DELETE CASCADE ON UPDATE CASCADE,
7901     subfield    TEXT    NOT NULL,
7902     start_pos   INT     NOT NULL,
7903     length      INT     NOT NULL,
7904     label       TEXT    NOT NULL -- I18N
7905 );
7906
7907 CREATE TABLE config.marc21_physical_characteristic_value_map (
7908     id              SERIAL  PRIMARY KEY,
7909     value           TEXT    NOT NULL,
7910     ptype_subfield  INT     NOT NULL REFERENCES config.marc21_physical_characteristic_subfield_map (id),
7911     label           TEXT    NOT NULL -- I18N
7912 );
7913
7914 ----------------------------------
7915 -- MARC21 record structure data --
7916 ----------------------------------
7917
7918 -- Record type map
7919 INSERT INTO config.marc21_rec_type_map (code, type_val, blvl_val) VALUES ('BKS','at','acdm');
7920 INSERT INTO config.marc21_rec_type_map (code, type_val, blvl_val) VALUES ('SER','a','bsi');
7921 INSERT INTO config.marc21_rec_type_map (code, type_val, blvl_val) VALUES ('VIS','gkro','abcdmsi');
7922 INSERT INTO config.marc21_rec_type_map (code, type_val, blvl_val) VALUES ('MIX','p','cdi');
7923 INSERT INTO config.marc21_rec_type_map (code, type_val, blvl_val) VALUES ('MAP','ef','abcdmsi');
7924 INSERT INTO config.marc21_rec_type_map (code, type_val, blvl_val) VALUES ('SCO','cd','abcdmsi');
7925 INSERT INTO config.marc21_rec_type_map (code, type_val, blvl_val) VALUES ('REC','ij','abcdmsi');
7926 INSERT INTO config.marc21_rec_type_map (code, type_val, blvl_val) VALUES ('COM','m','abcdmsi');
7927
7928 ------ Physical Characteristics
7929
7930 -- Map
7931 INSERT INTO config.marc21_physical_characteristic_type_map (ptype_key, label) VALUES ('a','Map');
7932 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('a','b','1','1','SMD');
7933 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('d',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Atlas');
7934 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('g',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Diagram');
7935 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('j',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Map');
7936 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('k',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Profile');
7937 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('q',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Model');
7938 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');
7939 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('s',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Section');
7940 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unspecified');
7941 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('y',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'View');
7942 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
7943 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('a','d','3','1','Color');
7944 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');
7945 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('c',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Multicolored');
7946 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('a','e','4','1','Physical medium');
7947 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('a',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Paper');
7948 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('b',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Wood');
7949 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('c',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Stone');
7950 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('d',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Metal');
7951 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('e',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Synthetics');
7952 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('f',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Skins');
7953 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('g',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Textile');
7954 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('p',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Plaster');
7955 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');
7956 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');
7957 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');
7958 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');
7959 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
7960 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');
7961 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
7962 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('a','f','5','1','Type of reproduction');
7963 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('f',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Facsimile');
7964 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');
7965 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
7966 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
7967 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('a','g','6','1','Production/reproduction details');
7968 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');
7969 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('b',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Photocopy');
7970 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');
7971 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('d',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Film');
7972 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
7973 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
7974 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('a','h','7','1','Positive/negative');
7975 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('a',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Positive');
7976 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('b',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Negative');
7977 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('m',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Mixed');
7978 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');
7979
7980 -- Electronic Resource
7981 INSERT INTO config.marc21_physical_characteristic_type_map (ptype_key, label) VALUES ('c','Electronic Resource');
7982 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('c','b','1','1','SMD');
7983 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');
7984 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');
7985 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');
7986 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');
7987 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');
7988 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');
7989 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');
7990 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');
7991 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('r',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Remote');
7992 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unspecified');
7993 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
7994 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('c','d','3','1','Color');
7995 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');
7996 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');
7997 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('c',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Multicolored');
7998 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');
7999 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('m',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Mixed');
8000 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');
8001 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
8002 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
8003 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('c','e','4','1','Dimensions');
8004 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.');
8005 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.');
8006 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.');
8007 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.');
8008 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.');
8009 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');
8010 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.');
8011 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
8012 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.');
8013 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
8014 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('c','f','5','1','Sound');
8015 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)');
8016 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('a',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Sound');
8017 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
8018 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('c','g','6','3','Image bit depth');
8019 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('---',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
8020 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('mmm',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Multiple');
8021 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');
8022 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('c','h','9','1','File formats');
8023 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');
8024 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');
8025 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
8026 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('c','i','10','1','Quality assurance target(s)');
8027 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('a',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Absent');
8028 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');
8029 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('p',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Present');
8030 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
8031 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('c','j','11','1','Antecedent/Source');
8032 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');
8033 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');
8034 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');
8035 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)');
8036 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('m',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Mixed');
8037 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');
8038 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
8039 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('c','k','12','1','Level of compression');
8040 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('a',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Uncompressed');
8041 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('b',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Lossless');
8042 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('d',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Lossy');
8043 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('m',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Mixed');
8044 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
8045 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('c','l','13','1','Reformatting quality');
8046 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('a',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Access');
8047 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');
8048 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('p',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Preservation');
8049 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('r',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Replacement');
8050 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
8051
8052 -- Globe
8053 INSERT INTO config.marc21_physical_characteristic_type_map (ptype_key, label) VALUES ('d','Globe');
8054 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('d','b','1','1','SMD');
8055 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');
8056 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');
8057 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');
8058 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');
8059 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unspecified');
8060 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
8061 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('d','d','3','1','Color');
8062 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');
8063 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('c',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Multicolored');
8064 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('d','e','4','1','Physical medium');
8065 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('a',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Paper');
8066 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('b',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Wood');
8067 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('c',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Stone');
8068 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('d',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Metal');
8069 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('e',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Synthetics');
8070 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('f',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Skins');
8071 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('g',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Textile');
8072 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('p',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Plaster');
8073 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
8074 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
8075 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('d','f','5','1','Type of reproduction');
8076 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('f',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Facsimile');
8077 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');
8078 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
8079 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
8080
8081 -- Tactile Material
8082 INSERT INTO config.marc21_physical_characteristic_type_map (ptype_key, label) VALUES ('f','Tactile Material');
8083 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('f','b','1','1','SMD');
8084 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('a',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Moon');
8085 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('b',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Braille');
8086 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('c',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Combination');
8087 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');
8088 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unspecified');
8089 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
8090 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('f','d','3','2','Class of braille writing');
8091 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');
8092 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');
8093 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');
8094 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');
8095 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');
8096 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');
8097 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');
8098 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
8099 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
8100 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('f','e','4','1','Level of contraction');
8101 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('a',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Uncontracted');
8102 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('b',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Contracted');
8103 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('m',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Combination');
8104 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');
8105 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
8106 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
8107 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('f','f','6','3','Braille music format');
8108 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');
8109 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');
8110 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');
8111 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('d',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Paragraph');
8112 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');
8113 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');
8114 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');
8115 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');
8116 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');
8117 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');
8118 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('k',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Outline');
8119 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');
8120 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');
8121 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
8122 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
8123 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('f','g','9','1','Special physical characteristics');
8124 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');
8125 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');
8126 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');
8127 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
8128 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
8129
8130 -- Projected Graphic
8131 INSERT INTO config.marc21_physical_characteristic_type_map (ptype_key, label) VALUES ('g','Projected Graphic');
8132 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('g','b','1','1','SMD');
8133 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');
8134 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('d',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Filmstrip');
8135 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');
8136 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');
8137 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('s',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Slide');
8138 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('t',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Transparency');
8139 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
8140 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('g','d','3','1','Color');
8141 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');
8142 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('c',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Multicolored');
8143 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');
8144 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('m',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Mixed');
8145 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');
8146 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
8147 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
8148 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('g','e','4','1','Base of emulsion');
8149 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('d',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Glass');
8150 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('e',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Synthetics');
8151 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');
8152 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');
8153 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');
8154 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('o',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Paper');
8155 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
8156 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
8157 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');
8158 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');
8159 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');
8160 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
8161 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('g','g','6','1','Medium for sound');
8162 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');
8163 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');
8164 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');
8165 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');
8166 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');
8167 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');
8168 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');
8169 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('h',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Videotape');
8170 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('i',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Videodisc');
8171 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
8172 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
8173 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('g','h','7','1','Dimensions');
8174 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.');
8175 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.');
8176 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.');
8177 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.');
8178 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.');
8179 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.');
8180 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.');
8181 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.)');
8182 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.)');
8183 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.)');
8184 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.)');
8185 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
8186 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.)');
8187 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.)');
8188 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.)');
8189 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.)');
8190 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
8191 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('g','i','8','1','Secondary support material');
8192 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('c',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Cardboard');
8193 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('d',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Glass');
8194 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('e',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Synthetics');
8195 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('h',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'metal');
8196 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');
8197 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');
8198 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');
8199 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
8200 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
8201
8202 -- Microform
8203 INSERT INTO config.marc21_physical_characteristic_type_map (ptype_key, label) VALUES ('h','Microform');
8204 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('h','b','1','1','SMD');
8205 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');
8206 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');
8207 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');
8208 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');
8209 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('e',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Microfiche');
8210 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');
8211 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('g',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Microopaque');
8212 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unspecified');
8213 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
8214 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('h','d','3','1','Positive/negative');
8215 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('a',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Positive');
8216 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('b',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Negative');
8217 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('m',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Mixed');
8218 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
8219 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('h','e','4','1','Dimensions');
8220 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.');
8221 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.');
8222 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.');
8223 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('g',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'70mm.');
8224 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.');
8225 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.)');
8226 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.)');
8227 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.)');
8228 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.)');
8229 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
8230 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
8231 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');
8232 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)');
8233 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)');
8234 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)');
8235 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)');
8236 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-)');
8237 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
8238 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');
8239 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('h','g','9','1','Color');
8240 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');
8241 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('c',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Multicolored');
8242 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('m',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Mixed');
8243 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
8244 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
8245 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('h','h','10','1','Emulsion on film');
8246 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');
8247 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('b',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Diazo');
8248 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('c',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Vesicular');
8249 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('m',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Mixed');
8250 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');
8251 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
8252 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
8253 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('h','i','11','1','Quality assurance target(s)');
8254 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');
8255 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');
8256 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');
8257 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');
8258 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
8259 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('h','j','12','1','Base of film');
8260 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');
8261 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');
8262 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');
8263 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');
8264 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');
8265 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');
8266 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');
8267 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');
8268 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');
8269 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
8270 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
8271
8272 -- Non-projected Graphic
8273 INSERT INTO config.marc21_physical_characteristic_type_map (ptype_key, label) VALUES ('k','Non-projected Graphic');
8274 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('k','b','1','1','SMD');
8275 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('c',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Collage');
8276 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('d',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Drawing');
8277 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('e',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Painting');
8278 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');
8279 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('g',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Photonegative');
8280 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('h',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Photoprint');
8281 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('i',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Picture');
8282 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('j',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Print');
8283 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');
8284 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('n',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Chart');
8285 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');
8286 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unspecified');
8287 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
8288 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('k','d','3','1','Color');
8289 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');
8290 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');
8291 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('c',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Multicolored');
8292 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');
8293 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('m',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Mixed');
8294 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
8295 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
8296 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('k','e','4','1','Primary support material');
8297 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('a',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Canvas');
8298 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');
8299 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');
8300 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('d',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Glass');
8301 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('e',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Synthetics');
8302 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('f',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Skins');
8303 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('g',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Textile');
8304 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('h',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Metal');
8305 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');
8306 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('o',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Paper');
8307 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('p',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Plaster');
8308 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('q',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Hardboard');
8309 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('r',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Porcelain');
8310 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('s',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Stone');
8311 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('t',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Wood');
8312 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
8313 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
8314 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('k','f','5','1','Secondary support material');
8315 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('a',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Canvas');
8316 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');
8317 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');
8318 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('d',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Glass');
8319 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('e',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Synthetics');
8320 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('f',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Skins');
8321 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('g',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Textile');
8322 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('h',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Metal');
8323 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');
8324 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('o',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Paper');
8325 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('p',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Plaster');
8326 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('q',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Hardboard');
8327 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('r',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Porcelain');
8328 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('s',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Stone');
8329 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('t',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Wood');
8330 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
8331 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
8332
8333 -- Motion Picture
8334 INSERT INTO config.marc21_physical_characteristic_type_map (ptype_key, label) VALUES ('m','Motion Picture');
8335 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('m','b','1','1','SMD');
8336 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');
8337 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');
8338 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');
8339 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unspecified');
8340 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
8341 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('m','d','3','1','Color');
8342 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');
8343 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('c',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Multicolored');
8344 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');
8345 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('m',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Mixed');
8346 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
8347 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
8348 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('m','e','4','1','Motion picture presentation format');
8349 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');
8350 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)');
8351 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('c',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'3D');
8352 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)');
8353 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');
8354 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');
8355 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
8356 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
8357 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');
8358 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');
8359 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');
8360 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
8361 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('m','g','6','1','Medium for sound');
8362 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');
8363 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');
8364 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');
8365 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');
8366 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');
8367 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');
8368 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');
8369 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('h',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Videotape');
8370 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('i',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Videodisc');
8371 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
8372 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
8373 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('m','h','7','1','Dimensions');
8374 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.');
8375 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.');
8376 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.');
8377 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.');
8378 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.');
8379 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.');
8380 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.');
8381 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
8382 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
8383 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('m','i','8','1','Configuration of playback channels');
8384 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('k',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Mixed');
8385 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('m',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Monaural');
8386 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');
8387 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');
8388 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('s',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Stereophonic');
8389 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
8390 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
8391 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('m','j','9','1','Production elements');
8392 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');
8393 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('b',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Trims');
8394 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('c',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Outtakes');
8395 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('d',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Rushes');
8396 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');
8397 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');
8398 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');
8399 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');
8400 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
8401
8402 -- Remote-sensing Image
8403 INSERT INTO config.marc21_physical_characteristic_type_map (ptype_key, label) VALUES ('r','Remote-sensing Image');
8404 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('r','b','1','1','SMD');
8405 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unspecified');
8406 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('r','d','3','1','Altitude of sensor');
8407 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('a',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Surface');
8408 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('b',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Airborne');
8409 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('c',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Spaceborne');
8410 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');
8411 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
8412 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
8413 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('r','e','4','1','Attitude of sensor');
8414 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');
8415 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');
8416 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('c',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Vertical');
8417 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');
8418 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
8419 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('r','f','5','1','Cloud cover');
8420 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%');
8421 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%');
8422 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%');
8423 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%');
8424 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%');
8425 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%');
8426 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%');
8427 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%');
8428 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%');
8429 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%');
8430 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');
8431 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
8432 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('r','g','6','1','Platform construction type');
8433 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('a',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Balloon');
8434 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');
8435 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');
8436 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');
8437 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');
8438 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');
8439 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');
8440 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');
8441 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');
8442 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');
8443 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
8444 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
8445 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('r','h','7','1','Platform use category');
8446 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('a',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Meteorological');
8447 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');
8448 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');
8449 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');
8450 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');
8451 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
8452 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
8453 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('r','i','8','1','Sensor type');
8454 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('a',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Active');
8455 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('b',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Passive');
8456 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
8457 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
8458 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('r','j','9','2','Data type');
8459 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');
8460 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');
8461 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');
8462 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');
8463 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');
8464 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)');
8465 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');
8466 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('dv',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Combinations');
8467 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');
8468 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)');
8469 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)');
8470 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)');
8471 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');
8472 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');
8473 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');
8474 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');
8475 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');
8476 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');
8477 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');
8478 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');
8479 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');
8480 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');
8481 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');
8482 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');
8483 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');
8484 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');
8485 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');
8486 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');
8487 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');
8488 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');
8489 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');
8490 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');
8491 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');
8492 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)');
8493 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');
8494 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('rc',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Bouger');
8495 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('rd',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Isostatic');
8496 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');
8497 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');
8498 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('uu',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
8499 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('zz',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
8500
8501 -- Sound Recording
8502 INSERT INTO config.marc21_physical_characteristic_type_map (ptype_key, label) VALUES ('s','Sound Recording');
8503 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('s','b','1','1','SMD');
8504 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');
8505 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('e',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Cylinder');
8506 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');
8507 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');
8508 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('q',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Roll');
8509 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');
8510 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');
8511 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unspecified');
8512 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');
8513 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
8514 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('s','d','3','1','Speed');
8515 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');
8516 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');
8517 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');
8518 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');
8519 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');
8520 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');
8521 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');
8522 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');
8523 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');
8524 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');
8525 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');
8526 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');
8527 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');
8528 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');
8529 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
8530 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
8531 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('s','e','4','1','Configuration of playback channels');
8532 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('m',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Monaural');
8533 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('q',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Quadraphonic');
8534 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('s',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Stereophonic');
8535 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
8536 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
8537 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('s','f','5','1','Groove width or pitch');
8538 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');
8539 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');
8540 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');
8541 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
8542 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
8543 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('s','g','6','1','Dimensions');
8544 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.');
8545 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.');
8546 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.');
8547 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.');
8548 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.');
8549 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.');
8550 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.)');
8551 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.');
8552 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');
8553 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.');
8554 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.');
8555 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
8556 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
8557 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('s','h','7','1','Tape width');
8558 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.');
8559 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.');
8560 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');
8561 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.');
8562 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.');
8563 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
8564 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
8565 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('s','i','8','1','Tape configuration ');
8566 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');
8567 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');
8568 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');
8569 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');
8570 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');
8571 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');
8572 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');
8573 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
8574 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
8575 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('s','m','12','1','Special playback');
8576 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');
8577 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');
8578 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');
8579 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');
8580 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');
8581 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');
8582 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');
8583 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');
8584 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');
8585 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
8586 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
8587 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('s','n','13','1','Capture and storage');
8588 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');
8589 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');
8590 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');
8591 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');
8592 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
8593 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
8594
8595 -- Videorecording
8596 INSERT INTO config.marc21_physical_characteristic_type_map (ptype_key, label) VALUES ('v','Videorecording');
8597 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('v','b','1','1','SMD');
8598 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('c',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Videocartridge');
8599 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('d',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Videodisc');
8600 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('f',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Videocassette');
8601 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('r',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Videoreel');
8602 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unspecified');
8603 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
8604 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('v','d','3','1','Color');
8605 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');
8606 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('c',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Multicolored');
8607 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('m',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Mixed');
8608 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');
8609 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
8610 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
8611 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('v','e','4','1','Videorecording format');
8612 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('a',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Beta');
8613 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('b',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'VHS');
8614 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');
8615 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('d',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'EIAJ');
8616 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');
8617 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('f',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Quadruplex');
8618 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('g',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Laserdisc');
8619 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('h',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'CED');
8620 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('i',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Betacam');
8621 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');
8622 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');
8623 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');
8624 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');
8625 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.');
8626 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.');
8627 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
8628 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('v',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'DVD');
8629 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
8630 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');
8631 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');
8632 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');
8633 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
8634 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('v','g','6','1','Medium for sound');
8635 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');
8636 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');
8637 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');
8638 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');
8639 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');
8640 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');
8641 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');
8642 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('h',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Videotape');
8643 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('i',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Videodisc');
8644 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
8645 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
8646 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('v','h','7','1','Dimensions');
8647 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.');
8648 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.');
8649 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.');
8650 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.');
8651 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.');
8652 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.');
8653 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
8654 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
8655 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('v','i','8','1','Configuration of playback channel');
8656 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('k',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Mixed');
8657 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('m',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Monaural');
8658 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');
8659 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');
8660 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('s',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Stereophonic');
8661 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
8662 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
8663
8664 -- Fixed Field position data -- 0-based!
8665 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Alph', '006', 'SER', 16, 1, ' ');
8666 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Alph', '008', 'SER', 33, 1, ' ');
8667 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Audn', '006', 'BKS', 5, 1, ' ');
8668 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Audn', '006', 'COM', 5, 1, ' ');
8669 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Audn', '006', 'REC', 5, 1, ' ');
8670 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Audn', '006', 'SCO', 5, 1, ' ');
8671 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Audn', '006', 'SER', 5, 1, ' ');
8672 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Audn', '006', 'VIS', 5, 1, ' ');
8673 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Audn', '008', 'BKS', 22, 1, ' ');
8674 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Audn', '008', 'COM', 22, 1, ' ');
8675 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Audn', '008', 'REC', 22, 1, ' ');
8676 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Audn', '008', 'SCO', 22, 1, ' ');
8677 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Audn', '008', 'SER', 22, 1, ' ');
8678 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Audn', '008', 'VIS', 22, 1, ' ');
8679 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('BLvl', 'ldr', 'BKS', 7, 1, 'm');
8680 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('BLvl', 'ldr', 'COM', 7, 1, 'm');
8681 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('BLvl', 'ldr', 'MAP', 7, 1, 'm');
8682 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('BLvl', 'ldr', 'MIX', 7, 1, 'c');
8683 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('BLvl', 'ldr', 'REC', 7, 1, 'm');
8684 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('BLvl', 'ldr', 'SCO', 7, 1, 'm');
8685 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('BLvl', 'ldr', 'SER', 7, 1, 's');
8686 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('BLvl', 'ldr', 'VIS', 7, 1, 'm');
8687 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Biog', '006', 'BKS', 17, 1, ' ');
8688 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Biog', '008', 'BKS', 34, 1, ' ');
8689 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Conf', '006', 'BKS', 7, 4, ' ');
8690 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Conf', '006', 'SER', 8, 3, ' ');
8691 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Conf', '008', 'BKS', 24, 4, ' ');
8692 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Conf', '008', 'SER', 25, 3, ' ');
8693 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Ctrl', 'ldr', 'BKS', 8, 1, ' ');
8694 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Ctrl', 'ldr', 'COM', 8, 1, ' ');
8695 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Ctrl', 'ldr', 'MAP', 8, 1, ' ');
8696 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Ctrl', 'ldr', 'MIX', 8, 1, ' ');
8697 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Ctrl', 'ldr', 'REC', 8, 1, ' ');
8698 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Ctrl', 'ldr', 'SCO', 8, 1, ' ');
8699 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Ctrl', 'ldr', 'SER', 8, 1, ' ');
8700 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Ctrl', 'ldr', 'VIS', 8, 1, ' ');
8701 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Ctry', '008', 'BKS', 15, 3, ' ');
8702 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Ctry', '008', 'COM', 15, 3, ' ');
8703 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Ctry', '008', 'MAP', 15, 3, ' ');
8704 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Ctry', '008', 'MIX', 15, 3, ' ');
8705 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Ctry', '008', 'REC', 15, 3, ' ');
8706 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Ctry', '008', 'SCO', 15, 3, ' ');
8707 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Ctry', '008', 'SER', 15, 3, ' ');
8708 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Ctry', '008', 'VIS', 15, 3, ' ');
8709 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Date1', '008', 'BKS', 7, 4, ' ');
8710 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Date1', '008', 'COM', 7, 4, ' ');
8711 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Date1', '008', 'MAP', 7, 4, ' ');
8712 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Date1', '008', 'MIX', 7, 4, ' ');
8713 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Date1', '008', 'REC', 7, 4, ' ');
8714 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Date1', '008', 'SCO', 7, 4, ' ');
8715 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Date1', '008', 'SER', 7, 4, ' ');
8716 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Date1', '008', 'VIS', 7, 4, ' ');
8717 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Date2', '008', 'BKS', 11, 4, ' ');
8718 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Date2', '008', 'COM', 11, 4, ' ');
8719 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Date2', '008', 'MAP', 11, 4, ' ');
8720 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Date2', '008', 'MIX', 11, 4, ' ');
8721 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Date2', '008', 'REC', 11, 4, ' ');
8722 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Date2', '008', 'SCO', 11, 4, ' ');
8723 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Date2', '008', 'SER', 11, 4, '9');
8724 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Date2', '008', 'VIS', 11, 4, ' ');
8725 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Desc', 'ldr', 'BKS', 18, 1, ' ');
8726 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Desc', 'ldr', 'COM', 18, 1, ' ');
8727 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Desc', 'ldr', 'MAP', 18, 1, ' ');
8728 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Desc', 'ldr', 'MIX', 18, 1, ' ');
8729 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Desc', 'ldr', 'REC', 18, 1, ' ');
8730 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Desc', 'ldr', 'SCO', 18, 1, ' ');
8731 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Desc', 'ldr', 'SER', 18, 1, ' ');
8732 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Desc', 'ldr', 'VIS', 18, 1, ' ');
8733 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('DtSt', '008', 'BKS', 6, 1, ' ');
8734 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('DtSt', '008', 'COM', 6, 1, ' ');
8735 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('DtSt', '008', 'MAP', 6, 1, ' ');
8736 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('DtSt', '008', 'MIX', 6, 1, ' ');
8737 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('DtSt', '008', 'REC', 6, 1, ' ');
8738 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('DtSt', '008', 'SCO', 6, 1, ' ');
8739 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('DtSt', '008', 'SER', 6, 1, 'c');
8740 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('DtSt', '008', 'VIS', 6, 1, ' ');
8741 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('ELvl', 'ldr', 'BKS', 17, 1, ' ');
8742 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('ELvl', 'ldr', 'COM', 17, 1, ' ');
8743 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('ELvl', 'ldr', 'MAP', 17, 1, ' ');
8744 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('ELvl', 'ldr', 'MIX', 17, 1, ' ');
8745 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('ELvl', 'ldr', 'REC', 17, 1, ' ');
8746 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('ELvl', 'ldr', 'SCO', 17, 1, ' ');
8747 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('ELvl', 'ldr', 'SER', 17, 1, ' ');
8748 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('ELvl', 'ldr', 'VIS', 17, 1, ' ');
8749 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Fest', '006', 'BKS', 13, 1, '0');
8750 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Fest', '008', 'BKS', 30, 1, '0');
8751 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Form', '006', 'BKS', 6, 1, ' ');
8752 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Form', '006', 'MAP', 12, 1, ' ');
8753 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Form', '006', 'MIX', 6, 1, ' ');
8754 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Form', '006', 'REC', 6, 1, ' ');
8755 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Form', '006', 'SCO', 6, 1, ' ');
8756 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Form', '006', 'SER', 6, 1, ' ');
8757 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Form', '006', 'VIS', 12, 1, ' ');
8758 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Form', '008', 'BKS', 23, 1, ' ');
8759 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Form', '008', 'MAP', 29, 1, ' ');
8760 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Form', '008', 'MIX', 23, 1, ' ');
8761 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Form', '008', 'REC', 23, 1, ' ');
8762 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Form', '008', 'SCO', 23, 1, ' ');
8763 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Form', '008', 'SER', 23, 1, ' ');
8764 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Form', '008', 'VIS', 29, 1, ' ');
8765 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('GPub', '006', 'BKS', 11, 1, ' ');
8766 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('GPub', '006', 'COM', 11, 1, ' ');
8767 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('GPub', '006', 'MAP', 11, 1, ' ');
8768 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('GPub', '006', 'SER', 11, 1, ' ');
8769 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('GPub', '006', 'VIS', 11, 1, ' ');
8770 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('GPub', '008', 'BKS', 28, 1, ' ');
8771 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('GPub', '008', 'COM', 28, 1, ' ');
8772 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('GPub', '008', 'MAP', 28, 1, ' ');
8773 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('GPub', '008', 'SER', 28, 1, ' ');
8774 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('GPub', '008', 'VIS', 28, 1, ' ');
8775 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Ills', '006', 'BKS', 1, 4, ' ');
8776 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Ills', '008', 'BKS', 18, 4, ' ');
8777 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Indx', '006', 'BKS', 14, 1, '0');
8778 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Indx', '006', 'MAP', 14, 1, '0');
8779 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Indx', '008', 'BKS', 31, 1, '0');
8780 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Indx', '008', 'MAP', 31, 1, '0');
8781 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Lang', '008', 'BKS', 35, 3, ' ');
8782 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Lang', '008', 'COM', 35, 3, ' ');
8783 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Lang', '008', 'MAP', 35, 3, ' ');
8784 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Lang', '008', 'MIX', 35, 3, ' ');
8785 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Lang', '008', 'REC', 35, 3, ' ');
8786 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Lang', '008', 'SCO', 35, 3, ' ');
8787 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Lang', '008', 'SER', 35, 3, ' ');
8788 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Lang', '008', 'VIS', 35, 3, ' ');
8789 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('LitF', '006', 'BKS', 16, 1, '0');
8790 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('LitF', '008', 'BKS', 33, 1, '0');
8791 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('MRec', '008', 'BKS', 38, 1, ' ');
8792 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('MRec', '008', 'COM', 38, 1, ' ');
8793 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('MRec', '008', 'MAP', 38, 1, ' ');
8794 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('MRec', '008', 'MIX', 38, 1, ' ');
8795 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('MRec', '008', 'REC', 38, 1, ' ');
8796 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('MRec', '008', 'SCO', 38, 1, ' ');
8797 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('MRec', '008', 'SER', 38, 1, ' ');
8798 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('MRec', '008', 'VIS', 38, 1, ' ');
8799 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');
8800 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');
8801 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('TMat', '006', 'VIS', 16, 1, ' ');
8802 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('TMat', '008', 'VIS', 33, 1, ' ');
8803 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Type', 'ldr', 'BKS', 6, 1, 'a');
8804 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Type', 'ldr', 'COM', 6, 1, 'm');
8805 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Type', 'ldr', 'MAP', 6, 1, 'e');
8806 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Type', 'ldr', 'MIX', 6, 1, 'p');
8807 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Type', 'ldr', 'REC', 6, 1, 'i');
8808 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Type', 'ldr', 'SCO', 6, 1, 'c');
8809 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Type', 'ldr', 'SER', 6, 1, 'a');
8810 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Type', 'ldr', 'VIS', 6, 1, 'g');
8811
8812 CREATE OR REPLACE FUNCTION biblio.marc21_record_type( rid BIGINT ) RETURNS config.marc21_rec_type_map AS $func$
8813 DECLARE
8814         ldr         RECORD;
8815         tval        TEXT;
8816         tval_rec    RECORD;
8817         bval        TEXT;
8818         bval_rec    RECORD;
8819     retval      config.marc21_rec_type_map%ROWTYPE;
8820 BEGIN
8821     SELECT * INTO ldr FROM metabib.full_rec WHERE record = rid AND tag = 'LDR' LIMIT 1;
8822
8823     IF ldr.id IS NULL THEN
8824         SELECT * INTO retval FROM config.marc21_rec_type_map WHERE code = 'BKS';
8825         RETURN retval;
8826     END IF;
8827
8828     SELECT * INTO tval_rec FROM config.marc21_ff_pos_map WHERE fixed_field = 'Type' LIMIT 1; -- They're all the same
8829     SELECT * INTO bval_rec FROM config.marc21_ff_pos_map WHERE fixed_field = 'BLvl' LIMIT 1; -- They're all the same
8830
8831
8832     tval := SUBSTRING( ldr.value, tval_rec.start_pos + 1, tval_rec.length );
8833     bval := SUBSTRING( ldr.value, bval_rec.start_pos + 1, bval_rec.length );
8834
8835     -- RAISE NOTICE 'type %, blvl %, ldr %', tval, bval, ldr.value;
8836
8837     SELECT * INTO retval FROM config.marc21_rec_type_map WHERE type_val LIKE '%' || tval || '%' AND blvl_val LIKE '%' || bval || '%';
8838
8839
8840     IF retval.code IS NULL THEN
8841         SELECT * INTO retval FROM config.marc21_rec_type_map WHERE code = 'BKS';
8842     END IF;
8843
8844     RETURN retval;
8845 END;
8846 $func$ LANGUAGE PLPGSQL;
8847
8848 CREATE OR REPLACE FUNCTION biblio.marc21_extract_fixed_field( rid BIGINT, ff TEXT ) RETURNS TEXT AS $func$
8849 DECLARE
8850     rtype       TEXT;
8851     ff_pos      RECORD;
8852     tag_data    RECORD;
8853     val         TEXT;
8854 BEGIN
8855     rtype := (biblio.marc21_record_type( rid )).code;
8856     FOR ff_pos IN SELECT * FROM config.marc21_ff_pos_map WHERE fixed_field = ff AND rec_type = rtype ORDER BY tag DESC LOOP
8857         FOR tag_data IN SELECT * FROM metabib.full_rec WHERE tag = UPPER(ff_pos.tag) AND record = rid LOOP
8858             val := SUBSTRING( tag_data.value, ff_pos.start_pos + 1, ff_pos.length );
8859             RETURN val;
8860         END LOOP;
8861         val := REPEAT( ff_pos.default_val, ff_pos.length );
8862         RETURN val;
8863     END LOOP;
8864
8865     RETURN NULL;
8866 END;
8867 $func$ LANGUAGE PLPGSQL;
8868
8869 CREATE TYPE biblio.marc21_physical_characteristics AS ( id INT, record BIGINT, ptype TEXT, subfield INT, value INT );
8870 CREATE OR REPLACE FUNCTION biblio.marc21_physical_characteristics( rid BIGINT ) RETURNS SETOF biblio.marc21_physical_characteristics AS $func$
8871 DECLARE
8872     rowid   INT := 0;
8873     _007    RECORD;
8874     ptype   config.marc21_physical_characteristic_type_map%ROWTYPE;
8875     psf     config.marc21_physical_characteristic_subfield_map%ROWTYPE;
8876     pval    config.marc21_physical_characteristic_value_map%ROWTYPE;
8877     retval  biblio.marc21_physical_characteristics%ROWTYPE;
8878 BEGIN
8879
8880     SELECT * INTO _007 FROM metabib.full_rec WHERE record = rid AND tag = '007' LIMIT 1;
8881
8882     IF _007.id IS NOT NULL THEN
8883         SELECT * INTO ptype FROM config.marc21_physical_characteristic_type_map WHERE ptype_key = SUBSTRING( _007.value, 1, 1 );
8884
8885         IF ptype.ptype_key IS NOT NULL THEN
8886             FOR psf IN SELECT * FROM config.marc21_physical_characteristic_subfield_map WHERE ptype_key = ptype.ptype_key LOOP
8887                 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 );
8888
8889                 IF pval.id IS NOT NULL THEN
8890                     rowid := rowid + 1;
8891                     retval.id := rowid;
8892                     retval.record := rid;
8893                     retval.ptype := ptype.ptype_key;
8894                     retval.subfield := psf.id;
8895                     retval.value := pval.id;
8896                     RETURN NEXT retval;
8897                 END IF;
8898
8899             END LOOP;
8900         END IF;
8901     END IF;
8902
8903     RETURN;
8904 END;
8905 $func$ LANGUAGE PLPGSQL;
8906
8907 DROP VIEW IF EXISTS money.open_usr_circulation_summary;
8908 DROP VIEW IF EXISTS money.open_usr_summary;
8909 DROP VIEW IF EXISTS money.open_billable_xact_summary;
8910
8911 -- The view should supply defaults for numeric (amount) columns
8912 CREATE OR REPLACE VIEW money.billable_xact_summary AS
8913     SELECT  xact.id,
8914         xact.usr,
8915         xact.xact_start,
8916         xact.xact_finish,
8917         COALESCE(credit.amount, 0.0::numeric) AS total_paid,
8918         credit.payment_ts AS last_payment_ts,
8919         credit.note AS last_payment_note,
8920         credit.payment_type AS last_payment_type,
8921         COALESCE(debit.amount, 0.0::numeric) AS total_owed,
8922         debit.billing_ts AS last_billing_ts,
8923         debit.note AS last_billing_note,
8924         debit.billing_type AS last_billing_type,
8925         COALESCE(debit.amount, 0.0::numeric) - COALESCE(credit.amount, 0.0::numeric) AS balance_owed,
8926         p.relname AS xact_type
8927       FROM  money.billable_xact xact
8928         JOIN pg_class p ON xact.tableoid = p.oid
8929         LEFT JOIN (
8930             SELECT  billing.xact,
8931                 sum(billing.amount) AS amount,
8932                 max(billing.billing_ts) AS billing_ts,
8933                 last(billing.note) AS note,
8934                 last(billing.billing_type) AS billing_type
8935               FROM  money.billing
8936               WHERE billing.voided IS FALSE
8937               GROUP BY billing.xact
8938             ) debit ON xact.id = debit.xact
8939         LEFT JOIN (
8940             SELECT  payment_view.xact,
8941                 sum(payment_view.amount) AS amount,
8942                 max(payment_view.payment_ts) AS payment_ts,
8943                 last(payment_view.note) AS note,
8944                 last(payment_view.payment_type) AS payment_type
8945               FROM  money.payment_view
8946               WHERE payment_view.voided IS FALSE
8947               GROUP BY payment_view.xact
8948             ) credit ON xact.id = credit.xact
8949       ORDER BY debit.billing_ts, credit.payment_ts;
8950
8951 CREATE OR REPLACE VIEW money.open_billable_xact_summary AS 
8952     SELECT * FROM money.billable_xact_summary_location_view
8953     WHERE xact_finish IS NULL;
8954
8955 CREATE OR REPLACE VIEW money.open_usr_circulation_summary AS
8956     SELECT 
8957         usr,
8958         SUM(total_paid) AS total_paid,
8959         SUM(total_owed) AS total_owed,
8960         SUM(balance_owed) AS balance_owed
8961     FROM  money.materialized_billable_xact_summary
8962     WHERE xact_type = 'circulation' AND xact_finish IS NULL
8963     GROUP BY usr;
8964
8965 CREATE OR REPLACE VIEW money.usr_summary AS
8966     SELECT 
8967         usr, 
8968         sum(total_paid) AS total_paid, 
8969         sum(total_owed) AS total_owed, 
8970         sum(balance_owed) AS balance_owed
8971     FROM money.materialized_billable_xact_summary
8972     GROUP BY usr;
8973
8974 CREATE OR REPLACE VIEW money.open_usr_summary AS
8975     SELECT 
8976         usr, 
8977         sum(total_paid) AS total_paid, 
8978         sum(total_owed) AS total_owed, 
8979         sum(balance_owed) AS balance_owed
8980     FROM money.materialized_billable_xact_summary
8981     WHERE xact_finish IS NULL
8982     GROUP BY usr;
8983
8984 -- 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;
8985
8986 CREATE TABLE config.biblio_fingerprint (
8987         id                      SERIAL  PRIMARY KEY,
8988         name            TEXT    NOT NULL, 
8989         xpath           TEXT    NOT NULL,
8990     first_word  BOOL    NOT NULL DEFAULT FALSE,
8991         format          TEXT    NOT NULL DEFAULT 'marcxml'
8992 );
8993
8994 INSERT INTO config.biblio_fingerprint (name, xpath, format)
8995     VALUES (
8996         'Title',
8997         '//marc:datafield[@tag="700"]/marc:subfield[@code="t"]|' ||
8998             '//marc:datafield[@tag="240"]/marc:subfield[@code="a"]|' ||
8999             '//marc:datafield[@tag="242"]/marc:subfield[@code="a"]|' ||
9000             '//marc:datafield[@tag="246"]/marc:subfield[@code="a"]|' ||
9001             '//marc:datafield[@tag="245"]/marc:subfield[@code="a"]',
9002         'marcxml'
9003     );
9004
9005 INSERT INTO config.biblio_fingerprint (name, xpath, format, first_word)
9006     VALUES (
9007         'Author',
9008         '//marc:datafield[@tag="700" and ./*[@code="t"]]/marc:subfield[@code="a"]|'
9009             '//marc:datafield[@tag="100"]/marc:subfield[@code="a"]|'
9010             '//marc:datafield[@tag="110"]/marc:subfield[@code="a"]|'
9011             '//marc:datafield[@tag="111"]/marc:subfield[@code="a"]|'
9012             '//marc:datafield[@tag="260"]/marc:subfield[@code="b"]',
9013         'marcxml',
9014         TRUE
9015     );
9016
9017 CREATE OR REPLACE FUNCTION biblio.extract_quality ( marc TEXT, best_lang TEXT, best_type TEXT ) RETURNS INT AS $func$
9018 DECLARE
9019     qual        INT;
9020     ldr         TEXT;
9021     tval        TEXT;
9022     tval_rec    RECORD;
9023     bval        TEXT;
9024     bval_rec    RECORD;
9025     type_map    RECORD;
9026     ff_pos      RECORD;
9027     ff_tag_data TEXT;
9028 BEGIN
9029
9030     IF marc IS NULL OR marc = '' THEN
9031         RETURN NULL;
9032     END IF;
9033
9034     -- First, the count of tags
9035     qual := ARRAY_UPPER(oils_xpath('*[local-name()="datafield"]', marc), 1);
9036
9037     -- now go through a bunch of pain to get the record type
9038     IF best_type IS NOT NULL THEN
9039         ldr := (oils_xpath('//*[local-name()="leader"]/text()', marc))[1];
9040
9041         IF ldr IS NOT NULL THEN
9042             SELECT * INTO tval_rec FROM config.marc21_ff_pos_map WHERE fixed_field = 'Type' LIMIT 1; -- They're all the same
9043             SELECT * INTO bval_rec FROM config.marc21_ff_pos_map WHERE fixed_field = 'BLvl' LIMIT 1; -- They're all the same
9044
9045
9046             tval := SUBSTRING( ldr, tval_rec.start_pos + 1, tval_rec.length );
9047             bval := SUBSTRING( ldr, bval_rec.start_pos + 1, bval_rec.length );
9048
9049             -- RAISE NOTICE 'type %, blvl %, ldr %', tval, bval, ldr;
9050
9051             SELECT * INTO type_map FROM config.marc21_rec_type_map WHERE type_val LIKE '%' || tval || '%' AND blvl_val LIKE '%' || bval || '%';
9052
9053             IF type_map.code IS NOT NULL THEN
9054                 IF best_type = type_map.code THEN
9055                     qual := qual + qual / 2;
9056                 END IF;
9057
9058                 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
9059                     ff_tag_data := SUBSTRING((oils_xpath('//*[@tag="' || ff_pos.tag || '"]/text()',marc))[1], ff_pos.start_pos + 1, ff_pos.length);
9060                     IF ff_tag_data = best_lang THEN
9061                             qual := qual + 100;
9062                     END IF;
9063                 END LOOP;
9064             END IF;
9065         END IF;
9066     END IF;
9067
9068     -- Now look for some quality metrics
9069     -- DCL record?
9070     IF ARRAY_UPPER(oils_xpath('//*[@tag="040"]/*[@code="a" and contains(.,"DLC")]', marc), 1) = 1 THEN
9071         qual := qual + 10;
9072     END IF;
9073
9074     -- From OCLC?
9075     IF (oils_xpath('//*[@tag="003"]/text()', marc))[1] ~* E'oclo?c' THEN
9076         qual := qual + 10;
9077     END IF;
9078
9079     RETURN qual;
9080
9081 END;
9082 $func$ LANGUAGE PLPGSQL;
9083
9084 CREATE OR REPLACE FUNCTION biblio.extract_fingerprint ( marc text ) RETURNS TEXT AS $func$
9085 DECLARE
9086     idx     config.biblio_fingerprint%ROWTYPE;
9087     xfrm        config.xml_transform%ROWTYPE;
9088     prev_xfrm   TEXT;
9089     transformed_xml TEXT;
9090     xml_node    TEXT;
9091     xml_node_list   TEXT[];
9092     raw_text    TEXT;
9093     output_text TEXT := '';
9094 BEGIN
9095
9096     IF marc IS NULL OR marc = '' THEN
9097         RETURN NULL;
9098     END IF;
9099
9100     -- Loop over the indexing entries
9101     FOR idx IN SELECT * FROM config.biblio_fingerprint ORDER BY format, id LOOP
9102
9103         SELECT INTO xfrm * from config.xml_transform WHERE name = idx.format;
9104
9105         -- See if we can skip the XSLT ... it's expensive
9106         IF prev_xfrm IS NULL OR prev_xfrm <> xfrm.name THEN
9107             -- Can't skip the transform
9108             IF xfrm.xslt <> '---' THEN
9109                 transformed_xml := oils_xslt_process(marc,xfrm.xslt);
9110             ELSE
9111                 transformed_xml := marc;
9112             END IF;
9113
9114             prev_xfrm := xfrm.name;
9115         END IF;
9116
9117         raw_text := COALESCE(
9118             naco_normalize(
9119                 ARRAY_TO_STRING(
9120                     oils_xpath(
9121                         '//text()',
9122                         (oils_xpath(
9123                             idx.xpath,
9124                             transformed_xml,
9125                             ARRAY[ARRAY[xfrm.prefix, xfrm.namespace_uri]]
9126                         ))[1]
9127                     ),
9128                     ''
9129                 )
9130             ),
9131             ''
9132         );
9133
9134         raw_text := REGEXP_REPLACE(raw_text, E'\\[.+?\\]', E'');
9135         raw_text := REGEXP_REPLACE(raw_text, E'\\mthe\\M|\\man?d?d\\M', E'', 'g'); -- arg! the pain!
9136
9137         IF idx.first_word IS TRUE THEN
9138             raw_text := REGEXP_REPLACE(raw_text, E'^(\\w+).*?$', E'\\1');
9139         END IF;
9140
9141         output_text := output_text || REGEXP_REPLACE(raw_text, E'\\s+', '', 'g');
9142
9143     END LOOP;
9144
9145     RETURN output_text;
9146
9147 END;
9148 $func$ LANGUAGE PLPGSQL;
9149
9150 -- BEFORE UPDATE OR INSERT trigger for biblio.record_entry
9151 CREATE OR REPLACE FUNCTION biblio.fingerprint_trigger () RETURNS TRIGGER AS $func$
9152 BEGIN
9153
9154     -- For TG_ARGV, first param is language (like 'eng'), second is record type (like 'BKS')
9155
9156     IF NEW.deleted IS TRUE THEN -- we don't much care, then, do we?
9157         RETURN NEW;
9158     END IF;
9159
9160     NEW.fingerprint := biblio.extract_fingerprint(NEW.marc);
9161     NEW.quality := biblio.extract_quality(NEW.marc, TG_ARGV[0], TG_ARGV[1]);
9162
9163     RETURN NEW;
9164
9165 END;
9166 $func$ LANGUAGE PLPGSQL;
9167
9168 CREATE TABLE config.internal_flag (
9169     name    TEXT    PRIMARY KEY,
9170     value   TEXT,
9171     enabled BOOL    NOT NULL DEFAULT FALSE
9172 );
9173 INSERT INTO config.internal_flag (name) VALUES ('ingest.metarecord_mapping.skip_on_insert');
9174 INSERT INTO config.internal_flag (name) VALUES ('ingest.reingest.force_on_same_marc');
9175 INSERT INTO config.internal_flag (name) VALUES ('ingest.reingest.skip_located_uri');
9176 INSERT INTO config.internal_flag (name) VALUES ('ingest.disable_located_uri');
9177 INSERT INTO config.internal_flag (name) VALUES ('ingest.disable_metabib_full_rec');
9178 INSERT INTO config.internal_flag (name) VALUES ('ingest.disable_metabib_rec_descriptor');
9179 INSERT INTO config.internal_flag (name) VALUES ('ingest.disable_metabib_field_entry');
9180 INSERT INTO config.internal_flag (name) VALUES ('ingest.disable_authority_linking');
9181
9182 CREATE TABLE authority.bib_linking (
9183     id          BIGSERIAL   PRIMARY KEY,
9184     bib         BIGINT      NOT NULL REFERENCES biblio.record_entry (id),
9185     authority   BIGINT      NOT NULL REFERENCES authority.record_entry (id)
9186 );
9187 CREATE INDEX authority_bl_bib_idx ON authority.bib_linking ( bib );
9188 CREATE UNIQUE INDEX authority_bl_bib_authority_once_idx ON authority.bib_linking ( authority, bib );
9189
9190 CREATE OR REPLACE FUNCTION public.remove_paren_substring( TEXT ) RETURNS TEXT AS $func$
9191     SELECT regexp_replace($1, $$\([^)]+\)$$, '', 'g');
9192 $func$ LANGUAGE SQL STRICT IMMUTABLE;
9193
9194 CREATE OR REPLACE FUNCTION biblio.map_authority_linking (bibid BIGINT, marc TEXT) RETURNS BIGINT AS $func$
9195     DELETE FROM authority.bib_linking WHERE bib = $1;
9196     INSERT INTO authority.bib_linking (bib, authority)
9197         SELECT  y.bib,
9198                 y.authority
9199           FROM (    SELECT  DISTINCT $1 AS bib,
9200                             BTRIM(remove_paren_substring(txt))::BIGINT AS authority
9201                       FROM  explode_array(oils_xpath('//*[@code="0"]/text()',$2)) x(txt)
9202                       WHERE BTRIM(remove_paren_substring(txt)) ~ $re$^\d+$$re$
9203                 ) y JOIN authority.record_entry r ON r.id = y.authority;
9204     SELECT $1;
9205 $func$ LANGUAGE SQL;
9206
9207 CREATE OR REPLACE FUNCTION metabib.reingest_metabib_rec_descriptor( bib_id BIGINT ) RETURNS VOID AS $func$
9208 BEGIN
9209     DELETE FROM metabib.rec_descriptor WHERE record = bib_id;
9210     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)
9211         SELECT  bib_id,
9212                 biblio.marc21_extract_fixed_field( bib_id, 'Type' ),
9213                 biblio.marc21_extract_fixed_field( bib_id, 'Form' ),
9214                 biblio.marc21_extract_fixed_field( bib_id, 'BLvl' ),
9215                 biblio.marc21_extract_fixed_field( bib_id, 'Ctrl' ),
9216                 biblio.marc21_extract_fixed_field( bib_id, 'ELvl' ),
9217                 biblio.marc21_extract_fixed_field( bib_id, 'Audn' ),
9218                 biblio.marc21_extract_fixed_field( bib_id, 'LitF' ),
9219                 biblio.marc21_extract_fixed_field( bib_id, 'TMat' ),
9220                 biblio.marc21_extract_fixed_field( bib_id, 'Desc' ),
9221                 biblio.marc21_extract_fixed_field( bib_id, 'DtSt' ),
9222                 biblio.marc21_extract_fixed_field( bib_id, 'Lang' ),
9223                 (   SELECT  v.value
9224                       FROM  biblio.marc21_physical_characteristics( bib_id) p
9225                             JOIN config.marc21_physical_characteristic_subfield_map s ON (s.id = p.subfield)
9226                             JOIN config.marc21_physical_characteristic_value_map v ON (v.id = p.value)
9227                       WHERE p.ptype = 'v' AND s.subfield = 'e'    ),
9228                 biblio.marc21_extract_fixed_field( bib_id, 'Date1'),
9229                 biblio.marc21_extract_fixed_field( bib_id, 'Date2');
9230
9231     RETURN;
9232 END;
9233 $func$ LANGUAGE PLPGSQL;
9234
9235 CREATE TABLE config.metabib_class (
9236     name    TEXT    PRIMARY KEY,
9237     label   TEXT    NOT NULL UNIQUE
9238 );
9239
9240 INSERT INTO config.metabib_class ( name, label ) VALUES ( 'keyword', oils_i18n_gettext('keyword', 'Keyword', 'cmc', 'label') );
9241 INSERT INTO config.metabib_class ( name, label ) VALUES ( 'title', oils_i18n_gettext('title', 'Title', 'cmc', 'label') );
9242 INSERT INTO config.metabib_class ( name, label ) VALUES ( 'author', oils_i18n_gettext('author', 'Author', 'cmc', 'label') );
9243 INSERT INTO config.metabib_class ( name, label ) VALUES ( 'subject', oils_i18n_gettext('subject', 'Subject', 'cmc', 'label') );
9244 INSERT INTO config.metabib_class ( name, label ) VALUES ( 'series', oils_i18n_gettext('series', 'Series', 'cmc', 'label') );
9245
9246 CREATE TABLE metabib.facet_entry (
9247         id              BIGSERIAL       PRIMARY KEY,
9248         source          BIGINT          NOT NULL,
9249         field           INT             NOT NULL,
9250         value           TEXT            NOT NULL
9251 );
9252
9253 CREATE OR REPLACE FUNCTION metabib.reingest_metabib_field_entries( bib_id BIGINT ) RETURNS VOID AS $func$
9254 DECLARE
9255     fclass          RECORD;
9256     ind_data        metabib.field_entry_template%ROWTYPE;
9257 BEGIN
9258     FOR fclass IN SELECT * FROM config.metabib_class LOOP
9259         -- RAISE NOTICE 'Emptying out %', fclass.name;
9260         EXECUTE $$DELETE FROM metabib.$$ || fclass.name || $$_field_entry WHERE source = $$ || bib_id;
9261     END LOOP;
9262
9263     DELETE FROM metabib.facet_entry WHERE source = bib_id;
9264
9265     FOR ind_data IN SELECT * FROM biblio.extract_metabib_field_entry( bib_id ) LOOP
9266         IF ind_data.field < 0 THEN
9267             ind_data.field = -1 * ind_data.field;
9268             INSERT INTO metabib.facet_entry (field, source, value)
9269                 VALUES (ind_data.field, ind_data.source, ind_data.value);
9270         ELSE
9271             EXECUTE $$
9272                 INSERT INTO metabib.$$ || ind_data.field_class || $$_field_entry (field, source, value)
9273                     VALUES ($$ ||
9274                         quote_literal(ind_data.field) || $$, $$ ||
9275                         quote_literal(ind_data.source) || $$, $$ ||
9276                         quote_literal(ind_data.value) ||
9277                     $$);$$;
9278         END IF;
9279
9280     END LOOP;
9281
9282     RETURN;
9283 END;
9284 $func$ LANGUAGE PLPGSQL;
9285
9286 CREATE OR REPLACE FUNCTION biblio.extract_located_uris( bib_id BIGINT, marcxml TEXT, editor_id INT ) RETURNS VOID AS $func$
9287 DECLARE
9288     uris            TEXT[];
9289     uri_xml         TEXT;
9290     uri_label       TEXT;
9291     uri_href        TEXT;
9292     uri_use         TEXT;
9293     uri_owner       TEXT;
9294     uri_owner_id    INT;
9295     uri_id          INT;
9296     uri_cn_id       INT;
9297     uri_map_id      INT;
9298 BEGIN
9299
9300     uris := oils_xpath('//*[@tag="856" and (@ind1="4" or @ind1="1") and (@ind2="0" or @ind2="1")]',marcxml);
9301     IF ARRAY_UPPER(uris,1) > 0 THEN
9302         FOR i IN 1 .. ARRAY_UPPER(uris, 1) LOOP
9303             -- First we pull info out of the 856
9304             uri_xml     := uris[i];
9305
9306             uri_href    := (oils_xpath('//*[@code="u"]/text()',uri_xml))[1];
9307             CONTINUE WHEN uri_href IS NULL;
9308
9309             uri_label   := (oils_xpath('//*[@code="y"]/text()|//*[@code="3"]/text()|//*[@code="u"]/text()',uri_xml))[1];
9310             CONTINUE WHEN uri_label IS NULL;
9311
9312             uri_owner   := (oils_xpath('//*[@code="9"]/text()|//*[@code="w"]/text()|//*[@code="n"]/text()',uri_xml))[1];
9313             CONTINUE WHEN uri_owner IS NULL;
9314
9315             uri_use     := (oils_xpath('//*[@code="z"]/text()|//*[@code="2"]/text()|//*[@code="n"]/text()',uri_xml))[1];
9316
9317             uri_owner := REGEXP_REPLACE(uri_owner, $re$^.*?\((\w+)\).*$$re$, E'\\1');
9318
9319             SELECT id INTO uri_owner_id FROM actor.org_unit WHERE shortname = uri_owner;
9320             CONTINUE WHEN NOT FOUND;
9321
9322             -- now we look for a matching uri
9323             SELECT id INTO uri_id FROM asset.uri WHERE label = uri_label AND href = uri_href AND use_restriction = uri_use AND active;
9324             IF NOT FOUND THEN -- create one
9325                 INSERT INTO asset.uri (label, href, use_restriction) VALUES (uri_label, uri_href, uri_use);
9326                 SELECT id INTO uri_id FROM asset.uri WHERE label = uri_label AND href = uri_href AND use_restriction = uri_use AND active;
9327             END IF;
9328
9329             -- we need a call number to link through
9330             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;
9331             IF NOT FOUND THEN
9332                 INSERT INTO asset.call_number (owning_lib, record, create_date, edit_date, creator, editor, label)
9333                     VALUES (uri_owner_id, bib_id, 'now', 'now', editor_id, editor_id, '##URI##');
9334                 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;
9335             END IF;
9336
9337             -- now, link them if they're not already
9338             SELECT id INTO uri_map_id FROM asset.uri_call_number_map WHERE call_number = uri_cn_id AND uri = uri_id;
9339             IF NOT FOUND THEN
9340                 INSERT INTO asset.uri_call_number_map (call_number, uri) VALUES (uri_cn_id, uri_id);
9341             END IF;
9342
9343         END LOOP;
9344     END IF;
9345
9346     RETURN;
9347 END;
9348 $func$ LANGUAGE PLPGSQL;
9349
9350 CREATE OR REPLACE FUNCTION metabib.remap_metarecord_for_bib( bib_id BIGINT, fp TEXT ) RETURNS BIGINT AS $func$
9351 DECLARE
9352     source_count    INT;
9353     old_mr          BIGINT;
9354     tmp_mr          metabib.metarecord%ROWTYPE;
9355     deleted_mrs     BIGINT[];
9356 BEGIN
9357
9358     DELETE FROM metabib.metarecord_source_map WHERE source = bib_id; -- Rid ourselves of the search-estimate-killing linkage
9359
9360     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
9361
9362         IF old_mr IS NULL AND fp = tmp_mr.fingerprint THEN -- Find the first fingerprint-matching
9363             old_mr := tmp_mr.id;
9364         ELSE
9365             SELECT COUNT(*) INTO source_count FROM metabib.metarecord_source_map WHERE metarecord = tmp_mr.id;
9366             IF source_count = 0 THEN -- No other records
9367                 deleted_mrs := ARRAY_APPEND(deleted_mrs, tmp_mr.id);
9368                 DELETE FROM metabib.metarecord WHERE id = tmp_mr.id;
9369             END IF;
9370         END IF;
9371
9372     END LOOP;
9373
9374     IF old_mr IS NULL THEN -- we found no suitable, preexisting MR based on old source maps
9375         SELECT id INTO old_mr FROM metabib.metarecord WHERE fingerprint = fp; -- is there one for our current fingerprint?
9376         IF old_mr IS NULL THEN -- nope, create one and grab its id
9377             INSERT INTO metabib.metarecord ( fingerprint, master_record ) VALUES ( fp, bib_id );
9378             SELECT id INTO old_mr FROM metabib.metarecord WHERE fingerprint = fp;
9379         ELSE -- indeed there is. update it with a null cache and recalcualated master record
9380             UPDATE  metabib.metarecord
9381               SET   mods = NULL,
9382                     master_record = ( SELECT id FROM biblio.record_entry WHERE fingerprint = fp ORDER BY quality DESC LIMIT 1)
9383               WHERE id = old_mr;
9384         END IF;
9385     ELSE -- there was one we already attached to, update its mods cache and master_record
9386         UPDATE  metabib.metarecord
9387           SET   mods = NULL,
9388                 master_record = ( SELECT id FROM biblio.record_entry WHERE fingerprint = fp ORDER BY quality DESC LIMIT 1)
9389           WHERE id = old_mr;
9390     END IF;
9391
9392     INSERT INTO metabib.metarecord_source_map (metarecord, source) VALUES (old_mr, bib_id); -- new source mapping
9393
9394     IF ARRAY_UPPER(deleted_mrs,1) > 0 THEN
9395         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
9396     END IF;
9397
9398     RETURN old_mr;
9399
9400 END;
9401 $func$ LANGUAGE PLPGSQL;
9402
9403 CREATE OR REPLACE FUNCTION metabib.reingest_metabib_full_rec( bib_id BIGINT ) RETURNS VOID AS $func$
9404 BEGIN
9405     DELETE FROM metabib.real_full_rec WHERE record = bib_id;
9406     INSERT INTO metabib.real_full_rec (record, tag, ind1, ind2, subfield, value)
9407         SELECT record, tag, ind1, ind2, subfield, value FROM biblio.flatten_marc( bib_id );
9408
9409     RETURN;
9410 END;
9411 $func$ LANGUAGE PLPGSQL;
9412
9413 -- AFTER UPDATE OR INSERT trigger for biblio.record_entry
9414 CREATE OR REPLACE FUNCTION biblio.indexing_ingest_or_delete () RETURNS TRIGGER AS $func$
9415 BEGIN
9416
9417     IF NEW.deleted IS TRUE THEN -- If this bib is deleted
9418         DELETE FROM metabib.metarecord_source_map WHERE source = NEW.id; -- Rid ourselves of the search-estimate-killing linkage
9419         DELETE FROM authority.bib_linking WHERE bib = NEW.id; -- Avoid updating fields in bibs that are no longer visible
9420         RETURN NEW; -- and we're done
9421     END IF;
9422
9423     IF TG_OP = 'UPDATE' THEN -- re-ingest?
9424         PERFORM * FROM config.internal_flag WHERE name = 'ingest.reingest.force_on_same_marc' AND enabled;
9425
9426         IF NOT FOUND AND OLD.marc = NEW.marc THEN -- don't do anything if the MARC didn't change
9427             RETURN NEW;
9428         END IF;
9429     END IF;
9430
9431     -- Record authority linking
9432     PERFORM * FROM config.internal_flag WHERE name = 'ingest.disable_authority_linking' AND enabled;
9433     IF NOT FOUND THEN
9434         PERFORM biblio.map_authority_linking( NEW.id, NEW.marc );
9435     END IF;
9436
9437     -- Flatten and insert the mfr data
9438     PERFORM * FROM config.internal_flag WHERE name = 'ingest.disable_metabib_full_rec' AND enabled;
9439     IF NOT FOUND THEN
9440         PERFORM metabib.reingest_metabib_full_rec(NEW.id);
9441         PERFORM * FROM config.internal_flag WHERE name = 'ingest.disable_metabib_rec_descriptor' AND enabled;
9442         IF NOT FOUND THEN
9443             PERFORM metabib.reingest_metabib_rec_descriptor(NEW.id);
9444         END IF;
9445     END IF;
9446
9447     -- Gather and insert the field entry data
9448     PERFORM metabib.reingest_metabib_field_entries(NEW.id);
9449
9450     -- Located URI magic
9451     IF TG_OP = 'INSERT' THEN
9452         PERFORM * FROM config.internal_flag WHERE name = 'ingest.disable_located_uri' AND enabled;
9453         IF NOT FOUND THEN
9454             PERFORM biblio.extract_located_uris( NEW.id, NEW.marc, NEW.editor );
9455         END IF;
9456     ELSE
9457         PERFORM * FROM config.internal_flag WHERE name = 'ingest.disable_located_uri' AND enabled;
9458         IF NOT FOUND THEN
9459             PERFORM biblio.extract_located_uris( NEW.id, NEW.marc, NEW.editor );
9460         END IF;
9461     END IF;
9462
9463     -- (re)map metarecord-bib linking
9464     IF TG_OP = 'INSERT' THEN -- if not deleted and performing an insert, check for the flag
9465         PERFORM * FROM config.internal_flag WHERE name = 'ingest.metarecord_mapping.skip_on_insert' AND enabled;
9466         IF NOT FOUND THEN
9467             PERFORM metabib.remap_metarecord_for_bib( NEW.id, NEW.fingerprint );
9468         END IF;
9469     ELSE -- we're doing an update, and we're not deleted, remap
9470         PERFORM metabib.remap_metarecord_for_bib( NEW.id, NEW.fingerprint );
9471     END IF;
9472
9473     RETURN NEW;
9474 END;
9475 $func$ LANGUAGE PLPGSQL;
9476
9477 CREATE OR REPLACE FUNCTION reporter.simple_rec_trigger () RETURNS TRIGGER AS $func$
9478 BEGIN
9479     IF TG_OP = 'DELETE' THEN
9480         PERFORM reporter.simple_rec_delete(NEW.id);
9481     ELSE
9482         PERFORM reporter.simple_rec_update(NEW.id);
9483     END IF;
9484
9485     RETURN NEW;
9486 END;
9487 $func$ LANGUAGE PLPGSQL;
9488
9489 CREATE TRIGGER fingerprint_tgr BEFORE INSERT OR UPDATE ON biblio.record_entry FOR EACH ROW EXECUTE PROCEDURE biblio.fingerprint_trigger ('eng','BKS');
9490 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 ();
9491 CREATE TRIGGER bbb_simple_rec_trigger AFTER INSERT OR UPDATE ON biblio.record_entry FOR EACH ROW EXECUTE PROCEDURE reporter.simple_rec_trigger ();
9492
9493 DROP TRIGGER IF EXISTS zzz_update_materialized_simple_rec_delete_tgr ON biblio.record_entry;
9494
9495 CREATE OR REPLACE FUNCTION oils_xpath_table ( key TEXT, document_field TEXT, relation_name TEXT, xpaths TEXT, criteria TEXT )
9496 RETURNS SETOF RECORD AS $func$
9497 DECLARE
9498     xpath_list  TEXT[];
9499     select_list TEXT[];
9500     where_list  TEXT[];
9501     q           TEXT;
9502     out_record  RECORD;
9503     empty_test  RECORD;
9504 BEGIN
9505     xpath_list := STRING_TO_ARRAY( xpaths, '|' );
9506
9507     select_list := ARRAY_APPEND( select_list, key || '::INT AS key' );
9508
9509     FOR i IN 1 .. ARRAY_UPPER(xpath_list,1) LOOP
9510         select_list := ARRAY_APPEND(
9511             select_list,
9512             $sel$
9513             EXPLODE_ARRAY(
9514                 COALESCE(
9515                     NULLIF(
9516                         oils_xpath(
9517                             $sel$ ||
9518                                 quote_literal(
9519                                     CASE
9520                                         WHEN xpath_list[i] ~ $re$/[^/[]*@[^/]+$$re$ OR xpath_list[i] ~ $re$text\(\)$$re$ THEN xpath_list[i]
9521                                         ELSE xpath_list[i] || '//text()'
9522                                     END
9523                                 ) ||
9524                             $sel$,
9525                             $sel$ || document_field || $sel$
9526                         ),
9527                        '{}'::TEXT[]
9528                     ),
9529                     '{NULL}'::TEXT[]
9530                 )
9531             ) AS c_$sel$ || i
9532         );
9533         where_list := ARRAY_APPEND(
9534             where_list,
9535             'c_' || i || ' IS NOT NULL'
9536         );
9537     END LOOP;
9538
9539     q := $q$
9540 SELECT * FROM (
9541     SELECT $q$ || ARRAY_TO_STRING( select_list, ', ' ) || $q$ FROM $q$ || relation_name || $q$ WHERE ($q$ || criteria || $q$)
9542 )x WHERE $q$ || ARRAY_TO_STRING( where_list, ' AND ' );
9543     -- RAISE NOTICE 'query: %', q;
9544
9545     FOR out_record IN EXECUTE q LOOP
9546         RETURN NEXT out_record;
9547     END LOOP;
9548
9549     RETURN;
9550 END;
9551 $func$ LANGUAGE PLPGSQL;
9552
9553 CREATE OR REPLACE FUNCTION vandelay.ingest_items ( import_id BIGINT, attr_def_id BIGINT ) RETURNS SETOF vandelay.import_item AS $$
9554 DECLARE
9555
9556     owning_lib      TEXT;
9557     circ_lib        TEXT;
9558     call_number     TEXT;
9559     copy_number     TEXT;
9560     status          TEXT;
9561     location        TEXT;
9562     circulate       TEXT;
9563     deposit         TEXT;
9564     deposit_amount  TEXT;
9565     ref             TEXT;
9566     holdable        TEXT;
9567     price           TEXT;
9568     barcode         TEXT;
9569     circ_modifier   TEXT;
9570     circ_as_type    TEXT;
9571     alert_message   TEXT;
9572     opac_visible    TEXT;
9573     pub_note        TEXT;
9574     priv_note       TEXT;
9575
9576     attr_def        RECORD;
9577     tmp_attr_set    RECORD;
9578     attr_set        vandelay.import_item%ROWTYPE;
9579
9580     xpath           TEXT;
9581
9582 BEGIN
9583
9584     SELECT * INTO attr_def FROM vandelay.import_item_attr_definition WHERE id = attr_def_id;
9585
9586     IF FOUND THEN
9587
9588         attr_set.definition := attr_def.id; 
9589     
9590         -- Build the combined XPath
9591     
9592         owning_lib :=
9593             CASE
9594                 WHEN attr_def.owning_lib IS NULL THEN 'null()'
9595                 WHEN LENGTH( attr_def.owning_lib ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.owning_lib || '"]'
9596                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.owning_lib
9597             END;
9598     
9599         circ_lib :=
9600             CASE
9601                 WHEN attr_def.circ_lib IS NULL THEN 'null()'
9602                 WHEN LENGTH( attr_def.circ_lib ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.circ_lib || '"]'
9603                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.circ_lib
9604             END;
9605     
9606         call_number :=
9607             CASE
9608                 WHEN attr_def.call_number IS NULL THEN 'null()'
9609                 WHEN LENGTH( attr_def.call_number ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.call_number || '"]'
9610                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.call_number
9611             END;
9612     
9613         copy_number :=
9614             CASE
9615                 WHEN attr_def.copy_number IS NULL THEN 'null()'
9616                 WHEN LENGTH( attr_def.copy_number ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.copy_number || '"]'
9617                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.copy_number
9618             END;
9619     
9620         status :=
9621             CASE
9622                 WHEN attr_def.status IS NULL THEN 'null()'
9623                 WHEN LENGTH( attr_def.status ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.status || '"]'
9624                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.status
9625             END;
9626     
9627         location :=
9628             CASE
9629                 WHEN attr_def.location IS NULL THEN 'null()'
9630                 WHEN LENGTH( attr_def.location ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.location || '"]'
9631                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.location
9632             END;
9633     
9634         circulate :=
9635             CASE
9636                 WHEN attr_def.circulate IS NULL THEN 'null()'
9637                 WHEN LENGTH( attr_def.circulate ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.circulate || '"]'
9638                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.circulate
9639             END;
9640     
9641         deposit :=
9642             CASE
9643                 WHEN attr_def.deposit IS NULL THEN 'null()'
9644                 WHEN LENGTH( attr_def.deposit ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.deposit || '"]'
9645                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.deposit
9646             END;
9647     
9648         deposit_amount :=
9649             CASE
9650                 WHEN attr_def.deposit_amount IS NULL THEN 'null()'
9651                 WHEN LENGTH( attr_def.deposit_amount ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.deposit_amount || '"]'
9652                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.deposit_amount
9653             END;
9654     
9655         ref :=
9656             CASE
9657                 WHEN attr_def.ref IS NULL THEN 'null()'
9658                 WHEN LENGTH( attr_def.ref ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.ref || '"]'
9659                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.ref
9660             END;
9661     
9662         holdable :=
9663             CASE
9664                 WHEN attr_def.holdable IS NULL THEN 'null()'
9665                 WHEN LENGTH( attr_def.holdable ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.holdable || '"]'
9666                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.holdable
9667             END;
9668     
9669         price :=
9670             CASE
9671                 WHEN attr_def.price IS NULL THEN 'null()'
9672                 WHEN LENGTH( attr_def.price ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.price || '"]'
9673                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.price
9674             END;
9675     
9676         barcode :=
9677             CASE
9678                 WHEN attr_def.barcode IS NULL THEN 'null()'
9679                 WHEN LENGTH( attr_def.barcode ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.barcode || '"]'
9680                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.barcode
9681             END;
9682     
9683         circ_modifier :=
9684             CASE
9685                 WHEN attr_def.circ_modifier IS NULL THEN 'null()'
9686                 WHEN LENGTH( attr_def.circ_modifier ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.circ_modifier || '"]'
9687                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.circ_modifier
9688             END;
9689     
9690         circ_as_type :=
9691             CASE
9692                 WHEN attr_def.circ_as_type IS NULL THEN 'null()'
9693                 WHEN LENGTH( attr_def.circ_as_type ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.circ_as_type || '"]'
9694                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.circ_as_type
9695             END;
9696     
9697         alert_message :=
9698             CASE
9699                 WHEN attr_def.alert_message IS NULL THEN 'null()'
9700                 WHEN LENGTH( attr_def.alert_message ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.alert_message || '"]'
9701                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.alert_message
9702             END;
9703     
9704         opac_visible :=
9705             CASE
9706                 WHEN attr_def.opac_visible IS NULL THEN 'null()'
9707                 WHEN LENGTH( attr_def.opac_visible ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.opac_visible || '"]'
9708                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.opac_visible
9709             END;
9710
9711         pub_note :=
9712             CASE
9713                 WHEN attr_def.pub_note IS NULL THEN 'null()'
9714                 WHEN LENGTH( attr_def.pub_note ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.pub_note || '"]'
9715                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.pub_note
9716             END;
9717         priv_note :=
9718             CASE
9719                 WHEN attr_def.priv_note IS NULL THEN 'null()'
9720                 WHEN LENGTH( attr_def.priv_note ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.priv_note || '"]'
9721                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.priv_note
9722             END;
9723     
9724     
9725         xpath := 
9726             owning_lib      || '|' || 
9727             circ_lib        || '|' || 
9728             call_number     || '|' || 
9729             copy_number     || '|' || 
9730             status          || '|' || 
9731             location        || '|' || 
9732             circulate       || '|' || 
9733             deposit         || '|' || 
9734             deposit_amount  || '|' || 
9735             ref             || '|' || 
9736             holdable        || '|' || 
9737             price           || '|' || 
9738             barcode         || '|' || 
9739             circ_modifier   || '|' || 
9740             circ_as_type    || '|' || 
9741             alert_message   || '|' || 
9742             pub_note        || '|' || 
9743             priv_note       || '|' || 
9744             opac_visible;
9745
9746         -- RAISE NOTICE 'XPath: %', xpath;
9747         
9748         FOR tmp_attr_set IN
9749                 SELECT  *
9750                   FROM  oils_xpath_table( 'id', 'marc', 'vandelay.queued_bib_record', xpath, 'id = ' || import_id )
9751                             AS t( id INT, ol TEXT, clib TEXT, cn TEXT, cnum TEXT, cs TEXT, cl TEXT, circ TEXT,
9752                                   dep TEXT, dep_amount TEXT, r TEXT, hold TEXT, pr TEXT, bc TEXT, circ_mod TEXT,
9753                                   circ_as TEXT, amessage TEXT, note TEXT, pnote TEXT, opac_vis TEXT )
9754         LOOP
9755     
9756             tmp_attr_set.pr = REGEXP_REPLACE(tmp_attr_set.pr, E'[^0-9\\.]', '', 'g');
9757             tmp_attr_set.dep_amount = REGEXP_REPLACE(tmp_attr_set.dep_amount, E'[^0-9\\.]', '', 'g');
9758
9759             tmp_attr_set.pr := NULLIF( tmp_attr_set.pr, '' );
9760             tmp_attr_set.dep_amount := NULLIF( tmp_attr_set.dep_amount, '' );
9761     
9762             SELECT id INTO attr_set.owning_lib FROM actor.org_unit WHERE shortname = UPPER(tmp_attr_set.ol); -- INT
9763             SELECT id INTO attr_set.circ_lib FROM actor.org_unit WHERE shortname = UPPER(tmp_attr_set.clib); -- INT
9764             SELECT id INTO attr_set.status FROM config.copy_status WHERE LOWER(name) = LOWER(tmp_attr_set.cs); -- INT
9765     
9766             SELECT  id INTO attr_set.location
9767               FROM  asset.copy_location
9768               WHERE LOWER(name) = LOWER(tmp_attr_set.cl)
9769                     AND asset.copy_location.owning_lib = COALESCE(attr_set.owning_lib, attr_set.circ_lib); -- INT
9770     
9771             attr_set.circulate      :=
9772                 LOWER( SUBSTRING( tmp_attr_set.circ, 1, 1)) IN ('t','y','1')
9773                 OR LOWER(tmp_attr_set.circ) = 'circulating'; -- BOOL
9774
9775             attr_set.deposit        :=
9776                 LOWER( SUBSTRING( tmp_attr_set.dep, 1, 1 ) ) IN ('t','y','1')
9777                 OR LOWER(tmp_attr_set.dep) = 'deposit'; -- BOOL
9778
9779             attr_set.holdable       :=
9780                 LOWER( SUBSTRING( tmp_attr_set.hold, 1, 1 ) ) IN ('t','y','1')
9781                 OR LOWER(tmp_attr_set.hold) = 'holdable'; -- BOOL
9782
9783             attr_set.opac_visible   :=
9784                 LOWER( SUBSTRING( tmp_attr_set.opac_vis, 1, 1 ) ) IN ('t','y','1')
9785                 OR LOWER(tmp_attr_set.opac_vis) = 'visible'; -- BOOL
9786
9787             attr_set.ref            :=
9788                 LOWER( SUBSTRING( tmp_attr_set.r, 1, 1 ) ) IN ('t','y','1')
9789                 OR LOWER(tmp_attr_set.r) = 'reference'; -- BOOL
9790     
9791             attr_set.copy_number    := tmp_attr_set.cnum::INT; -- INT,
9792             attr_set.deposit_amount := tmp_attr_set.dep_amount::NUMERIC(6,2); -- NUMERIC(6,2),
9793             attr_set.price          := tmp_attr_set.pr::NUMERIC(8,2); -- NUMERIC(8,2),
9794     
9795             attr_set.call_number    := tmp_attr_set.cn; -- TEXT
9796             attr_set.barcode        := tmp_attr_set.bc; -- TEXT,
9797             attr_set.circ_modifier  := tmp_attr_set.circ_mod; -- TEXT,
9798             attr_set.circ_as_type   := tmp_attr_set.circ_as; -- TEXT,
9799             attr_set.alert_message  := tmp_attr_set.amessage; -- TEXT,
9800             attr_set.pub_note       := tmp_attr_set.note; -- TEXT,
9801             attr_set.priv_note      := tmp_attr_set.pnote; -- TEXT,
9802             attr_set.alert_message  := tmp_attr_set.amessage; -- TEXT,
9803     
9804             RETURN NEXT attr_set;
9805     
9806         END LOOP;
9807     
9808     END IF;
9809
9810     RETURN;
9811
9812 END;
9813 $$ LANGUAGE PLPGSQL;
9814
9815 CREATE OR REPLACE FUNCTION vandelay.ingest_bib_items ( ) RETURNS TRIGGER AS $func$
9816 DECLARE
9817     attr_def    BIGINT;
9818     item_data   vandelay.import_item%ROWTYPE;
9819 BEGIN
9820
9821     SELECT item_attr_def INTO attr_def FROM vandelay.bib_queue WHERE id = NEW.queue;
9822
9823     FOR item_data IN SELECT * FROM vandelay.ingest_items( NEW.id::BIGINT, attr_def ) LOOP
9824         INSERT INTO vandelay.import_item (
9825             record,
9826             definition,
9827             owning_lib,
9828             circ_lib,
9829             call_number,
9830             copy_number,
9831             status,
9832             location,
9833             circulate,
9834             deposit,
9835             deposit_amount,
9836             ref,
9837             holdable,
9838             price,
9839             barcode,
9840             circ_modifier,
9841             circ_as_type,
9842             alert_message,
9843             pub_note,
9844             priv_note,
9845             opac_visible
9846         ) VALUES (
9847             NEW.id,
9848             item_data.definition,
9849             item_data.owning_lib,
9850             item_data.circ_lib,
9851             item_data.call_number,
9852             item_data.copy_number,
9853             item_data.status,
9854             item_data.location,
9855             item_data.circulate,
9856             item_data.deposit,
9857             item_data.deposit_amount,
9858             item_data.ref,
9859             item_data.holdable,
9860             item_data.price,
9861             item_data.barcode,
9862             item_data.circ_modifier,
9863             item_data.circ_as_type,
9864             item_data.alert_message,
9865             item_data.pub_note,
9866             item_data.priv_note,
9867             item_data.opac_visible
9868         );
9869     END LOOP;
9870
9871     RETURN NULL;
9872 END;
9873 $func$ LANGUAGE PLPGSQL;
9874
9875 CREATE OR REPLACE FUNCTION acq.create_acq_seq     ( sch TEXT, tbl TEXT ) RETURNS BOOL AS $creator$
9876 BEGIN
9877     EXECUTE $$
9878         CREATE SEQUENCE acq.$$ || sch || $$_$$ || tbl || $$_pkey_seq;
9879     $$;
9880         RETURN TRUE;
9881 END;
9882 $creator$ LANGUAGE 'plpgsql';
9883
9884 CREATE OR REPLACE FUNCTION acq.create_acq_history ( sch TEXT, tbl TEXT ) RETURNS BOOL AS $creator$
9885 BEGIN
9886     EXECUTE $$
9887         CREATE TABLE acq.$$ || sch || $$_$$ || tbl || $$_history (
9888             audit_id    BIGINT                          PRIMARY KEY,
9889             audit_time  TIMESTAMP WITH TIME ZONE        NOT NULL,
9890             audit_action        TEXT                            NOT NULL,
9891             LIKE $$ || sch || $$.$$ || tbl || $$
9892         );
9893     $$;
9894         RETURN TRUE;
9895 END;
9896 $creator$ LANGUAGE 'plpgsql';
9897
9898 CREATE OR REPLACE FUNCTION acq.create_acq_func    ( sch TEXT, tbl TEXT ) RETURNS BOOL AS $creator$
9899 BEGIN
9900     EXECUTE $$
9901         CREATE OR REPLACE FUNCTION acq.audit_$$ || sch || $$_$$ || tbl || $$_func ()
9902         RETURNS TRIGGER AS $func$
9903         BEGIN
9904             INSERT INTO acq.$$ || sch || $$_$$ || tbl || $$_history
9905                 SELECT  nextval('acq.$$ || sch || $$_$$ || tbl || $$_pkey_seq'),
9906                     now(),
9907                     SUBSTR(TG_OP,1,1),
9908                     OLD.*;
9909             RETURN NULL;
9910         END;
9911         $func$ LANGUAGE 'plpgsql';
9912     $$;
9913         RETURN TRUE;
9914 END;
9915 $creator$ LANGUAGE 'plpgsql';
9916
9917 CREATE OR REPLACE FUNCTION acq.create_acq_update_trigger ( sch TEXT, tbl TEXT ) RETURNS BOOL AS $creator$
9918 BEGIN
9919     EXECUTE $$
9920         CREATE TRIGGER audit_$$ || sch || $$_$$ || tbl || $$_update_trigger
9921             AFTER UPDATE OR DELETE ON $$ || sch || $$.$$ || tbl || $$ FOR EACH ROW
9922             EXECUTE PROCEDURE acq.audit_$$ || sch || $$_$$ || tbl || $$_func ();
9923     $$;
9924         RETURN TRUE;
9925 END;
9926 $creator$ LANGUAGE 'plpgsql';
9927
9928 CREATE OR REPLACE FUNCTION acq.create_acq_lifecycle     ( sch TEXT, tbl TEXT ) RETURNS BOOL AS $creator$
9929 BEGIN
9930     EXECUTE $$
9931         CREATE OR REPLACE VIEW acq.$$ || sch || $$_$$ || tbl || $$_lifecycle AS
9932             SELECT      -1, now() as audit_time, '-' as audit_action, *
9933               FROM      $$ || sch || $$.$$ || tbl || $$
9934                 UNION ALL
9935             SELECT      *
9936               FROM      acq.$$ || sch || $$_$$ || tbl || $$_history;
9937     $$;
9938         RETURN TRUE;
9939 END;
9940 $creator$ LANGUAGE 'plpgsql';
9941
9942 -- The main event
9943
9944 CREATE OR REPLACE FUNCTION acq.create_acq_auditor ( sch TEXT, tbl TEXT ) RETURNS BOOL AS $creator$
9945 BEGIN
9946     PERFORM acq.create_acq_seq(sch, tbl);
9947     PERFORM acq.create_acq_history(sch, tbl);
9948     PERFORM acq.create_acq_func(sch, tbl);
9949     PERFORM acq.create_acq_update_trigger(sch, tbl);
9950     PERFORM acq.create_acq_lifecycle(sch, tbl);
9951     RETURN TRUE;
9952 END;
9953 $creator$ LANGUAGE 'plpgsql';
9954
9955 SELECT acq.create_acq_auditor ( 'acq', 'purchase_order' );
9956 CREATE INDEX acq_po_hist_id_idx            ON acq.acq_purchase_order_history( id );
9957
9958 SELECT acq.create_acq_auditor ( 'acq', 'lineitem' );
9959 CREATE INDEX acq_lineitem_hist_id_idx            ON acq.acq_lineitem_history( id );
9960
9961 CREATE OR REPLACE VIEW acq.fund_debit_total AS
9962     SELECT  fund.id AS fund,
9963             fund_debit.encumbrance AS encumbrance,
9964             SUM( COALESCE( fund_debit.amount, 0 ) ) AS amount
9965       FROM acq.fund AS fund
9966                         LEFT JOIN acq.fund_debit AS fund_debit
9967                                 ON ( fund.id = fund_debit.fund )
9968       GROUP BY 1,2;
9969
9970 CREATE TABLE acq.debit_attribution (
9971         id                     INT         NOT NULL PRIMARY KEY,
9972         fund_debit             INT         NOT NULL
9973                                            REFERENCES acq.fund_debit
9974                                            DEFERRABLE INITIALLY DEFERRED,
9975     debit_amount           NUMERIC     NOT NULL,
9976         funding_source_credit  INT         REFERENCES acq.funding_source_credit
9977                                            DEFERRABLE INITIALLY DEFERRED,
9978     credit_amount          NUMERIC
9979 );
9980
9981 CREATE INDEX acq_attribution_debit_idx
9982         ON acq.debit_attribution( fund_debit );
9983
9984 CREATE INDEX acq_attribution_credit_idx
9985         ON acq.debit_attribution( funding_source_credit );
9986
9987 CREATE OR REPLACE FUNCTION acq.attribute_debits() RETURNS VOID AS $$
9988 /*
9989 Function to attribute expenditures and encumbrances to funding source credits,
9990 and thereby to funding sources.
9991
9992 Read the debits in chonological order, attributing each one to one or
9993 more funding source credits.  Constraints:
9994
9995 1. Don't attribute more to a credit than the amount of the credit.
9996
9997 2. For a given fund, don't attribute more to a funding source than the
9998 source has allocated to that fund.
9999
10000 3. Attribute debits to credits with deadlines before attributing them to
10001 credits without deadlines.  Otherwise attribute to the earliest credits
10002 first, based on the deadline date when present, or on the effective date
10003 when there is no deadline.  Use funding_source_credit.id as a tie-breaker.
10004 This ordering is defined by an ORDER BY clause on the view
10005 acq.ordered_funding_source_credit.
10006
10007 Start by truncating the table acq.debit_attribution.  Then insert a row
10008 into that table for each attribution.  If a debit cannot be fully
10009 attributed, insert a row for the unattributable balance, with the 
10010 funding_source_credit and credit_amount columns NULL.
10011 */
10012 DECLARE
10013         curr_fund_source_bal RECORD;
10014         seqno                INT;     -- sequence num for credits applicable to a fund
10015         fund_credit          RECORD;  -- current row in temp t_fund_credit table
10016         fc                   RECORD;  -- used for loading t_fund_credit table
10017         sc                   RECORD;  -- used for loading t_fund_credit table
10018         --
10019         -- Used exclusively in the main loop:
10020         --
10021         deb                 RECORD;   -- current row from acq.fund_debit table
10022         curr_credit_bal     RECORD;   -- current row from temp t_credit table
10023         debit_balance       NUMERIC;  -- amount left to attribute for current debit
10024         conv_debit_balance  NUMERIC;  -- debit balance in currency of the fund
10025         attr_amount         NUMERIC;  -- amount being attributed, in currency of debit
10026         conv_attr_amount    NUMERIC;  -- amount being attributed, in currency of source
10027         conv_cred_balance   NUMERIC;  -- credit_balance in the currency of the fund
10028         conv_alloc_balance  NUMERIC;  -- allocated balance in the currency of the fund
10029         attrib_count        INT;      -- populates id of acq.debit_attribution
10030 BEGIN
10031         --
10032         -- Load a temporary table.  For each combination of fund and funding source,
10033         -- load an entry with the total amount allocated to that fund by that source.
10034         -- This sum may reflect transfers as well as original allocations.  We will
10035         -- reduce this balance whenever we attribute debits to it.
10036         --
10037         CREATE TEMP TABLE t_fund_source_bal
10038         ON COMMIT DROP AS
10039                 SELECT
10040                         fund AS fund,
10041                         funding_source AS source,
10042                         sum( amount ) AS balance
10043                 FROM
10044                         acq.fund_allocation
10045                 GROUP BY
10046                         fund,
10047                         funding_source
10048                 HAVING
10049                         sum( amount ) > 0;
10050         --
10051         CREATE INDEX t_fund_source_bal_idx
10052                 ON t_fund_source_bal( fund, source );
10053         -------------------------------------------------------------------------------
10054         --
10055         -- Load another temporary table.  For each fund, load zero or more
10056         -- funding source credits from which that fund can get money.
10057         --
10058         CREATE TEMP TABLE t_fund_credit (
10059                 fund        INT,
10060                 seq         INT,
10061                 credit      INT
10062         ) ON COMMIT DROP;
10063         --
10064         FOR fc IN
10065                 SELECT DISTINCT fund
10066                 FROM acq.fund_allocation
10067                 ORDER BY fund
10068         LOOP                  -- Loop over the funds
10069                 seqno := 1;
10070                 FOR sc IN
10071                         SELECT
10072                                 ofsc.id
10073                         FROM
10074                                 acq.ordered_funding_source_credit AS ofsc
10075                         WHERE
10076                                 ofsc.funding_source IN
10077                                 (
10078                                         SELECT funding_source
10079                                         FROM acq.fund_allocation
10080                                         WHERE fund = fc.fund
10081                                 )
10082                 ORDER BY
10083                     ofsc.sort_priority,
10084                     ofsc.sort_date,
10085                     ofsc.id
10086                 LOOP                        -- Add each credit to the list
10087                         INSERT INTO t_fund_credit (
10088                                 fund,
10089                                 seq,
10090                                 credit
10091                         ) VALUES (
10092                                 fc.fund,
10093                                 seqno,
10094                                 sc.id
10095                         );
10096                         --RAISE NOTICE 'Fund % credit %', fc.fund, sc.id;
10097                         seqno := seqno + 1;
10098                 END LOOP;     -- Loop over credits for a given fund
10099         END LOOP;         -- Loop over funds
10100         --
10101         CREATE INDEX t_fund_credit_idx
10102                 ON t_fund_credit( fund, seq );
10103         -------------------------------------------------------------------------------
10104         --
10105         -- Load yet another temporary table.  This one is a list of funding source
10106         -- credits, with their balances.  We shall reduce those balances as we
10107         -- attribute debits to them.
10108         --
10109         CREATE TEMP TABLE t_credit
10110         ON COMMIT DROP AS
10111         SELECT
10112             fsc.id AS credit,
10113             fsc.funding_source AS source,
10114             fsc.amount AS balance,
10115             fs.currency_type AS currency_type
10116         FROM
10117             acq.funding_source_credit AS fsc,
10118             acq.funding_source fs
10119         WHERE
10120             fsc.funding_source = fs.id
10121                         AND fsc.amount > 0;
10122         --
10123         CREATE INDEX t_credit_idx
10124                 ON t_credit( credit );
10125         --
10126         -------------------------------------------------------------------------------
10127         --
10128         -- Now that we have loaded the lookup tables: loop through the debits,
10129         -- attributing each one to one or more funding source credits.
10130         -- 
10131         truncate table acq.debit_attribution;
10132         --
10133         attrib_count := 0;
10134         FOR deb in
10135                 SELECT
10136                         fd.id,
10137                         fd.fund,
10138                         fd.amount,
10139                         f.currency_type,
10140                         fd.encumbrance
10141                 FROM
10142                         acq.fund_debit fd,
10143                         acq.fund f
10144                 WHERE
10145                         fd.fund = f.id
10146                 ORDER BY
10147                         fd.id
10148         LOOP
10149                 --RAISE NOTICE 'Debit %, fund %', deb.id, deb.fund;
10150                 --
10151                 debit_balance := deb.amount;
10152                 --
10153                 -- Loop over the funding source credits that are eligible
10154                 -- to pay for this debit
10155                 --
10156                 FOR fund_credit IN
10157                         SELECT
10158                                 credit
10159                         FROM
10160                                 t_fund_credit
10161                         WHERE
10162                                 fund = deb.fund
10163                         ORDER BY
10164                                 seq
10165                 LOOP
10166                         --RAISE NOTICE '   Examining credit %', fund_credit.credit;
10167                         --
10168                         -- Look up the balance for this credit.  If it's zero, then
10169                         -- it's not useful, so treat it as if you didn't find it.
10170                         -- (Actually there shouldn't be any zero balances in the table,
10171                         -- but we check just to make sure.)
10172                         --
10173                         SELECT *
10174                         INTO curr_credit_bal
10175                         FROM t_credit
10176                         WHERE
10177                                 credit = fund_credit.credit
10178                                 AND balance > 0;
10179                         --
10180                         IF curr_credit_bal IS NULL THEN
10181                                 --
10182                                 -- This credit is exhausted; try the next one.
10183                                 --
10184                                 CONTINUE;
10185                         END IF;
10186                         --
10187                         --
10188                         -- At this point we have an applicable credit with some money left.
10189                         -- Now see if the relevant funding_source has any money left.
10190                         --
10191                         -- Look up the balance of the allocation for this combination of
10192                         -- fund and source.  If you find such an entry, but it has a zero
10193                         -- balance, then it's not useful, so treat it as unfound.
10194                         -- (Actually there shouldn't be any zero balances in the table,
10195                         -- but we check just to make sure.)
10196                         --
10197                         SELECT *
10198                         INTO curr_fund_source_bal
10199                         FROM t_fund_source_bal
10200                         WHERE
10201                                 fund = deb.fund
10202                                 AND source = curr_credit_bal.source
10203                                 AND balance > 0;
10204                         --
10205                         IF curr_fund_source_bal IS NULL THEN
10206                                 --
10207                                 -- This fund/source doesn't exist or is already exhausted,
10208                                 -- so we can't use this credit.  Go on to the next one.
10209                                 --
10210                                 CONTINUE;
10211                         END IF;
10212                         --
10213                         -- Convert the available balances to the currency of the fund
10214                         --
10215                         conv_alloc_balance := curr_fund_source_bal.balance * acq.exchange_ratio(
10216                                 curr_credit_bal.currency_type, deb.currency_type );
10217                         conv_cred_balance := curr_credit_bal.balance * acq.exchange_ratio(
10218                                 curr_credit_bal.currency_type, deb.currency_type );
10219                         --
10220                         -- Determine how much we can attribute to this credit: the minimum
10221                         -- of the debit amount, the fund/source balance, and the
10222                         -- credit balance
10223                         --
10224                         --RAISE NOTICE '   deb bal %', debit_balance;
10225                         --RAISE NOTICE '      source % balance %', curr_credit_bal.source, conv_alloc_balance;
10226                         --RAISE NOTICE '      credit % balance %', curr_credit_bal.credit, conv_cred_balance;
10227                         --
10228                         conv_attr_amount := NULL;
10229                         attr_amount := debit_balance;
10230                         --
10231                         IF attr_amount > conv_alloc_balance THEN
10232                                 attr_amount := conv_alloc_balance;
10233                                 conv_attr_amount := curr_fund_source_bal.balance;
10234                         END IF;
10235                         IF attr_amount > conv_cred_balance THEN
10236                                 attr_amount := conv_cred_balance;
10237                                 conv_attr_amount := curr_credit_bal.balance;
10238                         END IF;
10239                         --
10240                         -- If we're attributing all of one of the balances, then that's how
10241                         -- much we will deduct from the balances, and we already captured
10242                         -- that amount above.  Otherwise we must convert the amount of the
10243                         -- attribution from the currency of the fund back to the currency of
10244                         -- the funding source.
10245                         --
10246                         IF conv_attr_amount IS NULL THEN
10247                                 conv_attr_amount := attr_amount * acq.exchange_ratio(
10248                                         deb.currency_type, curr_credit_bal.currency_type );
10249                         END IF;
10250                         --
10251                         -- Insert a row to record the attribution
10252                         --
10253                         attrib_count := attrib_count + 1;
10254                         INSERT INTO acq.debit_attribution (
10255                                 id,
10256                                 fund_debit,
10257                                 debit_amount,
10258                                 funding_source_credit,
10259                                 credit_amount
10260                         ) VALUES (
10261                                 attrib_count,
10262                                 deb.id,
10263                                 attr_amount,
10264                                 curr_credit_bal.credit,
10265                                 conv_attr_amount
10266                         );
10267                         --
10268                         -- Subtract the attributed amount from the various balances
10269                         --
10270                         debit_balance := debit_balance - attr_amount;
10271                         curr_fund_source_bal.balance := curr_fund_source_bal.balance - conv_attr_amount;
10272                         --
10273                         IF curr_fund_source_bal.balance <= 0 THEN
10274                                 --
10275                                 -- This allocation is exhausted.  Delete it so
10276                                 -- that we don't waste time looking at it again.
10277                                 --
10278                                 DELETE FROM t_fund_source_bal
10279                                 WHERE
10280                                         fund = curr_fund_source_bal.fund
10281                                         AND source = curr_fund_source_bal.source;
10282                         ELSE
10283                                 UPDATE t_fund_source_bal
10284                                 SET balance = balance - conv_attr_amount
10285                                 WHERE
10286                                         fund = curr_fund_source_bal.fund
10287                                         AND source = curr_fund_source_bal.source;
10288                         END IF;
10289                         --
10290                         IF curr_credit_bal.balance <= 0 THEN
10291                                 --
10292                                 -- This funding source credit is exhausted.  Delete it
10293                                 -- so that we don't waste time looking at it again.
10294                                 --
10295                                 --DELETE FROM t_credit
10296                                 --WHERE
10297                                 --      credit = curr_credit_bal.credit;
10298                                 --
10299                                 DELETE FROM t_fund_credit
10300                                 WHERE
10301                                         credit = curr_credit_bal.credit;
10302                         ELSE
10303                                 UPDATE t_credit
10304                                 SET balance = curr_credit_bal.balance
10305                                 WHERE
10306                                         credit = curr_credit_bal.credit;
10307                         END IF;
10308                         --
10309                         -- Are we done with this debit yet?
10310                         --
10311                         IF debit_balance <= 0 THEN
10312                                 EXIT;       -- We've fully attributed this debit; stop looking at credits.
10313                         END IF;
10314                 END LOOP;       -- End loop over credits
10315                 --
10316                 IF debit_balance <> 0 THEN
10317                         --
10318                         -- We weren't able to attribute this debit, or at least not
10319                         -- all of it.  Insert a row for the unattributed balance.
10320                         --
10321                         attrib_count := attrib_count + 1;
10322                         INSERT INTO acq.debit_attribution (
10323                                 id,
10324                                 fund_debit,
10325                                 debit_amount,
10326                                 funding_source_credit,
10327                                 credit_amount
10328                         ) VALUES (
10329                                 attrib_count,
10330                                 deb.id,
10331                                 debit_balance,
10332                                 NULL,
10333                                 NULL
10334                         );
10335                 END IF;
10336         END LOOP;   -- End of loop over debits
10337 END;
10338 $$ LANGUAGE 'plpgsql';
10339
10340 CREATE OR REPLACE FUNCTION extract_marc_field ( TEXT, BIGINT, TEXT, TEXT ) RETURNS TEXT AS $$
10341 DECLARE
10342     query TEXT;
10343     output TEXT;
10344 BEGIN
10345     query := $q$
10346         SELECT  regexp_replace(
10347                     oils_xpath_string(
10348                         $q$ || quote_literal($3) || $q$,
10349                         marc,
10350                         ' '
10351                     ),
10352                     $q$ || quote_literal($4) || $q$,
10353                     '',
10354                     'g')
10355           FROM  $q$ || $1 || $q$
10356           WHERE id = $q$ || $2;
10357
10358     EXECUTE query INTO output;
10359
10360     -- RAISE NOTICE 'query: %, output; %', query, output;
10361
10362     RETURN output;
10363 END;
10364 $$ LANGUAGE PLPGSQL IMMUTABLE;
10365
10366 CREATE OR REPLACE FUNCTION extract_marc_field ( TEXT, BIGINT, TEXT ) RETURNS TEXT AS $$
10367     SELECT extract_marc_field($1,$2,$3,'');
10368 $$ LANGUAGE SQL IMMUTABLE;
10369
10370 CREATE OR REPLACE FUNCTION asset.merge_record_assets( target_record BIGINT, source_record BIGINT ) RETURNS INT AS $func$
10371 DECLARE
10372     moved_objects INT := 0;
10373     source_cn     asset.call_number%ROWTYPE;
10374     target_cn     asset.call_number%ROWTYPE;
10375     metarec       metabib.metarecord%ROWTYPE;
10376     hold          action.hold_request%ROWTYPE;
10377     ser_rec       serial.record_entry%ROWTYPE;
10378     uri_count     INT := 0;
10379     counter       INT := 0;
10380     uri_datafield TEXT;
10381     uri_text      TEXT := '';
10382 BEGIN
10383
10384     -- move any 856 entries on records that have at least one MARC-mapped URI entry
10385     SELECT  INTO uri_count COUNT(*)
10386       FROM  asset.uri_call_number_map m
10387             JOIN asset.call_number cn ON (m.call_number = cn.id)
10388       WHERE cn.record = source_record;
10389
10390     IF uri_count > 0 THEN
10391
10392         SELECT  COUNT(*) INTO counter
10393           FROM  oils_xpath_table(
10394                     'id',
10395                     'marc',
10396                     'biblio.record_entry',
10397                     '//*[@tag="856"]',
10398                     'id=' || source_record
10399                 ) as t(i int,c text);
10400
10401         FOR i IN 1 .. counter LOOP
10402             SELECT  '<datafield xmlns="http://www.loc.gov/MARC21/slim"' ||
10403                         ' tag="856"' || 
10404                         ' ind1="' || FIRST(ind1) || '"'  || 
10405                         ' ind2="' || FIRST(ind2) || '">' || 
10406                         array_to_string(
10407                             array_accum(
10408                                 '<subfield code="' || subfield || '">' ||
10409                                 regexp_replace(
10410                                     regexp_replace(
10411                                         regexp_replace(data,'&','&amp;','g'),
10412                                         '>', '&gt;', 'g'
10413                                     ),
10414                                     '<', '&lt;', 'g'
10415                                 ) || '</subfield>'
10416                             ), ''
10417                         ) || '</datafield>' INTO uri_datafield
10418               FROM  oils_xpath_table(
10419                         'id',
10420                         'marc',
10421                         'biblio.record_entry',
10422                         '//*[@tag="856"][position()=' || i || ']/@ind1|' || 
10423                         '//*[@tag="856"][position()=' || i || ']/@ind2|' || 
10424                         '//*[@tag="856"][position()=' || i || ']/*/@code|' ||
10425                         '//*[@tag="856"][position()=' || i || ']/*[@code]',
10426                         'id=' || source_record
10427                     ) as t(id int,ind1 text, ind2 text,subfield text,data text);
10428
10429             uri_text := uri_text || uri_datafield;
10430         END LOOP;
10431
10432         IF uri_text <> '' THEN
10433             UPDATE  biblio.record_entry
10434               SET   marc = regexp_replace(marc,'(</[^>]*record>)', uri_text || E'\\1')
10435               WHERE id = target_record;
10436         END IF;
10437
10438     END IF;
10439
10440     -- Find and move metarecords to the target record
10441     SELECT  INTO metarec *
10442       FROM  metabib.metarecord
10443       WHERE master_record = source_record;
10444
10445     IF FOUND THEN
10446         UPDATE  metabib.metarecord
10447           SET   master_record = target_record,
10448             mods = NULL
10449           WHERE id = metarec.id;
10450
10451         moved_objects := moved_objects + 1;
10452     END IF;
10453
10454     -- Find call numbers attached to the source ...
10455     FOR source_cn IN SELECT * FROM asset.call_number WHERE record = source_record LOOP
10456
10457         SELECT  INTO target_cn *
10458           FROM  asset.call_number
10459           WHERE label = source_cn.label
10460             AND owning_lib = source_cn.owning_lib
10461             AND record = target_record;
10462
10463         -- ... and if there's a conflicting one on the target ...
10464         IF FOUND THEN
10465
10466             -- ... move the copies to that, and ...
10467             UPDATE  asset.copy
10468               SET   call_number = target_cn.id
10469               WHERE call_number = source_cn.id;
10470
10471             -- ... move V holds to the move-target call number
10472             FOR hold IN SELECT * FROM action.hold_request WHERE target = source_cn.id AND hold_type = 'V' LOOP
10473
10474                 UPDATE  action.hold_request
10475                   SET   target = target_cn.id
10476                   WHERE id = hold.id;
10477
10478                 moved_objects := moved_objects + 1;
10479             END LOOP;
10480
10481         -- ... if not ...
10482         ELSE
10483             -- ... just move the call number to the target record
10484             UPDATE  asset.call_number
10485               SET   record = target_record
10486               WHERE id = source_cn.id;
10487         END IF;
10488
10489         moved_objects := moved_objects + 1;
10490     END LOOP;
10491
10492     -- Find T holds targeting the source record ...
10493     FOR hold IN SELECT * FROM action.hold_request WHERE target = source_record AND hold_type = 'T' LOOP
10494
10495         -- ... and move them to the target record
10496         UPDATE  action.hold_request
10497           SET   target = target_record
10498           WHERE id = hold.id;
10499
10500         moved_objects := moved_objects + 1;
10501     END LOOP;
10502
10503     -- Find serial records targeting the source record ...
10504     FOR ser_rec IN SELECT * FROM serial.record_entry WHERE record = source_record LOOP
10505         -- ... and move them to the target record
10506         UPDATE  serial.record_entry
10507           SET   record = target_record
10508           WHERE id = ser_rec.id;
10509
10510         moved_objects := moved_objects + 1;
10511     END LOOP;
10512
10513     -- Finally, "delete" the source record
10514     DELETE FROM biblio.record_entry WHERE id = source_record;
10515
10516     -- That's all, folks!
10517     RETURN moved_objects;
10518 END;
10519 $func$ LANGUAGE plpgsql;
10520
10521 CREATE OR REPLACE FUNCTION acq.transfer_fund(
10522         old_fund   IN INT,
10523         old_amount IN NUMERIC,     -- in currency of old fund
10524         new_fund   IN INT,
10525         new_amount IN NUMERIC,     -- in currency of new fund
10526         user_id    IN INT,
10527         xfer_note  IN TEXT         -- to be recorded in acq.fund_transfer
10528         -- ,funding_source_in IN INT  -- if user wants to specify a funding source (see notes)
10529 ) RETURNS VOID AS $$
10530 /* -------------------------------------------------------------------------------
10531
10532 Function to transfer money from one fund to another.
10533
10534 A transfer is represented as a pair of entries in acq.fund_allocation, with a
10535 negative amount for the old (losing) fund and a positive amount for the new
10536 (gaining) fund.  In some cases there may be more than one such pair of entries
10537 in order to pull the money from different funding sources, or more specifically
10538 from different funding source credits.  For each such pair there is also an
10539 entry in acq.fund_transfer.
10540
10541 Since funding_source is a non-nullable column in acq.fund_allocation, we must
10542 choose a funding source for the transferred money to come from.  This choice
10543 must meet two constraints, so far as possible:
10544
10545 1. The amount transferred from a given funding source must not exceed the
10546 amount allocated to the old fund by the funding source.  To that end we
10547 compare the amount being transferred to the amount allocated.
10548
10549 2. We shouldn't transfer money that has already been spent or encumbered, as
10550 defined by the funding attribution process.  We attribute expenses to the
10551 oldest funding source credits first.  In order to avoid transferring that
10552 attributed money, we reverse the priority, transferring from the newest funding
10553 source credits first.  There can be no guarantee that this approach will
10554 avoid overcommitting a fund, but no other approach can do any better.
10555
10556 In this context the age of a funding source credit is defined by the
10557 deadline_date for credits with deadline_dates, and by the effective_date for
10558 credits without deadline_dates, with the proviso that credits with deadline_dates
10559 are all considered "older" than those without.
10560
10561 ----------
10562
10563 In the signature for this function, there is one last parameter commented out,
10564 named "funding_source_in".  Correspondingly, the WHERE clause for the query
10565 driving the main loop has an OR clause commented out, which references the
10566 funding_source_in parameter.
10567
10568 If these lines are uncommented, this function will allow the user optionally to
10569 restrict a fund transfer to a specified funding source.  If the source
10570 parameter is left NULL, then there will be no such restriction.
10571
10572 ------------------------------------------------------------------------------- */ 
10573 DECLARE
10574         same_currency      BOOLEAN;
10575         currency_ratio     NUMERIC;
10576         old_fund_currency  TEXT;
10577         old_remaining      NUMERIC;  -- in currency of old fund
10578         new_fund_currency  TEXT;
10579         new_fund_active    BOOLEAN;
10580         new_remaining      NUMERIC;  -- in currency of new fund
10581         curr_old_amt       NUMERIC;  -- in currency of old fund
10582         curr_new_amt       NUMERIC;  -- in currency of new fund
10583         source_addition    NUMERIC;  -- in currency of funding source
10584         source_deduction   NUMERIC;  -- in currency of funding source
10585         orig_allocated_amt NUMERIC;  -- in currency of funding source
10586         allocated_amt      NUMERIC;  -- in currency of fund
10587         source             RECORD;
10588 BEGIN
10589         --
10590         -- Sanity checks
10591         --
10592         IF old_fund IS NULL THEN
10593                 RAISE EXCEPTION 'acq.transfer_fund: old fund id is NULL';
10594         END IF;
10595         --
10596         IF old_amount IS NULL THEN
10597                 RAISE EXCEPTION 'acq.transfer_fund: amount to transfer is NULL';
10598         END IF;
10599         --
10600         -- The new fund and its amount must be both NULL or both not NULL.
10601         --
10602         IF new_fund IS NOT NULL AND new_amount IS NULL THEN
10603                 RAISE EXCEPTION 'acq.transfer_fund: amount to transfer to receiving fund is NULL';
10604         END IF;
10605         --
10606         IF new_fund IS NULL AND new_amount IS NOT NULL THEN
10607                 RAISE EXCEPTION 'acq.transfer_fund: receiving fund is NULL, its amount is not NULL';
10608         END IF;
10609         --
10610         IF user_id IS NULL THEN
10611                 RAISE EXCEPTION 'acq.transfer_fund: user id is NULL';
10612         END IF;
10613         --
10614         -- Initialize the amounts to be transferred, each denominated
10615         -- in the currency of its respective fund.  They will be
10616         -- reduced on each iteration of the loop.
10617         --
10618         old_remaining := old_amount;
10619         new_remaining := new_amount;
10620         --
10621         -- RAISE NOTICE 'Transferring % in fund % to % in fund %',
10622         --      old_amount, old_fund, new_amount, new_fund;
10623         --
10624         -- Get the currency types of the old and new funds.
10625         --
10626         SELECT
10627                 currency_type
10628         INTO
10629                 old_fund_currency
10630         FROM
10631                 acq.fund
10632         WHERE
10633                 id = old_fund;
10634         --
10635         IF old_fund_currency IS NULL THEN
10636                 RAISE EXCEPTION 'acq.transfer_fund: old fund id % is not defined', old_fund;
10637         END IF;
10638         --
10639         IF new_fund IS NOT NULL THEN
10640                 SELECT
10641                         currency_type,
10642                         active
10643                 INTO
10644                         new_fund_currency,
10645                         new_fund_active
10646                 FROM
10647                         acq.fund
10648                 WHERE
10649                         id = new_fund;
10650                 --
10651                 IF new_fund_currency IS NULL THEN
10652                         RAISE EXCEPTION 'acq.transfer_fund: new fund id % is not defined', new_fund;
10653                 ELSIF NOT new_fund_active THEN
10654                         --
10655                         -- No point in putting money into a fund from whence you can't spend it
10656                         --
10657                         RAISE EXCEPTION 'acq.transfer_fund: new fund id % is inactive', new_fund;
10658                 END IF;
10659                 --
10660                 IF new_amount = old_amount THEN
10661                         same_currency := true;
10662                         currency_ratio := 1;
10663                 ELSE
10664                         --
10665                         -- We'll have to translate currency between funds.  We presume that
10666                         -- the calling code has already applied an appropriate exchange rate,
10667                         -- so we'll apply the same conversion to each sub-transfer.
10668                         --
10669                         same_currency := false;
10670                         currency_ratio := new_amount / old_amount;
10671                 END IF;
10672         END IF;
10673         --
10674         -- Identify the funding source(s) from which we want to transfer the money.
10675         -- The principle is that we want to transfer the newest money first, because
10676         -- we spend the oldest money first.  The priority for spending is defined
10677         -- by a sort of the view acq.ordered_funding_source_credit.
10678         --
10679         FOR source in
10680                 SELECT
10681                         ofsc.id,
10682                         ofsc.funding_source,
10683                         ofsc.amount,
10684                         ofsc.amount * acq.exchange_ratio( fs.currency_type, old_fund_currency )
10685                                 AS converted_amt,
10686                         fs.currency_type
10687                 FROM
10688                         acq.ordered_funding_source_credit AS ofsc,
10689                         acq.funding_source fs
10690                 WHERE
10691                         ofsc.funding_source = fs.id
10692                         and ofsc.funding_source IN
10693                         (
10694                                 SELECT funding_source
10695                                 FROM acq.fund_allocation
10696                                 WHERE fund = old_fund
10697                         )
10698                         -- and
10699                         -- (
10700                         --      ofsc.funding_source = funding_source_in
10701                         --      OR funding_source_in IS NULL
10702                         -- )
10703                 ORDER BY
10704                         ofsc.sort_priority desc,
10705                         ofsc.sort_date desc,
10706                         ofsc.id desc
10707         LOOP
10708                 --
10709                 -- Determine how much money the old fund got from this funding source,
10710                 -- denominated in the currency types of the source and of the fund.
10711                 -- This result may reflect transfers from previous iterations.
10712                 --
10713                 SELECT
10714                         COALESCE( sum( amount ), 0 ),
10715                         COALESCE( sum( amount )
10716                                 * acq.exchange_ratio( source.currency_type, old_fund_currency ), 0 )
10717                 INTO
10718                         orig_allocated_amt,     -- in currency of the source
10719                         allocated_amt           -- in currency of the old fund
10720                 FROM
10721                         acq.fund_allocation
10722                 WHERE
10723                         fund = old_fund
10724                         and funding_source = source.funding_source;
10725                 --      
10726                 -- Determine how much to transfer from this credit, in the currency
10727                 -- of the fund.   Begin with the amount remaining to be attributed:
10728                 --
10729                 curr_old_amt := old_remaining;
10730                 --
10731                 -- Can't attribute more than was allocated from the fund:
10732                 --
10733                 IF curr_old_amt > allocated_amt THEN
10734                         curr_old_amt := allocated_amt;
10735                 END IF;
10736                 --
10737                 -- Can't attribute more than the amount of the current credit:
10738                 --
10739                 IF curr_old_amt > source.converted_amt THEN
10740                         curr_old_amt := source.converted_amt;
10741                 END IF;
10742                 --
10743                 curr_old_amt := trunc( curr_old_amt, 2 );
10744                 --
10745                 old_remaining := old_remaining - curr_old_amt;
10746                 --
10747                 -- Determine the amount to be deducted, if any,
10748                 -- from the old allocation.
10749                 --
10750                 IF old_remaining > 0 THEN
10751                         --
10752                         -- In this case we're using the whole allocation, so use that
10753                         -- amount directly instead of applying a currency translation
10754                         -- and thereby inviting round-off errors.
10755                         --
10756                         source_deduction := - orig_allocated_amt;
10757                 ELSE 
10758                         source_deduction := trunc(
10759                                 ( - curr_old_amt ) *
10760                                         acq.exchange_ratio( old_fund_currency, source.currency_type ),
10761                                 2 );
10762                 END IF;
10763                 --
10764                 IF source_deduction <> 0 THEN
10765                         --
10766                         -- Insert negative allocation for old fund in fund_allocation,
10767                         -- converted into the currency of the funding source
10768                         --
10769                         INSERT INTO acq.fund_allocation (
10770                                 funding_source,
10771                                 fund,
10772                                 amount,
10773                                 allocator,
10774                                 note
10775                         ) VALUES (
10776                                 source.funding_source,
10777                                 old_fund,
10778                                 source_deduction,
10779                                 user_id,
10780                                 'Transfer to fund ' || new_fund
10781                         );
10782                 END IF;
10783                 --
10784                 IF new_fund IS NOT NULL THEN
10785                         --
10786                         -- Determine how much to add to the new fund, in
10787                         -- its currency, and how much remains to be added:
10788                         --
10789                         IF same_currency THEN
10790                                 curr_new_amt := curr_old_amt;
10791                         ELSE
10792                                 IF old_remaining = 0 THEN
10793                                         --
10794                                         -- This is the last iteration, so nothing should be left
10795                                         --
10796                                         curr_new_amt := new_remaining;
10797                                         new_remaining := 0;
10798                                 ELSE
10799                                         curr_new_amt := trunc( curr_old_amt * currency_ratio, 2 );
10800                                         new_remaining := new_remaining - curr_new_amt;
10801                                 END IF;
10802                         END IF;
10803                         --
10804                         -- Determine how much to add, if any,
10805                         -- to the new fund's allocation.
10806                         --
10807                         IF old_remaining > 0 THEN
10808                                 --
10809                                 -- In this case we're using the whole allocation, so use that amount
10810                                 -- amount directly instead of applying a currency translation and
10811                                 -- thereby inviting round-off errors.
10812                                 --
10813                                 source_addition := orig_allocated_amt;
10814                         ELSIF source.currency_type = old_fund_currency THEN
10815                                 --
10816                                 -- In this case we don't need a round trip currency translation,
10817                                 -- thereby inviting round-off errors:
10818                                 --
10819                                 source_addition := curr_old_amt;
10820                         ELSE 
10821                                 source_addition := trunc(
10822                                         curr_new_amt *
10823                                                 acq.exchange_ratio( new_fund_currency, source.currency_type ),
10824                                         2 );
10825                         END IF;
10826                         --
10827                         IF source_addition <> 0 THEN
10828                                 --
10829                                 -- Insert positive allocation for new fund in fund_allocation,
10830                                 -- converted to the currency of the founding source
10831                                 --
10832                                 INSERT INTO acq.fund_allocation (
10833                                         funding_source,
10834                                         fund,
10835                                         amount,
10836                                         allocator,
10837                                         note
10838                                 ) VALUES (
10839                                         source.funding_source,
10840                                         new_fund,
10841                                         source_addition,
10842                                         user_id,
10843                                         'Transfer from fund ' || old_fund
10844                                 );
10845                         END IF;
10846                 END IF;
10847                 --
10848                 IF trunc( curr_old_amt, 2 ) <> 0
10849                 OR trunc( curr_new_amt, 2 ) <> 0 THEN
10850                         --
10851                         -- Insert row in fund_transfer, using amounts in the currency of the funds
10852                         --
10853                         INSERT INTO acq.fund_transfer (
10854                                 src_fund,
10855                                 src_amount,
10856                                 dest_fund,
10857                                 dest_amount,
10858                                 transfer_user,
10859                                 note,
10860                                 funding_source_credit
10861                         ) VALUES (
10862                                 old_fund,
10863                                 trunc( curr_old_amt, 2 ),
10864                                 new_fund,
10865                                 trunc( curr_new_amt, 2 ),
10866                                 user_id,
10867                                 xfer_note,
10868                                 source.id
10869                         );
10870                 END IF;
10871                 --
10872                 if old_remaining <= 0 THEN
10873                         EXIT;                   -- Nothing more to be transferred
10874                 END IF;
10875         END LOOP;
10876 END;
10877 $$ LANGUAGE plpgsql;
10878
10879 CREATE OR REPLACE FUNCTION acq.propagate_funds_by_org_unit(
10880         old_year INTEGER,
10881         user_id INTEGER,
10882         org_unit_id INTEGER
10883 ) RETURNS VOID AS $$
10884 DECLARE
10885 --
10886 new_id      INT;
10887 old_fund    RECORD;
10888 org_found   BOOLEAN;
10889 --
10890 BEGIN
10891         --
10892         -- Sanity checks
10893         --
10894         IF old_year IS NULL THEN
10895                 RAISE EXCEPTION 'Input year argument is NULL';
10896         ELSIF old_year NOT BETWEEN 2008 and 2200 THEN
10897                 RAISE EXCEPTION 'Input year is out of range';
10898         END IF;
10899         --
10900         IF user_id IS NULL THEN
10901                 RAISE EXCEPTION 'Input user id argument is NULL';
10902         END IF;
10903         --
10904         IF org_unit_id IS NULL THEN
10905                 RAISE EXCEPTION 'Org unit id argument is NULL';
10906         ELSE
10907                 SELECT TRUE INTO org_found
10908                 FROM actor.org_unit
10909                 WHERE id = org_unit_id;
10910                 --
10911                 IF org_found IS NULL THEN
10912                         RAISE EXCEPTION 'Org unit id is invalid';
10913                 END IF;
10914         END IF;
10915         --
10916         -- Loop over the applicable funds
10917         --
10918         FOR old_fund in SELECT * FROM acq.fund
10919         WHERE
10920                 year = old_year
10921                 AND propagate
10922                 AND org = org_unit_id
10923         LOOP
10924                 BEGIN
10925                         INSERT INTO acq.fund (
10926                                 org,
10927                                 name,
10928                                 year,
10929                                 currency_type,
10930                                 code,
10931                                 rollover,
10932                                 propagate,
10933                                 balance_warning_percent,
10934                                 balance_stop_percent
10935                         ) VALUES (
10936                                 old_fund.org,
10937                                 old_fund.name,
10938                                 old_year + 1,
10939                                 old_fund.currency_type,
10940                                 old_fund.code,
10941                                 old_fund.rollover,
10942                                 true,
10943                                 old_fund.balance_warning_percent,
10944                                 old_fund.balance_stop_percent
10945                         )
10946                         RETURNING id INTO new_id;
10947                 EXCEPTION
10948                         WHEN unique_violation THEN
10949                                 --RAISE NOTICE 'Fund % already propagated', old_fund.id;
10950                                 CONTINUE;
10951                 END;
10952                 --RAISE NOTICE 'Propagating fund % to fund %',
10953                 --      old_fund.code, new_id;
10954         END LOOP;
10955 END;
10956 $$ LANGUAGE plpgsql;
10957
10958 CREATE OR REPLACE FUNCTION acq.propagate_funds_by_org_tree(
10959         old_year INTEGER,
10960         user_id INTEGER,
10961         org_unit_id INTEGER
10962 ) RETURNS VOID AS $$
10963 DECLARE
10964 --
10965 new_id      INT;
10966 old_fund    RECORD;
10967 org_found   BOOLEAN;
10968 --
10969 BEGIN
10970         --
10971         -- Sanity checks
10972         --
10973         IF old_year IS NULL THEN
10974                 RAISE EXCEPTION 'Input year argument is NULL';
10975         ELSIF old_year NOT BETWEEN 2008 and 2200 THEN
10976                 RAISE EXCEPTION 'Input year is out of range';
10977         END IF;
10978         --
10979         IF user_id IS NULL THEN
10980                 RAISE EXCEPTION 'Input user id argument is NULL';
10981         END IF;
10982         --
10983         IF org_unit_id IS NULL THEN
10984                 RAISE EXCEPTION 'Org unit id argument is NULL';
10985         ELSE
10986                 SELECT TRUE INTO org_found
10987                 FROM actor.org_unit
10988                 WHERE id = org_unit_id;
10989                 --
10990                 IF org_found IS NULL THEN
10991                         RAISE EXCEPTION 'Org unit id is invalid';
10992                 END IF;
10993         END IF;
10994         --
10995         -- Loop over the applicable funds
10996         --
10997         FOR old_fund in SELECT * FROM acq.fund
10998         WHERE
10999                 year = old_year
11000                 AND propagate
11001                 AND org in (
11002                         SELECT id FROM actor.org_unit_descendants( org_unit_id )
11003                 )
11004         LOOP
11005                 BEGIN
11006                         INSERT INTO acq.fund (
11007                                 org,
11008                                 name,
11009                                 year,
11010                                 currency_type,
11011                                 code,
11012                                 rollover,
11013                                 propagate,
11014                                 balance_warning_percent,
11015                                 balance_stop_percent
11016                         ) VALUES (
11017                                 old_fund.org,
11018                                 old_fund.name,
11019                                 old_year + 1,
11020                                 old_fund.currency_type,
11021                                 old_fund.code,
11022                                 old_fund.rollover,
11023                                 true,
11024                                 old_fund.balance_warning_percent,
11025                                 old_fund.balance_stop_percent
11026                         )
11027                         RETURNING id INTO new_id;
11028                 EXCEPTION
11029                         WHEN unique_violation THEN
11030                                 --RAISE NOTICE 'Fund % already propagated', old_fund.id;
11031                                 CONTINUE;
11032                 END;
11033                 --RAISE NOTICE 'Propagating fund % to fund %',
11034                 --      old_fund.code, new_id;
11035         END LOOP;
11036 END;
11037 $$ LANGUAGE plpgsql;
11038
11039 CREATE OR REPLACE FUNCTION acq.rollover_funds_by_org_unit(
11040         old_year INTEGER,
11041         user_id INTEGER,
11042         org_unit_id INTEGER
11043 ) RETURNS VOID AS $$
11044 DECLARE
11045 --
11046 new_fund    INT;
11047 new_year    INT := old_year + 1;
11048 org_found   BOOL;
11049 xfer_amount NUMERIC;
11050 roll_fund   RECORD;
11051 deb         RECORD;
11052 detail      RECORD;
11053 --
11054 BEGIN
11055         --
11056         -- Sanity checks
11057         --
11058         IF old_year IS NULL THEN
11059                 RAISE EXCEPTION 'Input year argument is NULL';
11060     ELSIF old_year NOT BETWEEN 2008 and 2200 THEN
11061         RAISE EXCEPTION 'Input year is out of range';
11062         END IF;
11063         --
11064         IF user_id IS NULL THEN
11065                 RAISE EXCEPTION 'Input user id argument is NULL';
11066         END IF;
11067         --
11068         IF org_unit_id IS NULL THEN
11069                 RAISE EXCEPTION 'Org unit id argument is NULL';
11070         ELSE
11071                 --
11072                 -- Validate the org unit
11073                 --
11074                 SELECT TRUE
11075                 INTO org_found
11076                 FROM actor.org_unit
11077                 WHERE id = org_unit_id;
11078                 --
11079                 IF org_found IS NULL THEN
11080                         RAISE EXCEPTION 'Org unit id % is invalid', org_unit_id;
11081                 END IF;
11082         END IF;
11083         --
11084         -- Loop over the propagable funds to identify the details
11085         -- from the old fund plus the id of the new one, if it exists.
11086         --
11087         FOR roll_fund in
11088         SELECT
11089             oldf.id AS old_fund,
11090             oldf.org,
11091             oldf.name,
11092             oldf.currency_type,
11093             oldf.code,
11094                 oldf.rollover,
11095             newf.id AS new_fund_id
11096         FROM
11097         acq.fund AS oldf
11098         LEFT JOIN acq.fund AS newf
11099                 ON ( oldf.code = newf.code )
11100         WHERE
11101                     oldf.org = org_unit_id
11102                 and oldf.year = old_year
11103                 and oldf.propagate
11104         and newf.year = new_year
11105         LOOP
11106                 --RAISE NOTICE 'Processing fund %', roll_fund.old_fund;
11107                 --
11108                 IF roll_fund.new_fund_id IS NULL THEN
11109                         --
11110                         -- The old fund hasn't been propagated yet.  Propagate it now.
11111                         --
11112                         INSERT INTO acq.fund (
11113                                 org,
11114                                 name,
11115                                 year,
11116                                 currency_type,
11117                                 code,
11118                                 rollover,
11119                                 propagate,
11120                                 balance_warning_percent,
11121                                 balance_stop_percent
11122                         ) VALUES (
11123                                 roll_fund.org,
11124                                 roll_fund.name,
11125                                 new_year,
11126                                 roll_fund.currency_type,
11127                                 roll_fund.code,
11128                                 true,
11129                                 true,
11130                                 roll_fund.balance_warning_percent,
11131                                 roll_fund.balance_stop_percent
11132                         )
11133                         RETURNING id INTO new_fund;
11134                 ELSE
11135                         new_fund = roll_fund.new_fund_id;
11136                 END IF;
11137                 --
11138                 -- Determine the amount to transfer
11139                 --
11140                 SELECT amount
11141                 INTO xfer_amount
11142                 FROM acq.fund_spent_balance
11143                 WHERE fund = roll_fund.old_fund;
11144                 --
11145                 IF xfer_amount <> 0 THEN
11146                         IF roll_fund.rollover THEN
11147                                 --
11148                                 -- Transfer balance from old fund to new
11149                                 --
11150                                 --RAISE NOTICE 'Transferring % from fund % to %', xfer_amount, roll_fund.old_fund, new_fund;
11151                                 --
11152                                 PERFORM acq.transfer_fund(
11153                                         roll_fund.old_fund,
11154                                         xfer_amount,
11155                                         new_fund,
11156                                         xfer_amount,
11157                                         user_id,
11158                                         'Rollover'
11159                                 );
11160                         ELSE
11161                                 --
11162                                 -- Transfer balance from old fund to the void
11163                                 --
11164                                 -- RAISE NOTICE 'Transferring % from fund % to the void', xfer_amount, roll_fund.old_fund;
11165                                 --
11166                                 PERFORM acq.transfer_fund(
11167                                         roll_fund.old_fund,
11168                                         xfer_amount,
11169                                         NULL,
11170                                         NULL,
11171                                         user_id,
11172                                         'Rollover'
11173                                 );
11174                         END IF;
11175                 END IF;
11176                 --
11177                 IF roll_fund.rollover THEN
11178                         --
11179                         -- Move any lineitems from the old fund to the new one
11180                         -- where the associated debit is an encumbrance.
11181                         --
11182                         -- Any other tables tying expenditure details to funds should
11183                         -- receive similar treatment.  At this writing there are none.
11184                         --
11185                         UPDATE acq.lineitem_detail
11186                         SET fund = new_fund
11187                         WHERE
11188                         fund = roll_fund.old_fund -- this condition may be redundant
11189                         AND fund_debit in
11190                         (
11191                                 SELECT id
11192                                 FROM acq.fund_debit
11193                                 WHERE
11194                                 fund = roll_fund.old_fund
11195                                 AND encumbrance
11196                         );
11197                         --
11198                         -- Move encumbrance debits from the old fund to the new fund
11199                         --
11200                         UPDATE acq.fund_debit
11201                         SET fund = new_fund
11202                         wHERE
11203                                 fund = roll_fund.old_fund
11204                                 AND encumbrance;
11205                 END IF;
11206                 --
11207                 -- Mark old fund as inactive, now that we've closed it
11208                 --
11209                 UPDATE acq.fund
11210                 SET active = FALSE
11211                 WHERE id = roll_fund.old_fund;
11212         END LOOP;
11213 END;
11214 $$ LANGUAGE plpgsql;
11215
11216 CREATE OR REPLACE FUNCTION acq.rollover_funds_by_org_tree(
11217         old_year INTEGER,
11218         user_id INTEGER,
11219         org_unit_id INTEGER
11220 ) RETURNS VOID AS $$
11221 DECLARE
11222 --
11223 new_fund    INT;
11224 new_year    INT := old_year + 1;
11225 org_found   BOOL;
11226 xfer_amount NUMERIC;
11227 roll_fund   RECORD;
11228 deb         RECORD;
11229 detail      RECORD;
11230 --
11231 BEGIN
11232         --
11233         -- Sanity checks
11234         --
11235         IF old_year IS NULL THEN
11236                 RAISE EXCEPTION 'Input year argument is NULL';
11237     ELSIF old_year NOT BETWEEN 2008 and 2200 THEN
11238         RAISE EXCEPTION 'Input year is out of range';
11239         END IF;
11240         --
11241         IF user_id IS NULL THEN
11242                 RAISE EXCEPTION 'Input user id argument is NULL';
11243         END IF;
11244         --
11245         IF org_unit_id IS NULL THEN
11246                 RAISE EXCEPTION 'Org unit id argument is NULL';
11247         ELSE
11248                 --
11249                 -- Validate the org unit
11250                 --
11251                 SELECT TRUE
11252                 INTO org_found
11253                 FROM actor.org_unit
11254                 WHERE id = org_unit_id;
11255                 --
11256                 IF org_found IS NULL THEN
11257                         RAISE EXCEPTION 'Org unit id % is invalid', org_unit_id;
11258                 END IF;
11259         END IF;
11260         --
11261         -- Loop over the propagable funds to identify the details
11262         -- from the old fund plus the id of the new one, if it exists.
11263         --
11264         FOR roll_fund in
11265         SELECT
11266             oldf.id AS old_fund,
11267             oldf.org,
11268             oldf.name,
11269             oldf.currency_type,
11270             oldf.code,
11271                 oldf.rollover,
11272             newf.id AS new_fund_id
11273         FROM
11274         acq.fund AS oldf
11275         LEFT JOIN acq.fund AS newf
11276                 ON ( oldf.code = newf.code )
11277         WHERE
11278                     oldf.year = old_year
11279                 AND oldf.propagate
11280         AND newf.year = new_year
11281                 AND oldf.org in (
11282                         SELECT id FROM actor.org_unit_descendants( org_unit_id )
11283                 )
11284         LOOP
11285                 --RAISE NOTICE 'Processing fund %', roll_fund.old_fund;
11286                 --
11287                 IF roll_fund.new_fund_id IS NULL THEN
11288                         --
11289                         -- The old fund hasn't been propagated yet.  Propagate it now.
11290                         --
11291                         INSERT INTO acq.fund (
11292                                 org,
11293                                 name,
11294                                 year,
11295                                 currency_type,
11296                                 code,
11297                                 rollover,
11298                                 propagate,
11299                                 balance_warning_percent,
11300                                 balance_stop_percent
11301                         ) VALUES (
11302                                 roll_fund.org,
11303                                 roll_fund.name,
11304                                 new_year,
11305                                 roll_fund.currency_type,
11306                                 roll_fund.code,
11307                                 true,
11308                                 true,
11309                                 roll_fund.balance_warning_percent,
11310                                 roll_fund.balance_stop_percent
11311                         )
11312                         RETURNING id INTO new_fund;
11313                 ELSE
11314                         new_fund = roll_fund.new_fund_id;
11315                 END IF;
11316                 --
11317                 -- Determine the amount to transfer
11318                 --
11319                 SELECT amount
11320                 INTO xfer_amount
11321                 FROM acq.fund_spent_balance
11322                 WHERE fund = roll_fund.old_fund;
11323                 --
11324                 IF xfer_amount <> 0 THEN
11325                         IF roll_fund.rollover THEN
11326                                 --
11327                                 -- Transfer balance from old fund to new
11328                                 --
11329                                 --RAISE NOTICE 'Transferring % from fund % to %', xfer_amount, roll_fund.old_fund, new_fund;
11330                                 --
11331                                 PERFORM acq.transfer_fund(
11332                                         roll_fund.old_fund,
11333                                         xfer_amount,
11334                                         new_fund,
11335                                         xfer_amount,
11336                                         user_id,
11337                                         'Rollover'
11338                                 );
11339                         ELSE
11340                                 --
11341                                 -- Transfer balance from old fund to the void
11342                                 --
11343                                 -- RAISE NOTICE 'Transferring % from fund % to the void', xfer_amount, roll_fund.old_fund;
11344                                 --
11345                                 PERFORM acq.transfer_fund(
11346                                         roll_fund.old_fund,
11347                                         xfer_amount,
11348                                         NULL,
11349                                         NULL,
11350                                         user_id,
11351                                         'Rollover'
11352                                 );
11353                         END IF;
11354                 END IF;
11355                 --
11356                 IF roll_fund.rollover THEN
11357                         --
11358                         -- Move any lineitems from the old fund to the new one
11359                         -- where the associated debit is an encumbrance.
11360                         --
11361                         -- Any other tables tying expenditure details to funds should
11362                         -- receive similar treatment.  At this writing there are none.
11363                         --
11364                         UPDATE acq.lineitem_detail
11365                         SET fund = new_fund
11366                         WHERE
11367                         fund = roll_fund.old_fund -- this condition may be redundant
11368                         AND fund_debit in
11369                         (
11370                                 SELECT id
11371                                 FROM acq.fund_debit
11372                                 WHERE
11373                                 fund = roll_fund.old_fund
11374                                 AND encumbrance
11375                         );
11376                         --
11377                         -- Move encumbrance debits from the old fund to the new fund
11378                         --
11379                         UPDATE acq.fund_debit
11380                         SET fund = new_fund
11381                         wHERE
11382                                 fund = roll_fund.old_fund
11383                                 AND encumbrance;
11384                 END IF;
11385                 --
11386                 -- Mark old fund as inactive, now that we've closed it
11387                 --
11388                 UPDATE acq.fund
11389                 SET active = FALSE
11390                 WHERE id = roll_fund.old_fund;
11391         END LOOP;
11392 END;
11393 $$ LANGUAGE plpgsql;
11394
11395 CREATE OR REPLACE FUNCTION public.remove_commas( TEXT ) RETURNS TEXT AS $$
11396     SELECT regexp_replace($1, ',', '', 'g');
11397 $$ LANGUAGE SQL STRICT IMMUTABLE;
11398
11399 CREATE OR REPLACE FUNCTION public.remove_whitespace( TEXT ) RETURNS TEXT AS $$
11400     SELECT regexp_replace(normalize_space($1), E'\\s+', '', 'g');
11401 $$ LANGUAGE SQL STRICT IMMUTABLE;
11402
11403 CREATE TABLE acq.distribution_formula_application (
11404     id BIGSERIAL PRIMARY KEY,
11405     creator INT NOT NULL REFERENCES actor.usr(id) DEFERRABLE INITIALLY DEFERRED,
11406     create_time TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(),
11407     formula INT NOT NULL
11408         REFERENCES acq.distribution_formula(id) DEFERRABLE INITIALLY DEFERRED,
11409     lineitem INT NOT NULL
11410         REFERENCES acq.lineitem( id )
11411                 ON DELETE CASCADE
11412                 DEFERRABLE INITIALLY DEFERRED
11413 );
11414
11415 CREATE INDEX acqdfa_df_idx
11416     ON acq.distribution_formula_application(formula);
11417 CREATE INDEX acqdfa_li_idx
11418     ON acq.distribution_formula_application(lineitem);
11419 CREATE INDEX acqdfa_creator_idx
11420     ON acq.distribution_formula_application(creator);
11421
11422 CREATE TABLE acq.user_request_type (
11423     id      SERIAL  PRIMARY KEY,
11424     label   TEXT    NOT NULL UNIQUE -- i18n-ize
11425 );
11426
11427 INSERT INTO acq.user_request_type (id,label) VALUES (1, oils_i18n_gettext('1', 'Books', 'aurt', 'label'));
11428 INSERT INTO acq.user_request_type (id,label) VALUES (2, oils_i18n_gettext('2', 'Journal/Magazine & Newspaper Articles', 'aurt', 'label'));
11429 INSERT INTO acq.user_request_type (id,label) VALUES (3, oils_i18n_gettext('3', 'Audiobooks', 'aurt', 'label'));
11430 INSERT INTO acq.user_request_type (id,label) VALUES (4, oils_i18n_gettext('4', 'Music', 'aurt', 'label'));
11431 INSERT INTO acq.user_request_type (id,label) VALUES (5, oils_i18n_gettext('5', 'DVDs', 'aurt', 'label'));
11432
11433 SELECT SETVAL('acq.user_request_type_id_seq'::TEXT, 6);
11434
11435 CREATE TABLE acq.cancel_reason (
11436         id            SERIAL            PRIMARY KEY,
11437         org_unit      INTEGER           NOT NULL REFERENCES actor.org_unit( id )
11438                                         DEFERRABLE INITIALLY DEFERRED,
11439         label         TEXT              NOT NULL,
11440         description   TEXT              NOT NULL,
11441         keep_debits   BOOL              NOT NULL DEFAULT FALSE,
11442         CONSTRAINT acq_cancel_reason_one_per_org_unit UNIQUE( org_unit, label )
11443 );
11444
11445 -- Reserve ids 1-999 for stock reasons
11446 -- Reserve ids 1000-1999 for EDI reasons
11447 -- 2000+ are available for staff to create
11448
11449 SELECT SETVAL('acq.cancel_reason_id_seq'::TEXT, 2000);
11450
11451 CREATE TABLE acq.user_request (
11452     id                  SERIAL  PRIMARY KEY,
11453     usr                 INT     NOT NULL REFERENCES actor.usr (id), -- requesting user
11454     hold                BOOL    NOT NULL DEFAULT TRUE,
11455
11456     pickup_lib          INT     NOT NULL REFERENCES actor.org_unit (id), -- pickup lib
11457     holdable_formats    TEXT,           -- nullable, for use in hold creation
11458     phone_notify        TEXT,
11459     email_notify        BOOL    NOT NULL DEFAULT TRUE,
11460     lineitem            INT     REFERENCES acq.lineitem (id) ON DELETE CASCADE,
11461     eg_bib              BIGINT  REFERENCES biblio.record_entry (id) ON DELETE CASCADE,
11462     request_date        TIMESTAMPTZ NOT NULL DEFAULT NOW(), -- when they requested it
11463     need_before         TIMESTAMPTZ,    -- don't create holds after this
11464     max_fee             TEXT,
11465
11466     request_type        INT     NOT NULL REFERENCES acq.user_request_type (id), 
11467     isxn                TEXT,
11468     title               TEXT,
11469     volume              TEXT,
11470     author              TEXT,
11471     article_title       TEXT,
11472     article_pages       TEXT,
11473     publisher           TEXT,
11474     location            TEXT,
11475     pubdate             TEXT,
11476     mentioned           TEXT,
11477     other_info          TEXT,
11478         cancel_reason       INT              REFERENCES acq.cancel_reason( id )
11479                                              DEFERRABLE INITIALLY DEFERRED
11480 );
11481
11482 CREATE TABLE acq.lineitem_alert_text (
11483         id               SERIAL         PRIMARY KEY,
11484         code             TEXT           UNIQUE NOT NULL,
11485         description      TEXT,
11486         owning_lib       INT            NOT NULL
11487                                         REFERENCES actor.org_unit(id)
11488                                         DEFERRABLE INITIALLY DEFERRED,
11489         CONSTRAINT alert_one_code_per_org UNIQUE (code, owning_lib)
11490 );
11491
11492 ALTER TABLE acq.lineitem_note
11493         ADD COLUMN alert_text    INT     REFERENCES acq.lineitem_alert_text(id)
11494                                          DEFERRABLE INITIALLY DEFERRED;
11495
11496 -- add ON DELETE CASCADE clause
11497
11498 ALTER TABLE acq.lineitem_note
11499         DROP CONSTRAINT lineitem_note_lineitem_fkey;
11500
11501 ALTER TABLE acq.lineitem_note
11502         ADD FOREIGN KEY (lineitem) REFERENCES acq.lineitem( id )
11503                 ON DELETE CASCADE
11504                 DEFERRABLE INITIALLY DEFERRED;
11505
11506 ALTER TABLE acq.lineitem_note
11507         ADD COLUMN vendor_public BOOLEAN NOT NULL DEFAULT FALSE;
11508
11509 CREATE TABLE acq.invoice_method (
11510     code    TEXT    PRIMARY KEY,
11511     name    TEXT    NOT NULL -- i18n-ize
11512 );
11513 INSERT INTO acq.invoice_method (code,name) VALUES ('EDI',oils_i18n_gettext('EDI', 'EDI', 'acqim', 'name'));
11514 INSERT INTO acq.invoice_method (code,name) VALUES ('PPR',oils_i18n_gettext('PPR', 'Paper', 'acqit', 'name'));
11515
11516 CREATE TABLE acq.invoice_payment_method (
11517         code      TEXT     PRIMARY KEY,
11518         name      TEXT     NOT NULL
11519 );
11520
11521 CREATE TABLE acq.invoice (
11522     id             SERIAL      PRIMARY KEY,
11523     receiver       INT         NOT NULL REFERENCES actor.org_unit (id),
11524     provider       INT         NOT NULL REFERENCES acq.provider (id),
11525     shipper        INT         NOT NULL REFERENCES acq.provider (id),
11526     recv_date      TIMESTAMPTZ NOT NULL DEFAULT NOW(),
11527     recv_method    TEXT        NOT NULL REFERENCES acq.invoice_method (code) DEFAULT 'EDI',
11528     inv_type       TEXT,       -- A "type" field is desired, but no idea what goes here
11529     inv_ident      TEXT        NOT NULL, -- vendor-supplied invoice id/number
11530         payment_auth   TEXT,
11531         payment_method TEXT        REFERENCES acq.invoice_payment_method (code)
11532                                    DEFERRABLE INITIALLY DEFERRED,
11533         note           TEXT,
11534     complete       BOOL        NOT NULL DEFAULT FALSE,
11535     CONSTRAINT inv_ident_once_per_provider UNIQUE(provider, inv_ident)
11536 );
11537
11538 CREATE TABLE acq.invoice_entry (
11539     id              SERIAL      PRIMARY KEY,
11540     invoice         INT         NOT NULL REFERENCES acq.invoice (id) ON DELETE CASCADE,
11541     purchase_order  INT         REFERENCES acq.purchase_order (id) ON UPDATE CASCADE ON DELETE SET NULL,
11542     lineitem        INT         REFERENCES acq.lineitem (id) ON UPDATE CASCADE ON DELETE SET NULL,
11543     inv_item_count  INT         NOT NULL, -- How many acqlids did they say they sent
11544     phys_item_count INT, -- and how many did staff count
11545     note            TEXT,
11546     billed_per_item BOOL,
11547     cost_billed     NUMERIC(8,2),
11548     actual_cost     NUMERIC(8,2),
11549         amount_paid     NUMERIC (8,2)
11550 );
11551
11552 CREATE TABLE acq.invoice_item_type (
11553     code    TEXT    PRIMARY KEY,
11554     name    TEXT    NOT NULL, -- i18n-ize
11555         prorate BOOL    NOT NULL DEFAULT FALSE
11556 );
11557
11558 INSERT INTO acq.invoice_item_type (code,name) VALUES ('TAX',oils_i18n_gettext('TAX', 'Tax', 'aiit', 'name'));
11559 INSERT INTO acq.invoice_item_type (code,name) VALUES ('PRO',oils_i18n_gettext('PRO', 'Processing Fee', 'aiit', 'name'));
11560 INSERT INTO acq.invoice_item_type (code,name) VALUES ('SHP',oils_i18n_gettext('SHP', 'Shipping Charge', 'aiit', 'name'));
11561 INSERT INTO acq.invoice_item_type (code,name) VALUES ('HND',oils_i18n_gettext('HND', 'Handling Charge', 'aiit', 'name'));
11562 INSERT INTO acq.invoice_item_type (code,name) VALUES ('ITM',oils_i18n_gettext('ITM', 'Non-library Item', 'aiit', 'name'));
11563 INSERT INTO acq.invoice_item_type (code,name) VALUES ('SUB',oils_i18n_gettext('SUB', 'Searial Subscription', 'aiit', 'name'));
11564
11565 CREATE TABLE acq.po_item (
11566         id              SERIAL      PRIMARY KEY,
11567         purchase_order  INT         REFERENCES acq.purchase_order (id)
11568                                     ON UPDATE CASCADE ON DELETE SET NULL
11569                                     DEFERRABLE INITIALLY DEFERRED,
11570         fund_debit      INT         REFERENCES acq.fund_debit (id)
11571                                     DEFERRABLE INITIALLY DEFERRED,
11572         inv_item_type   TEXT        NOT NULL
11573                                     REFERENCES acq.invoice_item_type (code)
11574                                     DEFERRABLE INITIALLY DEFERRED,
11575         title           TEXT,
11576         author          TEXT,
11577         note            TEXT,
11578         estimated_cost  NUMERIC(8,2),
11579         fund            INT         REFERENCES acq.fund (id)
11580                                     DEFERRABLE INITIALLY DEFERRED,
11581         target          BIGINT
11582 );
11583
11584 CREATE TABLE acq.invoice_item ( -- for invoice-only debits: taxes/fees/non-bib items/etc
11585     id              SERIAL      PRIMARY KEY,
11586     invoice         INT         NOT NULL REFERENCES acq.invoice (id) ON UPDATE CASCADE ON DELETE CASCADE,
11587     purchase_order  INT         REFERENCES acq.purchase_order (id) ON UPDATE CASCADE ON DELETE SET NULL,
11588     fund_debit      INT         REFERENCES acq.fund_debit (id),
11589     inv_item_type   TEXT        NOT NULL REFERENCES acq.invoice_item_type (code),
11590     title           TEXT,
11591     author          TEXT,
11592     note            TEXT,
11593     cost_billed     NUMERIC(8,2),
11594     actual_cost     NUMERIC(8,2),
11595     fund            INT         REFERENCES acq.fund (id)
11596                                 DEFERRABLE INITIALLY DEFERRED,
11597     amount_paid     NUMERIC (8,2),
11598     po_item         INT         REFERENCES acq.po_item (id)
11599                                 DEFERRABLE INITIALLY DEFERRED,
11600     target          BIGINT
11601 );
11602
11603 CREATE TABLE acq.edi_message (
11604     id               SERIAL          PRIMARY KEY,
11605     account          INTEGER         REFERENCES acq.edi_account(id)
11606                                      DEFERRABLE INITIALLY DEFERRED,
11607     remote_file      TEXT,
11608     create_time      TIMESTAMPTZ     NOT NULL DEFAULT now(),
11609     translate_time   TIMESTAMPTZ,
11610     process_time     TIMESTAMPTZ,
11611     error_time       TIMESTAMPTZ,
11612     status           TEXT            NOT NULL DEFAULT 'new'
11613                                      CONSTRAINT status_value CHECK
11614                                      ( status IN (
11615                                         'new',          -- needs to be translated
11616                                         'translated',   -- needs to be processed
11617                                         'trans_error',  -- error in translation step
11618                                         'processed',    -- needs to have remote_file deleted
11619                                         'proc_error',   -- error in processing step
11620                                         'delete_error', -- error in deletion
11621                                         'retry',        -- need to retry
11622                                         'complete'      -- done
11623                                      )),
11624     edi              TEXT,
11625     jedi             TEXT,
11626     error            TEXT,
11627     purchase_order   INT             REFERENCES acq.purchase_order
11628                                      DEFERRABLE INITIALLY DEFERRED,
11629     message_type     TEXT            NOT NULL CONSTRAINT valid_message_type
11630                                      CHECK ( message_type IN (
11631                                         'ORDERS',
11632                                         'ORDRSP',
11633                                         'INVOIC',
11634                                         'OSTENQ',
11635                                         'OSTRPT'
11636                                      ))
11637 );
11638
11639 ALTER TABLE actor.org_address ADD COLUMN san TEXT;
11640
11641 ALTER TABLE acq.provider_address
11642         ADD COLUMN fax_phone TEXT;
11643
11644 ALTER TABLE acq.provider_contact_address
11645         ADD COLUMN fax_phone TEXT;
11646
11647 CREATE TABLE acq.provider_note (
11648     id      SERIAL              PRIMARY KEY,
11649     provider    INT             NOT NULL REFERENCES acq.provider (id) DEFERRABLE INITIALLY DEFERRED,
11650     creator     INT             NOT NULL REFERENCES actor.usr (id) DEFERRABLE INITIALLY DEFERRED,
11651     editor      INT             NOT NULL REFERENCES actor.usr (id) DEFERRABLE INITIALLY DEFERRED,
11652     create_time TIMESTAMP WITH TIME ZONE    NOT NULL DEFAULT NOW(),
11653     edit_time   TIMESTAMP WITH TIME ZONE    NOT NULL DEFAULT NOW(),
11654     value       TEXT            NOT NULL
11655 );
11656 CREATE INDEX acq_pro_note_pro_idx      ON acq.provider_note ( provider );
11657 CREATE INDEX acq_pro_note_creator_idx  ON acq.provider_note ( creator );
11658 CREATE INDEX acq_pro_note_editor_idx   ON acq.provider_note ( editor );
11659
11660 -- For each fund: the total allocation from all sources, in the
11661 -- currency of the fund (or 0 if there are no allocations)
11662
11663 CREATE VIEW acq.all_fund_allocation_total AS
11664 SELECT
11665     f.id AS fund,
11666     COALESCE( SUM( a.amount * acq.exchange_ratio(
11667         s.currency_type, f.currency_type))::numeric(100,2), 0 )
11668     AS amount
11669 FROM
11670     acq.fund f
11671         LEFT JOIN acq.fund_allocation a
11672             ON a.fund = f.id
11673         LEFT JOIN acq.funding_source s
11674             ON a.funding_source = s.id
11675 GROUP BY
11676     f.id;
11677
11678 -- For every fund: the total encumbrances (or 0 if none),
11679 -- in the currency of the fund.
11680
11681 CREATE VIEW acq.all_fund_encumbrance_total AS
11682 SELECT
11683         f.id AS fund,
11684         COALESCE( encumb.amount, 0 ) AS amount
11685 FROM
11686         acq.fund AS f
11687                 LEFT JOIN (
11688                         SELECT
11689                                 fund,
11690                                 sum( amount ) AS amount
11691                         FROM
11692                                 acq.fund_debit
11693                         WHERE
11694                                 encumbrance
11695                         GROUP BY fund
11696                 ) AS encumb
11697                         ON f.id = encumb.fund;
11698
11699 -- For every fund: the total spent (or 0 if none),
11700 -- in the currency of the fund.
11701
11702 CREATE VIEW acq.all_fund_spent_total AS
11703 SELECT
11704     f.id AS fund,
11705     COALESCE( spent.amount, 0 ) AS amount
11706 FROM
11707     acq.fund AS f
11708         LEFT JOIN (
11709             SELECT
11710                 fund,
11711                 sum( amount ) AS amount
11712             FROM
11713                 acq.fund_debit
11714             WHERE
11715                 NOT encumbrance
11716             GROUP BY fund
11717         ) AS spent
11718             ON f.id = spent.fund;
11719
11720 -- For each fund: the amount not yet spent, in the currency
11721 -- of the fund.  May include encumbrances.
11722
11723 CREATE VIEW acq.all_fund_spent_balance AS
11724 SELECT
11725         c.fund,
11726         c.amount - d.amount AS amount
11727 FROM acq.all_fund_allocation_total c
11728     LEFT JOIN acq.all_fund_spent_total d USING (fund);
11729
11730 -- For each fund: the amount neither spent nor encumbered,
11731 -- in the currency of the fund
11732
11733 CREATE VIEW acq.all_fund_combined_balance AS
11734 SELECT
11735      a.fund,
11736      a.amount - COALESCE( c.amount, 0 ) AS amount
11737 FROM
11738      acq.all_fund_allocation_total a
11739         LEFT OUTER JOIN (
11740             SELECT
11741                 fund,
11742                 SUM( amount ) AS amount
11743             FROM
11744                 acq.fund_debit
11745             GROUP BY
11746                 fund
11747         ) AS c USING ( fund );
11748
11749 CREATE OR REPLACE FUNCTION actor.usr_merge(
11750         src_usr INT,
11751         dest_usr INT,
11752         del_addrs BOOLEAN,
11753         del_cards BOOLEAN,
11754         deactivate_cards BOOLEAN
11755 ) RETURNS VOID AS $$
11756 DECLARE
11757         suffix TEXT;
11758         bucket_row RECORD;
11759         picklist_row RECORD;
11760         queue_row RECORD;
11761         folder_row RECORD;
11762 BEGIN
11763
11764     -- do some initial cleanup 
11765     UPDATE actor.usr SET card = NULL WHERE id = src_usr;
11766     UPDATE actor.usr SET mailing_address = NULL WHERE id = src_usr;
11767     UPDATE actor.usr SET billing_address = NULL WHERE id = src_usr;
11768
11769     -- actor.*
11770     IF del_cards THEN
11771         DELETE FROM actor.card where usr = src_usr;
11772     ELSE
11773         IF deactivate_cards THEN
11774             UPDATE actor.card SET active = 'f' WHERE usr = src_usr;
11775         END IF;
11776         UPDATE actor.card SET usr = dest_usr WHERE usr = src_usr;
11777     END IF;
11778
11779
11780     IF del_addrs THEN
11781         DELETE FROM actor.usr_address WHERE usr = src_usr;
11782     ELSE
11783         UPDATE actor.usr_address SET usr = dest_usr WHERE usr = src_usr;
11784     END IF;
11785
11786     UPDATE actor.usr_note SET usr = dest_usr WHERE usr = src_usr;
11787     -- dupes are technically OK in actor.usr_standing_penalty, should manually delete them...
11788     UPDATE actor.usr_standing_penalty SET usr = dest_usr WHERE usr = src_usr;
11789     PERFORM actor.usr_merge_rows('actor.usr_org_unit_opt_in', 'usr', src_usr, dest_usr);
11790     PERFORM actor.usr_merge_rows('actor.usr_setting', 'usr', src_usr, dest_usr);
11791
11792     -- permission.*
11793     PERFORM actor.usr_merge_rows('permission.usr_perm_map', 'usr', src_usr, dest_usr);
11794     PERFORM actor.usr_merge_rows('permission.usr_object_perm_map', 'usr', src_usr, dest_usr);
11795     PERFORM actor.usr_merge_rows('permission.usr_grp_map', 'usr', src_usr, dest_usr);
11796     PERFORM actor.usr_merge_rows('permission.usr_work_ou_map', 'usr', src_usr, dest_usr);
11797
11798
11799     -- container.*
11800         
11801         -- For each *_bucket table: transfer every bucket belonging to src_usr
11802         -- into the custody of dest_usr.
11803         --
11804         -- In order to avoid colliding with an existing bucket owned by
11805         -- the destination user, append the source user's id (in parenthesese)
11806         -- to the name.  If you still get a collision, add successive
11807         -- spaces to the name and keep trying until you succeed.
11808         --
11809         FOR bucket_row in
11810                 SELECT id, name
11811                 FROM   container.biblio_record_entry_bucket
11812                 WHERE  owner = src_usr
11813         LOOP
11814                 suffix := ' (' || src_usr || ')';
11815                 LOOP
11816                         BEGIN
11817                                 UPDATE  container.biblio_record_entry_bucket
11818                                 SET     owner = dest_usr, name = name || suffix
11819                                 WHERE   id = bucket_row.id;
11820                         EXCEPTION WHEN unique_violation THEN
11821                                 suffix := suffix || ' ';
11822                                 CONTINUE;
11823                         END;
11824                         EXIT;
11825                 END LOOP;
11826         END LOOP;
11827
11828         FOR bucket_row in
11829                 SELECT id, name
11830                 FROM   container.call_number_bucket
11831                 WHERE  owner = src_usr
11832         LOOP
11833                 suffix := ' (' || src_usr || ')';
11834                 LOOP
11835                         BEGIN
11836                                 UPDATE  container.call_number_bucket
11837                                 SET     owner = dest_usr, name = name || suffix
11838                                 WHERE   id = bucket_row.id;
11839                         EXCEPTION WHEN unique_violation THEN
11840                                 suffix := suffix || ' ';
11841                                 CONTINUE;
11842                         END;
11843                         EXIT;
11844                 END LOOP;
11845         END LOOP;
11846
11847         FOR bucket_row in
11848                 SELECT id, name
11849                 FROM   container.copy_bucket
11850                 WHERE  owner = src_usr
11851         LOOP
11852                 suffix := ' (' || src_usr || ')';
11853                 LOOP
11854                         BEGIN
11855                                 UPDATE  container.copy_bucket
11856                                 SET     owner = dest_usr, name = name || suffix
11857                                 WHERE   id = bucket_row.id;
11858                         EXCEPTION WHEN unique_violation THEN
11859                                 suffix := suffix || ' ';
11860                                 CONTINUE;
11861                         END;
11862                         EXIT;
11863                 END LOOP;
11864         END LOOP;
11865
11866         FOR bucket_row in
11867                 SELECT id, name
11868                 FROM   container.user_bucket
11869                 WHERE  owner = src_usr
11870         LOOP
11871                 suffix := ' (' || src_usr || ')';
11872                 LOOP
11873                         BEGIN
11874                                 UPDATE  container.user_bucket
11875                                 SET     owner = dest_usr, name = name || suffix
11876                                 WHERE   id = bucket_row.id;
11877                         EXCEPTION WHEN unique_violation THEN
11878                                 suffix := suffix || ' ';
11879                                 CONTINUE;
11880                         END;
11881                         EXIT;
11882                 END LOOP;
11883         END LOOP;
11884
11885         UPDATE container.user_bucket_item SET target_user = dest_usr WHERE target_user = src_usr;
11886
11887     -- vandelay.*
11888         -- transfer queues the same way we transfer buckets (see above)
11889         FOR queue_row in
11890                 SELECT id, name
11891                 FROM   vandelay.queue
11892                 WHERE  owner = src_usr
11893         LOOP
11894                 suffix := ' (' || src_usr || ')';
11895                 LOOP
11896                         BEGIN
11897                                 UPDATE  vandelay.queue
11898                                 SET     owner = dest_usr, name = name || suffix
11899                                 WHERE   id = queue_row.id;
11900                         EXCEPTION WHEN unique_violation THEN
11901                                 suffix := suffix || ' ';
11902                                 CONTINUE;
11903                         END;
11904                         EXIT;
11905                 END LOOP;
11906         END LOOP;
11907
11908     -- money.*
11909     PERFORM actor.usr_merge_rows('money.collections_tracker', 'usr', src_usr, dest_usr);
11910     PERFORM actor.usr_merge_rows('money.collections_tracker', 'collector', src_usr, dest_usr);
11911     UPDATE money.billable_xact SET usr = dest_usr WHERE usr = src_usr;
11912     UPDATE money.billing SET voider = dest_usr WHERE voider = src_usr;
11913     UPDATE money.bnm_payment SET accepting_usr = dest_usr WHERE accepting_usr = src_usr;
11914
11915     -- action.*
11916     UPDATE action.circulation SET usr = dest_usr WHERE usr = src_usr;
11917     UPDATE action.circulation SET circ_staff = dest_usr WHERE circ_staff = src_usr;
11918     UPDATE action.circulation SET checkin_staff = dest_usr WHERE checkin_staff = src_usr;
11919
11920     UPDATE action.hold_request SET usr = dest_usr WHERE usr = src_usr;
11921     UPDATE action.hold_request SET fulfillment_staff = dest_usr WHERE fulfillment_staff = src_usr;
11922     UPDATE action.hold_request SET requestor = dest_usr WHERE requestor = src_usr;
11923     UPDATE action.hold_notification SET notify_staff = dest_usr WHERE notify_staff = src_usr;
11924
11925     UPDATE action.in_house_use SET staff = dest_usr WHERE staff = src_usr;
11926     UPDATE action.non_cataloged_circulation SET staff = dest_usr WHERE staff = src_usr;
11927     UPDATE action.non_cataloged_circulation SET patron = dest_usr WHERE patron = src_usr;
11928     UPDATE action.non_cat_in_house_use SET staff = dest_usr WHERE staff = src_usr;
11929     UPDATE action.survey_response SET usr = dest_usr WHERE usr = src_usr;
11930
11931     -- acq.*
11932     UPDATE acq.fund_allocation SET allocator = dest_usr WHERE allocator = src_usr;
11933     UPDATE acq.fund_transfer SET transfer_user = dest_usr WHERE transfer_user = src_usr;
11934
11935         -- transfer picklists the same way we transfer buckets (see above)
11936         FOR picklist_row in
11937                 SELECT id, name
11938                 FROM   acq.picklist
11939                 WHERE  owner = src_usr
11940         LOOP
11941                 suffix := ' (' || src_usr || ')';
11942                 LOOP
11943                         BEGIN
11944                                 UPDATE  acq.picklist
11945                                 SET     owner = dest_usr, name = name || suffix
11946                                 WHERE   id = picklist_row.id;
11947                         EXCEPTION WHEN unique_violation THEN
11948                                 suffix := suffix || ' ';
11949                                 CONTINUE;
11950                         END;
11951                         EXIT;
11952                 END LOOP;
11953         END LOOP;
11954
11955     UPDATE acq.purchase_order SET owner = dest_usr WHERE owner = src_usr;
11956     UPDATE acq.po_note SET creator = dest_usr WHERE creator = src_usr;
11957     UPDATE acq.po_note SET editor = dest_usr WHERE editor = src_usr;
11958         UPDATE acq.provider_note SET creator = dest_usr WHERE creator = src_usr;
11959         UPDATE acq.provider_note SET editor = dest_usr WHERE editor = src_usr;
11960     UPDATE acq.lineitem_note SET creator = dest_usr WHERE creator = src_usr;
11961     UPDATE acq.lineitem_note SET editor = dest_usr WHERE editor = src_usr;
11962     UPDATE acq.lineitem_usr_attr_definition SET usr = dest_usr WHERE usr = src_usr;
11963
11964     -- asset.*
11965     UPDATE asset.copy SET creator = dest_usr WHERE creator = src_usr;
11966     UPDATE asset.copy SET editor = dest_usr WHERE editor = src_usr;
11967     UPDATE asset.copy_note SET creator = dest_usr WHERE creator = src_usr;
11968     UPDATE asset.call_number SET creator = dest_usr WHERE creator = src_usr;
11969     UPDATE asset.call_number SET editor = dest_usr WHERE editor = src_usr;
11970     UPDATE asset.call_number_note SET creator = dest_usr WHERE creator = src_usr;
11971
11972     -- serial.*
11973     UPDATE serial.record_entry SET creator = dest_usr WHERE creator = src_usr;
11974     UPDATE serial.record_entry SET editor = dest_usr WHERE editor = src_usr;
11975
11976     -- reporter.*
11977     -- It's not uncommon to define the reporter schema in a replica 
11978     -- DB only, so don't assume these tables exist in the write DB.
11979     BEGIN
11980         UPDATE reporter.template SET owner = dest_usr WHERE owner = src_usr;
11981     EXCEPTION WHEN undefined_table THEN
11982         -- do nothing
11983     END;
11984     BEGIN
11985         UPDATE reporter.report SET owner = dest_usr WHERE owner = src_usr;
11986     EXCEPTION WHEN undefined_table THEN
11987         -- do nothing
11988     END;
11989     BEGIN
11990         UPDATE reporter.schedule SET runner = dest_usr WHERE runner = src_usr;
11991     EXCEPTION WHEN undefined_table THEN
11992         -- do nothing
11993     END;
11994     BEGIN
11995                 -- transfer folders the same way we transfer buckets (see above)
11996                 FOR folder_row in
11997                         SELECT id, name
11998                         FROM   reporter.template_folder
11999                         WHERE  owner = src_usr
12000                 LOOP
12001                         suffix := ' (' || src_usr || ')';
12002                         LOOP
12003                                 BEGIN
12004                                         UPDATE  reporter.template_folder
12005                                         SET     owner = dest_usr, name = name || suffix
12006                                         WHERE   id = folder_row.id;
12007                                 EXCEPTION WHEN unique_violation THEN
12008                                         suffix := suffix || ' ';
12009                                         CONTINUE;
12010                                 END;
12011                                 EXIT;
12012                         END LOOP;
12013                 END LOOP;
12014     EXCEPTION WHEN undefined_table THEN
12015         -- do nothing
12016     END;
12017     BEGIN
12018                 -- transfer folders the same way we transfer buckets (see above)
12019                 FOR folder_row in
12020                         SELECT id, name
12021                         FROM   reporter.report_folder
12022                         WHERE  owner = src_usr
12023                 LOOP
12024                         suffix := ' (' || src_usr || ')';
12025                         LOOP
12026                                 BEGIN
12027                                         UPDATE  reporter.report_folder
12028                                         SET     owner = dest_usr, name = name || suffix
12029                                         WHERE   id = folder_row.id;
12030                                 EXCEPTION WHEN unique_violation THEN
12031                                         suffix := suffix || ' ';
12032                                         CONTINUE;
12033                                 END;
12034                                 EXIT;
12035                         END LOOP;
12036                 END LOOP;
12037     EXCEPTION WHEN undefined_table THEN
12038         -- do nothing
12039     END;
12040     BEGIN
12041                 -- transfer folders the same way we transfer buckets (see above)
12042                 FOR folder_row in
12043                         SELECT id, name
12044                         FROM   reporter.output_folder
12045                         WHERE  owner = src_usr
12046                 LOOP
12047                         suffix := ' (' || src_usr || ')';
12048                         LOOP
12049                                 BEGIN
12050                                         UPDATE  reporter.output_folder
12051                                         SET     owner = dest_usr, name = name || suffix
12052                                         WHERE   id = folder_row.id;
12053                                 EXCEPTION WHEN unique_violation THEN
12054                                         suffix := suffix || ' ';
12055                                         CONTINUE;
12056                                 END;
12057                                 EXIT;
12058                         END LOOP;
12059                 END LOOP;
12060     EXCEPTION WHEN undefined_table THEN
12061         -- do nothing
12062     END;
12063
12064     -- Finally, delete the source user
12065     DELETE FROM actor.usr WHERE id = src_usr;
12066
12067 END;
12068 $$ LANGUAGE plpgsql;
12069
12070 -- The "add" trigger functions should protect against existing NULLed values, just in case
12071 CREATE OR REPLACE FUNCTION money.materialized_summary_billing_add () RETURNS TRIGGER AS $$
12072 BEGIN
12073     IF NOT NEW.voided THEN
12074         UPDATE  money.materialized_billable_xact_summary
12075           SET   total_owed = COALESCE(total_owed, 0.0::numeric) + NEW.amount,
12076             last_billing_ts = NEW.billing_ts,
12077             last_billing_note = NEW.note,
12078             last_billing_type = NEW.billing_type,
12079             balance_owed = balance_owed + NEW.amount
12080           WHERE id = NEW.xact;
12081     END IF;
12082
12083     RETURN NEW;
12084 END;
12085 $$ LANGUAGE PLPGSQL;
12086
12087 CREATE OR REPLACE FUNCTION money.materialized_summary_payment_add () RETURNS TRIGGER AS $$
12088 BEGIN
12089     IF NOT NEW.voided THEN
12090         UPDATE  money.materialized_billable_xact_summary
12091           SET   total_paid = COALESCE(total_paid, 0.0::numeric) + NEW.amount,
12092             last_payment_ts = NEW.payment_ts,
12093             last_payment_note = NEW.note,
12094             last_payment_type = TG_ARGV[0],
12095             balance_owed = balance_owed - NEW.amount
12096           WHERE id = NEW.xact;
12097     END IF;
12098
12099     RETURN NEW;
12100 END;
12101 $$ LANGUAGE PLPGSQL;
12102
12103 -- Refresh the mat view with the corrected underlying view
12104 TRUNCATE money.materialized_billable_xact_summary;
12105 INSERT INTO money.materialized_billable_xact_summary SELECT * FROM money.billable_xact_summary;
12106
12107 CREATE OR REPLACE FUNCTION permission.usr_has_perm_at_nd(
12108     user_id    IN INTEGER,
12109     perm_code  IN TEXT
12110 )
12111 RETURNS SETOF INTEGER AS $$
12112 --
12113 -- Return a set of all the org units for which a given user has a given
12114 -- permission, granted directly (not through inheritance from a parent
12115 -- org unit).
12116 --
12117 -- The permissions apply to a minimum depth of the org unit hierarchy,
12118 -- for the org unit(s) to which the user is assigned.  (They also apply
12119 -- to the subordinates of those org units, but we don't report the
12120 -- subordinates here.)
12121 --
12122 -- For purposes of this function, the permission.usr_work_ou_map table
12123 -- defines which users belong to which org units.  I.e. we ignore the
12124 -- home_ou column of actor.usr.
12125 --
12126 -- The result set may contain duplicates, which should be eliminated
12127 -- by a DISTINCT clause.
12128 --
12129 DECLARE
12130     b_super       BOOLEAN;
12131     n_perm        INTEGER;
12132     n_min_depth   INTEGER;
12133     n_work_ou     INTEGER;
12134     n_curr_ou     INTEGER;
12135     n_depth       INTEGER;
12136     n_curr_depth  INTEGER;
12137 BEGIN
12138     --
12139     -- Check for superuser
12140     --
12141     SELECT INTO b_super
12142         super_user
12143     FROM
12144         actor.usr
12145     WHERE
12146         id = user_id;
12147     --
12148     IF NOT FOUND THEN
12149         return;             -- No user?  No permissions.
12150     ELSIF b_super THEN
12151         --
12152         -- Super user has all permissions everywhere
12153         --
12154         FOR n_work_ou IN
12155             SELECT
12156                 id
12157             FROM
12158                 actor.org_unit
12159             WHERE
12160                 parent_ou IS NULL
12161         LOOP
12162             RETURN NEXT n_work_ou;
12163         END LOOP;
12164         RETURN;
12165     END IF;
12166     --
12167     -- Translate the permission name
12168     -- to a numeric permission id
12169     --
12170     SELECT INTO n_perm
12171         id
12172     FROM
12173         permission.perm_list
12174     WHERE
12175         code = perm_code;
12176     --
12177     IF NOT FOUND THEN
12178         RETURN;               -- No such permission
12179     END IF;
12180     --
12181     -- Find the highest-level org unit (i.e. the minimum depth)
12182     -- to which the permission is applied for this user
12183     --
12184     -- This query is modified from the one in permission.usr_perms().
12185     --
12186     SELECT INTO n_min_depth
12187         min( depth )
12188     FROM    (
12189         SELECT depth
12190           FROM permission.usr_perm_map upm
12191          WHERE upm.usr = user_id
12192            AND (upm.perm = n_perm OR upm.perm = -1)
12193                     UNION
12194         SELECT  gpm.depth
12195           FROM  permission.grp_perm_map gpm
12196           WHERE (gpm.perm = n_perm OR gpm.perm = -1)
12197             AND gpm.grp IN (
12198                SELECT   (permission.grp_ancestors(
12199                     (SELECT profile FROM actor.usr WHERE id = user_id)
12200                 )).id
12201             )
12202                     UNION
12203         SELECT  p.depth
12204           FROM  permission.grp_perm_map p
12205           WHERE (p.perm = n_perm OR p.perm = -1)
12206             AND p.grp IN (
12207                 SELECT (permission.grp_ancestors(m.grp)).id
12208                 FROM   permission.usr_grp_map m
12209                 WHERE  m.usr = user_id
12210             )
12211     ) AS x;
12212     --
12213     IF NOT FOUND THEN
12214         RETURN;                -- No such permission for this user
12215     END IF;
12216     --
12217     -- Identify the org units to which the user is assigned.  Note that
12218     -- we pay no attention to the home_ou column in actor.usr.
12219     --
12220     FOR n_work_ou IN
12221         SELECT
12222             work_ou
12223         FROM
12224             permission.usr_work_ou_map
12225         WHERE
12226             usr = user_id
12227     LOOP            -- For each org unit to which the user is assigned
12228         --
12229         -- Determine the level of the org unit by a lookup in actor.org_unit_type.
12230         -- We take it on faith that this depth agrees with the actual hierarchy
12231         -- defined in actor.org_unit.
12232         --
12233         SELECT INTO n_depth
12234             type.depth
12235         FROM
12236             actor.org_unit_type type
12237                 INNER JOIN actor.org_unit ou
12238                     ON ( ou.ou_type = type.id )
12239         WHERE
12240             ou.id = n_work_ou;
12241         --
12242         IF NOT FOUND THEN
12243             CONTINUE;        -- Maybe raise exception?
12244         END IF;
12245         --
12246         -- Compare the depth of the work org unit to the
12247         -- minimum depth, and branch accordingly
12248         --
12249         IF n_depth = n_min_depth THEN
12250             --
12251             -- The org unit is at the right depth, so return it.
12252             --
12253             RETURN NEXT n_work_ou;
12254         ELSIF n_depth > n_min_depth THEN
12255             --
12256             -- Traverse the org unit tree toward the root,
12257             -- until you reach the minimum depth determined above
12258             --
12259             n_curr_depth := n_depth;
12260             n_curr_ou := n_work_ou;
12261             WHILE n_curr_depth > n_min_depth LOOP
12262                 SELECT INTO n_curr_ou
12263                     parent_ou
12264                 FROM
12265                     actor.org_unit
12266                 WHERE
12267                     id = n_curr_ou;
12268                 --
12269                 IF FOUND THEN
12270                     n_curr_depth := n_curr_depth - 1;
12271                 ELSE
12272                     --
12273                     -- This can happen only if the hierarchy defined in
12274                     -- actor.org_unit is corrupted, or out of sync with
12275                     -- the depths defined in actor.org_unit_type.
12276                     -- Maybe we should raise an exception here, instead
12277                     -- of silently ignoring the problem.
12278                     --
12279                     n_curr_ou = NULL;
12280                     EXIT;
12281                 END IF;
12282             END LOOP;
12283             --
12284             IF n_curr_ou IS NOT NULL THEN
12285                 RETURN NEXT n_curr_ou;
12286             END IF;
12287         ELSE
12288             --
12289             -- The permission applies only at a depth greater than the work org unit.
12290             -- Use connectby() to find all dependent org units at the specified depth.
12291             --
12292             FOR n_curr_ou IN
12293                 SELECT ou::INTEGER
12294                 FROM connectby(
12295                         'actor.org_unit',         -- table name
12296                         'id',                     -- key column
12297                         'parent_ou',              -- recursive foreign key
12298                         n_work_ou::TEXT,          -- id of starting point
12299                         (n_min_depth - n_depth)   -- max depth to search, relative
12300                     )                             --   to starting point
12301                     AS t(
12302                         ou text,            -- dependent org unit
12303                         parent_ou text,     -- (ignore)
12304                         level int           -- depth relative to starting point
12305                     )
12306                 WHERE
12307                     level = n_min_depth - n_depth
12308             LOOP
12309                 RETURN NEXT n_curr_ou;
12310             END LOOP;
12311         END IF;
12312         --
12313     END LOOP;
12314     --
12315     RETURN;
12316     --
12317 END;
12318 $$ LANGUAGE 'plpgsql';
12319
12320 ALTER TABLE acq.purchase_order
12321         ADD COLUMN cancel_reason        INT REFERENCES acq.cancel_reason( id )
12322                                             DEFERRABLE INITIALLY DEFERRED;
12323
12324 ALTER TABLE acq.acq_purchase_order_history
12325         ADD COLUMN cancel_reason INTEGER;
12326
12327 ALTER TABLE acq.purchase_order
12328         ADD COLUMN prepayment_required BOOLEAN NOT NULL DEFAULT FALSE;
12329
12330 ALTER TABLE acq.acq_purchase_order_history
12331         ADD COLUMN prepayment_required BOOLEAN NOT NULL DEFAULT FALSE;
12332
12333 ALTER TABLE acq.lineitem
12334         ADD COLUMN cancel_reason        INT REFERENCES acq.cancel_reason( id )
12335                                             DEFERRABLE INITIALLY DEFERRED;
12336
12337 ALTER TABLE acq.acq_lineitem_history
12338         ADD COLUMN cancel_reason INTEGER;
12339
12340 ALTER TABLE acq.lineitem
12341         ADD COLUMN estimated_unit_price NUMERIC;
12342
12343 ALTER TABLE acq.acq_lineitem_history
12344         ADD COLUMN estimated_unit_price NUMERIC;
12345
12346 ALTER TABLE acq.lineitem
12347         ADD COLUMN claim_policy INT
12348                 REFERENCES acq.claim_policy
12349                 DEFERRABLE INITIALLY DEFERRED;
12350
12351 ALTER TABLE acq.acq_lineitem_history
12352         ADD COLUMN claim_policy INT
12353                 REFERENCES acq.claim_policy
12354                 DEFERRABLE INITIALLY DEFERRED;
12355
12356 ALTER TABLE acq.lineitem_detail
12357         ADD COLUMN cancel_reason        INT REFERENCES acq.cancel_reason( id )
12358                                             DEFERRABLE INITIALLY DEFERRED;
12359
12360 ALTER TABLE acq.lineitem_detail
12361         DROP CONSTRAINT lineitem_detail_lineitem_fkey;
12362
12363 ALTER TABLE acq.lineitem_detail
12364         ADD FOREIGN KEY (lineitem) REFERENCES acq.lineitem( id )
12365                 ON DELETE CASCADE
12366                 DEFERRABLE INITIALLY DEFERRED;
12367
12368 ALTER TABLE acq.lineitem_detail DROP CONSTRAINT lineitem_detail_eg_copy_id_fkey;
12369
12370 INSERT INTO acq.cancel_reason ( id, org_unit, label, description ) VALUES (
12371         1, 1, 'invalid_isbn', oils_i18n_gettext( 1, 'ISBN is unrecognizable', 'acqcr', 'label' ));
12372
12373 INSERT INTO acq.cancel_reason ( id, org_unit, label, description ) VALUES (
12374         2, 1, 'postpone', oils_i18n_gettext( 2, 'Title has been postponed', 'acqcr', 'label' ));
12375
12376 CREATE OR REPLACE FUNCTION vandelay.add_field ( target_xml TEXT, source_xml TEXT, field TEXT ) RETURNS TEXT AS $_$
12377
12378     use MARC::Record;
12379     use MARC::File::XML (BinaryEncoding => 'UTF-8');
12380     use strict;
12381
12382     my $target_xml = shift;
12383     my $source_xml = shift;
12384     my $field_spec = shift;
12385
12386     my $target_r = MARC::Record->new_from_xml( $target_xml );
12387     my $source_r = MARC::Record->new_from_xml( $source_xml );
12388
12389     return $target_xml unless ($target_r && $source_r);
12390
12391     my @field_list = split(',', $field_spec);
12392
12393     my %fields;
12394     for my $f (@field_list) {
12395         $f =~ s/^\s*//; $f =~ s/\s*$//;
12396         if ($f =~ /^(.{3})(\w*)(?:\[([^]]*)\])?$/) {
12397             my $field = $1;
12398             $field =~ s/\s+//;
12399             my $sf = $2;
12400             $sf =~ s/\s+//;
12401             my $match = $3;
12402             $match =~ s/^\s*//; $match =~ s/\s*$//;
12403             $fields{$field} = { sf => [ split('', $sf) ] };
12404             if ($match) {
12405                 my ($msf,$mre) = split('~', $match);
12406                 if (length($msf) > 0 and length($mre) > 0) {
12407                     $msf =~ s/^\s*//; $msf =~ s/\s*$//;
12408                     $mre =~ s/^\s*//; $mre =~ s/\s*$//;
12409                     $fields{$field}{match} = { sf => $msf, re => qr/$mre/ };
12410                 }
12411             }
12412         }
12413     }
12414
12415     for my $f ( keys %fields) {
12416         if ( @{$fields{$f}{sf}} ) {
12417             for my $from_field ($source_r->field( $f )) {
12418                 for my $to_field ($target_r->field( $f )) {
12419                     if (exists($fields{$f}{match})) {
12420                         next unless (grep { $_ =~ $fields{$f}{match}{re} } $to_field->subfield($fields{$f}{match}{sf}));
12421                     }
12422                     my @new_sf = map { ($_ => $from_field->subfield($_)) } @{$fields{$f}{sf}};
12423                     $to_field->add_subfields( @new_sf );
12424                 }
12425             }
12426         } else {
12427             my @new_fields = map { $_->clone } $source_r->field( $f );
12428             $target_r->insert_fields_ordered( @new_fields );
12429         }
12430     }
12431
12432     $target_xml = $target_r->as_xml_record;
12433     $target_xml =~ s/^<\?.+?\?>$//mo;
12434     $target_xml =~ s/\n//sgo;
12435     $target_xml =~ s/>\s+</></sgo;
12436
12437     return $target_xml;
12438
12439 $_$ LANGUAGE PLPERLU;
12440
12441 CREATE OR REPLACE FUNCTION vandelay.strip_field ( xml TEXT, field TEXT ) RETURNS TEXT AS $_$
12442
12443     use MARC::Record;
12444     use MARC::File::XML (BinaryEncoding => 'UTF-8');
12445     use strict;
12446
12447     my $xml = shift;
12448     my $r = MARC::Record->new_from_xml( $xml );
12449
12450     return $xml unless ($r);
12451
12452     my $field_spec = shift;
12453     my @field_list = split(',', $field_spec);
12454
12455     my %fields;
12456     for my $f (@field_list) {
12457         $f =~ s/^\s*//; $f =~ s/\s*$//;
12458         if ($f =~ /^(.{3})(\w*)(?:\[([^]]*)\])?$/) {
12459             my $field = $1;
12460             $field =~ s/\s+//;
12461             my $sf = $2;
12462             $sf =~ s/\s+//;
12463             my $match = $3;
12464             $match =~ s/^\s*//; $match =~ s/\s*$//;
12465             $fields{$field} = { sf => [ split('', $sf) ] };
12466             if ($match) {
12467                 my ($msf,$mre) = split('~', $match);
12468                 if (length($msf) > 0 and length($mre) > 0) {
12469                     $msf =~ s/^\s*//; $msf =~ s/\s*$//;
12470                     $mre =~ s/^\s*//; $mre =~ s/\s*$//;
12471                     $fields{$field}{match} = { sf => $msf, re => qr/$mre/ };
12472                 }
12473             }
12474         }
12475     }
12476
12477     for my $f ( keys %fields) {
12478         for my $to_field ($r->field( $f )) {
12479             if (exists($fields{$f}{match})) {
12480                 next unless (grep { $_ =~ $fields{$f}{match}{re} } $to_field->subfield($fields{$f}{match}{sf}));
12481             }
12482
12483             if ( @{$fields{$f}{sf}} ) {
12484                 $to_field->delete_subfield(code => $fields{$f}{sf});
12485             } else {
12486                 $r->delete_field( $to_field );
12487             }
12488         }
12489     }
12490
12491     $xml = $r->as_xml_record;
12492     $xml =~ s/^<\?.+?\?>$//mo;
12493     $xml =~ s/\n//sgo;
12494     $xml =~ s/>\s+</></sgo;
12495
12496     return $xml;
12497
12498 $_$ LANGUAGE PLPERLU;
12499
12500 CREATE OR REPLACE FUNCTION vandelay.replace_field ( target_xml TEXT, source_xml TEXT, field TEXT ) RETURNS TEXT AS $_$
12501     SELECT vandelay.add_field( vandelay.strip_field( $1, $3), $2, $3 );
12502 $_$ LANGUAGE SQL;
12503
12504 CREATE OR REPLACE FUNCTION vandelay.preserve_field ( incumbent_xml TEXT, incoming_xml TEXT, field TEXT ) RETURNS TEXT AS $_$
12505     SELECT vandelay.add_field( vandelay.strip_field( $2, $3), $1, $3 );
12506 $_$ LANGUAGE SQL;
12507
12508 CREATE VIEW action.unfulfilled_hold_max_loop AS
12509         SELECT  hold,
12510                 max(count) AS max
12511         FROM    action.unfulfilled_hold_loops
12512         GROUP BY 1;
12513
12514 ALTER TABLE acq.lineitem_attr
12515         DROP CONSTRAINT lineitem_attr_lineitem_fkey;
12516
12517 ALTER TABLE acq.lineitem_attr
12518         ADD FOREIGN KEY (lineitem) REFERENCES acq.lineitem( id )
12519                 ON DELETE CASCADE
12520                 DEFERRABLE INITIALLY DEFERRED;
12521
12522 ALTER TABLE acq.po_note
12523         ADD COLUMN vendor_public BOOLEAN NOT NULL DEFAULT FALSE;
12524
12525 CREATE TABLE vandelay.merge_profile (
12526     id              BIGSERIAL   PRIMARY KEY,
12527     owner           INT         NOT NULL REFERENCES actor.org_unit (id) ON UPDATE CASCADE ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED,
12528     name            TEXT        NOT NULL,
12529     add_spec        TEXT,
12530     replace_spec    TEXT,
12531     strip_spec      TEXT,
12532     preserve_spec   TEXT,
12533     CONSTRAINT vand_merge_prof_owner_name_idx UNIQUE (owner,name),
12534     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))
12535 );
12536
12537 CREATE OR REPLACE FUNCTION vandelay.match_bib_record ( ) RETURNS TRIGGER AS $func$
12538 DECLARE
12539     attr        RECORD;
12540     attr_def    RECORD;
12541     eg_rec      RECORD;
12542     id_value    TEXT;
12543     exact_id    BIGINT;
12544 BEGIN
12545
12546     DELETE FROM vandelay.bib_match WHERE queued_record = NEW.id;
12547
12548     SELECT * INTO attr_def FROM vandelay.bib_attr_definition WHERE xpath = '//*[@tag="901"]/*[@code="c"]' ORDER BY id LIMIT 1;
12549
12550     IF attr_def IS NOT NULL AND attr_def.id IS NOT NULL THEN
12551         id_value := extract_marc_field('vandelay.queued_bib_record', NEW.id, attr_def.xpath, attr_def.remove);
12552
12553         IF id_value IS NOT NULL AND id_value <> '' AND id_value ~ $r$^\d+$$r$ THEN
12554             SELECT id INTO exact_id FROM biblio.record_entry WHERE id = id_value::BIGINT AND NOT deleted;
12555             SELECT * INTO attr FROM vandelay.queued_bib_record_attr WHERE record = NEW.id and field = attr_def.id LIMIT 1;
12556             IF exact_id IS NOT NULL THEN
12557                 INSERT INTO vandelay.bib_match (field_type, matched_attr, queued_record, eg_record) VALUES ('id', attr.id, NEW.id, exact_id);
12558             END IF;
12559         END IF;
12560     END IF;
12561
12562     IF exact_id IS NULL THEN
12563         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
12564
12565             -- All numbers? check for an id match
12566             IF (attr.attr_value ~ $r$^\d+$$r$) THEN
12567                 FOR eg_rec IN SELECT * FROM biblio.record_entry WHERE id = attr.attr_value::BIGINT AND deleted IS FALSE LOOP
12568                     INSERT INTO vandelay.bib_match (field_type, matched_attr, queued_record, eg_record) VALUES ('id', attr.id, NEW.id, eg_rec.id);
12569                 END LOOP;
12570             END IF;
12571
12572             -- Looks like an ISBN? check for an isbn match
12573             IF (attr.attr_value ~* $r$^[0-9x]+$$r$ AND character_length(attr.attr_value) IN (10,13)) THEN
12574                 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
12575                     PERFORM id FROM biblio.record_entry WHERE id = eg_rec.record AND deleted IS FALSE;
12576                     IF FOUND THEN
12577                         INSERT INTO vandelay.bib_match (field_type, matched_attr, queued_record, eg_record) VALUES ('isbn', attr.id, NEW.id, eg_rec.record);
12578                     END IF;
12579                 END LOOP;
12580
12581                 -- subcheck for isbn-as-tcn
12582                 FOR eg_rec IN SELECT * FROM biblio.record_entry WHERE tcn_value = 'i' || attr.attr_value AND deleted IS FALSE LOOP
12583                     INSERT INTO vandelay.bib_match (field_type, matched_attr, queued_record, eg_record) VALUES ('tcn_value', attr.id, NEW.id, eg_rec.id);
12584                 END LOOP;
12585             END IF;
12586
12587             -- check for an OCLC tcn_value match
12588             IF (attr.attr_value ~ $r$^o\d+$$r$) THEN
12589                 FOR eg_rec IN SELECT * FROM biblio.record_entry WHERE tcn_value = regexp_replace(attr.attr_value,'^o','ocm') AND deleted IS FALSE LOOP
12590                     INSERT INTO vandelay.bib_match (field_type, matched_attr, queued_record, eg_record) VALUES ('tcn_value', attr.id, NEW.id, eg_rec.id);
12591                 END LOOP;
12592             END IF;
12593
12594             -- check for a direct tcn_value match
12595             FOR eg_rec IN SELECT * FROM biblio.record_entry WHERE tcn_value = attr.attr_value AND deleted IS FALSE LOOP
12596                 INSERT INTO vandelay.bib_match (field_type, matched_attr, queued_record, eg_record) VALUES ('tcn_value', attr.id, NEW.id, eg_rec.id);
12597             END LOOP;
12598
12599             -- check for a direct item barcode match
12600             FOR eg_rec IN
12601                     SELECT  DISTINCT b.*
12602                       FROM  biblio.record_entry b
12603                             JOIN asset.call_number cn ON (cn.record = b.id)
12604                             JOIN asset.copy cp ON (cp.call_number = cn.id)
12605                       WHERE cp.barcode = attr.attr_value AND cp.deleted IS FALSE
12606             LOOP
12607                 INSERT INTO vandelay.bib_match (field_type, matched_attr, queued_record, eg_record) VALUES ('id', attr.id, NEW.id, eg_rec.id);
12608             END LOOP;
12609
12610         END LOOP;
12611     END IF;
12612
12613     RETURN NULL;
12614 END;
12615 $func$ LANGUAGE PLPGSQL;
12616
12617 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 $_$
12618     SELECT vandelay.replace_field( vandelay.add_field( vandelay.strip_field( $1, $5) , $2, $3 ), $2, $4);
12619 $_$ LANGUAGE SQL;
12620
12621 CREATE TYPE vandelay.compile_profile AS (add_rule TEXT, replace_rule TEXT, preserve_rule TEXT, strip_rule TEXT);
12622 CREATE OR REPLACE FUNCTION vandelay.compile_profile ( incoming_xml TEXT ) RETURNS vandelay.compile_profile AS $_$
12623 DECLARE
12624     output              vandelay.compile_profile%ROWTYPE;
12625     profile             vandelay.merge_profile%ROWTYPE;
12626     profile_tmpl        TEXT;
12627     profile_tmpl_owner  TEXT;
12628     add_rule            TEXT := '';
12629     strip_rule          TEXT := '';
12630     replace_rule        TEXT := '';
12631     preserve_rule       TEXT := '';
12632
12633 BEGIN
12634
12635     profile_tmpl := (oils_xpath('//*[@tag="905"]/*[@code="t"]/text()',incoming_xml))[1];
12636     profile_tmpl_owner := (oils_xpath('//*[@tag="905"]/*[@code="o"]/text()',incoming_xml))[1];
12637
12638     IF profile_tmpl IS NOT NULL AND profile_tmpl <> '' AND profile_tmpl_owner IS NOT NULL AND profile_tmpl_owner <> '' THEN
12639         SELECT  p.* INTO profile
12640           FROM  vandelay.merge_profile p
12641                 JOIN actor.org_unit u ON (u.id = p.owner)
12642           WHERE p.name = profile_tmpl
12643                 AND u.shortname = profile_tmpl_owner;
12644
12645         IF profile.id IS NOT NULL THEN
12646             add_rule := COALESCE(profile.add_spec,'');
12647             strip_rule := COALESCE(profile.strip_spec,'');
12648             replace_rule := COALESCE(profile.replace_spec,'');
12649             preserve_rule := COALESCE(profile.preserve_spec,'');
12650         END IF;
12651     END IF;
12652
12653     add_rule := add_rule || ',' || COALESCE(ARRAY_TO_STRING(oils_xpath('//*[@tag="905"]/*[@code="a"]/text()',incoming_xml),''),'');
12654     strip_rule := strip_rule || ',' || COALESCE(ARRAY_TO_STRING(oils_xpath('//*[@tag="905"]/*[@code="d"]/text()',incoming_xml),''),'');
12655     replace_rule := replace_rule || ',' || COALESCE(ARRAY_TO_STRING(oils_xpath('//*[@tag="905"]/*[@code="r"]/text()',incoming_xml),''),'');
12656     preserve_rule := preserve_rule || ',' || COALESCE(ARRAY_TO_STRING(oils_xpath('//*[@tag="905"]/*[@code="p"]/text()',incoming_xml),''),'');
12657
12658     output.add_rule := BTRIM(add_rule,',');
12659     output.replace_rule := BTRIM(replace_rule,',');
12660     output.strip_rule := BTRIM(strip_rule,',');
12661     output.preserve_rule := BTRIM(preserve_rule,',');
12662
12663     RETURN output;
12664 END;
12665 $_$ LANGUAGE PLPGSQL;
12666
12667 -- Template-based marc munging functions
12668 CREATE OR REPLACE FUNCTION vandelay.template_overlay_bib_record ( v_marc TEXT, eg_id BIGINT, merge_profile_id INT ) RETURNS BOOL AS $$
12669 DECLARE
12670     merge_profile   vandelay.merge_profile%ROWTYPE;
12671     dyn_profile     vandelay.compile_profile%ROWTYPE;
12672     editor_string   TEXT;
12673     editor_id       INT;
12674     source_marc     TEXT;
12675     target_marc     TEXT;
12676     eg_marc         TEXT;
12677     replace_rule    TEXT;
12678     match_count     INT;
12679 BEGIN
12680
12681     SELECT  b.marc INTO eg_marc
12682       FROM  biblio.record_entry b
12683       WHERE b.id = eg_id
12684       LIMIT 1;
12685
12686     IF eg_marc IS NULL OR v_marc IS NULL THEN
12687         -- RAISE NOTICE 'no marc for template or bib record';
12688         RETURN FALSE;
12689     END IF;
12690
12691     dyn_profile := vandelay.compile_profile( v_marc );
12692
12693     IF merge_profile_id IS NOT NULL THEN
12694         SELECT * INTO merge_profile FROM vandelay.merge_profile WHERE id = merge_profile_id;
12695         IF FOUND THEN
12696             dyn_profile.add_rule := BTRIM( dyn_profile.add_rule || ',' || COALESCE(merge_profile.add_spec,''), ',');
12697             dyn_profile.strip_rule := BTRIM( dyn_profile.strip_rule || ',' || COALESCE(merge_profile.strip_spec,''), ',');
12698             dyn_profile.replace_rule := BTRIM( dyn_profile.replace_rule || ',' || COALESCE(merge_profile.replace_spec,''), ',');
12699             dyn_profile.preserve_rule := BTRIM( dyn_profile.preserve_rule || ',' || COALESCE(merge_profile.preserve_spec,''), ',');
12700         END IF;
12701     END IF;
12702
12703     IF dyn_profile.replace_rule <> '' AND dyn_profile.preserve_rule <> '' THEN
12704         -- RAISE NOTICE 'both replace [%] and preserve [%] specified', dyn_profile.replace_rule, dyn_profile.preserve_rule;
12705         RETURN FALSE;
12706     END IF;
12707
12708     IF dyn_profile.replace_rule <> '' THEN
12709         source_marc = v_marc;
12710         target_marc = eg_marc;
12711         replace_rule = dyn_profile.replace_rule;
12712     ELSE
12713         source_marc = eg_marc;
12714         target_marc = v_marc;
12715         replace_rule = dyn_profile.preserve_rule;
12716     END IF;
12717
12718     UPDATE  biblio.record_entry
12719       SET   marc = vandelay.merge_record_xml( target_marc, source_marc, dyn_profile.add_rule, replace_rule, dyn_profile.strip_rule )
12720       WHERE id = eg_id;
12721
12722     IF NOT FOUND THEN
12723         -- RAISE NOTICE 'update of biblio.record_entry failed';
12724         RETURN FALSE;
12725     END IF;
12726
12727     RETURN TRUE;
12728
12729 END;
12730 $$ LANGUAGE PLPGSQL;
12731
12732 CREATE OR REPLACE FUNCTION vandelay.template_overlay_bib_record ( v_marc TEXT, eg_id BIGINT) RETURNS BOOL AS $$
12733     SELECT vandelay.template_overlay_bib_record( $1, $2, NULL);
12734 $$ LANGUAGE SQL;
12735
12736 CREATE OR REPLACE FUNCTION vandelay.overlay_bib_record ( import_id BIGINT, eg_id BIGINT, merge_profile_id INT ) RETURNS BOOL AS $$
12737 DECLARE
12738     merge_profile   vandelay.merge_profile%ROWTYPE;
12739     dyn_profile     vandelay.compile_profile%ROWTYPE;
12740     editor_string   TEXT;
12741     editor_id       INT;
12742     source_marc     TEXT;
12743     target_marc     TEXT;
12744     eg_marc         TEXT;
12745     v_marc          TEXT;
12746     replace_rule    TEXT;
12747     match_count     INT;
12748 BEGIN
12749
12750     SELECT  q.marc INTO v_marc
12751       FROM  vandelay.queued_record q
12752             JOIN vandelay.bib_match m ON (m.queued_record = q.id AND q.id = import_id)
12753       LIMIT 1;
12754
12755     IF v_marc IS NULL THEN
12756         -- RAISE NOTICE 'no marc for vandelay or bib record';
12757         RETURN FALSE;
12758     END IF;
12759
12760     IF vandelay.template_overlay_bib_record( v_marc, eg_id, merge_profile_id) THEN
12761         UPDATE  vandelay.queued_bib_record
12762           SET   imported_as = eg_id,
12763                 import_time = NOW()
12764           WHERE id = import_id;
12765
12766         editor_string := (oils_xpath('//*[@tag="905"]/*[@code="u"]/text()',v_marc))[1];
12767
12768         IF editor_string IS NOT NULL AND editor_string <> '' THEN
12769             SELECT usr INTO editor_id FROM actor.card WHERE barcode = editor_string;
12770
12771             IF editor_id IS NULL THEN
12772                 SELECT id INTO editor_id FROM actor.usr WHERE usrname = editor_string;
12773             END IF;
12774
12775             IF editor_id IS NOT NULL THEN
12776                 UPDATE biblio.record_entry SET editor = editor_id WHERE id = eg_id;
12777             END IF;
12778         END IF;
12779
12780         RETURN TRUE;
12781     END IF;
12782
12783     -- RAISE NOTICE 'update of biblio.record_entry failed';
12784
12785     RETURN FALSE;
12786
12787 END;
12788 $$ LANGUAGE PLPGSQL;
12789
12790 CREATE OR REPLACE FUNCTION vandelay.auto_overlay_bib_record ( import_id BIGINT, merge_profile_id INT ) RETURNS BOOL AS $$
12791 DECLARE
12792     eg_id           BIGINT;
12793     match_count     INT;
12794     match_attr      vandelay.bib_attr_definition%ROWTYPE;
12795 BEGIN
12796
12797     PERFORM * FROM vandelay.queued_bib_record WHERE import_time IS NOT NULL AND id = import_id;
12798
12799     IF FOUND THEN
12800         -- RAISE NOTICE 'already imported, cannot auto-overlay'
12801         RETURN FALSE;
12802     END IF;
12803
12804     SELECT COUNT(*) INTO match_count FROM vandelay.bib_match WHERE queued_record = import_id;
12805
12806     IF match_count <> 1 THEN
12807         -- RAISE NOTICE 'not an exact match';
12808         RETURN FALSE;
12809     END IF;
12810
12811     SELECT  d.* INTO match_attr
12812       FROM  vandelay.bib_attr_definition d
12813             JOIN vandelay.queued_bib_record_attr a ON (a.field = d.id)
12814             JOIN vandelay.bib_match m ON (m.matched_attr = a.id)
12815       WHERE m.queued_record = import_id;
12816
12817     IF NOT (match_attr.xpath ~ '@tag="901"' AND match_attr.xpath ~ '@code="c"') THEN
12818         -- RAISE NOTICE 'not a 901c match: %', match_attr.xpath;
12819         RETURN FALSE;
12820     END IF;
12821
12822     SELECT  m.eg_record INTO eg_id
12823       FROM  vandelay.bib_match m
12824       WHERE m.queued_record = import_id
12825       LIMIT 1;
12826
12827     IF eg_id IS NULL THEN
12828         RETURN FALSE;
12829     END IF;
12830
12831     RETURN vandelay.overlay_bib_record( import_id, eg_id, merge_profile_id );
12832 END;
12833 $$ LANGUAGE PLPGSQL;
12834
12835 CREATE OR REPLACE FUNCTION vandelay.auto_overlay_bib_queue ( queue_id BIGINT, merge_profile_id INT ) RETURNS SETOF BIGINT AS $$
12836 DECLARE
12837     queued_record   vandelay.queued_bib_record%ROWTYPE;
12838 BEGIN
12839
12840     FOR queued_record IN SELECT * FROM vandelay.queued_bib_record WHERE queue = queue_id AND import_time IS NULL LOOP
12841
12842         IF vandelay.auto_overlay_bib_record( queued_record.id, merge_profile_id ) THEN
12843             RETURN NEXT queued_record.id;
12844         END IF;
12845
12846     END LOOP;
12847
12848     RETURN;
12849
12850 END;
12851 $$ LANGUAGE PLPGSQL;
12852
12853 CREATE OR REPLACE FUNCTION vandelay.auto_overlay_bib_queue ( queue_id BIGINT ) RETURNS SETOF BIGINT AS $$
12854     SELECT * FROM vandelay.auto_overlay_bib_queue( $1, NULL );
12855 $$ LANGUAGE SQL;
12856
12857 CREATE OR REPLACE FUNCTION vandelay.overlay_authority_record ( import_id BIGINT, eg_id BIGINT, merge_profile_id INT ) RETURNS BOOL AS $$
12858 DECLARE
12859     merge_profile   vandelay.merge_profile%ROWTYPE;
12860     dyn_profile     vandelay.compile_profile%ROWTYPE;
12861     source_marc     TEXT;
12862     target_marc     TEXT;
12863     eg_marc         TEXT;
12864     v_marc          TEXT;
12865     replace_rule    TEXT;
12866     match_count     INT;
12867 BEGIN
12868
12869     SELECT  b.marc INTO eg_marc
12870       FROM  authority.record_entry b
12871             JOIN vandelay.authority_match m ON (m.eg_record = b.id AND m.queued_record = import_id)
12872       LIMIT 1;
12873
12874     SELECT  q.marc INTO v_marc
12875       FROM  vandelay.queued_record q
12876             JOIN vandelay.authority_match m ON (m.queued_record = q.id AND q.id = import_id)
12877       LIMIT 1;
12878
12879     IF eg_marc IS NULL OR v_marc IS NULL THEN
12880         -- RAISE NOTICE 'no marc for vandelay or authority record';
12881         RETURN FALSE;
12882     END IF;
12883
12884     dyn_profile := vandelay.compile_profile( v_marc );
12885
12886     IF merge_profile_id IS NOT NULL THEN
12887         SELECT * INTO merge_profile FROM vandelay.merge_profile WHERE id = merge_profile_id;
12888         IF FOUND THEN
12889             dyn_profile.add_rule := BTRIM( dyn_profile.add_rule || ',' || COALESCE(merge_profile.add_spec,''), ',');
12890             dyn_profile.strip_rule := BTRIM( dyn_profile.strip_rule || ',' || COALESCE(merge_profile.strip_spec,''), ',');
12891             dyn_profile.replace_rule := BTRIM( dyn_profile.replace_rule || ',' || COALESCE(merge_profile.replace_spec,''), ',');
12892             dyn_profile.preserve_rule := BTRIM( dyn_profile.preserve_rule || ',' || COALESCE(merge_profile.preserve_spec,''), ',');
12893         END IF;
12894     END IF;
12895
12896     IF dyn_profile.replace_rule <> '' AND dyn_profile.preserve_rule <> '' THEN
12897         -- RAISE NOTICE 'both replace [%] and preserve [%] specified', dyn_profile.replace_rule, dyn_profile.preserve_rule;
12898         RETURN FALSE;
12899     END IF;
12900
12901     IF dyn_profile.replace_rule <> '' THEN
12902         source_marc = v_marc;
12903         target_marc = eg_marc;
12904         replace_rule = dyn_profile.replace_rule;
12905     ELSE
12906         source_marc = eg_marc;
12907         target_marc = v_marc;
12908         replace_rule = dyn_profile.preserve_rule;
12909     END IF;
12910
12911     UPDATE  authority.record_entry
12912       SET   marc = vandelay.merge_record_xml( target_marc, source_marc, dyn_profile.add_rule, replace_rule, dyn_profile.strip_rule )
12913       WHERE id = eg_id;
12914
12915     IF FOUND THEN
12916         UPDATE  vandelay.queued_authority_record
12917           SET   imported_as = eg_id,
12918                 import_time = NOW()
12919           WHERE id = import_id;
12920         RETURN TRUE;
12921     END IF;
12922
12923     -- RAISE NOTICE 'update of authority.record_entry failed';
12924
12925     RETURN FALSE;
12926
12927 END;
12928 $$ LANGUAGE PLPGSQL;
12929
12930 CREATE OR REPLACE FUNCTION vandelay.auto_overlay_authority_record ( import_id BIGINT, merge_profile_id INT ) RETURNS BOOL AS $$
12931 DECLARE
12932     eg_id           BIGINT;
12933     match_count     INT;
12934 BEGIN
12935     SELECT COUNT(*) INTO match_count FROM vandelay.authority_match WHERE queued_record = import_id;
12936
12937     IF match_count <> 1 THEN
12938         -- RAISE NOTICE 'not an exact match';
12939         RETURN FALSE;
12940     END IF;
12941
12942     SELECT  m.eg_record INTO eg_id
12943       FROM  vandelay.authority_match m
12944       WHERE m.queued_record = import_id
12945       LIMIT 1;
12946
12947     IF eg_id IS NULL THEN
12948         RETURN FALSE;
12949     END IF;
12950
12951     RETURN vandelay.overlay_authority_record( import_id, eg_id, merge_profile_id );
12952 END;
12953 $$ LANGUAGE PLPGSQL;
12954
12955 CREATE OR REPLACE FUNCTION vandelay.auto_overlay_authority_queue ( queue_id BIGINT, merge_profile_id INT ) RETURNS SETOF BIGINT AS $$
12956 DECLARE
12957     queued_record   vandelay.queued_authority_record%ROWTYPE;
12958 BEGIN
12959
12960     FOR queued_record IN SELECT * FROM vandelay.queued_authority_record WHERE queue = queue_id AND import_time IS NULL LOOP
12961
12962         IF vandelay.auto_overlay_authority_record( queued_record.id, merge_profile_id ) THEN
12963             RETURN NEXT queued_record.id;
12964         END IF;
12965
12966     END LOOP;
12967
12968     RETURN;
12969
12970 END;
12971 $$ LANGUAGE PLPGSQL;
12972
12973 CREATE OR REPLACE FUNCTION vandelay.auto_overlay_authority_queue ( queue_id BIGINT ) RETURNS SETOF BIGINT AS $$
12974     SELECT * FROM vandelay.auto_overlay_authority_queue( $1, NULL );
12975 $$ LANGUAGE SQL;
12976
12977 CREATE TYPE vandelay.tcn_data AS (tcn TEXT, tcn_source TEXT, used BOOL);
12978 CREATE OR REPLACE FUNCTION vandelay.find_bib_tcn_data ( xml TEXT ) RETURNS SETOF vandelay.tcn_data AS $_$
12979 DECLARE
12980     eg_tcn          TEXT;
12981     eg_tcn_source   TEXT;
12982     output          vandelay.tcn_data%ROWTYPE;
12983 BEGIN
12984
12985     -- 001/003
12986     eg_tcn := BTRIM((oils_xpath('//*[@tag="001"]/text()',xml))[1]);
12987     IF eg_tcn IS NOT NULL AND eg_tcn <> '' THEN
12988
12989         eg_tcn_source := BTRIM((oils_xpath('//*[@tag="003"]/text()',xml))[1]);
12990         IF eg_tcn_source IS NULL OR eg_tcn_source = '' THEN
12991             eg_tcn_source := 'System Local';
12992         END IF;
12993
12994         PERFORM id FROM biblio.record_entry WHERE tcn_value = eg_tcn  AND NOT deleted;
12995
12996         IF NOT FOUND THEN
12997             output.used := FALSE;
12998         ELSE
12999             output.used := TRUE;
13000         END IF;
13001
13002         output.tcn := eg_tcn;
13003         output.tcn_source := eg_tcn_source;
13004         RETURN NEXT output;
13005
13006     END IF;
13007
13008     -- 901 ab
13009     eg_tcn := BTRIM((oils_xpath('//*[@tag="901"]/*[@code="a"]/text()',xml))[1]);
13010     IF eg_tcn IS NOT NULL AND eg_tcn <> '' THEN
13011
13012         eg_tcn_source := BTRIM((oils_xpath('//*[@tag="901"]/*[@code="b"]/text()',xml))[1]);
13013         IF eg_tcn_source IS NULL OR eg_tcn_source = '' THEN
13014             eg_tcn_source := 'System Local';
13015         END IF;
13016
13017         PERFORM id FROM biblio.record_entry WHERE tcn_value = eg_tcn  AND NOT deleted;
13018
13019         IF NOT FOUND THEN
13020             output.used := FALSE;
13021         ELSE
13022             output.used := TRUE;
13023         END IF;
13024
13025         output.tcn := eg_tcn;
13026         output.tcn_source := eg_tcn_source;
13027         RETURN NEXT output;
13028
13029     END IF;
13030
13031     -- 039 ab
13032     eg_tcn := BTRIM((oils_xpath('//*[@tag="039"]/*[@code="a"]/text()',xml))[1]);
13033     IF eg_tcn IS NOT NULL AND eg_tcn <> '' THEN
13034
13035         eg_tcn_source := BTRIM((oils_xpath('//*[@tag="039"]/*[@code="b"]/text()',xml))[1]);
13036         IF eg_tcn_source IS NULL OR eg_tcn_source = '' THEN
13037             eg_tcn_source := 'System Local';
13038         END IF;
13039
13040         PERFORM id FROM biblio.record_entry WHERE tcn_value = eg_tcn  AND NOT deleted;
13041
13042         IF NOT FOUND THEN
13043             output.used := FALSE;
13044         ELSE
13045             output.used := TRUE;
13046         END IF;
13047
13048         output.tcn := eg_tcn;
13049         output.tcn_source := eg_tcn_source;
13050         RETURN NEXT output;
13051
13052     END IF;
13053
13054     -- 020 a
13055     eg_tcn := REGEXP_REPLACE((oils_xpath('//*[@tag="020"]/*[@code="a"]/text()',xml))[1], $re$^(\w+).*?$$re$, $re$\1$re$);
13056     IF eg_tcn IS NOT NULL AND eg_tcn <> '' THEN
13057
13058         eg_tcn_source := 'ISBN';
13059
13060         PERFORM id FROM biblio.record_entry WHERE tcn_value = eg_tcn  AND NOT deleted;
13061
13062         IF NOT FOUND THEN
13063             output.used := FALSE;
13064         ELSE
13065             output.used := TRUE;
13066         END IF;
13067
13068         output.tcn := eg_tcn;
13069         output.tcn_source := eg_tcn_source;
13070         RETURN NEXT output;
13071
13072     END IF;
13073
13074     -- 022 a
13075     eg_tcn := REGEXP_REPLACE((oils_xpath('//*[@tag="022"]/*[@code="a"]/text()',xml))[1], $re$^(\w+).*?$$re$, $re$\1$re$);
13076     IF eg_tcn IS NOT NULL AND eg_tcn <> '' THEN
13077
13078         eg_tcn_source := 'ISSN';
13079
13080         PERFORM id FROM biblio.record_entry WHERE tcn_value = eg_tcn  AND NOT deleted;
13081
13082         IF NOT FOUND THEN
13083             output.used := FALSE;
13084         ELSE
13085             output.used := TRUE;
13086         END IF;
13087
13088         output.tcn := eg_tcn;
13089         output.tcn_source := eg_tcn_source;
13090         RETURN NEXT output;
13091
13092     END IF;
13093
13094     -- 010 a
13095     eg_tcn := REGEXP_REPLACE((oils_xpath('//*[@tag="010"]/*[@code="a"]/text()',xml))[1], $re$^(\w+).*?$$re$, $re$\1$re$);
13096     IF eg_tcn IS NOT NULL AND eg_tcn <> '' THEN
13097
13098         eg_tcn_source := 'LCCN';
13099
13100         PERFORM id FROM biblio.record_entry WHERE tcn_value = eg_tcn  AND NOT deleted;
13101
13102         IF NOT FOUND THEN
13103             output.used := FALSE;
13104         ELSE
13105             output.used := TRUE;
13106         END IF;
13107
13108         output.tcn := eg_tcn;
13109         output.tcn_source := eg_tcn_source;
13110         RETURN NEXT output;
13111
13112     END IF;
13113
13114     -- 035 a
13115     eg_tcn := REGEXP_REPLACE((oils_xpath('//*[@tag="035"]/*[@code="a"]/text()',xml))[1], $re$^.*?(\w+)$$re$, $re$\1$re$);
13116     IF eg_tcn IS NOT NULL AND eg_tcn <> '' THEN
13117
13118         eg_tcn_source := 'System Legacy';
13119
13120         PERFORM id FROM biblio.record_entry WHERE tcn_value = eg_tcn  AND NOT deleted;
13121
13122         IF NOT FOUND THEN
13123             output.used := FALSE;
13124         ELSE
13125             output.used := TRUE;
13126         END IF;
13127
13128         output.tcn := eg_tcn;
13129         output.tcn_source := eg_tcn_source;
13130         RETURN NEXT output;
13131
13132     END IF;
13133
13134     RETURN;
13135 END;
13136 $_$ LANGUAGE PLPGSQL;
13137
13138 CREATE INDEX claim_lid_idx ON acq.claim( lineitem_detail );
13139
13140 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);
13141
13142 UPDATE biblio.record_entry SET marc = '<record xmlns="http://www.loc.gov/MARC21/slim"/>' WHERE id = -1;
13143
13144 CREATE INDEX metabib_title_field_entry_value_idx ON metabib.title_field_entry (SUBSTRING(value,1,1024)) WHERE index_vector = ''::TSVECTOR;
13145 CREATE INDEX metabib_author_field_entry_value_idx ON metabib.author_field_entry (SUBSTRING(value,1,1024)) WHERE index_vector = ''::TSVECTOR;
13146 CREATE INDEX metabib_subject_field_entry_value_idx ON metabib.subject_field_entry (SUBSTRING(value,1,1024)) WHERE index_vector = ''::TSVECTOR;
13147 CREATE INDEX metabib_keyword_field_entry_value_idx ON metabib.keyword_field_entry (SUBSTRING(value,1,1024)) WHERE index_vector = ''::TSVECTOR;
13148 CREATE INDEX metabib_series_field_entry_value_idx ON metabib.series_field_entry (SUBSTRING(value,1,1024)) WHERE index_vector = ''::TSVECTOR;
13149
13150 CREATE INDEX metabib_author_field_entry_source_idx ON metabib.author_field_entry (source);
13151 CREATE INDEX metabib_keyword_field_entry_source_idx ON metabib.keyword_field_entry (source);
13152 CREATE INDEX metabib_title_field_entry_source_idx ON metabib.title_field_entry (source);
13153 CREATE INDEX metabib_series_field_entry_source_idx ON metabib.series_field_entry (source);
13154
13155 CREATE TABLE acq.claim_policy_action (
13156         id              SERIAL       PRIMARY KEY,
13157         claim_policy    INT          NOT NULL REFERENCES acq.claim_policy
13158                                  ON DELETE CASCADE
13159                                      DEFERRABLE INITIALLY DEFERRED,
13160         action_interval INTERVAL     NOT NULL,
13161         action          INT          NOT NULL REFERENCES acq.claim_event_type
13162                                      DEFERRABLE INITIALLY DEFERRED,
13163         CONSTRAINT action_sequence UNIQUE (claim_policy, action_interval)
13164 );
13165
13166 CREATE OR REPLACE FUNCTION public.ingest_acq_marc ( ) RETURNS TRIGGER AS $function$
13167 DECLARE
13168     value       TEXT;
13169     atype       TEXT;
13170     prov        INT;
13171     pos         INT;
13172     adef        RECORD;
13173     xpath_string    TEXT;
13174 BEGIN
13175     FOR adef IN SELECT *,tableoid FROM acq.lineitem_attr_definition LOOP
13176  
13177         SELECT relname::TEXT INTO atype FROM pg_class WHERE oid = adef.tableoid;
13178  
13179         IF (atype NOT IN ('lineitem_usr_attr_definition','lineitem_local_attr_definition')) THEN
13180             IF (atype = 'lineitem_provider_attr_definition') THEN
13181                 SELECT provider INTO prov FROM acq.lineitem_provider_attr_definition WHERE id = adef.id;
13182                 CONTINUE WHEN NEW.provider IS NULL OR prov <> NEW.provider;
13183             END IF;
13184  
13185             IF (atype = 'lineitem_provider_attr_definition') THEN
13186                 SELECT xpath INTO xpath_string FROM acq.lineitem_provider_attr_definition WHERE id = adef.id;
13187             ELSIF (atype = 'lineitem_marc_attr_definition') THEN
13188                 SELECT xpath INTO xpath_string FROM acq.lineitem_marc_attr_definition WHERE id = adef.id;
13189             ELSIF (atype = 'lineitem_generated_attr_definition') THEN
13190                 SELECT xpath INTO xpath_string FROM acq.lineitem_generated_attr_definition WHERE id = adef.id;
13191             END IF;
13192  
13193             xpath_string := REGEXP_REPLACE(xpath_string,$re$//?text\(\)$$re$,'');
13194  
13195             pos := 1;
13196  
13197             LOOP
13198                 SELECT extract_acq_marc_field(id, xpath_string || '[' || pos || ']', adef.remove) INTO value FROM acq.lineitem WHERE id = NEW.id;
13199  
13200                 IF (value IS NOT NULL AND value <> '') THEN
13201                     INSERT INTO acq.lineitem_attr (lineitem, definition, attr_type, attr_name, attr_value)
13202                         VALUES (NEW.id, adef.id, atype, adef.code, value);
13203                 ELSE
13204                     EXIT;
13205                 END IF;
13206  
13207                 pos := pos + 1;
13208             END LOOP;
13209  
13210         END IF;
13211  
13212     END LOOP;
13213  
13214     RETURN NULL;
13215 END;
13216 $function$ LANGUAGE PLPGSQL;
13217
13218 UPDATE config.metabib_field SET label = name;
13219 ALTER TABLE config.metabib_field ALTER COLUMN label SET NOT NULL;
13220
13221 ALTER TABLE config.metabib_field ADD CONSTRAINT field_class_fkey FOREIGN KEY (field_class) REFERENCES config.metabib_class (name);
13222
13223 ALTER TABLE config.metabib_field DROP CONSTRAINT metabib_field_field_class_check;
13224
13225 ALTER TABLE config.metabib_field ADD CONSTRAINT metabib_field_format_fkey FOREIGN KEY (format) REFERENCES config.xml_transform (name);
13226
13227 CREATE TABLE config.metabib_search_alias (
13228     alias       TEXT    PRIMARY KEY,
13229     field_class TEXT    NOT NULL REFERENCES config.metabib_class (name),
13230     field       INT     REFERENCES config.metabib_field (id)
13231 );
13232
13233 INSERT INTO config.metabib_search_alias (alias,field_class) VALUES ('kw','keyword');
13234 INSERT INTO config.metabib_search_alias (alias,field_class) VALUES ('eg.keyword','keyword');
13235 INSERT INTO config.metabib_search_alias (alias,field_class) VALUES ('dc.publisher','keyword');
13236 INSERT INTO config.metabib_search_alias (alias,field_class) VALUES ('dc.identifier','keyword');
13237 INSERT INTO config.metabib_search_alias (alias,field_class) VALUES ('bib.subjecttitle','keyword');
13238 INSERT INTO config.metabib_search_alias (alias,field_class) VALUES ('bib.genre','keyword');
13239 INSERT INTO config.metabib_search_alias (alias,field_class) VALUES ('bib.edition','keyword');
13240 INSERT INTO config.metabib_search_alias (alias,field_class) VALUES ('srw.serverchoice','keyword');
13241
13242 INSERT INTO config.metabib_search_alias (alias,field_class) VALUES ('au','author');
13243 INSERT INTO config.metabib_search_alias (alias,field_class) VALUES ('name','author');
13244 INSERT INTO config.metabib_search_alias (alias,field_class) VALUES ('creator','author');
13245 INSERT INTO config.metabib_search_alias (alias,field_class) VALUES ('eg.author','author');
13246 INSERT INTO config.metabib_search_alias (alias,field_class) VALUES ('eg.name','author');
13247 INSERT INTO config.metabib_search_alias (alias,field_class) VALUES ('dc.creator','author');
13248 INSERT INTO config.metabib_search_alias (alias,field_class) VALUES ('dc.contributor','author');
13249 INSERT INTO config.metabib_search_alias (alias,field_class) VALUES ('bib.name','author');
13250 INSERT INTO config.metabib_search_alias (alias,field_class,field) VALUES ('bib.namepersonal','author',8);
13251 INSERT INTO config.metabib_search_alias (alias,field_class,field) VALUES ('bib.namepersonalfamily','author',8);
13252 INSERT INTO config.metabib_search_alias (alias,field_class,field) VALUES ('bib.namepersonalgiven','author',8);
13253 INSERT INTO config.metabib_search_alias (alias,field_class,field) VALUES ('bib.namecorporate','author',7);
13254 INSERT INTO config.metabib_search_alias (alias,field_class,field) VALUES ('bib.nameconference','author',9);
13255
13256 INSERT INTO config.metabib_search_alias (alias,field_class) VALUES ('ti','title');
13257 INSERT INTO config.metabib_search_alias (alias,field_class) VALUES ('eg.title','title');
13258 INSERT INTO config.metabib_search_alias (alias,field_class) VALUES ('dc.title','title');
13259 INSERT INTO config.metabib_search_alias (alias,field_class,field) VALUES ('bib.titleabbreviated','title',2);
13260 INSERT INTO config.metabib_search_alias (alias,field_class,field) VALUES ('bib.titleuniform','title',5);
13261 INSERT INTO config.metabib_search_alias (alias,field_class,field) VALUES ('bib.titletranslated','title',3);
13262 INSERT INTO config.metabib_search_alias (alias,field_class,field) VALUES ('bib.titlealternative','title',4);
13263 INSERT INTO config.metabib_search_alias (alias,field_class,field) VALUES ('bib.title','title',2);
13264
13265 INSERT INTO config.metabib_search_alias (alias,field_class) VALUES ('su','subject');
13266 INSERT INTO config.metabib_search_alias (alias,field_class) VALUES ('eg.subject','subject');
13267 INSERT INTO config.metabib_search_alias (alias,field_class) VALUES ('dc.subject','subject');
13268 INSERT INTO config.metabib_search_alias (alias,field_class,field) VALUES ('bib.subjectplace','subject',11);
13269 INSERT INTO config.metabib_search_alias (alias,field_class,field) VALUES ('bib.subjectname','subject',12);
13270 INSERT INTO config.metabib_search_alias (alias,field_class,field) VALUES ('bib.subjectoccupation','subject',16);
13271
13272 INSERT INTO config.metabib_search_alias (alias,field_class) VALUES ('se','series');
13273 INSERT INTO config.metabib_search_alias (alias,field_class) VALUES ('eg.series','series');
13274 INSERT INTO config.metabib_search_alias (alias,field_class,field) VALUES ('bib.titleseries','series',1);
13275
13276 UPDATE config.metabib_field SET facet_field=TRUE WHERE id = 1;
13277 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;
13278 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;
13279 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;
13280 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;
13281
13282 UPDATE config.metabib_field SET facet_field=TRUE WHERE id = 11;
13283 UPDATE config.metabib_field SET facet_field=TRUE , facet_xpath=$$*[local-name()='namePart']$$ WHERE id = 12;
13284 UPDATE config.metabib_field SET facet_field=TRUE WHERE id = 13;
13285 UPDATE config.metabib_field SET facet_field=TRUE WHERE id = 14;
13286
13287 CREATE INDEX metabib_rec_descriptor_item_type_idx ON metabib.rec_descriptor (item_type);
13288 CREATE INDEX metabib_rec_descriptor_item_form_idx ON metabib.rec_descriptor (item_form);
13289 CREATE INDEX metabib_rec_descriptor_bib_level_idx ON metabib.rec_descriptor (bib_level);
13290 CREATE INDEX metabib_rec_descriptor_control_type_idx ON metabib.rec_descriptor (control_type);
13291 CREATE INDEX metabib_rec_descriptor_char_encoding_idx ON metabib.rec_descriptor (char_encoding);
13292 CREATE INDEX metabib_rec_descriptor_enc_level_idx ON metabib.rec_descriptor (enc_level);
13293 CREATE INDEX metabib_rec_descriptor_audience_idx ON metabib.rec_descriptor (audience);
13294 CREATE INDEX metabib_rec_descriptor_lit_form_idx ON metabib.rec_descriptor (lit_form);
13295 CREATE INDEX metabib_rec_descriptor_cat_form_idx ON metabib.rec_descriptor (cat_form);
13296 CREATE INDEX metabib_rec_descriptor_pub_status_idx ON metabib.rec_descriptor (pub_status);
13297 CREATE INDEX metabib_rec_descriptor_item_lang_idx ON metabib.rec_descriptor (item_lang);
13298 CREATE INDEX metabib_rec_descriptor_vr_format_idx ON metabib.rec_descriptor (vr_format);
13299 CREATE INDEX metabib_rec_descriptor_date1_idx ON metabib.rec_descriptor (date1);
13300 CREATE INDEX metabib_rec_descriptor_dates_idx ON metabib.rec_descriptor (date1,date2);
13301
13302 CREATE TABLE asset.opac_visible_copies (
13303   id        BIGINT primary key, -- copy id
13304   record    BIGINT,
13305   circ_lib  INTEGER
13306 );
13307 COMMENT ON TABLE asset.opac_visible_copies IS $$
13308 Materialized view of copies that are visible in the OPAC, used by
13309 search.query_parser_fts() to speed up OPAC visibility checks on large
13310 databases.  Contents are maintained by a set of triggers.
13311 $$;
13312 CREATE INDEX opac_visible_copies_idx1 on asset.opac_visible_copies (record, circ_lib);
13313
13314 CREATE OR REPLACE FUNCTION search.query_parser_fts (
13315
13316     param_search_ou INT,
13317     param_depth     INT,
13318     param_query     TEXT,
13319     param_statuses  INT[],
13320     param_locations INT[],
13321     param_offset    INT,
13322     param_check     INT,
13323     param_limit     INT,
13324     metarecord      BOOL,
13325     staff           BOOL
13326  
13327 ) RETURNS SETOF search.search_result AS $func$
13328 DECLARE
13329
13330     current_res         search.search_result%ROWTYPE;
13331     search_org_list     INT[];
13332
13333     check_limit         INT;
13334     core_limit          INT;
13335     core_offset         INT;
13336     tmp_int             INT;
13337
13338     core_result         RECORD;
13339     core_cursor         REFCURSOR;
13340     core_rel_query      TEXT;
13341
13342     total_count         INT := 0;
13343     check_count         INT := 0;
13344     deleted_count       INT := 0;
13345     visible_count       INT := 0;
13346     excluded_count      INT := 0;
13347
13348 BEGIN
13349
13350     check_limit := COALESCE( param_check, 1000 );
13351     core_limit  := COALESCE( param_limit, 25000 );
13352     core_offset := COALESCE( param_offset, 0 );
13353
13354     -- core_skip_chk := COALESCE( param_skip_chk, 1 );
13355
13356     IF param_search_ou > 0 THEN
13357         IF param_depth IS NOT NULL THEN
13358             SELECT array_accum(distinct id) INTO search_org_list FROM actor.org_unit_descendants( param_search_ou, param_depth );
13359         ELSE
13360             SELECT array_accum(distinct id) INTO search_org_list FROM actor.org_unit_descendants( param_search_ou );
13361         END IF;
13362     ELSIF param_search_ou < 0 THEN
13363         SELECT array_accum(distinct org_unit) INTO search_org_list FROM actor.org_lasso_map WHERE lasso = -param_search_ou;
13364     ELSIF param_search_ou = 0 THEN
13365         -- reserved for user lassos (ou_buckets/type='lasso') with ID passed in depth ... hack? sure.
13366     END IF;
13367
13368     OPEN core_cursor FOR EXECUTE param_query;
13369
13370     LOOP
13371
13372         FETCH core_cursor INTO core_result;
13373         EXIT WHEN NOT FOUND;
13374         EXIT WHEN total_count >= core_limit;
13375
13376         total_count := total_count + 1;
13377
13378         CONTINUE WHEN total_count NOT BETWEEN  core_offset + 1 AND check_limit + core_offset;
13379
13380         check_count := check_count + 1;
13381
13382         PERFORM 1 FROM biblio.record_entry b WHERE NOT b.deleted AND b.id IN ( SELECT * FROM search.explode_array( core_result.records ) );
13383         IF NOT FOUND THEN
13384             -- RAISE NOTICE ' % were all deleted ... ', core_result.records;
13385             deleted_count := deleted_count + 1;
13386             CONTINUE;
13387         END IF;
13388
13389         PERFORM 1
13390           FROM  biblio.record_entry b
13391                 JOIN config.bib_source s ON (b.source = s.id)
13392           WHERE s.transcendant
13393                 AND b.id IN ( SELECT * FROM search.explode_array( core_result.records ) );
13394
13395         IF FOUND THEN
13396             -- RAISE NOTICE ' % were all transcendant ... ', core_result.records;
13397             visible_count := visible_count + 1;
13398
13399             current_res.id = core_result.id;
13400             current_res.rel = core_result.rel;
13401
13402             tmp_int := 1;
13403             IF metarecord THEN
13404                 SELECT COUNT(DISTINCT s.source) INTO tmp_int FROM metabib.metarecord_source_map s WHERE s.metarecord = core_result.id;
13405             END IF;
13406
13407             IF tmp_int = 1 THEN
13408                 current_res.record = core_result.records[1];
13409             ELSE
13410                 current_res.record = NULL;
13411             END IF;
13412
13413             RETURN NEXT current_res;
13414
13415             CONTINUE;
13416         END IF;
13417
13418         PERFORM 1
13419           FROM  asset.call_number cn
13420                 JOIN asset.uri_call_number_map map ON (map.call_number = cn.id)
13421                 JOIN asset.uri uri ON (map.uri = uri.id)
13422           WHERE NOT cn.deleted
13423                 AND cn.label = '##URI##'
13424                 AND uri.active
13425                 AND ( param_locations IS NULL OR array_upper(param_locations, 1) IS NULL )
13426                 AND cn.record IN ( SELECT * FROM search.explode_array( core_result.records ) )
13427                 AND cn.owning_lib IN ( SELECT * FROM search.explode_array( search_org_list ) )
13428           LIMIT 1;
13429
13430         IF FOUND THEN
13431             -- RAISE NOTICE ' % have at least one URI ... ', core_result.records;
13432             visible_count := visible_count + 1;
13433
13434             current_res.id = core_result.id;
13435             current_res.rel = core_result.rel;
13436
13437             tmp_int := 1;
13438             IF metarecord THEN
13439                 SELECT COUNT(DISTINCT s.source) INTO tmp_int FROM metabib.metarecord_source_map s WHERE s.metarecord = core_result.id;
13440             END IF;
13441
13442             IF tmp_int = 1 THEN
13443                 current_res.record = core_result.records[1];
13444             ELSE
13445                 current_res.record = NULL;
13446             END IF;
13447
13448             RETURN NEXT current_res;
13449
13450             CONTINUE;
13451         END IF;
13452
13453         IF param_statuses IS NOT NULL AND array_upper(param_statuses, 1) > 0 THEN
13454
13455             PERFORM 1
13456               FROM  asset.call_number cn
13457                     JOIN asset.copy cp ON (cp.call_number = cn.id)
13458               WHERE NOT cn.deleted
13459                     AND NOT cp.deleted
13460                     AND cp.status IN ( SELECT * FROM search.explode_array( param_statuses ) )
13461                     AND cn.record IN ( SELECT * FROM search.explode_array( core_result.records ) )
13462                     AND cp.circ_lib IN ( SELECT * FROM search.explode_array( search_org_list ) )
13463               LIMIT 1;
13464
13465             IF NOT FOUND THEN
13466                 -- RAISE NOTICE ' % were all status-excluded ... ', core_result.records;
13467                 excluded_count := excluded_count + 1;
13468                 CONTINUE;
13469             END IF;
13470
13471         END IF;
13472
13473         IF param_locations IS NOT NULL AND array_upper(param_locations, 1) > 0 THEN
13474
13475             PERFORM 1
13476               FROM  asset.call_number cn
13477                     JOIN asset.copy cp ON (cp.call_number = cn.id)
13478               WHERE NOT cn.deleted
13479                     AND NOT cp.deleted
13480                     AND cp.location IN ( SELECT * FROM search.explode_array( param_locations ) )
13481                     AND cn.record IN ( SELECT * FROM search.explode_array( core_result.records ) )
13482                     AND cp.circ_lib IN ( SELECT * FROM search.explode_array( search_org_list ) )
13483               LIMIT 1;
13484
13485             IF NOT FOUND THEN
13486                 -- RAISE NOTICE ' % were all copy_location-excluded ... ', core_result.records;
13487                 excluded_count := excluded_count + 1;
13488                 CONTINUE;
13489             END IF;
13490
13491         END IF;
13492
13493         IF staff IS NULL OR NOT staff THEN
13494
13495             PERFORM 1
13496               FROM  asset.opac_visible_copies
13497               WHERE circ_lib IN ( SELECT * FROM search.explode_array( search_org_list ) )
13498                     AND record IN ( SELECT * FROM search.explode_array( core_result.records ) )
13499               LIMIT 1;
13500
13501             IF NOT FOUND THEN
13502                 -- RAISE NOTICE ' % were all visibility-excluded ... ', core_result.records;
13503                 excluded_count := excluded_count + 1;
13504                 CONTINUE;
13505             END IF;
13506
13507         ELSE
13508
13509             PERFORM 1
13510               FROM  asset.call_number cn
13511                     JOIN asset.copy cp ON (cp.call_number = cn.id)
13512                     JOIN actor.org_unit a ON (cp.circ_lib = a.id)
13513               WHERE NOT cn.deleted
13514                     AND NOT cp.deleted
13515                     AND cp.circ_lib IN ( SELECT * FROM search.explode_array( search_org_list ) )
13516                     AND cn.record IN ( SELECT * FROM search.explode_array( core_result.records ) )
13517               LIMIT 1;
13518
13519             IF NOT FOUND THEN
13520
13521                 PERFORM 1
13522                   FROM  asset.call_number cn
13523                   WHERE cn.record IN ( SELECT * FROM search.explode_array( core_result.records ) )
13524                   LIMIT 1;
13525
13526                 IF FOUND THEN
13527                     -- RAISE NOTICE ' % were all visibility-excluded ... ', core_result.records;
13528                     excluded_count := excluded_count + 1;
13529                     CONTINUE;
13530                 END IF;
13531
13532             END IF;
13533
13534         END IF;
13535
13536         visible_count := visible_count + 1;
13537
13538         current_res.id = core_result.id;
13539         current_res.rel = core_result.rel;
13540
13541         tmp_int := 1;
13542         IF metarecord THEN
13543             SELECT COUNT(DISTINCT s.source) INTO tmp_int FROM metabib.metarecord_source_map s WHERE s.metarecord = core_result.id;
13544         END IF;
13545
13546         IF tmp_int = 1 THEN
13547             current_res.record = core_result.records[1];
13548         ELSE
13549             current_res.record = NULL;
13550         END IF;
13551
13552         RETURN NEXT current_res;
13553
13554         IF visible_count % 1000 = 0 THEN
13555             -- RAISE NOTICE ' % visible so far ... ', visible_count;
13556         END IF;
13557
13558     END LOOP;
13559
13560     current_res.id = NULL;
13561     current_res.rel = NULL;
13562     current_res.record = NULL;
13563     current_res.total = total_count;
13564     current_res.checked = check_count;
13565     current_res.deleted = deleted_count;
13566     current_res.visible = visible_count;
13567     current_res.excluded = excluded_count;
13568
13569     CLOSE core_cursor;
13570
13571     RETURN NEXT current_res;
13572
13573 END;
13574 $func$ LANGUAGE PLPGSQL;
13575
13576 ALTER TABLE biblio.record_entry ADD COLUMN owner INT REFERENCES actor.org_unit (id);
13577 ALTER TABLE biblio.record_entry ADD COLUMN share_depth INT;
13578
13579 ALTER TABLE auditor.biblio_record_entry_history ADD COLUMN owner INT;
13580 ALTER TABLE auditor.biblio_record_entry_history ADD COLUMN share_depth INT;
13581
13582 CREATE OR REPLACE FUNCTION public.first_word ( TEXT ) RETURNS TEXT AS $$
13583         SELECT COALESCE(SUBSTRING( $1 FROM $_$^\S+$_$), '');
13584 $$ LANGUAGE SQL STRICT IMMUTABLE;
13585
13586 CREATE OR REPLACE FUNCTION public.normalize_space( TEXT ) RETURNS TEXT AS $$
13587     SELECT regexp_replace(regexp_replace(regexp_replace($1, E'\\n', ' ', 'g'), E'(?:^\\s+)|(\\s+$)', '', 'g'), E'\\s+', ' ', 'g');
13588 $$ LANGUAGE SQL STRICT IMMUTABLE;
13589
13590 CREATE OR REPLACE FUNCTION public.lowercase( TEXT ) RETURNS TEXT AS $$
13591     return lc(shift);
13592 $$ LANGUAGE PLPERLU STRICT IMMUTABLE;
13593
13594 CREATE OR REPLACE FUNCTION public.uppercase( TEXT ) RETURNS TEXT AS $$
13595     return uc(shift);
13596 $$ LANGUAGE PLPERLU STRICT IMMUTABLE;
13597
13598 CREATE OR REPLACE FUNCTION public.remove_diacritics( TEXT ) RETURNS TEXT AS $$
13599     use Unicode::Normalize;
13600
13601     my $x = NFD(shift);
13602     $x =~ s/\pM+//go;
13603     return $x;
13604
13605 $$ LANGUAGE PLPERLU STRICT IMMUTABLE;
13606
13607 CREATE OR REPLACE FUNCTION public.entityize( TEXT ) RETURNS TEXT AS $$
13608     use Unicode::Normalize;
13609
13610     my $x = NFC(shift);
13611     $x =~ s/([\x{0080}-\x{fffd}])/sprintf('&#x%X;',ord($1))/sgoe;
13612     return $x;
13613
13614 $$ LANGUAGE PLPERLU STRICT IMMUTABLE;
13615
13616 CREATE OR REPLACE FUNCTION actor.org_unit_ancestor_setting( setting_name TEXT, org_id INT ) RETURNS SETOF actor.org_unit_setting AS $$
13617 DECLARE
13618     setting RECORD;
13619     cur_org INT;
13620 BEGIN
13621     cur_org := org_id;
13622     LOOP
13623         SELECT INTO setting * FROM actor.org_unit_setting WHERE org_unit = cur_org AND name = setting_name;
13624         IF FOUND THEN
13625             RETURN NEXT setting;
13626         END IF;
13627         SELECT INTO cur_org parent_ou FROM actor.org_unit WHERE id = cur_org;
13628         EXIT WHEN cur_org IS NULL;
13629     END LOOP;
13630     RETURN;
13631 END;
13632 $$ LANGUAGE plpgsql STABLE;
13633
13634 CREATE OR REPLACE FUNCTION acq.extract_holding_attr_table (lineitem int, tag text) RETURNS SETOF acq.flat_lineitem_holding_subfield AS $$
13635 DECLARE
13636     counter INT;
13637     lida    acq.flat_lineitem_holding_subfield%ROWTYPE;
13638 BEGIN
13639
13640     SELECT  COUNT(*) INTO counter
13641       FROM  oils_xpath_table(
13642                 'id',
13643                 'marc',
13644                 'acq.lineitem',
13645                 '//*[@tag="' || tag || '"]',
13646                 'id=' || lineitem
13647             ) as t(i int,c text);
13648
13649     FOR i IN 1 .. counter LOOP
13650         FOR lida IN
13651             SELECT  *
13652               FROM  (   SELECT  id,i,t,v
13653                           FROM  oils_xpath_table(
13654                                     'id',
13655                                     'marc',
13656                                     'acq.lineitem',
13657                                     '//*[@tag="' || tag || '"][position()=' || i || ']/*/@code|' ||
13658                                         '//*[@tag="' || tag || '"][position()=' || i || ']/*[@code]',
13659                                     'id=' || lineitem
13660                                 ) as t(id int,t text,v text)
13661                     )x
13662         LOOP
13663             RETURN NEXT lida;
13664         END LOOP;
13665     END LOOP;
13666
13667     RETURN;
13668 END;
13669 $$ LANGUAGE PLPGSQL;
13670
13671 CREATE OR REPLACE FUNCTION oils_i18n_xlate ( keytable TEXT, keyclass TEXT, keycol TEXT, identcol TEXT, keyvalue TEXT, raw_locale TEXT ) RETURNS TEXT AS $func$
13672 DECLARE
13673     locale      TEXT := REGEXP_REPLACE( REGEXP_REPLACE( raw_locale, E'[;, ].+$', '' ), E'_', '-', 'g' );
13674     language    TEXT := REGEXP_REPLACE( locale, E'-.+$', '' );
13675     result      config.i18n_core%ROWTYPE;
13676     fallback    TEXT;
13677     keyfield    TEXT := keyclass || '.' || keycol;
13678 BEGIN
13679
13680     -- Try the full locale
13681     SELECT  * INTO result
13682       FROM  config.i18n_core
13683       WHERE fq_field = keyfield
13684             AND identity_value = keyvalue
13685             AND translation = locale;
13686
13687     -- Try just the language
13688     IF NOT FOUND THEN
13689         SELECT  * INTO result
13690           FROM  config.i18n_core
13691           WHERE fq_field = keyfield
13692                 AND identity_value = keyvalue
13693                 AND translation = language;
13694     END IF;
13695
13696     -- Fall back to the string we passed in in the first place
13697     IF NOT FOUND THEN
13698     EXECUTE
13699             'SELECT ' ||
13700                 keycol ||
13701             ' FROM ' || keytable ||
13702             ' WHERE ' || identcol || ' = ' || quote_literal(keyvalue)
13703                 INTO fallback;
13704         RETURN fallback;
13705     END IF;
13706
13707     RETURN result.string;
13708 END;
13709 $func$ LANGUAGE PLPGSQL STABLE;
13710
13711 SELECT auditor.create_auditor ( 'acq', 'invoice' );
13712
13713 SELECT auditor.create_auditor ( 'acq', 'invoice_item' );
13714
13715 SELECT auditor.create_auditor ( 'acq', 'invoice_entry' );
13716
13717 INSERT INTO acq.cancel_reason ( id, org_unit, label, description, keep_debits ) VALUES (
13718     3, 1, 'delivered_but_lost',
13719     oils_i18n_gettext( 2, 'Delivered but not received; presumed lost', 'acqcr', 'label' ), TRUE );
13720
13721 CREATE TABLE config.global_flag (
13722     label   TEXT    NOT NULL
13723 ) INHERITS (config.internal_flag);
13724 ALTER TABLE config.global_flag ADD PRIMARY KEY (name);
13725
13726 INSERT INTO config.global_flag (name, label) -- defaults to enabled=FALSE
13727     VALUES (
13728         'cat.bib.use_id_for_tcn',
13729         oils_i18n_gettext(
13730             'cat.bib.use_id_for_tcn',
13731             'Cat: Use Internal ID for TCN Value',
13732             'cgf', 
13733             'label'
13734         )
13735     );
13736
13737 -- resolves performance issue noted by EG Indiana
13738
13739 CREATE INDEX scecm_owning_copy_idx ON asset.stat_cat_entry_copy_map(owning_copy);
13740
13741 INSERT INTO config.metabib_class ( name, label ) VALUES ( 'identifier', oils_i18n_gettext('identifier', 'Identifier', 'cmc', 'name') );
13742
13743 INSERT INTO config.metabib_field ( id, field_class, name, label, format, xpath, facet_field ) VALUES
13744     (17, 'identifier', 'accession', oils_i18n_gettext(17, 'Accession Number', 'cmf', 'label'), 'marcxml', $$//marcxml:datafield[tag="001"]/text()$$, TRUE );
13745 INSERT INTO config.metabib_field ( id, field_class, name, label, format, xpath, facet_field ) VALUES
13746     (18, 'identifier', 'isbn', oils_i18n_gettext(18, 'ISBN', 'cmf', 'label'), 'marcxml', $$//marcxml:datafield[tag="020"]/marcxml:subfield[code="a" or code="z"]/text()$$, TRUE );
13747 INSERT INTO config.metabib_field ( id, field_class, name, label, format, xpath, facet_field ) VALUES
13748     (19, 'identifier', 'issn', oils_i18n_gettext(19, 'ISSN', 'cmf', 'label'), 'marcxml', $$//marcxml:datafield[tag="022"]/marcxml:subfield[code="a" or code="z"]/text()$$, TRUE );
13749 INSERT INTO config.metabib_field ( id, field_class, name, label, format, xpath, facet_field ) VALUES
13750     (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 );
13751 INSERT INTO config.metabib_field ( id, field_class, name, label, format, xpath, facet_field ) VALUES
13752     (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 );
13753 INSERT INTO config.metabib_field ( id, field_class, name, label, format, xpath, facet_field ) VALUES
13754     (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 );
13755 INSERT INTO config.metabib_field ( id, field_class, name, label, format, xpath, facet_field ) VALUES
13756     (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 );
13757 INSERT INTO config.metabib_field ( id, field_class, name, label, format, xpath, facet_field ) VALUES
13758     (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 );
13759 INSERT INTO config.metabib_field ( id, field_class, name, label, format, xpath, facet_field ) VALUES
13760     (25, 'identifier', 'bibcn', oils_i18n_gettext(25, 'Local Free-Text Call Number', 'cmf', 'label'), 'marcxml', $$//marcxml:datafield[tag="099"]//text()$$, TRUE );
13761
13762 SELECT SETVAL('config.metabib_field_id_seq'::TEXT, (SELECT MAX(id) FROM config.metabib_field), TRUE);
13763  
13764
13765 DELETE FROM config.metabib_search_alias WHERE alias = 'dc.identifier';
13766
13767 INSERT INTO config.metabib_search_alias (alias,field_class) VALUES ('id','identifier');
13768 INSERT INTO config.metabib_search_alias (alias,field_class) VALUES ('dc.identifier','identifier');
13769 INSERT INTO config.metabib_search_alias (alias,field_class,field) VALUES ('eg.isbn','identifier', 18);
13770 INSERT INTO config.metabib_search_alias (alias,field_class,field) VALUES ('eg.issn','identifier', 19);
13771 INSERT INTO config.metabib_search_alias (alias,field_class,field) VALUES ('eg.upc','identifier', 20);
13772 INSERT INTO config.metabib_search_alias (alias,field_class,field) VALUES ('eg.callnumber','identifier', 25);
13773
13774 CREATE TABLE metabib.identifier_field_entry (
13775         id              BIGSERIAL       PRIMARY KEY,
13776         source          BIGINT          NOT NULL,
13777         field           INT             NOT NULL,
13778         value           TEXT            NOT NULL,
13779         index_vector    tsvector        NOT NULL
13780 );
13781 CREATE TRIGGER metabib_identifier_field_entry_fti_trigger
13782         BEFORE UPDATE OR INSERT ON metabib.identifier_field_entry
13783         FOR EACH ROW EXECUTE PROCEDURE oils_tsearch2('keyword');
13784
13785 CREATE INDEX metabib_identifier_field_entry_index_vector_idx ON metabib.identifier_field_entry USING GIST (index_vector);
13786 CREATE INDEX metabib_identifier_field_entry_value_idx ON metabib.identifier_field_entry
13787     (SUBSTRING(value,1,1024)) WHERE index_vector = ''::TSVECTOR;
13788 CREATE INDEX metabib_identifier_field_entry_source_idx ON metabib.identifier_field_entry (source);
13789
13790 ALTER TABLE metabib.identifier_field_entry ADD CONSTRAINT metabib_identifier_field_entry_source_pkey
13791     FOREIGN KEY (source) REFERENCES biblio.record_entry (id) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED;
13792 ALTER TABLE metabib.identifier_field_entry ADD CONSTRAINT metabib_identifier_field_entry_field_pkey
13793     FOREIGN KEY (field) REFERENCES config.metabib_field (id) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED;
13794
13795 CREATE OR REPLACE FUNCTION public.translate_isbn1013( TEXT ) RETURNS TEXT AS $func$
13796     use Business::ISBN;
13797     use strict;
13798     use warnings;
13799
13800     # For each ISBN found in a single string containing a set of ISBNs:
13801     #   * Normalize an incoming ISBN to have the correct checksum and no hyphens
13802     #   * Convert an incoming ISBN10 or ISBN13 to its counterpart and return
13803
13804     my $input = shift;
13805     my $output = '';
13806
13807     foreach my $word (split(/\s/, $input)) {
13808         my $isbn = Business::ISBN->new($word);
13809
13810         # First check the checksum; if it is not valid, fix it and add the original
13811         # bad-checksum ISBN to the output
13812         if ($isbn && $isbn->is_valid_checksum() == Business::ISBN::BAD_CHECKSUM) {
13813             $output .= $isbn->isbn() . " ";
13814             $isbn->fix_checksum();
13815         }
13816
13817         # If we now have a valid ISBN, convert it to its counterpart ISBN10/ISBN13
13818         # and add the normalized original ISBN to the output
13819         if ($isbn && $isbn->is_valid()) {
13820             my $isbn_xlated = ($isbn->type eq "ISBN13") ? $isbn->as_isbn10 : $isbn->as_isbn13;
13821             $output .= $isbn->isbn . " ";
13822
13823             # If we successfully converted the ISBN to its counterpart, add the
13824             # converted ISBN to the output as well
13825             $output .= ($isbn_xlated->isbn . " ") if ($isbn_xlated);
13826         }
13827     }
13828     return $output if $output;
13829
13830     # If there were no valid ISBNs, just return the raw input
13831     return $input;
13832 $func$ LANGUAGE PLPERLU;
13833
13834 COMMENT ON FUNCTION public.translate_isbn1013(TEXT) IS $$
13835 /*
13836  * Copyright (C) 2010 Merrimack Valley Library Consortium
13837  * Jason Stephenson <jstephenson@mvlc.org>
13838  * Copyright (C) 2010 Laurentian University
13839  * Dan Scott <dscott@laurentian.ca>
13840  *
13841  * The translate_isbn1013 function takes an input ISBN and returns the
13842  * following in a single space-delimited string if the input ISBN is valid:
13843  *   - The normalized input ISBN (hyphens stripped)
13844  *   - The normalized input ISBN with a fixed checksum if the checksum was bad
13845  *   - The ISBN converted to its ISBN10 or ISBN13 counterpart, if possible
13846  */
13847 $$;
13848
13849 UPDATE config.metabib_field SET facet_field = FALSE WHERE id BETWEEN 17 AND 25;
13850 UPDATE config.metabib_field SET xpath = REPLACE(xpath,'marcxml','marc') WHERE id BETWEEN 17 AND 25;
13851 UPDATE config.metabib_field SET xpath = REPLACE(xpath,'tag','@tag') WHERE id BETWEEN 17 AND 25;
13852 UPDATE config.metabib_field SET xpath = REPLACE(xpath,'code','@code') WHERE id BETWEEN 17 AND 25;
13853 UPDATE config.metabib_field SET xpath = REPLACE(xpath,'"',E'\'') WHERE id BETWEEN 17 AND 25;
13854 UPDATE config.metabib_field SET xpath = REPLACE(xpath,'/text()','') WHERE id BETWEEN 17 AND 24;
13855
13856 INSERT INTO config.index_normalizer (name, description, func, param_count) VALUES (
13857         'ISBN 10/13 conversion',
13858         'Translate ISBN10 to ISBN13, and vice versa, for indexing purposes.',
13859         'translate_isbn1013',
13860         0
13861 );
13862
13863 INSERT INTO config.index_normalizer (name, description, func, param_count) VALUES (
13864         'Replace',
13865         'Replace all occurances of first parameter in the string with the second parameter.',
13866         'replace',
13867         2
13868 );
13869
13870 INSERT INTO config.metabib_field_index_norm_map (field,norm,pos)
13871     SELECT  m.id, i.id, 1
13872       FROM  config.metabib_field m,
13873             config.index_normalizer i
13874       WHERE i.func IN ('first_word')
13875             AND m.id IN (18);
13876
13877 INSERT INTO config.metabib_field_index_norm_map (field,norm,pos)
13878     SELECT  m.id, i.id, 2
13879       FROM  config.metabib_field m,
13880             config.index_normalizer i
13881       WHERE i.func IN ('translate_isbn1013')
13882             AND m.id IN (18);
13883
13884 INSERT INTO config.metabib_field_index_norm_map (field,norm,params)
13885     SELECT  m.id, i.id, $$['-','']$$
13886       FROM  config.metabib_field m,
13887             config.index_normalizer i
13888       WHERE i.func IN ('replace')
13889             AND m.id IN (19);
13890
13891 INSERT INTO config.metabib_field_index_norm_map (field,norm,params)
13892     SELECT  m.id, i.id, $$[' ','']$$
13893       FROM  config.metabib_field m,
13894             config.index_normalizer i
13895       WHERE i.func IN ('replace')
13896             AND m.id IN (19);
13897
13898 DELETE FROM config.metabib_field_index_norm_map WHERE norm IN (1,2) and field > 16;
13899
13900 UPDATE  config.metabib_field_index_norm_map
13901   SET   params = REPLACE(params,E'\'','"')
13902   WHERE params IS NOT NULL AND params <> '';
13903
13904 DROP TRIGGER IF EXISTS metabib_identifier_field_entry_fti_trigger ON metabib.identifier_field_entry;
13905
13906 CREATE TEXT SEARCH CONFIGURATION identifier ( COPY = title );
13907
13908 ALTER TABLE config.circ_modifier
13909         ADD COLUMN avg_wait_time INTERVAL;
13910
13911 --CREATE TABLE actor.usr_password_reset (
13912 --  id SERIAL PRIMARY KEY,
13913 --  uuid TEXT NOT NULL, 
13914 --  usr BIGINT NOT NULL REFERENCES actor.usr(id) DEFERRABLE INITIALLY DEFERRED, 
13915 --  request_time TIMESTAMP NOT NULL DEFAULT NOW(), 
13916 --  has_been_reset BOOL NOT NULL DEFAULT false
13917 --);
13918 --COMMENT ON TABLE actor.usr_password_reset IS $$
13919 --/*
13920 -- * Copyright (C) 2010 Laurentian University
13921 -- * Dan Scott <dscott@laurentian.ca>
13922 -- *
13923 -- * Self-serve password reset requests
13924 -- *
13925 -- * ****
13926 -- *
13927 -- * This program is free software; you can redistribute it and/or
13928 -- * modify it under the terms of the GNU General Public License
13929 -- * as published by the Free Software Foundation; either version 2
13930 -- * of the License, or (at your option) any later version.
13931 -- *
13932 -- * This program is distributed in the hope that it will be useful,
13933 -- * but WITHOUT ANY WARRANTY; without even the implied warranty of
13934 -- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13935 -- * GNU General Public License for more details.
13936 -- */
13937 --$$;
13938 --CREATE UNIQUE INDEX actor_usr_password_reset_uuid_idx ON actor.usr_password_reset (uuid);
13939 --CREATE INDEX actor_usr_password_reset_usr_idx ON actor.usr_password_reset (usr);
13940 --CREATE INDEX actor_usr_password_reset_request_time_idx ON actor.usr_password_reset (request_time);
13941 --CREATE INDEX actor_usr_password_reset_has_been_reset_idx ON actor.usr_password_reset (has_been_reset);
13942
13943 -- Use the identifier search class tsconfig
13944 DROP TRIGGER IF EXISTS metabib_identifier_field_entry_fti_trigger ON metabib.identifier_field_entry;
13945 CREATE TRIGGER metabib_identifier_field_entry_fti_trigger
13946     BEFORE INSERT OR UPDATE ON metabib.identifier_field_entry
13947     FOR EACH ROW
13948     EXECUTE PROCEDURE public.oils_tsearch2('identifier');
13949
13950 INSERT INTO config.global_flag (name,label,enabled)
13951     VALUES ('history.circ.retention_age',oils_i18n_gettext('history.circ.retention_age', 'Historical Circulation Retention Age', 'cgf', 'label'), TRUE);
13952 INSERT INTO config.global_flag (name,label,enabled)
13953     VALUES ('history.circ.retention_count',oils_i18n_gettext('history.circ.retention_count', 'Historical Circulations per Copy', 'cgf', 'label'), TRUE);
13954
13955 -- turn a JSON scalar into an SQL TEXT value
13956 CREATE OR REPLACE FUNCTION oils_json_to_text( TEXT ) RETURNS TEXT AS $f$
13957     use JSON::XS;                    
13958     my $json = shift();
13959     my $txt;
13960     eval { $txt = JSON::XS->new->allow_nonref->decode( $json ) };   
13961     return undef if ($@);
13962     return $txt
13963 $f$ LANGUAGE PLPERLU;
13964
13965 -- Return the list of circ chain heads in xact_start order that the user has chosen to "retain"
13966 CREATE OR REPLACE FUNCTION action.usr_visible_circs (usr_id INT) RETURNS SETOF action.circulation AS $func$
13967 DECLARE
13968     c               action.circulation%ROWTYPE;
13969     view_age        INTERVAL;
13970     usr_view_age    actor.usr_setting%ROWTYPE;
13971     usr_view_start  actor.usr_setting%ROWTYPE;
13972 BEGIN
13973     SELECT * INTO usr_view_age FROM actor.usr_setting WHERE usr = usr_id AND name = 'history.circ.retention_age';
13974     SELECT * INTO usr_view_start FROM actor.usr_setting WHERE usr = usr_id AND name = 'history.circ.retention_start';
13975
13976     IF usr_view_age.value IS NOT NULL AND usr_view_start.value IS NOT NULL THEN
13977         -- User opted in and supplied a retention age
13978         IF oils_json_to_text(usr_view_age.value)::INTERVAL > AGE(NOW(), oils_json_to_text(usr_view_start.value)::TIMESTAMPTZ) THEN
13979             view_age := AGE(NOW(), oils_json_to_text(usr_view_start.value)::TIMESTAMPTZ);
13980         ELSE
13981             view_age := oils_json_to_text(usr_view_age.value)::INTERVAL;
13982         END IF;
13983     ELSIF usr_view_start.value IS NOT NULL THEN
13984         -- User opted in
13985         view_age := AGE(NOW(), oils_json_to_text(usr_view_start.value)::TIMESTAMPTZ);
13986     ELSE
13987         -- User did not opt in
13988         RETURN;
13989     END IF;
13990
13991     FOR c IN
13992         SELECT  *
13993           FROM  action.circulation
13994           WHERE usr = usr_id
13995                 AND parent_circ IS NULL
13996                 AND xact_start > NOW() - view_age
13997           ORDER BY xact_start
13998     LOOP
13999         RETURN NEXT c;
14000     END LOOP;
14001
14002     RETURN;
14003 END;
14004 $func$ LANGUAGE PLPGSQL;
14005
14006 CREATE OR REPLACE FUNCTION action.purge_circulations () RETURNS INT AS $func$
14007 DECLARE
14008     usr_keep_age    actor.usr_setting%ROWTYPE;
14009     usr_keep_start  actor.usr_setting%ROWTYPE;
14010     org_keep_age    INTERVAL;
14011     org_keep_count  INT;
14012
14013     keep_age        INTERVAL;
14014
14015     target_acp      RECORD;
14016     circ_chain_head action.circulation%ROWTYPE;
14017     circ_chain_tail action.circulation%ROWTYPE;
14018
14019     purge_position  INT;
14020     count_purged    INT;
14021 BEGIN
14022
14023     count_purged := 0;
14024
14025     SELECT value::INTERVAL INTO org_keep_age FROM config.global_flag WHERE name = 'history.circ.retention_age' AND enabled;
14026
14027     SELECT value::INT INTO org_keep_count FROM config.global_flag WHERE name = 'history.circ.retention_count' AND enabled;
14028     IF org_keep_count IS NULL THEN
14029         RETURN count_purged; -- Gimme a count to keep, or I keep them all, forever
14030     END IF;
14031
14032     -- First, find copies with more than keep_count non-renewal circs
14033     FOR target_acp IN
14034         SELECT  target_copy,
14035                 COUNT(*) AS total_real_circs
14036           FROM  action.circulation
14037           WHERE parent_circ IS NULL
14038                 AND xact_finish IS NOT NULL
14039           GROUP BY target_copy
14040           HAVING COUNT(*) > org_keep_count
14041     LOOP
14042         purge_position := 0;
14043         -- And, for those, select circs that are finished and older than keep_age
14044         FOR circ_chain_head IN
14045             SELECT  *
14046               FROM  action.circulation
14047               WHERE target_copy = target_acp.target_copy
14048                     AND parent_circ IS NULL
14049               ORDER BY xact_start
14050         LOOP
14051
14052             -- Stop once we've purged enough circs to hit org_keep_count
14053             EXIT WHEN target_acp.total_real_circs - purge_position <= org_keep_count;
14054
14055             SELECT * INTO circ_chain_tail FROM action.circ_chain(circ_chain_head.id) ORDER BY xact_start DESC LIMIT 1;
14056             EXIT WHEN circ_chain_tail.xact_finish IS NULL;
14057
14058             -- Now get the user setings, if any, to block purging if the user wants to keep more circs
14059             usr_keep_age.value := NULL;
14060             SELECT * INTO usr_keep_age FROM actor.usr_setting WHERE usr = circ_chain_head.usr AND name = 'history.circ.retention_age';
14061
14062             usr_keep_start.value := NULL;
14063             SELECT * INTO usr_keep_start FROM actor.usr_setting WHERE usr = circ_chain_head.usr AND name = 'history.circ.retention_start_date';
14064
14065             IF usr_keep_age.value IS NOT NULL AND usr_keep_start.value IS NOT NULL THEN
14066                 IF oils_json_to_string(usr_keep_age.value)::INTERVAL > AGE(NOW(), oils_json_to_string(usr_keep_start.value)::TIMESTAMPTZ) THEN
14067                     keep_age := AGE(NOW(), oils_json_to_string(usr_keep_start.value)::TIMESTAMPTZ);
14068                 ELSE
14069                     keep_age := oils_json_to_string(usr_keep_age.value)::INTERVAL;
14070                 END IF;
14071             ELSIF usr_keep_start.value IS NOT NULL THEN
14072                 keep_age := AGE(NOW(), oils_json_to_string(usr_keep_start.value)::TIMESTAMPTZ);
14073             ELSE
14074                 keep_age := COALESCE( org_keep_age::INTERVAL, '2000 years'::INTEVAL );
14075             END IF;
14076
14077             EXIT WHEN AGE(NOW(), circ_chain_tail.xact_finish) < keep_age;
14078
14079             -- We've passed the purging tests, purge the circ chain starting at the end
14080             DELETE FROM action.circulation WHERE id = circ_chain_tail.id;
14081             WHILE circ_chain_tail.parent_circ IS NOT NULL LOOP
14082                 SELECT * INTO circ_chain_tail FROM action.circulation WHERE id = circ_chain_tail.parent_circ;
14083                 DELETE FROM action.circulation WHERE id = circ_chain_tail.id;
14084             END LOOP;
14085
14086             count_purged := count_purged + 1;
14087             purge_position := purge_position + 1;
14088
14089         END LOOP;
14090     END LOOP;
14091 END;
14092 $func$ LANGUAGE PLPGSQL;
14093
14094 CREATE OR REPLACE FUNCTION action.usr_visible_holds (usr_id INT) RETURNS SETOF action.hold_request AS $func$
14095 DECLARE
14096     h               action.hold_request%ROWTYPE;
14097     view_age        INTERVAL;
14098     view_count      INT;
14099     usr_view_count  actor.usr_setting%ROWTYPE;
14100     usr_view_age    actor.usr_setting%ROWTYPE;
14101     usr_view_start  actor.usr_setting%ROWTYPE;
14102 BEGIN
14103     SELECT * INTO usr_view_count FROM actor.usr_setting WHERE usr = usr_id AND name = 'history.hold.retention_count';
14104     SELECT * INTO usr_view_age FROM actor.usr_setting WHERE usr = usr_id AND name = 'history.hold.retention_age';
14105     SELECT * INTO usr_view_start FROM actor.usr_setting WHERE usr = usr_id AND name = 'history.hold.retention_start';
14106
14107     FOR h IN
14108         SELECT  *
14109           FROM  action.hold_request
14110           WHERE usr = usr_id
14111                 AND fulfillment_time IS NULL
14112                 AND cancel_time IS NULL
14113           ORDER BY request_time DESC
14114     LOOP
14115         RETURN NEXT h;
14116     END LOOP;
14117
14118     IF usr_view_start.value IS NULL THEN
14119         RETURN;
14120     END IF;
14121
14122     IF usr_view_age.value IS NOT NULL THEN
14123         -- User opted in and supplied a retention age
14124         IF oils_json_to_string(usr_view_age.value)::INTERVAL > AGE(NOW(), oils_json_to_string(usr_view_start.value)::TIMESTAMPTZ) THEN
14125             view_age := AGE(NOW(), oils_json_to_string(usr_view_start.value)::TIMESTAMPTZ);
14126         ELSE
14127             view_age := oils_json_to_string(usr_view_age.value)::INTERVAL;
14128         END IF;
14129     ELSE
14130         -- User opted in
14131         view_age := AGE(NOW(), oils_json_to_string(usr_view_start.value)::TIMESTAMPTZ);
14132     END IF;
14133
14134     IF usr_view_count.value IS NOT NULL THEN
14135         view_count := oils_json_to_text(usr_view_count.value)::INT;
14136     ELSE
14137         view_count := 1000;
14138     END IF;
14139
14140     -- show some fulfilled/canceled holds
14141     FOR h IN
14142         SELECT  *
14143           FROM  action.hold_request
14144           WHERE usr = usr_id
14145                 AND ( fulfillment_time IS NOT NULL OR cancel_time IS NOT NULL )
14146                 AND request_time > NOW() - view_age
14147           ORDER BY request_time DESC
14148           LIMIT view_count
14149     LOOP
14150         RETURN NEXT h;
14151     END LOOP;
14152
14153     RETURN;
14154 END;
14155 $func$ LANGUAGE PLPGSQL;
14156
14157 \qecho Progress: dropping several tables from serial schema
14158
14159 DROP TABLE IF EXISTS serial.bib_summary CASCADE;
14160
14161 DROP TABLE IF EXISTS serial.index_summary CASCADE;
14162
14163 DROP TABLE IF EXISTS serial.sup_summary CASCADE;
14164
14165 DROP TABLE IF EXISTS serial.issuance CASCADE;
14166
14167 DROP TABLE IF EXISTS serial.binding_unit CASCADE;
14168
14169 DROP TABLE IF EXISTS serial.subscription CASCADE;
14170
14171 CREATE TABLE asset.copy_template (
14172         id             SERIAL   PRIMARY KEY,
14173         owning_lib     INT      NOT NULL
14174                                 REFERENCES actor.org_unit (id)
14175                                 DEFERRABLE INITIALLY DEFERRED,
14176         creator        BIGINT   NOT NULL
14177                                 REFERENCES actor.usr (id)
14178                                 DEFERRABLE INITIALLY DEFERRED,
14179         editor         BIGINT   NOT NULL
14180                                 REFERENCES actor.usr (id)
14181                                 DEFERRABLE INITIALLY DEFERRED,
14182         create_date    TIMESTAMP WITH TIME ZONE    DEFAULT NOW(),
14183         edit_date      TIMESTAMP WITH TIME ZONE    DEFAULT NOW(),
14184         name           TEXT     NOT NULL,
14185         -- columns above this point are attributes of the template itself
14186         -- columns after this point are attributes of the copy this template modifies/creates
14187         circ_lib       INT      REFERENCES actor.org_unit (id)
14188                                 DEFERRABLE INITIALLY DEFERRED,
14189         status         INT      REFERENCES config.copy_status (id)
14190                                 DEFERRABLE INITIALLY DEFERRED,
14191         location       INT      REFERENCES asset.copy_location (id)
14192                                 DEFERRABLE INITIALLY DEFERRED,
14193         loan_duration  INT      CONSTRAINT valid_loan_duration CHECK (
14194                                     loan_duration IS NULL OR loan_duration IN (1,2,3)),
14195         fine_level     INT      CONSTRAINT valid_fine_level CHECK (
14196                                     fine_level IS NULL OR loan_duration IN (1,2,3)),
14197         age_protect    INT,
14198         circulate      BOOL,
14199         deposit        BOOL,
14200         ref            BOOL,
14201         holdable       BOOL,
14202         deposit_amount NUMERIC(6,2),
14203         price          NUMERIC(8,2),
14204         circ_modifier  TEXT,
14205         circ_as_type   TEXT,
14206         alert_message  TEXT,
14207         opac_visible   BOOL,
14208         floating       BOOL,
14209         mint_condition BOOL
14210 );
14211
14212 CREATE TABLE serial.subscription (
14213         id                     SERIAL       PRIMARY KEY,
14214         start_date             TIMESTAMP WITH TIME ZONE     NOT NULL,
14215         end_date               TIMESTAMP WITH TIME ZONE,    -- interpret NULL as current subscription
14216         record_entry           BIGINT       REFERENCES serial.record_entry (id)
14217                                             DEFERRABLE INITIALLY DEFERRED,
14218         expected_date_offset   INTERVAL,
14219         owning_lib             INT          NOT NULL DEFAULT 1
14220                                             REFERENCES actor.org_unit (id)
14221                                             ON DELETE SET NULL
14222                                             DEFERRABLE INITIALLY DEFERRED
14223         -- acquisitions/business-side tables link to here
14224 );
14225
14226 --at least one distribution per org_unit holding issues
14227 CREATE TABLE serial.distribution (
14228         id                    SERIAL  PRIMARY KEY,
14229         subscription          INT     NOT NULL
14230                                       REFERENCES serial.subscription (id)
14231                                                                   ON DELETE CASCADE
14232                                                                   DEFERRABLE INITIALLY DEFERRED,
14233         holding_lib           INT     NOT NULL
14234                                       REFERENCES actor.org_unit (id)
14235                                                                   DEFERRABLE INITIALLY DEFERRED,
14236         label                 TEXT    NOT NULL,
14237         receive_call_number   BIGINT  REFERENCES asset.call_number (id)
14238                                       DEFERRABLE INITIALLY DEFERRED,
14239         receive_unit_template INT     REFERENCES asset.copy_template (id)
14240                                       DEFERRABLE INITIALLY DEFERRED,
14241         bind_call_number      BIGINT  REFERENCES asset.call_number (id)
14242                                       DEFERRABLE INITIALLY DEFERRED,
14243         bind_unit_template    INT     REFERENCES asset.copy_template (id)
14244                                       DEFERRABLE INITIALLY DEFERRED,
14245         unit_label_prefix     TEXT,
14246         unit_label_suffix     TEXT,
14247         record_entry          INT     REFERENCES serial.record_entry (id)
14248                                       ON DELETE SET NULL
14249                                       DEFERRABLE INITIALLY DEFERRED
14250 );
14251
14252 CREATE UNIQUE INDEX one_dist_per_sre_idx ON serial.distribution (record_entry);
14253
14254 CREATE TABLE serial.stream (
14255         id              SERIAL  PRIMARY KEY,
14256         distribution    INT     NOT NULL
14257                                 REFERENCES serial.distribution (id)
14258                                 ON DELETE CASCADE
14259                                 DEFERRABLE INITIALLY DEFERRED,
14260         routing_label   TEXT
14261 );
14262
14263 CREATE UNIQUE INDEX label_once_per_dist
14264         ON serial.stream (distribution, routing_label)
14265         WHERE routing_label IS NOT NULL;
14266
14267 CREATE TABLE serial.routing_list_user (
14268         id             SERIAL       PRIMARY KEY,
14269         stream         INT          NOT NULL
14270                                     REFERENCES serial.stream
14271                                     ON DELETE CASCADE
14272                                     DEFERRABLE INITIALLY DEFERRED,
14273         pos            INT          NOT NULL DEFAULT 1,
14274         reader         INT          REFERENCES actor.usr
14275                                     ON DELETE CASCADE
14276                                     DEFERRABLE INITIALLY DEFERRED,
14277         department     TEXT,
14278         note           TEXT,
14279         CONSTRAINT one_pos_per_routing_list UNIQUE ( stream, pos ),
14280         CONSTRAINT reader_or_dept CHECK
14281         (
14282             -- Recipient is a person or a department, but not both
14283                 (reader IS NOT NULL AND department IS NULL) OR
14284                 (reader IS NULL AND department IS NOT NULL)
14285         )
14286 );
14287
14288 CREATE TABLE serial.caption_and_pattern (
14289         id           SERIAL       PRIMARY KEY,
14290         type         TEXT         NOT NULL
14291                                   CONSTRAINT cap_type CHECK ( type in
14292                                   ( 'basic', 'supplement', 'index' )),
14293         create_date  TIMESTAMPTZ  NOT NULL DEFAULT now(),
14294         active       BOOL         NOT NULL DEFAULT FALSE,
14295         pattern_code TEXT         NOT NULL,       -- must contain JSON
14296         enum_1       TEXT,
14297         enum_2       TEXT,
14298         enum_3       TEXT,
14299         enum_4       TEXT,
14300         enum_5       TEXT,
14301         enum_6       TEXT,
14302         chron_1      TEXT,
14303         chron_2      TEXT,
14304         chron_3      TEXT,
14305         chron_4      TEXT,
14306         chron_5      TEXT,
14307         subscription INT          NOT NULL REFERENCES serial.subscription (id)
14308                                   ON DELETE CASCADE
14309                                   DEFERRABLE INITIALLY DEFERRED
14310 );
14311
14312 CREATE TABLE serial.issuance (
14313         id              SERIAL    PRIMARY KEY,
14314         creator         INT       NOT NULL
14315                                   REFERENCES actor.usr (id)
14316                                                           DEFERRABLE INITIALLY DEFERRED,
14317         editor          INT       NOT NULL
14318                                   REFERENCES actor.usr (id)
14319                                   DEFERRABLE INITIALLY DEFERRED,
14320         create_date     TIMESTAMP WITH TIME ZONE        NOT NULL DEFAULT now(),
14321         edit_date       TIMESTAMP WITH TIME ZONE        NOT NULL DEFAULT now(),
14322         subscription    INT       NOT NULL
14323                                   REFERENCES serial.subscription (id)
14324                                   ON DELETE CASCADE
14325                                   DEFERRABLE INITIALLY DEFERRED,
14326         label           TEXT,
14327         date_published  TIMESTAMP WITH TIME ZONE,
14328         holding_code    TEXT,
14329         holding_type    TEXT      CONSTRAINT valid_holding_type CHECK
14330                                   (
14331                                       holding_type IS NULL
14332                                       OR holding_type IN ('basic','supplement','index')
14333                                   ),
14334         holding_link_id INT,
14335         caption_and_pattern  INT  REFERENCES serial.caption_and_pattern (id)
14336                               DEFERRABLE INITIALLY DEFERRED
14337         -- TODO: add columns for separate enumeration/chronology values
14338 );
14339
14340 CREATE TABLE serial.unit (
14341         label           TEXT,
14342         label_sort_key  TEXT,
14343         contents        TEXT    NOT NULL
14344 ) INHERITS (asset.copy);
14345
14346 ALTER TABLE serial.unit ADD PRIMARY KEY (id);
14347
14348 ALTER TABLE serial.unit ADD CONSTRAINT serial_unit_call_number_fkey FOREIGN KEY (call_number) REFERENCES asset.call_number (id) DEFERRABLE INITIALLY DEFERRED;
14349
14350 ALTER TABLE serial.unit ADD CONSTRAINT serial_unit_creator_fkey FOREIGN KEY (creator) REFERENCES actor.usr (id) ON DELETE SET NULL DEFERRABLE INITIALLY DEFERRED;
14351
14352 ALTER TABLE serial.unit ADD CONSTRAINT serial_unit_editor_fkey FOREIGN KEY (editor) REFERENCES actor.usr (id) ON DELETE SET NULL DEFERRABLE INITIALLY DEFERRED;
14353
14354 CREATE TABLE serial.item (
14355         id              SERIAL  PRIMARY KEY,
14356         creator         INT     NOT NULL
14357                                 REFERENCES actor.usr (id)
14358                                 DEFERRABLE INITIALLY DEFERRED,
14359         editor          INT     NOT NULL
14360                                 REFERENCES actor.usr (id)
14361                                 DEFERRABLE INITIALLY DEFERRED,
14362         create_date     TIMESTAMP WITH TIME ZONE        NOT NULL DEFAULT now(),
14363         edit_date       TIMESTAMP WITH TIME ZONE        NOT NULL DEFAULT now(),
14364         issuance        INT     NOT NULL
14365                                 REFERENCES serial.issuance (id)
14366                                 ON DELETE CASCADE
14367                                 DEFERRABLE INITIALLY DEFERRED,
14368         stream          INT     NOT NULL
14369                                 REFERENCES serial.stream (id)
14370                                 ON DELETE CASCADE
14371                                 DEFERRABLE INITIALLY DEFERRED,
14372         unit            INT     REFERENCES serial.unit (id)
14373                                 ON DELETE SET NULL
14374                                 DEFERRABLE INITIALLY DEFERRED,
14375         uri             INT     REFERENCES asset.uri (id)
14376                                 ON DELETE SET NULL
14377                                 DEFERRABLE INITIALLY DEFERRED,
14378         date_expected   TIMESTAMP WITH TIME ZONE,
14379         date_received   TIMESTAMP WITH TIME ZONE,
14380         status          TEXT    CONSTRAINT value_status_check CHECK (
14381                                status IN ( 'Bindery', 'Bound', 'Claimed', 'Discarded',
14382                                'Expected', 'Not Held', 'Not Published', 'Received'))
14383                             DEFAULT 'Expected',
14384         shadowed        BOOL    NOT NULL DEFAULT FALSE
14385 );
14386
14387 CREATE TABLE serial.item_note (
14388         id          SERIAL  PRIMARY KEY,
14389         item        INT     NOT NULL
14390                             REFERENCES serial.item (id)
14391                             ON DELETE CASCADE
14392                             DEFERRABLE INITIALLY DEFERRED,
14393         creator     INT     NOT NULL
14394                             REFERENCES actor.usr (id)
14395                             DEFERRABLE INITIALLY DEFERRED,
14396         create_date TIMESTAMP WITH TIME ZONE    DEFAULT NOW(),
14397         pub         BOOL    NOT NULL    DEFAULT FALSE,
14398         title       TEXT    NOT NULL,
14399         value       TEXT    NOT NULL
14400 );
14401
14402 CREATE TABLE serial.basic_summary (
14403         id                  SERIAL  PRIMARY KEY,
14404         distribution        INT     NOT NULL
14405                                     REFERENCES serial.distribution (id)
14406                                     ON DELETE CASCADE
14407                                     DEFERRABLE INITIALLY DEFERRED,
14408         generated_coverage  TEXT    NOT NULL,
14409         textual_holdings    TEXT
14410 );
14411
14412 CREATE TABLE serial.supplement_summary (
14413         id                  SERIAL  PRIMARY KEY,
14414         distribution        INT     NOT NULL
14415                                     REFERENCES serial.distribution (id)
14416                                     ON DELETE CASCADE
14417                                     DEFERRABLE INITIALLY DEFERRED,
14418         generated_coverage  TEXT    NOT NULL,
14419         textual_holdings    TEXT
14420 );
14421
14422 CREATE TABLE serial.index_summary (
14423         id                  SERIAL  PRIMARY KEY,
14424         distribution        INT     NOT NULL
14425                                     REFERENCES serial.distribution (id)
14426                                     ON DELETE CASCADE
14427                                     DEFERRABLE INITIALLY DEFERRED,
14428         generated_coverage  TEXT    NOT NULL,
14429         textual_holdings    TEXT
14430 );
14431
14432 -- 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.
14433
14434 DROP INDEX IF EXISTS authority.authority_record_unique_tcn;
14435 CREATE UNIQUE INDEX authority_record_unique_tcn ON authority.record_entry (arn_source,arn_value) WHERE deleted = FALSE OR deleted IS FALSE;
14436
14437 DROP INDEX IF EXISTS asset.asset_call_number_label_once_per_lib;
14438 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;
14439
14440 DROP INDEX IF EXISTS biblio.biblio_record_unique_tcn;
14441 CREATE UNIQUE INDEX biblio_record_unique_tcn ON biblio.record_entry (tcn_value) WHERE deleted = FALSE OR deleted IS FALSE;
14442
14443 CREATE OR REPLACE FUNCTION config.interval_to_seconds( interval_val INTERVAL )
14444 RETURNS INTEGER AS $$
14445 BEGIN
14446         RETURN EXTRACT( EPOCH FROM interval_val );
14447 END;
14448 $$ LANGUAGE plpgsql;
14449
14450 CREATE OR REPLACE FUNCTION config.interval_to_seconds( interval_string TEXT )
14451 RETURNS INTEGER AS $$
14452 BEGIN
14453         RETURN config.interval_to_seconds( interval_string::INTERVAL );
14454 END;
14455 $$ LANGUAGE plpgsql;
14456
14457 INSERT INTO container.biblio_record_entry_bucket_type( code, label ) VALUES (
14458     'temp',
14459     oils_i18n_gettext(
14460         'temp',
14461         'Temporary bucket which gets deleted after use.',
14462         'cbrebt',
14463         'label'
14464     )
14465 );
14466
14467 -- 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.
14468
14469 CREATE OR REPLACE FUNCTION biblio.check_marcxml_well_formed () RETURNS TRIGGER AS $func$
14470 BEGIN
14471
14472     IF xml_is_well_formed(NEW.marc) THEN
14473         RETURN NEW;
14474     ELSE
14475         RAISE EXCEPTION 'Attempted to % MARCXML that is not well formed', TG_OP;
14476     END IF;
14477     
14478 END;
14479 $func$ LANGUAGE PLPGSQL;
14480
14481 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();
14482
14483 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();
14484
14485 ALTER TABLE serial.record_entry
14486         ALTER COLUMN marc DROP NOT NULL;
14487
14488 insert INTO CONFIG.xml_transform(name, namespace_uri, prefix, xslt)
14489 VALUES ('marc21expand880', 'http://www.loc.gov/MARC21/slim', 'marc', $$<?xml version="1.0" encoding="UTF-8"?>
14490 <xsl:stylesheet
14491     xmlns:xsl="http://www.w3.org/1999/XSL/Transform" 
14492     xmlns:marc="http://www.loc.gov/MARC21/slim"
14493     version="1.0">
14494 <!--
14495 Copyright (C) 2010  Equinox Software, Inc.
14496 Galen Charlton <gmc@esilibrary.cOM.
14497
14498 This program is free software; you can redistribute it and/or
14499 modify it under the terms of the GNU General Public License
14500 as published by the Free Software Foundation; either version 2
14501 of the License, or (at your option) any later version.
14502
14503 This program is distributed in the hope that it will be useful,
14504 but WITHOUT ANY WARRANTY; without even the implied warranty of
14505 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14506 GNU General Public License for more details.
14507
14508 marc21_expand_880.xsl - stylesheet used during indexing to
14509                         map alternative graphical representations
14510                         of MARC fields stored in 880 fields
14511                         to the corresponding tag name and value.
14512
14513 For example, if a MARC record for a Chinese book has
14514
14515 245.00 $6 880-01 $a Ba shi san nian duan pian xiao shuo xuan
14516 880.00 $6 245-01/$1 $a八十三年短篇小說選
14517
14518 this stylesheet will transform it to the equivalent of
14519
14520 245.00 $6 880-01 $a Ba shi san nian duan pian xiao shuo xuan
14521 245.00 $6 245-01/$1 $a八十三年短篇小說選
14522
14523 -->
14524     <xsl:output encoding="UTF-8" indent="yes" method="xml"/>
14525
14526     <xsl:template match="@*|node()">
14527         <xsl:copy>
14528             <xsl:apply-templates select="@*|node()"/>
14529         </xsl:copy>
14530     </xsl:template>
14531
14532     <xsl:template match="//marc:datafield[@tag='880']">
14533         <xsl:if test="./marc:subfield[@code='6'] and string-length(./marc:subfield[@code='6']) &gt;= 6">
14534             <marc:datafield>
14535                 <xsl:attribute name="tag">
14536                     <xsl:value-of select="substring(./marc:subfield[@code='6'], 1, 3)" />
14537                 </xsl:attribute>
14538                 <xsl:attribute name="ind1">
14539                     <xsl:value-of select="@ind1" />
14540                 </xsl:attribute>
14541                 <xsl:attribute name="ind2">
14542                     <xsl:value-of select="@ind2" />
14543                 </xsl:attribute>
14544                 <xsl:apply-templates />
14545             </marc:datafield>
14546         </xsl:if>
14547     </xsl:template>
14548     
14549 </xsl:stylesheet>$$);
14550
14551 -- Splitting the ingest trigger up into little bits
14552
14553 CREATE TEMPORARY TABLE eg_0301_check_if_has_contents (
14554     flag INTEGER PRIMARY KEY
14555 ) ON COMMIT DROP;
14556 INSERT INTO eg_0301_check_if_has_contents VALUES (1);
14557
14558 -- cause failure if either of the tables we want to drop have rows
14559 INSERT INTO eg_0301_check_if_has_contents SELECT 1 FROM asset.copy_transparency LIMIT 1;
14560 INSERT INTO eg_0301_check_if_has_contents SELECT 1 FROM asset.copy_transparency_map LIMIT 1;
14561
14562 DROP TABLE IF EXISTS asset.copy_transparency_map;
14563 DROP TABLE IF EXISTS asset.copy_transparency;
14564
14565 UPDATE config.metabib_field SET facet_xpath = '//' || facet_xpath WHERE facet_xpath IS NOT NULL;
14566
14567 -- We won't necessarily use all of these, but they are here for completeness.
14568 -- Source is the EDI spec 1229 codelist, eg: http://www.stylusstudio.com/edifact/D04B/1229.htm
14569 -- Values are the EDI code value + 1000
14570
14571 INSERT INTO acq.cancel_reason (keep_debits, id, org_unit, label, description) VALUES 
14572 ('t',(  1+1000), 1, 'Added',     'The information is to be or has been added.'),
14573 ('f',(  2+1000), 1, 'Deleted',   'The information is to be or has been deleted.'),
14574 ('t',(  3+1000), 1, 'Changed',   'The information is to be or has been changed.'),
14575 ('t',(  4+1000), 1, 'No action',                  'This line item is not affected by the actual message.'),
14576 ('t',(  5+1000), 1, 'Accepted without amendment', 'This line item is entirely accepted by the seller.'),
14577 ('t',(  6+1000), 1, 'Accepted with amendment',    'This line item is accepted but amended by the seller.'),
14578 ('f',(  7+1000), 1, 'Not accepted',               'This line item is not accepted by the seller.'),
14579 ('t',(  8+1000), 1, 'Schedule only', 'Code specifying that the message is a schedule only.'),
14580 ('t',(  9+1000), 1, 'Amendments',    'Code specifying that amendments are requested/notified.'),
14581 ('f',( 10+1000), 1, 'Not found',   'This line item is not found in the referenced message.'),
14582 ('t',( 11+1000), 1, 'Not amended', 'This line is not amended by the buyer.'),
14583 ('t',( 12+1000), 1, 'Line item numbers changed', 'Code specifying that the line item numbers have changed.'),
14584 ('t',( 13+1000), 1, 'Buyer has deducted amount', 'Buyer has deducted amount from payment.'),
14585 ('t',( 14+1000), 1, 'Buyer claims against invoice', 'Buyer has a claim against an outstanding invoice.'),
14586 ('t',( 15+1000), 1, 'Charge back by seller', 'Factor has been requested to charge back the outstanding item.'),
14587 ('t',( 16+1000), 1, 'Seller will issue credit note', 'Seller agrees to issue a credit note.'),
14588 ('t',( 17+1000), 1, 'Terms changed for new terms', 'New settlement terms have been agreed.'),
14589 ('t',( 18+1000), 1, 'Abide outcome of negotiations', 'Factor agrees to abide by the outcome of negotiations between seller and buyer.'),
14590 ('t',( 19+1000), 1, 'Seller rejects dispute', 'Seller does not accept validity of dispute.'),
14591 ('t',( 20+1000), 1, 'Settlement', 'The reported situation is settled.'),
14592 ('t',( 21+1000), 1, 'No delivery', 'Code indicating that no delivery will be required.'),
14593 ('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).'),
14594 ('t',( 23+1000), 1, 'Proposed amendment', 'A code used to indicate an amendment suggested by the sender.'),
14595 ('t',( 24+1000), 1, 'Accepted with amendment, no confirmation required', 'Accepted with changes which require no confirmation.'),
14596 ('t',( 25+1000), 1, 'Equipment provisionally repaired', 'The equipment or component has been provisionally repaired.'),
14597 ('t',( 26+1000), 1, 'Included', 'Code indicating that the entity is included.'),
14598 ('t',( 27+1000), 1, 'Verified documents for coverage', 'Upon receipt and verification of documents we shall cover you when due as per your instructions.'),
14599 ('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.'),
14600 ('t',( 29+1000), 1, 'Authenticated advice for coverage',      'On receipt of your authenticated advice we shall cover you when due as per your instructions.'),
14601 ('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.'),
14602 ('t',( 31+1000), 1, 'Authenticated advice for credit',        'On receipt of your authenticated advice we shall credit your account with us when due.'),
14603 ('t',( 32+1000), 1, 'Credit advice requested for direct debit',           'A credit advice is requested for the direct debit.'),
14604 ('t',( 33+1000), 1, 'Credit advice and acknowledgement for direct debit', 'A credit advice and acknowledgement are requested for the direct debit.'),
14605 ('t',( 34+1000), 1, 'Inquiry',     'Request for information.'),
14606 ('t',( 35+1000), 1, 'Checked',     'Checked.'),
14607 ('t',( 36+1000), 1, 'Not checked', 'Not checked.'),
14608 ('f',( 37+1000), 1, 'Cancelled',   'Discontinued.'),
14609 ('t',( 38+1000), 1, 'Replaced',    'Provide a replacement.'),
14610 ('t',( 39+1000), 1, 'New',         'Not existing before.'),
14611 ('t',( 40+1000), 1, 'Agreed',      'Consent.'),
14612 ('t',( 41+1000), 1, 'Proposed',    'Put forward for consideration.'),
14613 ('t',( 42+1000), 1, 'Already delivered', 'Delivery has taken place.'),
14614 ('t',( 43+1000), 1, 'Additional subordinate structures will follow', 'Additional subordinate structures will follow the current hierarchy level.'),
14615 ('t',( 44+1000), 1, 'Additional subordinate structures will not follow', 'No additional subordinate structures will follow the current hierarchy level.'),
14616 ('t',( 45+1000), 1, 'Result opposed',         'A notification that the result is opposed.'),
14617 ('t',( 46+1000), 1, 'Auction held',           'A notification that an auction was held.'),
14618 ('t',( 47+1000), 1, 'Legal action pursued',   'A notification that legal action has been pursued.'),
14619 ('t',( 48+1000), 1, 'Meeting held',           'A notification that a meeting was held.'),
14620 ('t',( 49+1000), 1, 'Result set aside',       'A notification that the result has been set aside.'),
14621 ('t',( 50+1000), 1, 'Result disputed',        'A notification that the result has been disputed.'),
14622 ('t',( 51+1000), 1, 'Countersued',            'A notification that a countersuit has been filed.'),
14623 ('t',( 52+1000), 1, 'Pending',                'A notification that an action is awaiting settlement.'),
14624 ('f',( 53+1000), 1, 'Court action dismissed', 'A notification that a court action will no longer be heard.'),
14625 ('t',( 54+1000), 1, 'Referred item, accepted', 'The item being referred to has been accepted.'),
14626 ('f',( 55+1000), 1, 'Referred item, rejected', 'The item being referred to has been rejected.'),
14627 ('t',( 56+1000), 1, 'Debit advice statement line',  'Notification that the statement line is a debit advice.'),
14628 ('t',( 57+1000), 1, 'Credit advice statement line', 'Notification that the statement line is a credit advice.'),
14629 ('t',( 58+1000), 1, 'Grouped credit advices',       'Notification that the credit advices are grouped.'),
14630 ('t',( 59+1000), 1, 'Grouped debit advices',        'Notification that the debit advices are grouped.'),
14631 ('t',( 60+1000), 1, 'Registered', 'The name is registered.'),
14632 ('f',( 61+1000), 1, 'Payment denied', 'The payment has been denied.'),
14633 ('t',( 62+1000), 1, 'Approved as amended', 'Approved with modifications.'),
14634 ('t',( 63+1000), 1, 'Approved as submitted', 'The request has been approved as submitted.'),
14635 ('f',( 64+1000), 1, 'Cancelled, no activity', 'Cancelled due to the lack of activity.'),
14636 ('t',( 65+1000), 1, 'Under investigation', 'Investigation is being done.'),
14637 ('t',( 66+1000), 1, 'Initial claim received', 'Notification that the initial claim was received.'),
14638 ('f',( 67+1000), 1, 'Not in process', 'Not in process.'),
14639 ('f',( 68+1000), 1, 'Rejected, duplicate', 'Rejected because it is a duplicate.'),
14640 ('f',( 69+1000), 1, 'Rejected, resubmit with corrections', 'Rejected but may be resubmitted when corrected.'),
14641 ('t',( 70+1000), 1, 'Pending, incomplete', 'Pending because of incomplete information.'),
14642 ('t',( 71+1000), 1, 'Under field office investigation', 'Investigation by the field is being done.'),
14643 ('t',( 72+1000), 1, 'Pending, awaiting additional material', 'Pending awaiting receipt of additional material.'),
14644 ('t',( 73+1000), 1, 'Pending, awaiting review', 'Pending while awaiting review.'),
14645 ('t',( 74+1000), 1, 'Reopened', 'Opened again.'),
14646 ('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).'),
14647 ('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).'),
14648 ('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).'),
14649 ('t',( 78+1000), 1, 'Previous payment decision reversed', 'A previous payment decision has been reversed.'),
14650 ('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).'),
14651 ('t',( 80+1000), 1, 'Transferred to correct insurance carrier', 'The request has been transferred to the correct insurance carrier for processing.'),
14652 ('t',( 81+1000), 1, 'Not paid, predetermination pricing only', 'Payment has not been made and the enclosed response is predetermination pricing only.'),
14653 ('t',( 82+1000), 1, 'Documentation claim', 'The claim is for documentation purposes only, no payment required.'),
14654 ('t',( 83+1000), 1, 'Reviewed', 'Assessed.'),
14655 ('f',( 84+1000), 1, 'Repriced', 'This price was changed.'),
14656 ('t',( 85+1000), 1, 'Audited', 'An official examination has occurred.'),
14657 ('t',( 86+1000), 1, 'Conditionally paid', 'Payment has been conditionally made.'),
14658 ('t',( 87+1000), 1, 'On appeal', 'Reconsideration of the decision has been applied for.'),
14659 ('t',( 88+1000), 1, 'Closed', 'Shut.'),
14660 ('t',( 89+1000), 1, 'Reaudited', 'A subsequent official examination has occurred.'),
14661 ('t',( 90+1000), 1, 'Reissued', 'Issued again.'),
14662 ('t',( 91+1000), 1, 'Closed after reopening', 'Reopened and then closed.'),
14663 ('t',( 92+1000), 1, 'Redetermined', 'Determined again or differently.'),
14664 ('t',( 93+1000), 1, 'Processed as primary',   'Processed as the first.'),
14665 ('t',( 94+1000), 1, 'Processed as secondary', 'Processed as the second.'),
14666 ('t',( 95+1000), 1, 'Processed as tertiary',  'Processed as the third.'),
14667 ('t',( 96+1000), 1, 'Correction of error', 'A correction to information previously communicated which contained an error.'),
14668 ('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.'),
14669 ('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.'),
14670 ('t',( 99+1000), 1, 'Interim response', 'The response is an interim one.'),
14671 ('t',(100+1000), 1, 'Final response',   'The response is an final one.'),
14672 ('t',(101+1000), 1, 'Debit advice requested', 'A debit advice is requested for the transaction.'),
14673 ('t',(102+1000), 1, 'Transaction not impacted', 'Advice that the transaction is not impacted.'),
14674 ('t',(103+1000), 1, 'Patient to be notified',                    'The action to take is to notify the patient.'),
14675 ('t',(104+1000), 1, 'Healthcare provider to be notified',        'The action to take is to notify the healthcare provider.'),
14676 ('t',(105+1000), 1, 'Usual general practitioner to be notified', 'The action to take is to notify the usual general practitioner.'),
14677 ('t',(106+1000), 1, 'Advice without details', 'An advice without details is requested or notified.'),
14678 ('t',(107+1000), 1, 'Advice with details', 'An advice with details is requested or notified.'),
14679 ('t',(108+1000), 1, 'Amendment requested', 'An amendment is requested.'),
14680 ('t',(109+1000), 1, 'For information', 'Included for information only.'),
14681 ('f',(110+1000), 1, 'Withdraw', 'A code indicating discontinuance or retraction.'),
14682 ('t',(111+1000), 1, 'Delivery date change', 'The action / notiification is a change of the delivery date.'),
14683 ('f',(112+1000), 1, 'Quantity change',      'The action / notification is a change of quantity.'),
14684 ('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.'),
14685 ('t',(114+1000), 1, 'Resale',           'The identified items have been sold by the distributor to the end customer.'),
14686 ('t',(115+1000), 1, 'Prior addition', 'This existing line item becomes available at an earlier date.');
14687
14688 INSERT INTO config.metabib_field ( id, field_class, name, label, format, xpath, facet_field, search_field ) VALUES
14689     (26, 'identifier', 'arcn', oils_i18n_gettext(26, 'Authority record control number', 'cmf', 'label'), 'marcxml', $$//marc:subfield[@code='0']$$, TRUE, FALSE );
14690  
14691 SELECT SETVAL('config.metabib_field_id_seq'::TEXT, (SELECT MAX(id) FROM config.metabib_field), TRUE);
14692  
14693 INSERT INTO config.index_normalizer (name, description, func, param_count) VALUES (
14694         'Remove Parenthesized Substring',
14695         'Remove any parenthesized substrings from the extracted text, such as the agency code preceding authority record control numbers in subfield 0.',
14696         'remove_paren_substring',
14697         0
14698 );
14699
14700 INSERT INTO config.index_normalizer (name, description, func, param_count) VALUES (
14701         'Trim Surrounding Space',
14702         'Trim leading and trailing spaces from extracted text.',
14703         'btrim',
14704         0
14705 );
14706
14707 INSERT INTO config.metabib_field_index_norm_map (field,norm,pos)
14708     SELECT  m.id,
14709             i.id,
14710             -2
14711       FROM  config.metabib_field m,
14712             config.index_normalizer i
14713       WHERE i.func IN ('remove_paren_substring')
14714             AND m.id IN (26);
14715
14716 INSERT INTO config.metabib_field_index_norm_map (field,norm,pos)
14717     SELECT  m.id,
14718             i.id,
14719             -1
14720       FROM  config.metabib_field m,
14721             config.index_normalizer i
14722       WHERE i.func IN ('btrim')
14723             AND m.id IN (26);
14724
14725 -- Function that takes, and returns, marcxml and compiles an embedded ruleset for you, and they applys it
14726 CREATE OR REPLACE FUNCTION vandelay.merge_record_xml ( target_marc TEXT, template_marc TEXT ) RETURNS TEXT AS $$
14727 DECLARE
14728     dyn_profile     vandelay.compile_profile%ROWTYPE;
14729     replace_rule    TEXT;
14730     tmp_marc        TEXT;
14731     trgt_marc        TEXT;
14732     tmpl_marc        TEXT;
14733     match_count     INT;
14734 BEGIN
14735
14736     IF target_marc IS NULL OR template_marc IS NULL THEN
14737         -- RAISE NOTICE 'no marc for target or template record';
14738         RETURN NULL;
14739     END IF;
14740
14741     dyn_profile := vandelay.compile_profile( template_marc );
14742
14743     IF dyn_profile.replace_rule <> '' AND dyn_profile.preserve_rule <> '' THEN
14744         -- RAISE NOTICE 'both replace [%] and preserve [%] specified', dyn_profile.replace_rule, dyn_profile.preserve_rule;
14745         RETURN NULL;
14746     END IF;
14747
14748     IF dyn_profile.replace_rule <> '' THEN
14749         trgt_marc = target_marc;
14750         tmpl_marc = template_marc;
14751         replace_rule = dyn_profile.replace_rule;
14752     ELSE
14753         tmp_marc = target_marc;
14754         trgt_marc = template_marc;
14755         tmpl_marc = tmp_marc;
14756         replace_rule = dyn_profile.preserve_rule;
14757     END IF;
14758
14759     RETURN vandelay.merge_record_xml( trgt_marc, tmpl_marc, dyn_profile.add_rule, replace_rule, dyn_profile.strip_rule );
14760
14761 END;
14762 $$ LANGUAGE PLPGSQL;
14763
14764 -- Function to generate an ephemeral overlay template from an authority record
14765 CREATE OR REPLACE FUNCTION authority.generate_overlay_template ( TEXT, BIGINT ) RETURNS TEXT AS $func$
14766
14767     use MARC::Record;
14768     use MARC::File::XML (BinaryEncoding => 'UTF-8');
14769
14770     my $xml = shift;
14771     my $r = MARC::Record->new_from_xml( $xml );
14772
14773     return undef unless ($r);
14774
14775     my $id = shift() || $r->subfield( '901' => 'c' );
14776     $id =~ s/^\s*(?:\([^)]+\))?\s*(.+)\s*?$/$1/;
14777     return undef unless ($id); # We need an ID!
14778
14779     my $tmpl = MARC::Record->new();
14780
14781     my @rule_fields;
14782     for my $field ( $r->field( '1..' ) ) { # Get main entry fields from the authority record
14783
14784         my $tag = $field->tag;
14785         my $i1 = $field->indicator(1);
14786         my $i2 = $field->indicator(2);
14787         my $sf = join '', map { $_->[0] } $field->subfields;
14788         my @data = map { @$_ } $field->subfields;
14789
14790         my @replace_them;
14791
14792         # Map the authority field to bib fields it can control.
14793         if ($tag >= 100 and $tag <= 111) {       # names
14794             @replace_them = map { $tag + $_ } (0, 300, 500, 600, 700);
14795         } elsif ($tag eq '130') {                # uniform title
14796             @replace_them = qw/130 240 440 730 830/;
14797         } elsif ($tag >= 150 and $tag <= 155) {  # subjects
14798             @replace_them = ($tag + 500);
14799         } elsif ($tag >= 180 and $tag <= 185) {  # floating subdivisions
14800             @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/;
14801         } else {
14802             next;
14803         }
14804
14805         # Dummy up the bib-side data
14806         $tmpl->append_fields(
14807             map {
14808                 MARC::Field->new( $_, $i1, $i2, @data )
14809             } @replace_them
14810         );
14811
14812         # Construct some 'replace' rules
14813         push @rule_fields, map { $_ . $sf . '[0~\)' .$id . '$]' } @replace_them;
14814     }
14815
14816     # Insert the replace rules into the template
14817     $tmpl->append_fields(
14818         MARC::Field->new( '905' => ' ' => ' ' => 'r' => join(',', @rule_fields ) )
14819     );
14820
14821     $xml = $tmpl->as_xml_record;
14822     $xml =~ s/^<\?.+?\?>$//mo;
14823     $xml =~ s/\n//sgo;
14824     $xml =~ s/>\s+</></sgo;
14825
14826     return $xml;
14827
14828 $func$ LANGUAGE PLPERLU;
14829
14830 CREATE OR REPLACE FUNCTION authority.generate_overlay_template ( BIGINT ) RETURNS TEXT AS $func$
14831     SELECT authority.generate_overlay_template( marc, id ) FROM authority.record_entry WHERE id = $1;
14832 $func$ LANGUAGE SQL;
14833
14834 CREATE OR REPLACE FUNCTION authority.generate_overlay_template ( TEXT ) RETURNS TEXT AS $func$
14835     SELECT authority.generate_overlay_template( $1, NULL );
14836 $func$ LANGUAGE SQL;
14837
14838 DELETE FROM config.metabib_field_index_norm_map WHERE field = 26;
14839 DELETE FROM config.metabib_field WHERE id = 26;
14840
14841 -- Making this a global_flag (UI accessible) instead of an internal_flag
14842 INSERT INTO config.global_flag (name, label) -- defaults to enabled=FALSE
14843     VALUES (
14844         'ingest.disable_authority_linking',
14845         oils_i18n_gettext(
14846             'ingest.disable_authority_linking',
14847             'Authority Automation: Disable bib-authority link tracking',
14848             'cgf', 
14849             'label'
14850         )
14851     );
14852 UPDATE config.global_flag SET enabled = (SELECT enabled FROM ONLY config.internal_flag WHERE name = 'ingest.disable_authority_linking');
14853 DELETE FROM config.internal_flag WHERE name = 'ingest.disable_authority_linking';
14854
14855 INSERT INTO config.global_flag (name, label) -- defaults to enabled=FALSE
14856     VALUES (
14857         'ingest.disable_authority_auto_update',
14858         oils_i18n_gettext(
14859             'ingest.disable_authority_auto_update',
14860             'Authority Automation: Disable automatic authority updating (requires link tracking)',
14861             'cgf', 
14862             'label'
14863         )
14864     );
14865
14866 -- Enable automated ingest of authority records; just insert the row into
14867 -- authority.record_entry and authority.full_rec will automatically be populated
14868
14869 CREATE OR REPLACE FUNCTION authority.propagate_changes (aid BIGINT, bid BIGINT) RETURNS BIGINT AS $func$
14870     UPDATE  biblio.record_entry
14871       SET   marc = vandelay.merge_record_xml( marc, authority.generate_overlay_template( $1 ) )
14872       WHERE id = $2;
14873     SELECT $1;
14874 $func$ LANGUAGE SQL;
14875
14876 CREATE OR REPLACE FUNCTION authority.propagate_changes (aid BIGINT) RETURNS SETOF BIGINT AS $func$
14877     SELECT authority.propagate_changes( authority, bib ) FROM authority.bib_linking WHERE authority = $1;
14878 $func$ LANGUAGE SQL;
14879
14880 -- authority.rec_descriptor appears to be unused currently
14881 CREATE OR REPLACE FUNCTION authority.reingest_authority_rec_descriptor( auth_id BIGINT ) RETURNS VOID AS $func$
14882 BEGIN
14883     DELETE FROM authority.rec_descriptor WHERE record = auth_id;
14884 --    INSERT INTO authority.rec_descriptor (record, record_status, char_encoding)
14885 --        SELECT  auth_id, ;
14886
14887     RETURN;
14888 END;
14889 $func$ LANGUAGE PLPGSQL;
14890
14891 CREATE OR REPLACE FUNCTION authority.reingest_authority_full_rec( auth_id BIGINT ) RETURNS VOID AS $func$
14892 BEGIN
14893     DELETE FROM authority.full_rec WHERE record = auth_id;
14894     INSERT INTO authority.full_rec (record, tag, ind1, ind2, subfield, value)
14895         SELECT record, tag, ind1, ind2, subfield, value FROM authority.flatten_marc( auth_id );
14896
14897     RETURN;
14898 END;
14899 $func$ LANGUAGE PLPGSQL;
14900
14901 -- AFTER UPDATE OR INSERT trigger for authority.record_entry
14902 CREATE OR REPLACE FUNCTION authority.indexing_ingest_or_delete () RETURNS TRIGGER AS $func$
14903 BEGIN
14904
14905     IF NEW.deleted IS TRUE THEN -- If this authority is deleted
14906         DELETE FROM authority.bib_linking WHERE authority = NEW.id; -- Avoid updating fields in bibs that are no longer visible
14907           -- Should remove matching $0 from controlled fields at the same time?
14908         RETURN NEW; -- and we're done
14909     END IF;
14910
14911     IF TG_OP = 'UPDATE' THEN -- re-ingest?
14912         PERFORM * FROM config.internal_flag WHERE name = 'ingest.reingest.force_on_same_marc' AND enabled;
14913
14914         IF NOT FOUND AND OLD.marc = NEW.marc THEN -- don't do anything if the MARC didn't change
14915             RETURN NEW;
14916         END IF;
14917     END IF;
14918
14919     -- Flatten and insert the afr data
14920     PERFORM * FROM config.internal_flag WHERE name = 'ingest.disable_authority_full_rec' AND enabled;
14921     IF NOT FOUND THEN
14922         PERFORM authority.reingest_authority_full_rec(NEW.id);
14923 -- authority.rec_descriptor is not currently used
14924 --        PERFORM * FROM config.internal_flag WHERE name = 'ingest.disable_authority_rec_descriptor' AND enabled;
14925 --        IF NOT FOUND THEN
14926 --            PERFORM authority.reingest_authority_rec_descriptor(NEW.id);
14927 --        END IF;
14928     END IF;
14929
14930     RETURN NEW;
14931 END;
14932 $func$ LANGUAGE PLPGSQL;
14933
14934 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 ();
14935
14936 -- Some records manage to get XML namespace declarations into each element,
14937 -- like <datafield xmlns:marc="http://www.loc.gov/MARC21/slim"
14938 -- This broke the old maintain_901(), so we'll make the regex more robust
14939
14940 CREATE OR REPLACE FUNCTION maintain_901 () RETURNS TRIGGER AS $func$
14941 BEGIN
14942     -- Remove any existing 901 fields before we insert the authoritative one
14943     NEW.marc := REGEXP_REPLACE(NEW.marc, E'<datafield\s*[^<>]*?\s*tag="901".+?</datafield>', '', 'g');
14944     IF TG_TABLE_SCHEMA = 'biblio' THEN
14945         NEW.marc := REGEXP_REPLACE(
14946             NEW.marc,
14947             E'(</(?:[^:]*?:)?record>)',
14948             E'<datafield tag="901" ind1=" " ind2=" ">' ||
14949                 '<subfield code="a">' || NEW.tcn_value || E'</subfield>' ||
14950                 '<subfield code="b">' || NEW.tcn_source || E'</subfield>' ||
14951                 '<subfield code="c">' || NEW.id || E'</subfield>' ||
14952                 '<subfield code="t">' || TG_TABLE_SCHEMA || E'</subfield>' ||
14953                 CASE WHEN NEW.owner IS NOT NULL THEN '<subfield code="o">' || NEW.owner || E'</subfield>' ELSE '' END ||
14954                 CASE WHEN NEW.share_depth IS NOT NULL THEN '<subfield code="d">' || NEW.share_depth || E'</subfield>' ELSE '' END ||
14955              E'</datafield>\\1'
14956         );
14957     ELSIF TG_TABLE_SCHEMA = 'authority' THEN
14958         NEW.marc := REGEXP_REPLACE(
14959             NEW.marc,
14960             E'(</(?:[^:]*?:)?record>)',
14961             E'<datafield tag="901" ind1=" " ind2=" ">' ||
14962                 '<subfield code="a">' || NEW.arn_value || E'</subfield>' ||
14963                 '<subfield code="b">' || NEW.arn_source || E'</subfield>' ||
14964                 '<subfield code="c">' || NEW.id || E'</subfield>' ||
14965                 '<subfield code="t">' || TG_TABLE_SCHEMA || E'</subfield>' ||
14966              E'</datafield>\\1'
14967         );
14968     ELSIF TG_TABLE_SCHEMA = 'serial' THEN
14969         NEW.marc := REGEXP_REPLACE(
14970             NEW.marc,
14971             E'(</(?:[^:]*?:)?record>)',
14972             E'<datafield tag="901" ind1=" " ind2=" ">' ||
14973                 '<subfield code="c">' || NEW.id || E'</subfield>' ||
14974                 '<subfield code="t">' || TG_TABLE_SCHEMA || E'</subfield>' ||
14975                 '<subfield code="o">' || NEW.owning_lib || E'</subfield>' ||
14976                 CASE WHEN NEW.record IS NOT NULL THEN '<subfield code="r">' || NEW.record || E'</subfield>' ELSE '' END ||
14977              E'</datafield>\\1'
14978         );
14979     ELSE
14980         NEW.marc := REGEXP_REPLACE(
14981             NEW.marc,
14982             E'(</(?:[^:]*?:)?record>)',
14983             E'<datafield tag="901" ind1=" " ind2=" ">' ||
14984                 '<subfield code="c">' || NEW.id || E'</subfield>' ||
14985                 '<subfield code="t">' || TG_TABLE_SCHEMA || E'</subfield>' ||
14986              E'</datafield>\\1'
14987         );
14988     END IF;
14989
14990     RETURN NEW;
14991 END;
14992 $func$ LANGUAGE PLPGSQL;
14993
14994 CREATE TRIGGER b_maintain_901 BEFORE INSERT OR UPDATE ON biblio.record_entry FOR EACH ROW EXECUTE PROCEDURE maintain_901();
14995 CREATE TRIGGER b_maintain_901 BEFORE INSERT OR UPDATE ON authority.record_entry FOR EACH ROW EXECUTE PROCEDURE maintain_901();
14996 CREATE TRIGGER b_maintain_901 BEFORE INSERT OR UPDATE ON serial.record_entry FOR EACH ROW EXECUTE PROCEDURE maintain_901();
14997  
14998 -- In booking, elbow room defines:
14999 --  a) how far in the future you must make a reservation on a given item if
15000 --      that item will have to transit somewhere to fulfill the reservation.
15001 --  b) how soon a reservation must be starting for the reserved item to
15002 --      be op-captured by the checkin interface.
15003
15004 -- We don't want to clobber any default_elbow room at any level:
15005
15006 CREATE OR REPLACE FUNCTION pg_temp.default_elbow() RETURNS INTEGER AS $$
15007 DECLARE
15008     existing    actor.org_unit_setting%ROWTYPE;
15009 BEGIN
15010     SELECT INTO existing id FROM actor.org_unit_setting WHERE name = 'circ.booking_reservation.default_elbow_room';
15011     IF NOT FOUND THEN
15012         INSERT INTO actor.org_unit_setting (org_unit, name, value) VALUES (
15013             (SELECT id FROM actor.org_unit WHERE parent_ou IS NULL),
15014             'circ.booking_reservation.default_elbow_room',
15015             '"1 day"'
15016         );
15017         RETURN 1;
15018     END IF;
15019     RETURN 0;
15020 END;
15021 $$ LANGUAGE plpgsql;
15022
15023 SELECT pg_temp.default_elbow();
15024
15025 DROP FUNCTION IF EXISTS action.usr_visible_circ_copies( INTEGER );
15026
15027 -- returns the distinct set of target copy IDs from a user's visible circulation history
15028 CREATE OR REPLACE FUNCTION action.usr_visible_circ_copies( INTEGER ) RETURNS SETOF BIGINT AS $$
15029     SELECT DISTINCT(target_copy) FROM action.usr_visible_circs($1)
15030 $$ LANGUAGE SQL;
15031
15032 ALTER TABLE action.in_house_use DROP CONSTRAINT in_house_use_item_fkey;
15033 ALTER TABLE action.transit_copy DROP CONSTRAINT transit_copy_target_copy_fkey;
15034 ALTER TABLE action.hold_transit_copy DROP CONSTRAINT ahtc_tc_fkey;
15035
15036 ALTER TABLE asset.stat_cat_entry_copy_map DROP CONSTRAINT a_sc_oc_fkey;
15037
15038 -- This is optional, might fail, that's ok
15039 ALTER TABLE extend_reporter.legacy_circ_count DROP CONSTRAINT legacy_circ_count_id_fkey;
15040
15041 ALTER TABLE authority.record_entry ADD COLUMN owner INT;
15042 ALTER TABLE serial.record_entry ADD COLUMN owner INT;
15043
15044 INSERT INTO config.global_flag (name, label) -- defaults to enabled=FALSE
15045     VALUES (
15046         'cat.maintain_control_numbers',
15047         oils_i18n_gettext(
15048             'cat.maintain_control_numbers',
15049             'Cat: Maintain 001/003/035 according to the MARC21 specification',
15050             'cgf', 
15051             'label'
15052         )
15053     );
15054
15055 CREATE OR REPLACE FUNCTION maintain_control_numbers() RETURNS TRIGGER AS $func$
15056 use strict;
15057 use MARC::Record;
15058 use MARC::File::XML (BinaryEncoding => 'UTF-8');
15059 use Encode;
15060 use Unicode::Normalize;
15061
15062 my $record = MARC::Record->new_from_xml($_TD->{new}{marc});
15063 my $schema = $_TD->{table_schema};
15064 my $rec_id = $_TD->{new}{id};
15065
15066 # Short-circuit if maintaining control numbers per MARC21 spec is not enabled
15067 my $enable = spi_exec_query("SELECT enabled FROM config.global_flag WHERE name = 'cat.maintain_control_numbers'");
15068 if (!($enable->{processed}) or $enable->{rows}[0]->{enabled} eq 'f') {
15069     return;
15070 }
15071
15072 # Get the control number identifier from an OU setting based on $_TD->{new}{owner}
15073 my $ou_cni = 'EVRGRN';
15074
15075 my $owner;
15076 if ($schema eq 'serial') {
15077     $owner = $_TD->{new}{owning_lib};
15078 } else {
15079     # are.owner and bre.owner can be null, so fall back to the consortial setting
15080     $owner = $_TD->{new}{owner} || 1;
15081 }
15082
15083 my $ous_rv = spi_exec_query("SELECT value FROM actor.org_unit_ancestor_setting('cat.marc_control_number_identifier', $owner)");
15084 if ($ous_rv->{processed}) {
15085     $ou_cni = $ous_rv->{rows}[0]->{value};
15086     $ou_cni =~ s/"//g; # Stupid VIM syntax highlighting"
15087 } else {
15088     # Fall back to the shortname of the OU if there was no OU setting
15089     $ous_rv = spi_exec_query("SELECT shortname FROM actor.org_unit WHERE id = $owner");
15090     if ($ous_rv->{processed}) {
15091         $ou_cni = $ous_rv->{rows}[0]->{shortname};
15092     }
15093 }
15094
15095 my ($create, $munge) = (0, 0);
15096 my ($orig_001, $orig_003) = ('', '');
15097
15098 # Incoming MARC records may have multiple 001s or 003s, despite the spec
15099 my @control_ids = $record->field('003');
15100 my @scns = $record->field('035');
15101
15102 foreach my $id_field ('001', '003') {
15103     my $spec_value;
15104     my @controls = $record->field($id_field);
15105
15106     if ($id_field eq '001') {
15107         $spec_value = $rec_id;
15108     } else {
15109         $spec_value = $ou_cni;
15110     }
15111
15112     # Create the 001/003 if none exist
15113     if (scalar(@controls) == 0) {
15114         $record->insert_fields_ordered(MARC::Field->new($id_field, $spec_value));
15115         $create = 1;
15116     } elsif (scalar(@controls) > 1) {
15117         # Do we already have the right 001/003 value in the existing set?
15118         unless (grep $_->data() eq $spec_value, @controls) {
15119             $munge = 1;
15120         }
15121
15122         # Delete the other fields, as with more than 1 001/003 we do not know which 003/001 to match
15123         foreach my $control (@controls) {
15124             unless ($control->data() eq $spec_value) {
15125                 $record->delete_field($control);
15126             }
15127         }
15128     } else {
15129         # Only one field; check to see if we need to munge it
15130         unless (grep $_->data() eq $spec_value, @controls) {
15131             $munge = 1;
15132         }
15133     }
15134 }
15135
15136 # Now, if we need to munge the 001, we will first push the existing 001/003 into the 035
15137 if ($munge) {
15138     my $scn = "(" . $record->field('003')->data() . ")" . $record->field('001')->data();
15139
15140     # Do not create duplicate 035 fields
15141     unless (grep $_->subfield('a') eq $scn, @scns) {
15142         $record->insert_fields_ordered(MARC::Field->new('035', '', '', 'a' => $scn));
15143     }
15144 }
15145
15146 # Set the 001/003 and update the MARC
15147 if ($create or $munge) {
15148     $record->field('001')->data($rec_id);
15149     $record->field('003')->data($ou_cni);
15150
15151     my $xml = $record->as_xml_record();
15152     $xml =~ s/\n//sgo;
15153     $xml =~ s/^<\?xml.+\?\s*>//go;
15154     $xml =~ s/>\s+</></go;
15155     $xml =~ s/\p{Cc}//go;
15156
15157     # Embed a version of OpenILS::Application::AppUtils->entityize()
15158     # to avoid having to set PERL5LIB for PostgreSQL as well
15159
15160     # If we are going to convert non-ASCII characters to XML entities,
15161     # we had better be dealing with a UTF8 string to begin with
15162     $xml = decode_utf8($xml);
15163
15164     $xml = NFC($xml);
15165
15166     # Convert raw ampersands to entities
15167     $xml =~ s/&(?!\S+;)/&amp;/gso;
15168
15169     # Convert Unicode characters to entities
15170     $xml =~ s/([\x{0080}-\x{fffd}])/sprintf('&#x%X;',ord($1))/sgoe;
15171
15172     $xml =~ s/[\x00-\x1f]//go;
15173     $_TD->{new}{marc} = $xml;
15174
15175     return "MODIFY";
15176 }
15177
15178 return;
15179 $func$ LANGUAGE PLPERLU;
15180
15181 CREATE TRIGGER c_maintain_control_numbers BEFORE INSERT OR UPDATE ON authority.record_entry FOR EACH ROW EXECUTE PROCEDURE maintain_control_numbers();
15182 CREATE TRIGGER c_maintain_control_numbers BEFORE INSERT OR UPDATE ON biblio.record_entry FOR EACH ROW EXECUTE PROCEDURE maintain_control_numbers();
15183 CREATE TRIGGER c_maintain_control_numbers BEFORE INSERT OR UPDATE ON serial.record_entry FOR EACH ROW EXECUTE PROCEDURE maintain_control_numbers();
15184
15185 INSERT INTO metabib.facet_entry (source, field, value)
15186     SELECT source, field, value FROM (
15187         SELECT * FROM metabib.author_field_entry
15188             UNION ALL
15189         SELECT * FROM metabib.keyword_field_entry
15190             UNION ALL
15191         SELECT * FROM metabib.identifier_field_entry
15192             UNION ALL
15193         SELECT * FROM metabib.title_field_entry
15194             UNION ALL
15195         SELECT * FROM metabib.subject_field_entry
15196             UNION ALL
15197         SELECT * FROM metabib.series_field_entry
15198         )x
15199     WHERE x.index_vector = '';
15200         
15201 DELETE FROM metabib.author_field_entry WHERE index_vector = '';
15202 DELETE FROM metabib.keyword_field_entry WHERE index_vector = '';
15203 DELETE FROM metabib.identifier_field_entry WHERE index_vector = '';
15204 DELETE FROM metabib.title_field_entry WHERE index_vector = '';
15205 DELETE FROM metabib.subject_field_entry WHERE index_vector = '';
15206 DELETE FROM metabib.series_field_entry WHERE index_vector = '';
15207
15208 CREATE INDEX metabib_facet_entry_field_idx ON metabib.facet_entry (field);
15209 CREATE INDEX metabib_facet_entry_value_idx ON metabib.facet_entry (SUBSTRING(value,1,1024));
15210 CREATE INDEX metabib_facet_entry_source_idx ON metabib.facet_entry (source);
15211
15212 -- copy OPAC visibility materialized view
15213 CREATE OR REPLACE FUNCTION asset.refresh_opac_visible_copies_mat_view () RETURNS VOID AS $$
15214
15215     TRUNCATE TABLE asset.opac_visible_copies;
15216
15217     INSERT INTO asset.opac_visible_copies (id, circ_lib, record)
15218     SELECT  cp.id, cp.circ_lib, cn.record
15219     FROM  asset.copy cp
15220         JOIN asset.call_number cn ON (cn.id = cp.call_number)
15221         JOIN actor.org_unit a ON (cp.circ_lib = a.id)
15222         JOIN asset.copy_location cl ON (cp.location = cl.id)
15223         JOIN config.copy_status cs ON (cp.status = cs.id)
15224         JOIN biblio.record_entry b ON (cn.record = b.id)
15225     WHERE NOT cp.deleted
15226         AND NOT cn.deleted
15227         AND NOT b.deleted
15228         AND cs.opac_visible
15229         AND cl.opac_visible
15230         AND cp.opac_visible
15231         AND a.opac_visible;
15232
15233 $$ LANGUAGE SQL;
15234 COMMENT ON FUNCTION asset.refresh_opac_visible_copies_mat_view() IS $$
15235 Rebuild the copy OPAC visibility cache.  Useful during migrations.
15236 $$;
15237
15238 -- and actually populate the table
15239 SELECT asset.refresh_opac_visible_copies_mat_view();
15240
15241 CREATE OR REPLACE FUNCTION asset.cache_copy_visibility () RETURNS TRIGGER as $func$
15242 DECLARE
15243     add_query       TEXT;
15244     remove_query    TEXT;
15245     do_add          BOOLEAN := false;
15246     do_remove       BOOLEAN := false;
15247 BEGIN
15248     add_query := $$
15249             INSERT INTO asset.opac_visible_copies (id, circ_lib, record)
15250                 SELECT  cp.id, cp.circ_lib, cn.record
15251                   FROM  asset.copy cp
15252                         JOIN asset.call_number cn ON (cn.id = cp.call_number)
15253                         JOIN actor.org_unit a ON (cp.circ_lib = a.id)
15254                         JOIN asset.copy_location cl ON (cp.location = cl.id)
15255                         JOIN config.copy_status cs ON (cp.status = cs.id)
15256                         JOIN biblio.record_entry b ON (cn.record = b.id)
15257                   WHERE NOT cp.deleted
15258                         AND NOT cn.deleted
15259                         AND NOT b.deleted
15260                         AND cs.opac_visible
15261                         AND cl.opac_visible
15262                         AND cp.opac_visible
15263                         AND a.opac_visible
15264     $$;
15265  
15266     remove_query := $$ DELETE FROM asset.opac_visible_copies WHERE id IN ( SELECT id FROM asset.copy WHERE $$;
15267
15268     IF TG_OP = 'INSERT' THEN
15269
15270         IF TG_TABLE_NAME IN ('copy', 'unit') THEN
15271             add_query := add_query || 'AND cp.id = ' || NEW.id || ';';
15272             EXECUTE add_query;
15273         END IF;
15274
15275         RETURN NEW;
15276
15277     END IF;
15278
15279     -- handle items first, since with circulation activity
15280     -- their statuses change frequently
15281     IF TG_TABLE_NAME IN ('copy', 'unit') THEN
15282
15283         IF OLD.location    <> NEW.location OR
15284            OLD.call_number <> NEW.call_number OR
15285            OLD.status      <> NEW.status OR
15286            OLD.circ_lib    <> NEW.circ_lib THEN
15287             -- any of these could change visibility, but
15288             -- we'll save some queries and not try to calculate
15289             -- the change directly
15290             do_remove := true;
15291             do_add := true;
15292         ELSE
15293
15294             IF OLD.deleted <> NEW.deleted THEN
15295                 IF NEW.deleted THEN
15296                     do_remove := true;
15297                 ELSE
15298                     do_add := true;
15299                 END IF;
15300             END IF;
15301
15302             IF OLD.opac_visible <> NEW.opac_visible THEN
15303                 IF OLD.opac_visible THEN
15304                     do_remove := true;
15305                 ELSIF NOT do_remove THEN -- handle edge case where deleted item
15306                                         -- is also marked opac_visible
15307                     do_add := true;
15308                 END IF;
15309             END IF;
15310
15311         END IF;
15312
15313         IF do_remove THEN
15314             DELETE FROM asset.opac_visible_copies WHERE id = NEW.id;
15315         END IF;
15316         IF do_add THEN
15317             add_query := add_query || 'AND cp.id = ' || NEW.id || ';';
15318             EXECUTE add_query;
15319         END IF;
15320
15321         RETURN NEW;
15322
15323     END IF;
15324
15325     IF TG_TABLE_NAME IN ('call_number', 'record_entry') THEN -- these have a 'deleted' column
15326  
15327         IF OLD.deleted AND NEW.deleted THEN -- do nothing
15328
15329             RETURN NEW;
15330  
15331         ELSIF NEW.deleted THEN -- remove rows
15332  
15333             IF TG_TABLE_NAME = 'call_number' THEN
15334                 DELETE FROM asset.opac_visible_copies WHERE id IN (SELECT id FROM asset.copy WHERE call_number = NEW.id);
15335             ELSIF TG_TABLE_NAME = 'record_entry' THEN
15336                 DELETE FROM asset.opac_visible_copies WHERE record = NEW.id;
15337             END IF;
15338  
15339             RETURN NEW;
15340  
15341         ELSIF OLD.deleted THEN -- add rows
15342  
15343             IF TG_TABLE_NAME IN ('copy','unit') THEN
15344                 add_query := add_query || 'AND cp.id = ' || NEW.id || ';';
15345             ELSIF TG_TABLE_NAME = 'call_number' THEN
15346                 add_query := add_query || 'AND cp.call_number = ' || NEW.id || ';';
15347             ELSIF TG_TABLE_NAME = 'record_entry' THEN
15348                 add_query := add_query || 'AND cn.record = ' || NEW.id || ';';
15349             END IF;
15350  
15351             EXECUTE add_query;
15352             RETURN NEW;
15353  
15354         END IF;
15355  
15356     END IF;
15357
15358     IF TG_TABLE_NAME = 'call_number' THEN
15359
15360         IF OLD.record <> NEW.record THEN
15361             -- call number is linked to different bib
15362             remove_query := remove_query || 'call_number = ' || NEW.id || ');';
15363             EXECUTE remove_query;
15364             add_query := add_query || 'AND cp.call_number = ' || NEW.id || ';';
15365             EXECUTE add_query;
15366         END IF;
15367
15368         RETURN NEW;
15369
15370     END IF;
15371
15372     IF TG_TABLE_NAME IN ('record_entry') THEN
15373         RETURN NEW; -- don't have 'opac_visible'
15374     END IF;
15375
15376     -- actor.org_unit, asset.copy_location, asset.copy_status
15377     IF NEW.opac_visible = OLD.opac_visible THEN -- do nothing
15378
15379         RETURN NEW;
15380
15381     ELSIF NEW.opac_visible THEN -- add rows
15382
15383         IF TG_TABLE_NAME = 'org_unit' THEN
15384             add_query := add_query || 'AND cp.circ_lib = ' || NEW.id || ';';
15385         ELSIF TG_TABLE_NAME = 'copy_location' THEN
15386             add_query := add_query || 'AND cp.location = ' || NEW.id || ';';
15387         ELSIF TG_TABLE_NAME = 'copy_status' THEN
15388             add_query := add_query || 'AND cp.status = ' || NEW.id || ';';
15389         END IF;
15390  
15391         EXECUTE add_query;
15392  
15393     ELSE -- delete rows
15394
15395         IF TG_TABLE_NAME = 'org_unit' THEN
15396             remove_query := 'DELETE FROM asset.opac_visible_copies WHERE circ_lib = ' || NEW.id || ';';
15397         ELSIF TG_TABLE_NAME = 'copy_location' THEN
15398             remove_query := remove_query || 'location = ' || NEW.id || ');';
15399         ELSIF TG_TABLE_NAME = 'copy_status' THEN
15400             remove_query := remove_query || 'status = ' || NEW.id || ');';
15401         END IF;
15402  
15403         EXECUTE remove_query;
15404  
15405     END IF;
15406  
15407     RETURN NEW;
15408 END;
15409 $func$ LANGUAGE PLPGSQL;
15410 COMMENT ON FUNCTION asset.cache_copy_visibility() IS $$
15411 Trigger function to update the copy OPAC visiblity cache.
15412 $$;
15413 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();
15414 CREATE TRIGGER a_opac_vis_mat_view_tgr AFTER INSERT OR UPDATE ON asset.copy FOR EACH ROW EXECUTE PROCEDURE asset.cache_copy_visibility();
15415 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();
15416 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();
15417 CREATE TRIGGER a_opac_vis_mat_view_tgr AFTER INSERT OR UPDATE ON serial.unit FOR EACH ROW EXECUTE PROCEDURE asset.cache_copy_visibility();
15418 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();
15419 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();
15420
15421 -- must create this rule explicitly; it is not inherited from asset.copy
15422 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;
15423
15424 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);
15425
15426 CREATE OR REPLACE FUNCTION authority.merge_records ( target_record BIGINT, source_record BIGINT ) RETURNS INT AS $func$
15427 DECLARE
15428     moved_objects INT := 0;
15429     bib_id        INT := 0;
15430     bib_rec       biblio.record_entry%ROWTYPE;
15431     auth_link     authority.bib_linking%ROWTYPE;
15432 BEGIN
15433
15434     -- 1. Make source_record MARC a copy of the target_record to get auto-sync in linked bib records
15435     UPDATE authority.record_entry
15436       SET marc = (
15437         SELECT marc
15438           FROM authority.record_entry
15439           WHERE id = target_record
15440       )
15441       WHERE id = source_record;
15442
15443     -- 2. Update all bib records with the ID from target_record in their $0
15444     FOR bib_rec IN SELECT bre.* FROM biblio.record_entry bre 
15445       INNER JOIN authority.bib_linking abl ON abl.bib = bre.id
15446       WHERE abl.authority = target_record LOOP
15447
15448         UPDATE biblio.record_entry
15449           SET marc = REGEXP_REPLACE(marc, 
15450             E'(<subfield\\s+code="0"\\s*>[^<]*?\\))' || source_record || '<',
15451             E'\\1' || target_record || '<', 'g')
15452           WHERE id = bib_rec.id;
15453
15454           moved_objects := moved_objects + 1;
15455     END LOOP;
15456
15457     -- 3. "Delete" source_record
15458     DELETE FROM authority.record_entry
15459       WHERE id = source_record;
15460
15461     RETURN moved_objects;
15462 END;
15463 $func$ LANGUAGE plpgsql;
15464
15465 -- serial.record_entry already had an owner column spelled "owning_lib"
15466 -- Adjust the table and affected functions accordingly
15467
15468 ALTER TABLE serial.record_entry DROP COLUMN owner;
15469
15470 CREATE TABLE actor.usr_saved_search (
15471     id              SERIAL          PRIMARY KEY,
15472         owner           INT             NOT NULL REFERENCES actor.usr (id)
15473                                         ON DELETE CASCADE
15474                                         DEFERRABLE INITIALLY DEFERRED,
15475         name            TEXT            NOT NULL,
15476         create_date     TIMESTAMPTZ     NOT NULL DEFAULT now(),
15477         query_text      TEXT            NOT NULL,
15478         query_type      TEXT            NOT NULL
15479                                         CONSTRAINT valid_query_text CHECK (
15480                                         query_type IN ( 'URL' )) DEFAULT 'URL',
15481                                         -- we may add other types someday
15482         target          TEXT            NOT NULL
15483                                         CONSTRAINT valid_target CHECK (
15484                                         target IN ( 'record', 'metarecord', 'callnumber' )),
15485         CONSTRAINT name_once_per_user UNIQUE (owner, name)
15486 );
15487
15488 -- Apply Dan Wells' changes to the serial schema, from the
15489 -- seials-integration branch
15490
15491 CREATE TABLE serial.subscription_note (
15492         id           SERIAL PRIMARY KEY,
15493         subscription INT    NOT NULL
15494                             REFERENCES serial.subscription (id)
15495                             ON DELETE CASCADE
15496                             DEFERRABLE INITIALLY DEFERRED,
15497         creator      INT    NOT NULL
15498                             REFERENCES actor.usr (id)
15499                             DEFERRABLE INITIALLY DEFERRED,
15500         create_date  TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
15501         pub          BOOL   NOT NULL DEFAULT FALSE,
15502         title        TEXT   NOT NULL,
15503         value        TEXT   NOT NULL
15504 );
15505
15506 CREATE TABLE serial.distribution_note (
15507         id           SERIAL PRIMARY KEY,
15508         distribution INT    NOT NULL
15509                             REFERENCES serial.distribution (id)
15510                             ON DELETE CASCADE
15511                             DEFERRABLE INITIALLY DEFERRED,
15512         creator      INT    NOT NULL
15513                             REFERENCES actor.usr (id)
15514                             DEFERRABLE INITIALLY DEFERRED,
15515         create_date  TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
15516         pub          BOOL   NOT NULL DEFAULT FALSE,
15517         title        TEXT   NOT NULL,
15518         value        TEXT   NOT NULL
15519 );
15520
15521 ------- Begin surgery on serial.unit
15522
15523 ALTER TABLE serial.unit
15524         DROP COLUMN label;
15525
15526 ALTER TABLE serial.unit
15527         RENAME COLUMN label_sort_key TO sort_key;
15528
15529 ALTER TABLE serial.unit
15530         RENAME COLUMN contents TO detailed_contents;
15531
15532 ALTER TABLE serial.unit
15533         ADD COLUMN summary_contents TEXT;
15534
15535 UPDATE serial.unit
15536 SET summary_contents = detailed_contents;
15537
15538 ALTER TABLE serial.unit
15539         ALTER column summary_contents SET NOT NULL;
15540
15541 ------- End surgery on serial.unit
15542
15543 -- 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' );
15544
15545 -- Now rebuild the constraints dropped via cascade.
15546 -- ALTER TABLE acq.provider    ADD CONSTRAINT provider_edi_default_fkey FOREIGN KEY (edi_default) REFERENCES acq.edi_account (id) DEFERRABLE INITIALLY DEFERRED;
15547 DROP INDEX IF EXISTS money.money_mat_summary_id_idx;
15548 ALTER TABLE money.materialized_billable_xact_summary ADD PRIMARY KEY (id);
15549
15550 -- ALTER TABLE staging.billing_address_stage ADD PRIMARY KEY (row_id);
15551
15552 DELETE FROM config.metabib_field_index_norm_map
15553     WHERE norm IN (
15554         SELECT id 
15555             FROM config.index_normalizer
15556             WHERE func IN ('first_word', 'naco_normalize', 'split_date_range')
15557     )
15558     AND field = 18
15559 ;
15560
15561 -- We won't necessarily use all of these, but they are here for completeness.
15562 -- Source is the EDI spec 6063 codelist, eg: http://www.stylusstudio.com/edifact/D04B/6063.htm
15563 -- Values are the EDI code value + 1200
15564
15565 INSERT INTO acq.cancel_reason (org_unit, keep_debits, id, label, description) VALUES 
15566 (1, 't', 1201, 'Discrete quantity', 'Individually separated and distinct quantity.'),
15567 (1, 't', 1202, 'Charge', 'Quantity relevant for charge.'),
15568 (1, 't', 1203, 'Cumulative quantity', 'Quantity accumulated.'),
15569 (1, 't', 1204, 'Interest for overdrawn account', 'Interest for overdrawing the account.'),
15570 (1, 't', 1205, 'Active ingredient dose per unit', 'The dosage of active ingredient per unit.'),
15571 (1, 't', 1206, 'Auditor', 'The number of entities that audit accounts.'),
15572 (1, 't', 1207, 'Branch locations, leased', 'The number of branch locations being leased by an entity.'),
15573 (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.'),
15574 (1, 't', 1209, 'Branch locations, owned', 'The number of branch locations owned by an entity.'),
15575 (1, 't', 1210, 'Judgements registered', 'The number of judgements registered against an entity.'),
15576 (1, 't', 1211, 'Split quantity', 'Part of the whole quantity.'),
15577 (1, 't', 1212, 'Despatch quantity', 'Quantity despatched by the seller.'),
15578 (1, 't', 1213, 'Liens registered', 'The number of liens registered against an entity.'),
15579 (1, 't', 1214, 'Livestock', 'The number of animals kept for use or profit.'),
15580 (1, 't', 1215, 'Insufficient funds returned cheques', 'The number of cheques returned due to insufficient funds.'),
15581 (1, 't', 1216, 'Stolen cheques', 'The number of stolen cheques.'),
15582 (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.'),
15583 (1, 't', 1218, 'Previous quantity', 'Quantity previously referenced.'),
15584 (1, 't', 1219, 'Paid-in security shares', 'The number of security shares issued and for which full payment has been made.'),
15585 (1, 't', 1220, 'Unusable quantity', 'Quantity not usable.'),
15586 (1, 't', 1221, 'Ordered quantity', '[6024] The quantity which has been ordered.'),
15587 (1, 't', 1222, 'Quantity at 100%', 'Equivalent quantity at 100% purity.'),
15588 (1, 't', 1223, 'Active ingredient', 'Quantity at 100% active agent content.'),
15589 (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.'),
15590 (1, 't', 1225, 'Retail sales', 'Quantity of retail point of sale activity.'),
15591 (1, 't', 1226, 'Promotion quantity', 'A quantity associated with a promotional event.'),
15592 (1, 't', 1227, 'On hold for shipment', 'Article received which cannot be shipped in its present form.'),
15593 (1, 't', 1228, 'Military sales quantity', 'Quantity of goods or services sold to a military organization.'),
15594 (1, 't', 1229, 'On premises sales',  'Sale of product in restaurants or bars.'),
15595 (1, 't', 1230, 'Off premises sales', 'Sale of product directly to a store.'),
15596 (1, 't', 1231, 'Estimated annual volume', 'Volume estimated for a year.'),
15597 (1, 't', 1232, 'Minimum delivery batch', 'Minimum quantity of goods delivered at one time.'),
15598 (1, 't', 1233, 'Maximum delivery batch', 'Maximum quantity of goods delivered at one time.'),
15599 (1, 't', 1234, 'Pipes', 'The number of tubes used to convey a substance.'),
15600 (1, 't', 1235, 'Price break from', 'The minimum quantity of a quantity range for a specified (unit) price.'),
15601 (1, 't', 1236, 'Price break to', 'Maximum quantity to which the price break applies.'),
15602 (1, 't', 1237, 'Poultry', 'The number of domestic fowl.'),
15603 (1, 't', 1238, 'Secured charges registered', 'The number of secured charges registered against an entity.'),
15604 (1, 't', 1239, 'Total properties owned', 'The total number of properties owned by an entity.'),
15605 (1, 't', 1240, 'Normal delivery', 'Quantity normally delivered by the seller.'),
15606 (1, 't', 1241, 'Sales quantity not included in the replenishment', 'calculation Sales which will not be included in the calculation of replenishment requirements.'),
15607 (1, 't', 1242, 'Maximum supply quantity, supplier endorsed', 'Maximum supply quantity endorsed by a supplier.'),
15608 (1, 't', 1243, 'Buyer', 'The number of buyers.'),
15609 (1, 't', 1244, 'Debenture bond', 'The number of fixed-interest bonds of an entity backed by general credit rather than specified assets.'),
15610 (1, 't', 1245, 'Debentures filed against directors', 'The number of notices of indebtedness filed against an entity''s directors.'),
15611 (1, 't', 1246, 'Pieces delivered', 'Number of pieces actually received at the final destination.'),
15612 (1, 't', 1247, 'Invoiced quantity', 'The quantity as per invoice.'),
15613 (1, 't', 1248, 'Received quantity', 'The quantity which has been received.'),
15614 (1, 't', 1249, 'Chargeable distance', '[6110] The distance between two points for which a specific tariff applies.'),
15615 (1, 't', 1250, 'Disposition undetermined quantity', 'Product quantity that has not yet had its disposition determined.'),
15616 (1, 't', 1251, 'Inventory category transfer', 'Inventory that has been moved from one inventory category to another.'),
15617 (1, 't', 1252, 'Quantity per pack', 'Quantity for each pack.'),
15618 (1, 't', 1253, 'Minimum order quantity', 'Minimum quantity of goods for an order.'),
15619 (1, 't', 1254, 'Maximum order quantity', 'Maximum quantity of goods for an order.'),
15620 (1, 't', 1255, 'Total sales', 'The summation of total quantity sales.'),
15621 (1, 't', 1256, 'Wholesaler to wholesaler sales', 'Sale of product to other wholesalers by a wholesaler.'),
15622 (1, 't', 1257, 'In transit quantity', 'A quantity that is en route.'),
15623 (1, 't', 1258, 'Quantity withdrawn', 'Quantity withdrawn from a location.'),
15624 (1, 't', 1259, 'Numbers of consumer units in the traded unit', 'Number of units for consumer sales in a unit for trading.'),
15625 (1, 't', 1260, 'Current inventory quantity available for shipment', 'Current inventory quantity available for shipment.'),
15626 (1, 't', 1261, 'Return quantity', 'Quantity of goods returned.'),
15627 (1, 't', 1262, 'Sorted quantity', 'The quantity that is sorted.'),
15628 (1, 'f', 1263, 'Sorted quantity rejected', 'The sorted quantity that is rejected.'),
15629 (1, 't', 1264, 'Scrap quantity', 'Remainder of the total quantity after split deliveries.'),
15630 (1, 'f', 1265, 'Destroyed quantity', 'Quantity of goods destroyed.'),
15631 (1, 't', 1266, 'Committed quantity', 'Quantity a party is committed to.'),
15632 (1, 't', 1267, 'Estimated reading quantity', 'The value that is estimated to be the reading of a measuring device (e.g. meter).'),
15633 (1, 't', 1268, 'End quantity', 'The quantity recorded at the end of an agreement or period.'),
15634 (1, 't', 1269, 'Start quantity', 'The quantity recorded at the start of an agreement or period.'),
15635 (1, 't', 1270, 'Cumulative quantity received', 'Cumulative quantity of all deliveries of this article received by the buyer.'),
15636 (1, 't', 1271, 'Cumulative quantity ordered', 'Cumulative quantity of all deliveries, outstanding and scheduled orders.'),
15637 (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.'),
15638 (1, 't', 1273, 'Outstanding quantity', 'Difference between quantity ordered and quantity received.'),
15639 (1, 't', 1274, 'Latest cumulative quantity', 'Cumulative quantity after complete delivery of all scheduled quantities of the product.'),
15640 (1, 't', 1275, 'Previous highest cumulative quantity', 'Cumulative quantity after complete delivery of all scheduled quantities of the product from a prior schedule period.'),
15641 (1, 't', 1276, 'Adjusted corrector reading', 'A corrector reading after it has been adjusted.'),
15642 (1, 't', 1277, 'Work days', 'Number of work days, e.g. per respective period.'),
15643 (1, 't', 1278, 'Cumulative quantity scheduled', 'Adding the quantity actually scheduled to previous cumulative quantity.'),
15644 (1, 't', 1279, 'Previous cumulative quantity', 'Cumulative quantity prior the actual order.'),
15645 (1, 't', 1280, 'Unadjusted corrector reading', 'A corrector reading before it has been adjusted.'),
15646 (1, 't', 1281, 'Extra unplanned delivery', 'Non scheduled additional quantity.'),
15647 (1, 't', 1282, 'Quantity requirement for sample inspection', 'Required quantity for sample inspection.'),
15648 (1, 't', 1283, 'Backorder quantity', 'The quantity of goods that is on back-order.'),
15649 (1, 't', 1284, 'Urgent delivery quantity', 'Quantity for urgent delivery.'),
15650 (1, 'f', 1285, 'Previous order quantity to be cancelled', 'Quantity ordered previously to be cancelled.'),
15651 (1, 't', 1286, 'Normal reading quantity', 'The value recorded or read from a measuring device (e.g. meter) in the normal conditions.'),
15652 (1, 't', 1287, 'Customer reading quantity', 'The value recorded or read from a measuring device (e.g. meter) by the customer.'),
15653 (1, 't', 1288, 'Information reading quantity', 'The value recorded or read from a measuring device (e.g. meter) for information purposes.'),
15654 (1, 't', 1289, 'Quality control held', 'Quantity of goods held pending completion of a quality control assessment.'),
15655 (1, 't', 1290, 'As is quantity', 'Quantity as it is in the existing circumstances.'),
15656 (1, 't', 1291, 'Open quantity', 'Quantity remaining after partial delivery.'),
15657 (1, 't', 1292, 'Final delivery quantity', 'Quantity of final delivery to a respective order.'),
15658 (1, 't', 1293, 'Subsequent delivery quantity', 'Quantity delivered to a respective order after it''s final delivery.'),
15659 (1, 't', 1294, 'Substitutional quantity', 'Quantity delivered replacing previous deliveries.'),
15660 (1, 't', 1295, 'Redelivery after post processing', 'Quantity redelivered after post processing.'),
15661 (1, 'f', 1296, 'Quality control failed', 'Quantity of goods which have failed quality control.'),
15662 (1, 't', 1297, 'Minimum inventory', 'Minimum stock quantity on which replenishment is based.'),
15663 (1, 't', 1298, 'Maximum inventory', 'Maximum stock quantity on which replenishment is based.'),
15664 (1, 't', 1299, 'Estimated quantity', 'Quantity estimated.'),
15665 (1, 't', 1300, 'Chargeable weight', 'The weight on which charges are based.'),
15666 (1, 't', 1301, 'Chargeable gross weight', 'The gross weight on which charges are based.'),
15667 (1, 't', 1302, 'Chargeable tare weight', 'The tare weight on which charges are based.'),
15668 (1, 't', 1303, 'Chargeable number of axles', 'The number of axles on which charges are based.'),
15669 (1, 't', 1304, 'Chargeable number of containers', 'The number of containers on which charges are based.'),
15670 (1, 't', 1305, 'Chargeable number of rail wagons', 'The number of rail wagons on which charges are based.'),
15671 (1, 't', 1306, 'Chargeable number of packages', 'The number of packages on which charges are based.'),
15672 (1, 't', 1307, 'Chargeable number of units', 'The number of units on which charges are based.'),
15673 (1, 't', 1308, 'Chargeable period', 'The period of time on which charges are based.'),
15674 (1, 't', 1309, 'Chargeable volume', 'The volume on which charges are based.'),
15675 (1, 't', 1310, 'Chargeable cubic measurements', 'The cubic measurements on which charges are based.'),
15676 (1, 't', 1311, 'Chargeable surface', 'The surface area on which charges are based.'),
15677 (1, 't', 1312, 'Chargeable length', 'The length on which charges are based.'),
15678 (1, 't', 1313, 'Quantity to be delivered', 'The quantity to be delivered.'),
15679 (1, 't', 1314, 'Number of passengers', 'Total number of passengers on the conveyance.'),
15680 (1, 't', 1315, 'Number of crew', 'Total number of crew members on the conveyance.'),
15681 (1, 't', 1316, 'Number of transport documents', 'Total number of air waybills, bills of lading, etc. being reported for a specific conveyance.'),
15682 (1, 't', 1317, 'Quantity landed', 'Quantity of goods actually arrived.'),
15683 (1, 't', 1318, 'Quantity manifested', 'Quantity of goods contracted for delivery by the carrier.'),
15684 (1, 't', 1319, 'Short shipped', 'Indication that part of the consignment was not shipped.'),
15685 (1, 't', 1320, 'Split shipment', 'Indication that the consignment has been split into two or more shipments.'),
15686 (1, 't', 1321, 'Over shipped', 'The quantity of goods shipped that exceeds the quantity contracted.'),
15687 (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.'),
15688 (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.'),
15689 (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.'),
15690 (1, 'f', 1325, 'Pilferage goods', 'Quantity of goods stolen during transport.'),
15691 (1, 'f', 1326, 'Lost goods', 'Quantity of goods that disappeared in transport.'),
15692 (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.'),
15693 (1, 't', 1328, 'Quantity loaded', 'Quantity of goods loaded onto a means of transport.'),
15694 (1, 't', 1329, 'Units per unit price', 'Number of units per unit price.'),
15695 (1, 't', 1330, 'Allowance', 'Quantity relevant for allowance.'),
15696 (1, 't', 1331, 'Delivery quantity', 'Quantity required by buyer to be delivered.'),
15697 (1, 't', 1332, 'Cumulative quantity, preceding period, planned', 'Cumulative quantity originally planned for the preceding period.'),
15698 (1, 't', 1333, 'Cumulative quantity, preceding period, reached', 'Cumulative quantity reached in the preceding period.'),
15699 (1, 't', 1334, 'Cumulative quantity, actual planned',            'Cumulative quantity planned for now.'),
15700 (1, 't', 1335, 'Period quantity, planned', 'Quantity planned for this period.'),
15701 (1, 't', 1336, 'Period quantity, reached', 'Quantity reached during this period.'),
15702 (1, 't', 1337, 'Cumulative quantity, preceding period, estimated', 'Estimated cumulative quantity reached in the preceding period.'),
15703 (1, 't', 1338, 'Cumulative quantity, actual estimated',            'Estimated cumulative quantity reached now.'),
15704 (1, 't', 1339, 'Cumulative quantity, preceding period, measured', 'Surveyed cumulative quantity reached in the preceding period.'),
15705 (1, 't', 1340, 'Cumulative quantity, actual measured', 'Surveyed cumulative quantity reached now.'),
15706 (1, 't', 1341, 'Period quantity, measured',            'Surveyed quantity reached during this period.'),
15707 (1, 't', 1342, 'Total quantity, planned', 'Total quantity planned.'),
15708 (1, 't', 1343, 'Quantity, remaining', 'Quantity remaining.'),
15709 (1, 't', 1344, 'Tolerance', 'Plus or minus tolerance expressed as a monetary amount.'),
15710 (1, 't', 1345, 'Actual stock',          'The stock on hand, undamaged, and available for despatch, sale or use.'),
15711 (1, 't', 1346, 'Model or target stock', 'The stock quantity required or planned to have on hand, undamaged and available for use.'),
15712 (1, 't', 1347, 'Direct shipment quantity', 'Quantity to be shipped directly to a customer from a manufacturing site.'),
15713 (1, 't', 1348, 'Amortization total quantity',     'Indication of final quantity for amortization.'),
15714 (1, 't', 1349, 'Amortization order quantity',     'Indication of actual share of the order quantity for amortization.'),
15715 (1, 't', 1350, 'Amortization cumulated quantity', 'Indication of actual cumulated quantity of previous and actual amortization order quantity.'),
15716 (1, 't', 1351, 'Quantity advised',  'Quantity advised by supplier or shipper, in contrast to quantity actually received.'),
15717 (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.'),
15718 (1, 't', 1353, 'Statistical sales quantity', 'Quantity of goods sold in a specified period.'),
15719 (1, 't', 1354, 'Sales quantity planned',     'Quantity of goods required to meet future demands. - Market intelligence quantity.'),
15720 (1, 't', 1355, 'Replenishment quantity',     'Quantity required to maintain the requisite on-hand stock of goods.'),
15721 (1, 't', 1356, 'Inventory movement quantity', 'To specify the quantity of an inventory movement.'),
15722 (1, 't', 1357, 'Opening stock balance quantity', 'To specify the quantity of an opening stock balance.'),
15723 (1, 't', 1358, 'Closing stock balance quantity', 'To specify the quantity of a closing stock balance.'),
15724 (1, 't', 1359, 'Number of stops', 'Number of times a means of transport stops before arriving at destination.'),
15725 (1, 't', 1360, 'Minimum production batch', 'The quantity specified is the minimum output from a single production run.'),
15726 (1, 't', 1361, 'Dimensional sample quantity', 'The quantity defined is a sample for the purpose of validating dimensions.'),
15727 (1, 't', 1362, 'Functional sample quantity', 'The quantity defined is a sample for the purpose of validating function and performance.'),
15728 (1, 't', 1363, 'Pre-production quantity', 'Quantity of the referenced item required prior to full production.'),
15729 (1, 't', 1364, 'Delivery batch', 'Quantity of the referenced item which constitutes a standard batch for deliver purposes.'),
15730 (1, 't', 1365, 'Delivery batch multiple', 'The multiples in which delivery batches can be supplied.'),
15731 (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.'),
15732 (1, 't', 1367, 'Total delivery quantity',  'The total quantity required by the buyer to be delivered.'),
15733 (1, 't', 1368, 'Single delivery quantity', 'The quantity required by the buyer to be delivered in a single shipment.'),
15734 (1, 't', 1369, 'Supplied quantity',  'Quantity of the referenced item actually shipped.'),
15735 (1, 't', 1370, 'Allocated quantity', 'Quantity of the referenced item allocated from available stock for delivery.'),
15736 (1, 't', 1371, 'Maximum stackability', 'The number of pallets/handling units which can be safely stacked one on top of another.'),
15737 (1, 't', 1372, 'Amortisation quantity', 'The quantity of the referenced item which has a cost for tooling amortisation included in the item price.'),
15738 (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.'),
15739 (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.'),
15740 (1, 't', 1375, 'Number of moulds', 'The number of pressing moulds contained within a single piece of the referenced tooling.'),
15741 (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.'),
15742 (1, 't', 1377, 'Periodic capacity of tooling', 'Maximum production output of the referenced tool over a period of time.'),
15743 (1, 't', 1378, 'Lifetime capacity of tooling', 'Maximum production output of the referenced tool over its productive lifetime.'),
15744 (1, 't', 1379, 'Number of deliveries per despatch period', 'The number of deliveries normally expected to be despatched within each despatch period.'),
15745 (1, 't', 1380, 'Provided quantity', 'The quantity of a referenced component supplied by the buyer for manufacturing of an ordered item.'),
15746 (1, 't', 1381, 'Maximum production batch', 'The quantity specified is the maximum output from a single production run.'),
15747 (1, 'f', 1382, 'Cancelled quantity', 'Quantity of the referenced item which has previously been ordered and is now cancelled.'),
15748 (1, 't', 1383, 'No delivery requirement in this instruction', 'This delivery instruction does not contain any delivery requirements.'),
15749 (1, 't', 1384, 'Quantity of material in ordered time', 'Quantity of the referenced material within the ordered time.'),
15750 (1, 'f', 1385, 'Rejected quantity', 'The quantity of received goods rejected for quantity reasons.'),
15751 (1, 't', 1386, 'Cumulative quantity scheduled up to accumulation start date', 'The cumulative quantity scheduled up to the accumulation start date.'),
15752 (1, 't', 1387, 'Quantity scheduled', 'The quantity scheduled for delivery.'),
15753 (1, 't', 1388, 'Number of identical handling units', 'Number of identical handling units in terms of type and contents.'),
15754 (1, 't', 1389, 'Number of packages in handling unit', 'The number of packages contained in one handling unit.'),
15755 (1, 't', 1390, 'Despatch note quantity', 'The item quantity specified on the despatch note.'),
15756 (1, 't', 1391, 'Adjustment to inventory quantity', 'An adjustment to inventory quantity.'),
15757 (1, 't', 1392, 'Free goods quantity',    'Quantity of goods which are free of charge.'),
15758 (1, 't', 1393, 'Free quantity included', 'Quantity included to which no charge is applicable.'),
15759 (1, 't', 1394, 'Received and accepted',  'Quantity which has been received and accepted at a given location.'),
15760 (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.'),
15761 (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.'),
15762 (1, 't', 1397, 'Reordering level', 'Quantity at which an order may be triggered to replenish.'),
15763 (1, 't', 1399, 'Inventory withdrawal quantity', 'Quantity which has been withdrawn from inventory since the last inventory report.'),
15764 (1, 't', 1400, 'Free quantity not included', 'Free quantity not included in ordered quantity.'),
15765 (1, 't', 1401, 'Recommended overhaul and repair quantity', 'To indicate the recommended quantity of an article required to support overhaul and repair activities.'),
15766 (1, 't', 1402, 'Quantity per next higher assembly', 'To indicate the quantity required for the next higher assembly.'),
15767 (1, 't', 1403, 'Quantity per unit of issue', 'Provides the standard quantity of an article in which one unit can be issued.'),
15768 (1, 't', 1404, 'Cumulative scrap quantity',  'Provides the cumulative quantity of an item which has been identified as scrapped.'),
15769 (1, 't', 1405, 'Publication turn size', 'The quantity of magazines or newspapers grouped together with the spine facing alternate directions in a bundle.'),
15770 (1, 't', 1406, 'Recommended maintenance quantity', 'Recommended quantity of an article which is required to meet an agreed level of maintenance.'),
15771 (1, 't', 1407, 'Labour hours', 'Number of labour hours.'),
15772 (1, 't', 1408, 'Quantity requirement for maintenance and repair of', 'equipment Quantity of the material needed to maintain and repair equipment.'),
15773 (1, 't', 1409, 'Additional replenishment demand quantity', 'Incremental needs over and above normal replenishment calculations, but not intended to permanently change the model parameters.'),
15774 (1, 't', 1410, 'Returned by consumer quantity', 'Quantity returned by a consumer.'),
15775 (1, 't', 1411, 'Replenishment override quantity', 'Quantity to override the normal replenishment model calculations, but not intended to permanently change the model parameters.'),
15776 (1, 't', 1412, 'Quantity sold, net', 'Net quantity sold which includes returns of saleable inventory and other adjustments.'),
15777 (1, 't', 1413, 'Transferred out quantity',   'Quantity which was transferred out of this location.'),
15778 (1, 't', 1414, 'Transferred in quantity',    'Quantity which was transferred into this location.'),
15779 (1, 't', 1415, 'Unsaleable quantity',        'Quantity of inventory received which cannot be sold in its present condition.'),
15780 (1, 't', 1416, 'Consumer reserved quantity', 'Quantity reserved for consumer delivery or pickup and not yet withdrawn from inventory.'),
15781 (1, 't', 1417, 'Out of inventory quantity',  'Quantity of inventory which was requested but was not available.'),
15782 (1, 't', 1418, 'Quantity returned, defective or damaged', 'Quantity returned in a damaged or defective condition.'),
15783 (1, 't', 1419, 'Taxable quantity',           'Quantity subject to taxation.'),
15784 (1, 't', 1420, 'Meter reading', 'The numeric value of measure units counted by a meter.'),
15785 (1, 't', 1421, 'Maximum requestable quantity', 'The maximum quantity which may be requested.'),
15786 (1, 't', 1422, 'Minimum requestable quantity', 'The minimum quantity which may be requested.'),
15787 (1, 't', 1423, 'Daily average quantity', 'The quantity for a defined period divided by the number of days of the period.'),
15788 (1, 't', 1424, 'Budgeted hours',     'The number of budgeted hours.'),
15789 (1, 't', 1425, 'Actual hours',       'The number of actual hours.'),
15790 (1, 't', 1426, 'Earned value hours', 'The number of earned value hours.'),
15791 (1, 't', 1427, 'Estimated hours',    'The number of estimated hours.'),
15792 (1, 't', 1428, 'Level resource task quantity', 'Quantity of a resource that is level for the duration of the task.'),
15793 (1, 't', 1429, 'Available resource task quantity', 'Quantity of a resource available to complete a task.'),
15794 (1, 't', 1430, 'Work time units',   'Quantity of work units of time.'),
15795 (1, 't', 1431, 'Daily work shifts', 'Quantity of work shifts per day.'),
15796 (1, 't', 1432, 'Work time units per shift', 'Work units of time per work shift.'),
15797 (1, 't', 1433, 'Work calendar units',       'Work calendar units of time.'),
15798 (1, 't', 1434, 'Elapsed duration',   'Quantity representing the elapsed duration.'),
15799 (1, 't', 1435, 'Remaining duration', 'Quantity representing the remaining duration.'),
15800 (1, 't', 1436, 'Original duration',  'Quantity representing the original duration.'),
15801 (1, 't', 1437, 'Current duration',   'Quantity representing the current duration.'),
15802 (1, 't', 1438, 'Total float time',   'Quantity representing the total float time.'),
15803 (1, 't', 1439, 'Free float time',    'Quantity representing the free float time.'),
15804 (1, 't', 1440, 'Lag time',           'Quantity representing lag time.'),
15805 (1, 't', 1441, 'Lead time',          'Quantity representing lead time.'),
15806 (1, 't', 1442, 'Number of months', 'The number of months.'),
15807 (1, 't', 1443, 'Reserved quantity customer direct delivery sales', 'Quantity of products reserved for sales delivered direct to the customer.'),
15808 (1, 't', 1444, 'Reserved quantity retail sales', 'Quantity of products reserved for retail sales.'),
15809 (1, 't', 1445, 'Consolidated discount inventory', 'A quantity of inventory supplied at consolidated discount terms.'),
15810 (1, 't', 1446, 'Returns replacement quantity',    'A quantity of goods issued as a replacement for a returned quantity.'),
15811 (1, 't', 1447, 'Additional promotion sales forecast quantity', 'A forecast of additional quantity which will be sold during a period of promotional activity.'),
15812 (1, 't', 1448, 'Reserved quantity', 'Quantity reserved for specific purposes.'),
15813 (1, 't', 1449, 'Quantity displayed not available for sale', 'Quantity displayed within a retail outlet but not available for sale.'),
15814 (1, 't', 1450, 'Inventory discrepancy', 'The difference recorded between theoretical and physical inventory.'),
15815 (1, 't', 1451, 'Incremental order quantity', 'The incremental quantity by which ordering is carried out.'),
15816 (1, 't', 1452, 'Quantity requiring manipulation before despatch', 'A quantity of goods which needs manipulation before despatch.'),
15817 (1, 't', 1453, 'Quantity in quarantine',              'A quantity of goods which are held in a restricted area for quarantine purposes.'),
15818 (1, 't', 1454, 'Quantity withheld by owner of goods', 'A quantity of goods which has been withheld by the owner of the goods.'),
15819 (1, 't', 1455, 'Quantity not available for despatch', 'A quantity of goods not available for despatch.'),
15820 (1, 't', 1456, 'Quantity awaiting delivery', 'Quantity of goods which are awaiting delivery.'),
15821 (1, 't', 1457, 'Quantity in physical inventory',      'A quantity of goods held in physical inventory.'),
15822 (1, 't', 1458, 'Quantity held by logistic service provider', 'Quantity of goods under the control of a logistic service provider.'),
15823 (1, 't', 1459, 'Optimal quantity', 'The optimal quantity for a given purpose.'),
15824 (1, 't', 1460, 'Delivery quantity balance', 'The difference between the scheduled quantity and the quantity delivered to the consignee at a given date.'),
15825 (1, 't', 1461, 'Cumulative quantity shipped', 'Cumulative quantity of all shipments.'),
15826 (1, 't', 1462, 'Quantity suspended', 'The quantity of something which is suspended.'),
15827 (1, 't', 1463, 'Control quantity', 'The quantity designated for control purposes.'),
15828 (1, 't', 1464, 'Equipment quantity', 'A count of a quantity of equipment.'),
15829 (1, 't', 1465, 'Factor', 'Number by which the measured unit has to be multiplied to calculate the units used.'),
15830 (1, 't', 1466, 'Unsold quantity held by wholesaler', 'Unsold quantity held by the wholesaler.'),
15831 (1, 't', 1467, 'Quantity held by delivery vehicle', 'Quantity of goods held by the delivery vehicle.'),
15832 (1, 't', 1468, 'Quantity held by retail outlet', 'Quantity held by the retail outlet.'),
15833 (1, 'f', 1469, 'Rejected return quantity', 'A quantity for return which has been rejected.'),
15834 (1, 't', 1470, 'Accounts', 'The number of accounts.'),
15835 (1, 't', 1471, 'Accounts placed for collection', 'The number of accounts placed for collection.'),
15836 (1, 't', 1472, 'Activity codes', 'The number of activity codes.'),
15837 (1, 't', 1473, 'Agents', 'The number of agents.'),
15838 (1, 't', 1474, 'Airline attendants', 'The number of airline attendants.'),
15839 (1, 't', 1475, 'Authorised shares',  'The number of shares authorised for issue.'),
15840 (1, 't', 1476, 'Employee average',   'The average number of employees.'),
15841 (1, 't', 1477, 'Branch locations',   'The number of branch locations.'),
15842 (1, 't', 1478, 'Capital changes',    'The number of capital changes made.'),
15843 (1, 't', 1479, 'Clerks', 'The number of clerks.'),
15844 (1, 't', 1480, 'Companies in same activity', 'The number of companies doing business in the same activity category.'),
15845 (1, 't', 1481, 'Companies included in consolidated financial statement', 'The number of companies included in a consolidated financial statement.'),
15846 (1, 't', 1482, 'Cooperative shares', 'The number of cooperative shares.'),
15847 (1, 't', 1483, 'Creditors',   'The number of creditors.'),
15848 (1, 't', 1484, 'Departments', 'The number of departments.'),
15849 (1, 't', 1485, 'Design employees', 'The number of employees involved in the design process.'),
15850 (1, 't', 1486, 'Physicians', 'The number of medical doctors.'),
15851 (1, 't', 1487, 'Domestic affiliated companies', 'The number of affiliated companies located within the country.'),
15852 (1, 't', 1488, 'Drivers', 'The number of drivers.'),
15853 (1, 't', 1489, 'Employed at location',     'The number of employees at the specified location.'),
15854 (1, 't', 1490, 'Employed by this company', 'The number of employees at the specified company.'),
15855 (1, 't', 1491, 'Total employees',    'The total number of employees.'),
15856 (1, 't', 1492, 'Employees shared',   'The number of employees shared among entities.'),
15857 (1, 't', 1493, 'Engineers',          'The number of engineers.'),
15858 (1, 't', 1494, 'Estimated accounts', 'The estimated number of accounts.'),
15859 (1, 't', 1495, 'Estimated employees at location', 'The estimated number of employees at the specified location.'),
15860 (1, 't', 1496, 'Estimated total employees',       'The total estimated number of employees.'),
15861 (1, 't', 1497, 'Executives', 'The number of executives.'),
15862 (1, 't', 1498, 'Agricultural workers',   'The number of agricultural workers.'),
15863 (1, 't', 1499, 'Financial institutions', 'The number of financial institutions.'),
15864 (1, 't', 1500, 'Floors occupied', 'The number of floors occupied.'),
15865 (1, 't', 1501, 'Foreign related entities', 'The number of related entities located outside the country.'),
15866 (1, 't', 1502, 'Group employees',    'The number of employees within the group.'),
15867 (1, 't', 1503, 'Indirect employees', 'The number of employees not associated with direct production.'),
15868 (1, 't', 1504, 'Installers',    'The number of employees involved with the installation process.'),
15869 (1, 't', 1505, 'Invoices',      'The number of invoices.'),
15870 (1, 't', 1506, 'Issued shares', 'The number of shares actually issued.'),
15871 (1, 't', 1507, 'Labourers',     'The number of labourers.'),
15872 (1, 't', 1508, 'Manufactured units', 'The number of units manufactured.'),
15873 (1, 't', 1509, 'Maximum number of employees', 'The maximum number of people employed.'),
15874 (1, 't', 1510, 'Maximum number of employees at location', 'The maximum number of people employed at a location.'),
15875 (1, 't', 1511, 'Members in group', 'The number of members within a group.'),
15876 (1, 't', 1512, 'Minimum number of employees at location', 'The minimum number of people employed at a location.'),
15877 (1, 't', 1513, 'Minimum number of employees', 'The minimum number of people employed.'),
15878 (1, 't', 1514, 'Non-union employees', 'The number of employees not belonging to a labour union.'),
15879 (1, 't', 1515, 'Floors', 'The number of floors in a building.'),
15880 (1, 't', 1516, 'Nurses', 'The number of nurses.'),
15881 (1, 't', 1517, 'Office workers', 'The number of workers in an office.'),
15882 (1, 't', 1518, 'Other employees', 'The number of employees otherwise categorised.'),
15883 (1, 't', 1519, 'Part time employees', 'The number of employees working on a part time basis.'),
15884 (1, 't', 1520, 'Accounts payable average overdue days', 'The average number of days accounts payable are overdue.'),
15885 (1, 't', 1521, 'Pilots', 'The number of pilots.'),
15886 (1, 't', 1522, 'Plant workers', 'The number of workers within a plant.'),
15887 (1, 't', 1523, 'Previous number of accounts', 'The number of accounts which preceded the current count.'),
15888 (1, 't', 1524, 'Previous number of branch locations', 'The number of branch locations which preceded the current count.'),
15889 (1, 't', 1525, 'Principals included as employees', 'The number of principals which are included in the count of employees.'),
15890 (1, 't', 1526, 'Protested bills', 'The number of bills which are protested.'),
15891 (1, 't', 1527, 'Registered brands distributed', 'The number of registered brands which are being distributed.'),
15892 (1, 't', 1528, 'Registered brands manufactured', 'The number of registered brands which are being manufactured.'),
15893 (1, 't', 1529, 'Related business entities', 'The number of related business entities.'),
15894 (1, 't', 1530, 'Relatives employed', 'The number of relatives which are counted as employees.'),
15895 (1, 't', 1531, 'Rooms',        'The number of rooms.'),
15896 (1, 't', 1532, 'Salespersons', 'The number of salespersons.'),
15897 (1, 't', 1533, 'Seats',        'The number of seats.'),
15898 (1, 't', 1534, 'Shareholders', 'The number of shareholders.'),
15899 (1, 't', 1535, 'Shares of common stock', 'The number of shares of common stock.'),
15900 (1, 't', 1536, 'Shares of preferred stock', 'The number of shares of preferred stock.'),
15901 (1, 't', 1537, 'Silent partners', 'The number of silent partners.'),
15902 (1, 't', 1538, 'Subcontractors',  'The number of subcontractors.'),
15903 (1, 't', 1539, 'Subsidiaries',    'The number of subsidiaries.'),
15904 (1, 't', 1540, 'Law suits',       'The number of law suits.'),
15905 (1, 't', 1541, 'Suppliers',       'The number of suppliers.'),
15906 (1, 't', 1542, 'Teachers',        'The number of teachers.'),
15907 (1, 't', 1543, 'Technicians',     'The number of technicians.'),
15908 (1, 't', 1544, 'Trainees',        'The number of trainees.'),
15909 (1, 't', 1545, 'Union employees', 'The number of employees who are members of a labour union.'),
15910 (1, 't', 1546, 'Number of units', 'The quantity of units.'),
15911 (1, 't', 1547, 'Warehouse employees', 'The number of employees who work in a warehouse setting.'),
15912 (1, 't', 1548, 'Shareholders holding remainder of shares', 'Number of shareholders owning the remainder of shares.'),
15913 (1, 't', 1549, 'Payment orders filed', 'Number of payment orders filed.'),
15914 (1, 't', 1550, 'Uncovered cheques', 'Number of uncovered cheques.'),
15915 (1, 't', 1551, 'Auctions', 'Number of auctions.'),
15916 (1, 't', 1552, 'Units produced', 'The number of units produced.'),
15917 (1, 't', 1553, 'Added employees', 'Number of employees that were added to the workforce.'),
15918 (1, 't', 1554, 'Number of added locations', 'Number of locations that were added.'),
15919 (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.'),
15920 (1, 't', 1556, 'Number of closed locations', 'Number of locations that were closed.'),
15921 (1, 't', 1557, 'Counter clerks', 'The number of clerks that work behind a flat-topped fitment.'),
15922 (1, 't', 1558, 'Payment experiences in the last 3 months', 'The number of payment experiences received for an entity over the last 3 months.'),
15923 (1, 't', 1559, 'Payment experiences in the last 12 months', 'The number of payment experiences received for an entity over the last 12 months.'),
15924 (1, 't', 1560, 'Total number of subsidiaries not included in the financial', 'statement The total number of subsidiaries not included in the financial statement.'),
15925 (1, 't', 1561, 'Paid-in common shares', 'The number of paid-in common shares.'),
15926 (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.'),
15927 (1, 't', 1563, 'Total number of foreign subsidiaries included in financial statement', 'The total number of foreign subsidiaries included in the financial statement.'),
15928 (1, 't', 1564, 'Total number of domestic subsidiaries included in financial statement', 'The total number of domestic subsidiaries included in the financial statement.'),
15929 (1, 't', 1565, 'Total transactions', 'The total number of transactions.'),
15930 (1, 't', 1566, 'Paid-in preferred shares', 'The number of paid-in preferred shares.'),
15931 (1, 't', 1567, 'Employees', 'Code specifying the quantity of persons working for a company, whose services are used for pay.'),
15932 (1, 't', 1568, 'Active ingredient dose per unit, dispensed', 'The dosage of active ingredient per dispensed unit.'),
15933 (1, 't', 1569, 'Budget', 'Budget quantity.'),
15934 (1, 't', 1570, 'Budget, cumulative to date', 'Budget quantity, cumulative to date.'),
15935 (1, 't', 1571, 'Actual units', 'The number of actual units.'),
15936 (1, 't', 1572, 'Actual units, cumulative to date', 'The number of cumulative to date actual units.'),
15937 (1, 't', 1573, 'Earned value', 'Earned value quantity.'),
15938 (1, 't', 1574, 'Earned value, cumulative to date', 'Earned value quantity accumulated to date.'),
15939 (1, 't', 1575, 'At completion quantity, estimated', 'The estimated quantity when a project is complete.'),
15940 (1, 't', 1576, 'To complete quantity, estimated', 'The estimated quantity required to complete a project.'),
15941 (1, 't', 1577, 'Adjusted units', 'The number of adjusted units.'),
15942 (1, 't', 1578, 'Number of limited partnership shares', 'Number of shares held in a limited partnership.'),
15943 (1, 't', 1579, 'National business failure incidences', 'Number of firms in a country that discontinued with a loss to creditors.'),
15944 (1, 't', 1580, 'Industry business failure incidences', 'Number of firms in a specific industry that discontinued with a loss to creditors.'),
15945 (1, 't', 1581, 'Business class failure incidences', 'Number of firms in a specific class that discontinued with a loss to creditors.'),
15946 (1, 't', 1582, 'Mechanics', 'Number of mechanics.'),
15947 (1, 't', 1583, 'Messengers', 'Number of messengers.'),
15948 (1, 't', 1584, 'Primary managers', 'Number of primary managers.'),
15949 (1, 't', 1585, 'Secretaries', 'Number of secretaries.'),
15950 (1, 't', 1586, 'Detrimental legal filings', 'Number of detrimental legal filings.'),
15951 (1, 't', 1587, 'Branch office locations, estimated', 'Estimated number of branch office locations.'),
15952 (1, 't', 1588, 'Previous number of employees', 'The number of employees for a previous period.'),
15953 (1, 't', 1589, 'Asset seizers', 'Number of entities that seize assets of another entity.'),
15954 (1, 't', 1590, 'Out-turned quantity', 'The quantity discharged.'),
15955 (1, 't', 1591, 'Material on-board quantity, prior to loading', 'The material in vessel tanks, void spaces, and pipelines prior to loading.'),
15956 (1, 't', 1592, 'Supplier estimated previous meter reading', 'Previous meter reading estimated by the supplier.'),
15957 (1, 't', 1593, 'Supplier estimated latest meter reading',   'Latest meter reading estimated by the supplier.'),
15958 (1, 't', 1594, 'Customer estimated previous meter reading', 'Previous meter reading estimated by the customer.'),
15959 (1, 't', 1595, 'Customer estimated latest meter reading',   'Latest meter reading estimated by the customer.'),
15960 (1, 't', 1596, 'Supplier previous meter reading',           'Previous meter reading done by the supplier.'),
15961 (1, 't', 1597, 'Supplier latest meter reading',             'Latest meter reading recorded by the supplier.'),
15962 (1, 't', 1598, 'Maximum number of purchase orders allowed', 'Maximum number of purchase orders that are allowed.'),
15963 (1, 't', 1599, 'File size before compression', 'The size of a file before compression.'),
15964 (1, 't', 1600, 'File size after compression', 'The size of a file after compression.'),
15965 (1, 't', 1601, 'Securities shares', 'Number of shares of securities.'),
15966 (1, 't', 1602, 'Patients',         'Number of patients.'),
15967 (1, 't', 1603, 'Completed projects', 'Number of completed projects.'),
15968 (1, 't', 1604, 'Promoters',        'Number of entities who finance or organize an event or a production.'),
15969 (1, 't', 1605, 'Administrators',   'Number of administrators.'),
15970 (1, 't', 1606, 'Supervisors',      'Number of supervisors.'),
15971 (1, 't', 1607, 'Professionals',    'Number of professionals.'),
15972 (1, 't', 1608, 'Debt collectors',  'Number of debt collectors.'),
15973 (1, 't', 1609, 'Inspectors',       'Number of individuals who perform inspections.'),
15974 (1, 't', 1610, 'Operators',        'Number of operators.'),
15975 (1, 't', 1611, 'Trainers',         'Number of trainers.'),
15976 (1, 't', 1612, 'Active accounts',  'Number of accounts in a current or active status.'),
15977 (1, 't', 1613, 'Trademarks used',  'Number of trademarks used.'),
15978 (1, 't', 1614, 'Machines',         'Number of machines.'),
15979 (1, 't', 1615, 'Fuel pumps',       'Number of fuel pumps.'),
15980 (1, 't', 1616, 'Tables available', 'Number of tables available for use.'),
15981 (1, 't', 1617, 'Directors',        'Number of directors.'),
15982 (1, 't', 1618, 'Freelance debt collectors', 'Number of debt collectors who work on a freelance basis.'),
15983 (1, 't', 1619, 'Freelance salespersons',    'Number of salespersons who work on a freelance basis.'),
15984 (1, 't', 1620, 'Travelling employees',      'Number of travelling employees.'),
15985 (1, 't', 1621, 'Foremen', 'Number of workers with limited supervisory responsibilities.'),
15986 (1, 't', 1622, 'Production workers', 'Number of employees engaged in production.'),
15987 (1, 't', 1623, 'Employees not including owners', 'Number of employees excluding business owners.'),
15988 (1, 't', 1624, 'Beds', 'Number of beds.'),
15989 (1, 't', 1625, 'Resting quantity', 'A quantity of product that is at rest before it can be used.'),
15990 (1, 't', 1626, 'Production requirements', 'Quantity needed to meet production requirements.'),
15991 (1, 't', 1627, 'Corrected quantity', 'The quantity has been corrected.'),
15992 (1, 't', 1628, 'Operating divisions', 'Number of divisions operating.'),
15993 (1, 't', 1629, 'Quantitative incentive scheme base', 'Quantity constituting the base for the quantitative incentive scheme.'),
15994 (1, 't', 1630, 'Petitions filed', 'Number of petitions that have been filed.'),
15995 (1, 't', 1631, 'Bankruptcy petitions filed', 'Number of bankruptcy petitions that have been filed.'),
15996 (1, 't', 1632, 'Projects in process', 'Number of projects in process.'),
15997 (1, 't', 1633, 'Changes in capital structure', 'Number of modifications made to the capital structure of an entity.'),
15998 (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.'),
15999 (1, 't', 1635, 'Number of failed businesses of directors', 'The number of failed businesses with which the directors have been associated.'),
16000 (1, 't', 1636, 'Professor', 'The number of professors.'),
16001 (1, 't', 1637, 'Seller',    'The number of sellers.'),
16002 (1, 't', 1638, 'Skilled worker', 'The number of skilled workers.'),
16003 (1, 't', 1639, 'Trademark represented', 'The number of trademarks represented.'),
16004 (1, 't', 1640, 'Number of quantitative incentive scheme units', 'Number of units allocated to a quantitative incentive scheme.'),
16005 (1, 't', 1641, 'Quantity in manufacturing process', 'Quantity currently in the manufacturing process.'),
16006 (1, 't', 1642, 'Number of units in the width of a layer', 'Number of units which make up the width of a layer.'),
16007 (1, 't', 1643, 'Number of units in the depth of a layer', 'Number of units which make up the depth of a layer.'),
16008 (1, 't', 1644, 'Return to warehouse', 'A quantity of products sent back to the warehouse.'),
16009 (1, 't', 1645, 'Return to the manufacturer', 'A quantity of products sent back from the manufacturer.'),
16010 (1, 't', 1646, 'Delta quantity', 'An increment or decrement to a quantity.'),
16011 (1, 't', 1647, 'Quantity moved between outlets', 'A quantity of products moved between outlets.'),
16012 (1, 't', 1648, 'Pre-paid invoice annual consumption, estimated', 'The estimated annual consumption used for a prepayment invoice.'),
16013 (1, 't', 1649, 'Total quoted quantity', 'The sum of quoted quantities.'),
16014 (1, 't', 1650, 'Requests pertaining to entity in last 12 months', 'Number of requests received in last 12 months pertaining to the entity.'),
16015 (1, 't', 1651, 'Total inquiry matches', 'Number of instances which correspond with the inquiry.'),
16016 (1, 't', 1652, 'En route to warehouse quantity',   'A quantity of products that is en route to a warehouse.'),
16017 (1, 't', 1653, 'En route from warehouse quantity', 'A quantity of products that is en route from a warehouse.'),
16018 (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.'),
16019 (1, 't', 1655, 'Not yet ordered quantity', 'The quantity which has not yet been ordered.'),
16020 (1, 't', 1656, 'Net reserve power', 'The reserve power available for the net.'),
16021 (1, 't', 1657, 'Maximum number of units per shelf', 'Maximum number of units of a product that can be placed on a shelf.'),
16022 (1, 't', 1658, 'Stowaway', 'Number of stowaway(s) on a conveyance.'),
16023 (1, 't', 1659, 'Tug', 'The number of tugboat(s).'),
16024 (1, 't', 1660, 'Maximum quantity capability of the package', 'Maximum quantity of a product that can be contained in a package.'),
16025 (1, 't', 1661, 'Calculated', 'The calculated quantity.'),
16026 (1, 't', 1662, 'Monthly volume, estimated', 'Volume estimated for a month.'),
16027 (1, 't', 1663, 'Total number of persons', 'Quantity representing the total number of persons.'),
16028 (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.'),
16029 (1, 't', 1665, 'Deducted tariff quantity',   'Quantity deducted from tariff quantity to reckon duty/tax/fee assessment bases.'),
16030 (1, 't', 1666, 'Advised but not arrived',    'Goods are advised by the consignor or supplier, but have not yet arrived at the destination.'),
16031 (1, 't', 1667, 'Received but not available', 'Goods have been received in the arrival area but are not yet available.'),
16032 (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.'),
16033 (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.'),
16034 (1, 't', 1670, 'Chargeable number of trailers', 'The number of trailers on which charges are based.'),
16035 (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.'),
16036 (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.'),
16037 (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.'),
16038 (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.'),
16039 (1, 't', 1675, 'Agreed maximum buying quantity', 'The agreed maximum quantity of the trade item that may be purchased.'),
16040 (1, 't', 1676, 'Agreed minimum buying quantity', 'The agreed minimum quantity of the trade item that may be purchased.'),
16041 (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.'),
16042 (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.'),
16043 (1, 't', 1679, 'Marine Diesel Oil bunkers, loaded',                  'Number of Marine Diesel Oil (MDO) bunkers taken on in the port.'),
16044 (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.'),
16045 (1, 't', 1681, 'Intermediate Fuel Oil bunkers, loaded',              'Number of Intermediate Fuel Oil (IFO) bunkers taken on in the port.'),
16046 (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.'),
16047 (1, 't', 1683, 'Bunker C bunkers, loaded', 'Number of Bunker C, or Number 6 fuel oil bunkers, taken on in the port.'),
16048 (1, 't', 1684, 'Number of individual units within the smallest packaging', 'unit Total number of individual units contained within the smallest unit of packaging.'),
16049 (1, 't', 1685, 'Percentage of constituent element', 'The part of a product or material that is composed of the constituent element, as a percentage.'),
16050 (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).'),
16051 (1, 't', 1687, 'Regulated commodity count', 'The number of regulated items.'),
16052 (1, 't', 1688, 'Number of passengers, embarking', 'The number of passengers going aboard a conveyance.'),
16053 (1, 't', 1689, 'Number of passengers, disembarking', 'The number of passengers disembarking the conveyance.'),
16054 (1, 't', 1690, 'Constituent element or component quantity', 'The specific quantity of the identified constituent element.')
16055 ;
16056 -- ZZZ, 'Mutually defined', 'As agreed by the trading partners.'),
16057
16058 CREATE TABLE acq.serial_claim (
16059     id     SERIAL           PRIMARY KEY,
16060     type   INT              NOT NULL REFERENCES acq.claim_type
16061                                      DEFERRABLE INITIALLY DEFERRED,
16062     item    BIGINT          NOT NULL REFERENCES serial.item
16063                                      DEFERRABLE INITIALLY DEFERRED
16064 );
16065
16066 CREATE INDEX serial_claim_lid_idx ON acq.serial_claim( item );
16067
16068 CREATE TABLE acq.serial_claim_event (
16069     id             BIGSERIAL        PRIMARY KEY,
16070     type           INT              NOT NULL REFERENCES acq.claim_event_type
16071                                              DEFERRABLE INITIALLY DEFERRED,
16072     claim          SERIAL           NOT NULL REFERENCES acq.serial_claim
16073                                              DEFERRABLE INITIALLY DEFERRED,
16074     event_date     TIMESTAMPTZ      NOT NULL DEFAULT now(),
16075     creator        INT              NOT NULL REFERENCES actor.usr
16076                                              DEFERRABLE INITIALLY DEFERRED,
16077     note           TEXT
16078 );
16079
16080 CREATE INDEX serial_claim_event_claim_date_idx ON acq.serial_claim_event( claim, event_date );
16081
16082 ALTER TABLE asset.stat_cat ADD COLUMN required BOOL NOT NULL DEFAULT FALSE;
16083
16084 -- now what about the auditor.*_lifecycle views??
16085
16086 INSERT INTO config.metabib_field ( id, field_class, name, label, format, xpath ) VALUES
16087     (26, 'identifier', 'tcn', oils_i18n_gettext(26, 'Title Control Number', 'cmf', 'label'), 'marcxml', $$//marc:datafield[@tag='901']/marc:subfield[@code='a']$$ );
16088 INSERT INTO config.metabib_field ( id, field_class, name, label, format, xpath ) VALUES
16089     (27, 'identifier', 'bibid', oils_i18n_gettext(27, 'Internal ID', 'cmf', 'label'), 'marcxml', $$//marc:datafield[@tag='901']/marc:subfield[@code='c']$$ );
16090 INSERT INTO config.metabib_search_alias (alias,field_class,field) VALUES ('eg.tcn','identifier', 26);
16091 INSERT INTO config.metabib_search_alias (alias,field_class,field) VALUES ('eg.bibid','identifier', 27);
16092
16093 CREATE TABLE asset.call_number_class (
16094     id             bigserial     PRIMARY KEY,
16095     name           TEXT          NOT NULL,
16096     normalizer     TEXT          NOT NULL DEFAULT 'asset.normalize_generic',
16097     field          TEXT          NOT NULL DEFAULT '050ab,055ab,060ab,070ab,080ab,082ab,086ab,088ab,090,092,096,098,099'
16098 );
16099
16100 COMMENT ON TABLE asset.call_number_class IS $$
16101 Defines the call number normalization database functions in the "normalizer"
16102 column and the tag/subfield combinations to use to lookup the call number in
16103 the "field" column for a given classification scheme. Tag/subfield combinations
16104 are delimited by commas.
16105 $$;
16106
16107 INSERT INTO asset.call_number_class (name, normalizer) VALUES 
16108     ('Generic', 'asset.label_normalizer_generic'),
16109     ('Dewey (DDC)', 'asset.label_normalizer_dewey'),
16110     ('Library of Congress (LC)', 'asset.label_normalizer_lc')
16111 ;
16112
16113 -- Generic fields
16114 UPDATE asset.call_number_class
16115     SET field = '050ab,055ab,060ab,070ab,080ab,082ab,086ab,088ab,090,092,096,098,099'
16116     WHERE id = 1
16117 ;
16118
16119 -- Dewey fields
16120 UPDATE asset.call_number_class
16121     SET field = '080ab,082ab'
16122     WHERE id = 2
16123 ;
16124
16125 -- LC fields
16126 UPDATE asset.call_number_class
16127     SET field = '050ab,055ab'
16128     WHERE id = 3
16129 ;
16130  
16131 ALTER TABLE auditor.asset_call_number_history ADD COLUMN label_class BIGINT;
16132 ALTER TABLE auditor.asset_call_number_history ADD COLUMN label_sortkey TEXT;
16133 ALTER TABLE asset.call_number ADD COLUMN label_class BIGINT DEFAULT 1 NOT NULL REFERENCES asset.call_number_class(id) DEFERRABLE INITIALLY DEFERRED;
16134 ALTER TABLE asset.call_number ADD COLUMN label_sortkey TEXT;
16135 CREATE INDEX asset_call_number_label_sortkey ON asset.call_number(label_sortkey);
16136
16137 CREATE OR REPLACE FUNCTION asset.label_normalizer() RETURNS TRIGGER AS $func$
16138 DECLARE
16139     sortkey        TEXT := '';
16140 BEGIN
16141     sortkey := NEW.label_sortkey;
16142
16143     EXECUTE 'SELECT ' || acnc.normalizer || '(' || 
16144        quote_literal( NEW.label ) || ')'
16145        FROM asset.call_number_class acnc
16146        WHERE acnc.id = NEW.label_class
16147        INTO sortkey;
16148
16149     NEW.label_sortkey = sortkey;
16150
16151     RETURN NEW;
16152 END;
16153 $func$ LANGUAGE PLPGSQL;
16154
16155 CREATE OR REPLACE FUNCTION asset.label_normalizer_generic(TEXT) RETURNS TEXT AS $func$
16156     # Created after looking at the Koha C4::ClassSortRoutine::Generic module,
16157     # thus could probably be considered a derived work, although nothing was
16158     # directly copied - but to err on the safe side of providing attribution:
16159     # Copyright (C) 2007 LibLime
16160     # Licensed under the GPL v2 or later
16161
16162     use strict;
16163     use warnings;
16164
16165     # Converts the callnumber to uppercase
16166     # Strips spaces from start and end of the call number
16167     # Converts anything other than letters, digits, and periods into underscores
16168     # Collapses multiple underscores into a single underscore
16169     my $callnum = uc(shift);
16170     $callnum =~ s/^\s//g;
16171     $callnum =~ s/\s$//g;
16172     $callnum =~ s/[^A-Z0-9_.]/_/g;
16173     $callnum =~ s/_{2,}/_/g;
16174
16175     return $callnum;
16176 $func$ LANGUAGE PLPERLU;
16177
16178 CREATE OR REPLACE FUNCTION asset.label_normalizer_dewey(TEXT) RETURNS TEXT AS $func$
16179     # Derived from the Koha C4::ClassSortRoutine::Dewey module
16180     # Copyright (C) 2007 LibLime
16181     # Licensed under the GPL v2 or later
16182
16183     use strict;
16184     use warnings;
16185
16186     my $init = uc(shift);
16187     $init =~ s/^\s+//;
16188     $init =~ s/\s+$//;
16189     $init =~ s!/!!g;
16190     $init =~ s/^([\p{IsAlpha}]+)/$1 /;
16191     my @tokens = split /\.|\s+/, $init;
16192     my $digit_group_count = 0;
16193     for (my $i = 0; $i <= $#tokens; $i++) {
16194         if ($tokens[$i] =~ /^\d+$/) {
16195             $digit_group_count++;
16196             if (2 == $digit_group_count) {
16197                 $tokens[$i] = sprintf("%-15.15s", $tokens[$i]);
16198                 $tokens[$i] =~ tr/ /0/;
16199             }
16200         }
16201     }
16202     my $key = join("_", @tokens);
16203     $key =~ s/[^\p{IsAlnum}_]//g;
16204
16205     return $key;
16206
16207 $func$ LANGUAGE PLPERLU;
16208
16209 CREATE OR REPLACE FUNCTION asset.label_normalizer_lc(TEXT) RETURNS TEXT AS $func$
16210     use strict;
16211     use warnings;
16212
16213     # Library::CallNumber::LC is currently hosted at http://code.google.com/p/library-callnumber-lc/
16214     # The author hopes to upload it to CPAN some day, which would make our lives easier
16215     use Library::CallNumber::LC;
16216
16217     my $callnum = Library::CallNumber::LC->new(shift);
16218     return $callnum->normalize();
16219
16220 $func$ LANGUAGE PLPERLU;
16221
16222 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$
16223 DECLARE
16224     ans RECORD;
16225     trans INT;
16226 BEGIN
16227     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;
16228
16229     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
16230         RETURN QUERY
16231         SELECT  ans.depth,
16232                 ans.id,
16233                 COUNT( av.id ),
16234                 SUM( CASE WHEN cp.status IN (0,7,12) THEN 1 ELSE 0 END ),
16235                 COUNT( av.id ),
16236                 trans
16237           FROM
16238                 actor.org_unit_descendants(ans.id) d
16239                 JOIN asset.opac_visible_copies av ON (av.record = record AND av.circ_lib = d.id)
16240                 JOIN asset.copy cp ON (cp.id = av.id)
16241           GROUP BY 1,2,6;
16242
16243         IF NOT FOUND THEN
16244             RETURN QUERY SELECT ans.depth, ans.id, 0::BIGINT, 0::BIGINT, 0::BIGINT, trans;
16245         END IF;
16246
16247     END LOOP;
16248
16249     RETURN;
16250 END;
16251 $f$ LANGUAGE PLPGSQL;
16252
16253 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$
16254 DECLARE
16255     ans RECORD;
16256     trans INT;
16257 BEGIN
16258     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;
16259
16260     FOR ans IN SELECT u.org_unit AS id FROM actor.org_lasso_map AS u WHERE lasso = i_lasso LOOP
16261         RETURN QUERY
16262         SELECT  -1,
16263                 ans.id,
16264                 COUNT( av.id ),
16265                 SUM( CASE WHEN cp.status IN (0,7,12) THEN 1 ELSE 0 END ),
16266                 COUNT( av.id ),
16267                 trans
16268           FROM
16269                 actor.org_unit_descendants(ans.id) d
16270                 JOIN asset.opac_visible_copies av ON (av.record = record AND av.circ_lib = d.id)
16271                 JOIN asset.copy cp ON (cp.id = av.id)
16272           GROUP BY 1,2,6;
16273
16274         IF NOT FOUND THEN
16275             RETURN QUERY SELECT ans.depth, ans.id, 0::BIGINT, 0::BIGINT, 0::BIGINT, trans;
16276         END IF;
16277
16278     END LOOP;
16279
16280     RETURN;
16281 END;
16282 $f$ LANGUAGE PLPGSQL;
16283
16284 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$
16285 DECLARE
16286     ans RECORD;
16287     trans INT;
16288 BEGIN
16289     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;
16290
16291     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
16292         RETURN QUERY
16293         SELECT  ans.depth,
16294                 ans.id,
16295                 COUNT( cp.id ),
16296                 SUM( CASE WHEN cp.status IN (0,7,12) THEN 1 ELSE 0 END ),
16297                 COUNT( cp.id ),
16298                 trans
16299           FROM
16300                 actor.org_unit_descendants(ans.id) d
16301                 JOIN asset.copy cp ON (cp.circ_lib = d.id)
16302                 JOIN asset.call_number cn ON (cn.record = record AND cn.id = cp.call_number)
16303           GROUP BY 1,2,6;
16304
16305         IF NOT FOUND THEN
16306             RETURN QUERY SELECT ans.depth, ans.id, 0::BIGINT, 0::BIGINT, 0::BIGINT, trans;
16307         END IF;
16308
16309     END LOOP;
16310
16311     RETURN;
16312 END;
16313 $f$ LANGUAGE PLPGSQL;
16314
16315 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$
16316 DECLARE
16317     ans RECORD;
16318     trans INT;
16319 BEGIN
16320     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;
16321
16322     FOR ans IN SELECT u.org_unit AS id FROM actor.org_lasso_map AS u WHERE lasso = i_lasso LOOP
16323         RETURN QUERY
16324         SELECT  -1,
16325                 ans.id,
16326                 COUNT( cp.id ),
16327                 SUM( CASE WHEN cp.status IN (0,7,12) THEN 1 ELSE 0 END ),
16328                 COUNT( cp.id ),
16329                 trans
16330           FROM
16331                 actor.org_unit_descendants(ans.id) d
16332                 JOIN asset.copy cp ON (cp.circ_lib = d.id)
16333                 JOIN asset.call_number cn ON (cn.record = record AND cn.id = cp.call_number)
16334           GROUP BY 1,2,6;
16335
16336         IF NOT FOUND THEN
16337             RETURN QUERY SELECT ans.depth, ans.id, 0::BIGINT, 0::BIGINT, 0::BIGINT, trans;
16338         END IF;
16339
16340     END LOOP;
16341
16342     RETURN;
16343 END;
16344 $f$ LANGUAGE PLPGSQL;
16345
16346 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$
16347 BEGIN
16348     IF staff IS TRUE THEN
16349         IF place > 0 THEN
16350             RETURN QUERY SELECT * FROM asset.staff_ou_record_copy_count( place, record );
16351         ELSE
16352             RETURN QUERY SELECT * FROM asset.staff_lasso_record_copy_count( -place, record );
16353         END IF;
16354     ELSE
16355         IF place > 0 THEN
16356             RETURN QUERY SELECT * FROM asset.opac_ou_record_copy_count( place, record );
16357         ELSE
16358             RETURN QUERY SELECT * FROM asset.opac_lasso_record_copy_count( -place, record );
16359         END IF;
16360     END IF;
16361
16362     RETURN;
16363 END;
16364 $f$ LANGUAGE PLPGSQL;
16365
16366 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$
16367 DECLARE
16368     ans RECORD;
16369     trans INT;
16370 BEGIN
16371     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;
16372
16373     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
16374         RETURN QUERY
16375         SELECT  ans.depth,
16376                 ans.id,
16377                 COUNT( av.id ),
16378                 SUM( CASE WHEN cp.status IN (0,7,12) THEN 1 ELSE 0 END ),
16379                 COUNT( av.id ),
16380                 trans
16381           FROM
16382                 actor.org_unit_descendants(ans.id) d
16383                 JOIN asset.opac_visible_copies av ON (av.record = record AND av.circ_lib = d.id)
16384                 JOIN asset.copy cp ON (cp.id = av.id)
16385                 JOIN metabib.metarecord_source_map m ON (m.source = av.record)
16386           GROUP BY 1,2,6;
16387
16388         IF NOT FOUND THEN
16389             RETURN QUERY SELECT ans.depth, ans.id, 0::BIGINT, 0::BIGINT, 0::BIGINT, trans;
16390         END IF;
16391
16392     END LOOP;
16393
16394     RETURN;
16395 END;
16396 $f$ LANGUAGE PLPGSQL;
16397
16398 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$
16399 DECLARE
16400     ans RECORD;
16401     trans INT;
16402 BEGIN
16403     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;
16404
16405     FOR ans IN SELECT u.org_unit AS id FROM actor.org_lasso_map AS u WHERE lasso = i_lasso LOOP
16406         RETURN QUERY
16407         SELECT  -1,
16408                 ans.id,
16409                 COUNT( av.id ),
16410                 SUM( CASE WHEN cp.status IN (0,7,12) THEN 1 ELSE 0 END ),
16411                 COUNT( av.id ),
16412                 trans
16413           FROM
16414                 actor.org_unit_descendants(ans.id) d
16415                 JOIN asset.opac_visible_copies av ON (av.record = record AND av.circ_lib = d.id)
16416                 JOIN asset.copy cp ON (cp.id = av.id)
16417                 JOIN metabib.metarecord_source_map m ON (m.source = av.record)
16418           GROUP BY 1,2,6;
16419
16420         IF NOT FOUND THEN
16421             RETURN QUERY SELECT ans.depth, ans.id, 0::BIGINT, 0::BIGINT, 0::BIGINT, trans;
16422         END IF;
16423
16424     END LOOP;
16425
16426     RETURN;
16427 END;
16428 $f$ LANGUAGE PLPGSQL;
16429
16430 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$
16431 DECLARE
16432     ans RECORD;
16433     trans INT;
16434 BEGIN
16435     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;
16436
16437     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
16438         RETURN QUERY
16439         SELECT  ans.depth,
16440                 ans.id,
16441                 COUNT( cp.id ),
16442                 SUM( CASE WHEN cp.status IN (0,7,12) THEN 1 ELSE 0 END ),
16443                 COUNT( cp.id ),
16444                 trans
16445           FROM
16446                 actor.org_unit_descendants(ans.id) d
16447                 JOIN asset.copy cp ON (cp.circ_lib = d.id)
16448                 JOIN asset.call_number cn ON (cn.record = record AND cn.id = cp.call_number)
16449                 JOIN metabib.metarecord_source_map m ON (m.source = cn.record)
16450           GROUP BY 1,2,6;
16451
16452         IF NOT FOUND THEN
16453             RETURN QUERY SELECT ans.depth, ans.id, 0::BIGINT, 0::BIGINT, 0::BIGINT, trans;
16454         END IF;
16455
16456     END LOOP;
16457
16458     RETURN;
16459 END;
16460 $f$ LANGUAGE PLPGSQL;
16461
16462 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$
16463 DECLARE
16464     ans RECORD;
16465     trans INT;
16466 BEGIN
16467     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;
16468
16469     FOR ans IN SELECT u.org_unit AS id FROM actor.org_lasso_map AS u WHERE lasso = i_lasso LOOP
16470         RETURN QUERY
16471         SELECT  -1,
16472                 ans.id,
16473                 COUNT( cp.id ),
16474                 SUM( CASE WHEN cp.status IN (0,7,12) THEN 1 ELSE 0 END ),
16475                 COUNT( cp.id ),
16476                 trans
16477           FROM
16478                 actor.org_unit_descendants(ans.id) d
16479                 JOIN asset.copy cp ON (cp.circ_lib = d.id)
16480                 JOIN asset.call_number cn ON (cn.record = record AND cn.id = cp.call_number)
16481                 JOIN metabib.metarecord_source_map m ON (m.source = cn.record)
16482           GROUP BY 1,2,6;
16483
16484         IF NOT FOUND THEN
16485             RETURN QUERY SELECT ans.depth, ans.id, 0::BIGINT, 0::BIGINT, 0::BIGINT, trans;
16486         END IF;
16487
16488     END LOOP;
16489
16490     RETURN;
16491 END;
16492 $f$ LANGUAGE PLPGSQL;
16493
16494 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$
16495 BEGIN
16496     IF staff IS TRUE THEN
16497         IF place > 0 THEN
16498             RETURN QUERY SELECT * FROM asset.staff_ou_metarecord_copy_count( place, record );
16499         ELSE
16500             RETURN QUERY SELECT * FROM asset.staff_lasso_metarecord_copy_count( -place, record );
16501         END IF;
16502     ELSE
16503         IF place > 0 THEN
16504             RETURN QUERY SELECT * FROM asset.opac_ou_metarecord_copy_count( place, record );
16505         ELSE
16506             RETURN QUERY SELECT * FROM asset.opac_lasso_metarecord_copy_count( -place, record );
16507         END IF;
16508     END IF;
16509
16510     RETURN;
16511 END;
16512 $f$ LANGUAGE PLPGSQL;
16513
16514 -- No transaction is required
16515
16516 -- Triggers on the vandelay.queued_*_record tables delete entries from
16517 -- the associated vandelay.queued_*_record_attr tables based on the record's
16518 -- ID; create an index on that column to avoid sequential scans for each
16519 -- queued record that is deleted
16520 CREATE INDEX queued_bib_record_attr_record_idx ON vandelay.queued_bib_record_attr (record);
16521 CREATE INDEX queued_authority_record_attr_record_idx ON vandelay.queued_authority_record_attr (record);
16522
16523 -- Avoid sequential scans for queue retrieval operations by providing an
16524 -- index on the queue column
16525 CREATE INDEX queued_bib_record_queue_idx ON vandelay.queued_bib_record (queue);
16526 CREATE INDEX queued_authority_record_queue_idx ON vandelay.queued_authority_record (queue);
16527
16528 CREATE INDEX actor_card_barcode_lower_idx ON actor.card (lower(barcode));
16529
16530 -- Start picking up call number label prefixes and suffixes
16531 -- from asset.copy_location
16532 ALTER TABLE asset.copy_location ADD COLUMN label_prefix TEXT;
16533 ALTER TABLE asset.copy_location ADD COLUMN label_suffix TEXT;
16534
16535 COMMIT;