]> git.evergreen-ils.org Git - Evergreen.git/blob - Open-ILS/src/sql/Pg/1.6.1-2.0-upgrade-db.sql
The reporter and extend_reporter schemas are no longer optional.
[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 -- Extend the name change to some related views:
4971
4972 -- Drop a view temporarily in order to alter action.all_circulation, upon
4973 -- which it is dependent.  We will recreate the view later.
4974
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, having altered the action.all_circulation view:
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 CREATE UNIQUE INDEX only_one_concurrent_checkout_per_copy ON action.circulation(target_copy) WHERE checkin_time IS NULL;
5016
5017 ALTER TABLE action.circulation DROP CONSTRAINT action_circulation_target_copy_fkey;
5018
5019 CREATE OR REPLACE FUNCTION action.age_circ_on_delete () RETURNS TRIGGER AS $$
5020 DECLARE
5021 found char := 'N';
5022 BEGIN
5023
5024     -- If there are any renewals for this circulation, don't archive or delete
5025     -- it yet.   We'll do so later, when we archive and delete the renewals.
5026
5027     SELECT 'Y' INTO found
5028     FROM action.circulation
5029     WHERE parent_circ = OLD.id
5030     LIMIT 1;
5031
5032     IF found = 'Y' THEN
5033         RETURN NULL;  -- don't delete
5034         END IF;
5035
5036     -- Archive a copy of the old row to action.aged_circulation
5037
5038     INSERT INTO action.aged_circulation
5039         (id,usr_post_code, usr_home_ou, usr_profile, usr_birth_year, copy_call_number, copy_location,
5040         copy_owning_lib, copy_circ_lib, copy_bib_record, xact_start, xact_finish, target_copy,
5041         circ_lib, circ_staff, checkin_staff, checkin_lib, renewal_remaining, due_date,
5042         stop_fines_time, checkin_time, create_time, duration, fine_interval, recurring_fine,
5043         max_fine, phone_renewal, desk_renewal, opac_renewal, duration_rule, recurring_fine_rule,
5044         max_fine_rule, stop_fines, workstation, checkin_workstation, checkin_scan_time, parent_circ)
5045       SELECT
5046         id,usr_post_code, usr_home_ou, usr_profile, usr_birth_year, copy_call_number, copy_location,
5047         copy_owning_lib, copy_circ_lib, copy_bib_record, xact_start, xact_finish, target_copy,
5048         circ_lib, circ_staff, checkin_staff, checkin_lib, renewal_remaining, due_date,
5049         stop_fines_time, checkin_time, create_time, duration, fine_interval, recurring_fine,
5050         max_fine, phone_renewal, desk_renewal, opac_renewal, duration_rule, recurring_fine_rule,
5051         max_fine_rule, stop_fines, workstation, checkin_workstation, checkin_scan_time, parent_circ
5052         FROM action.all_circulation WHERE id = OLD.id;
5053
5054     RETURN OLD;
5055 END;
5056 $$ LANGUAGE 'plpgsql';
5057
5058 UPDATE config.z3950_attr SET truncation = 1 WHERE source = 'biblios' AND name = 'title';
5059
5060 UPDATE config.z3950_attr SET truncation = 1 WHERE source = 'biblios' AND truncation = 0;
5061
5062 -- Adding circ.holds.target_skip_me OU setting logic to the pre-matchpoint tests
5063
5064 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$
5065 DECLARE
5066     matchpoint_id        INT;
5067     user_object        actor.usr%ROWTYPE;
5068     age_protect_object    config.rule_age_hold_protect%ROWTYPE;
5069     standing_penalty    config.standing_penalty%ROWTYPE;
5070     transit_range_ou_type    actor.org_unit_type%ROWTYPE;
5071     transit_source        actor.org_unit%ROWTYPE;
5072     item_object        asset.copy%ROWTYPE;
5073     ou_skip              actor.org_unit_setting%ROWTYPE;
5074     result            action.matrix_test_result;
5075     hold_test        config.hold_matrix_matchpoint%ROWTYPE;
5076     hold_count        INT;
5077     hold_transit_prox    INT;
5078     frozen_hold_count    INT;
5079     context_org_list    INT[];
5080     done            BOOL := FALSE;
5081 BEGIN
5082     SELECT INTO user_object * FROM actor.usr WHERE id = match_user;
5083     SELECT INTO context_org_list ARRAY_ACCUM(id) FROM actor.org_unit_full_path( pickup_ou );
5084
5085     result.success := TRUE;
5086
5087     -- Fail if we couldn't find a user
5088     IF user_object.id IS NULL THEN
5089         result.fail_part := 'no_user';
5090         result.success := FALSE;
5091         done := TRUE;
5092         RETURN NEXT result;
5093         RETURN;
5094     END IF;
5095
5096     SELECT INTO item_object * FROM asset.copy WHERE id = match_item;
5097
5098     -- Fail if we couldn't find a copy
5099     IF item_object.id IS NULL THEN
5100         result.fail_part := 'no_item';
5101         result.success := FALSE;
5102         done := TRUE;
5103         RETURN NEXT result;
5104         RETURN;
5105     END IF;
5106
5107     SELECT INTO matchpoint_id action.find_hold_matrix_matchpoint(pickup_ou, request_ou, match_item, match_user, match_requestor);
5108     result.matchpoint := matchpoint_id;
5109
5110     SELECT INTO ou_skip * FROM actor.org_unit_setting WHERE name = 'circ.holds.target_skip_me' AND org_unit = item_object.circ_lib;
5111
5112     -- Fail if the circ_lib for the item has circ.holds.target_skip_me set to true
5113     IF ou_skip.id IS NOT NULL AND ou_skip.value = 'true' THEN
5114         result.fail_part := 'circ.holds.target_skip_me';
5115         result.success := FALSE;
5116         done := TRUE;
5117         RETURN NEXT result;
5118         RETURN;
5119     END IF;
5120
5121     -- Fail if user is barred
5122     IF user_object.barred IS TRUE THEN
5123         result.fail_part := 'actor.usr.barred';
5124         result.success := FALSE;
5125         done := TRUE;
5126         RETURN NEXT result;
5127         RETURN;
5128     END IF;
5129
5130     -- Fail if we couldn't find any matchpoint (requires a default)
5131     IF matchpoint_id IS NULL THEN
5132         result.fail_part := 'no_matchpoint';
5133         result.success := FALSE;
5134         done := TRUE;
5135         RETURN NEXT result;
5136         RETURN;
5137     END IF;
5138
5139     SELECT INTO hold_test * FROM config.hold_matrix_matchpoint WHERE id = matchpoint_id;
5140
5141     IF hold_test.holdable IS FALSE THEN
5142         result.fail_part := 'config.hold_matrix_test.holdable';
5143         result.success := FALSE;
5144         done := TRUE;
5145         RETURN NEXT result;
5146     END IF;
5147
5148     IF hold_test.transit_range IS NOT NULL THEN
5149         SELECT INTO transit_range_ou_type * FROM actor.org_unit_type WHERE id = hold_test.transit_range;
5150         IF hold_test.distance_is_from_owner THEN
5151             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;
5152         ELSE
5153             SELECT INTO transit_source * FROM actor.org_unit WHERE id = item_object.circ_lib;
5154         END IF;
5155
5156         PERFORM * FROM actor.org_unit_descendants( transit_source.id, transit_range_ou_type.depth ) WHERE id = pickup_ou;
5157
5158         IF NOT FOUND THEN
5159             result.fail_part := 'transit_range';
5160             result.success := FALSE;
5161             done := TRUE;
5162             RETURN NEXT result;
5163         END IF;
5164     END IF;
5165  
5166     FOR standing_penalty IN
5167         SELECT  DISTINCT csp.*
5168           FROM  actor.usr_standing_penalty usp
5169                 JOIN config.standing_penalty csp ON (csp.id = usp.standing_penalty)
5170           WHERE usr = match_user
5171                 AND usp.org_unit IN ( SELECT * FROM explode_array(context_org_list) )
5172                 AND (usp.stop_date IS NULL or usp.stop_date > NOW())
5173                 AND csp.block_list LIKE '%HOLD%' LOOP
5174
5175         result.fail_part := standing_penalty.name;
5176         result.success := FALSE;
5177         done := TRUE;
5178         RETURN NEXT result;
5179     END LOOP;
5180
5181     IF hold_test.stop_blocked_user IS TRUE THEN
5182         FOR standing_penalty IN
5183             SELECT  DISTINCT csp.*
5184               FROM  actor.usr_standing_penalty usp
5185                     JOIN config.standing_penalty csp ON (csp.id = usp.standing_penalty)
5186               WHERE usr = match_user
5187                     AND usp.org_unit IN ( SELECT * FROM explode_array(context_org_list) )
5188                     AND (usp.stop_date IS NULL or usp.stop_date > NOW())
5189                     AND csp.block_list LIKE '%CIRC%' LOOP
5190     
5191             result.fail_part := standing_penalty.name;
5192             result.success := FALSE;
5193             done := TRUE;
5194             RETURN NEXT result;
5195         END LOOP;
5196     END IF;
5197
5198     IF hold_test.max_holds IS NOT NULL THEN
5199         SELECT    INTO hold_count COUNT(*)
5200           FROM    action.hold_request
5201           WHERE    usr = match_user
5202             AND fulfillment_time IS NULL
5203             AND cancel_time IS NULL
5204             AND CASE WHEN hold_test.include_frozen_holds THEN TRUE ELSE frozen IS FALSE END;
5205
5206         IF hold_count >= hold_test.max_holds THEN
5207             result.fail_part := 'config.hold_matrix_test.max_holds';
5208             result.success := FALSE;
5209             done := TRUE;
5210             RETURN NEXT result;
5211         END IF;
5212     END IF;
5213
5214     IF item_object.age_protect IS NOT NULL THEN
5215         SELECT INTO age_protect_object * FROM config.rule_age_hold_protect WHERE id = item_object.age_protect;
5216
5217         IF item_object.create_date + age_protect_object.age > NOW() THEN
5218             IF hold_test.distance_is_from_owner THEN
5219                 SELECT INTO hold_transit_prox prox FROM actor.org_unit_proximity WHERE from_org = item_cn_object.owning_lib AND to_org = pickup_ou;
5220             ELSE
5221                 SELECT INTO hold_transit_prox prox FROM actor.org_unit_proximity WHERE from_org = item_object.circ_lib AND to_org = pickup_ou;
5222             END IF;
5223
5224             IF hold_transit_prox > age_protect_object.prox THEN
5225                 result.fail_part := 'config.rule_age_hold_protect.prox';
5226                 result.success := FALSE;
5227                 done := TRUE;
5228                 RETURN NEXT result;
5229             END IF;
5230         END IF;
5231     END IF;
5232
5233     IF NOT done THEN
5234         RETURN NEXT result;
5235     END IF;
5236
5237     RETURN;
5238 END;
5239 $func$ LANGUAGE plpgsql;
5240
5241 -- New post-delete trigger to propagate deletions to parent(s)
5242
5243 CREATE OR REPLACE FUNCTION action.age_parent_circ_on_delete () RETURNS TRIGGER AS $$
5244 BEGIN
5245
5246     -- Having deleted a renewal, we can delete the original circulation (or a previous
5247     -- renewal, if that's what parent_circ is pointing to).  That deletion will trigger
5248     -- deletion of any prior parents, etc. recursively.
5249
5250     IF OLD.parent_circ IS NOT NULL THEN
5251         DELETE FROM action.circulation
5252         WHERE id = OLD.parent_circ;
5253     END IF;
5254
5255     RETURN OLD;
5256 END;
5257 $$ LANGUAGE 'plpgsql';
5258
5259 CREATE TRIGGER age_parent_circ AFTER DELETE ON action.circulation
5260 FOR EACH ROW EXECUTE PROCEDURE action.age_parent_circ_on_delete ();
5261
5262 -- This only gets inserted if there are no other id > 100 billing types
5263 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;
5264 SELECT SETVAL('config.billing_type_id_seq'::TEXT, 101) FROM config.billing_type_id_seq WHERE last_value < 101;
5265
5266 -- Populate xact_type column in the materialized version of billable_xact_summary
5267
5268 CREATE OR REPLACE FUNCTION money.mat_summary_create () RETURNS TRIGGER AS $$
5269 BEGIN
5270         INSERT INTO money.materialized_billable_xact_summary (id, usr, xact_start, xact_finish, total_paid, total_owed, balance_owed, xact_type)
5271                 VALUES ( NEW.id, NEW.usr, NEW.xact_start, NEW.xact_finish, 0.0, 0.0, 0.0, TG_ARGV[0]);
5272         RETURN NEW;
5273 END;
5274 $$ LANGUAGE PLPGSQL;
5275  
5276 DROP TRIGGER IF EXISTS mat_summary_create_tgr ON action.circulation;
5277 CREATE TRIGGER mat_summary_create_tgr AFTER INSERT ON action.circulation FOR EACH ROW EXECUTE PROCEDURE money.mat_summary_create ('circulation');
5278  
5279 DROP TRIGGER IF EXISTS mat_summary_create_tgr ON money.grocery;
5280 CREATE TRIGGER mat_summary_create_tgr AFTER INSERT ON money.grocery FOR EACH ROW EXECUTE PROCEDURE money.mat_summary_create ('grocery');
5281
5282 CREATE RULE money_payment_view_update AS ON UPDATE TO money.payment_view DO INSTEAD 
5283     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;
5284
5285 -- Generate the equivalent of compound subject entries from the existing rows
5286 -- so that we don't have to laboriously reindex them
5287
5288 --INSERT INTO config.metabib_field (field_class, name, format, xpath ) VALUES
5289 --    ( 'subject', 'complete', 'mods32', $$//mods32:mods/mods32:subject//text()$$ );
5290 --
5291 --CREATE INDEX metabib_subject_field_entry_source_idx ON metabib.subject_field_entry (source);
5292 --
5293 --INSERT INTO metabib.subject_field_entry (source, field, value)
5294 --    SELECT source, (
5295 --            SELECT id 
5296 --            FROM config.metabib_field
5297 --            WHERE field_class = 'subject' AND name = 'complete'
5298 --        ), 
5299 --        ARRAY_TO_STRING ( 
5300 --            ARRAY (
5301 --                SELECT value 
5302 --                FROM metabib.subject_field_entry msfe
5303 --                WHERE msfe.source = groupee.source
5304 --                ORDER BY source 
5305 --            ), ' ' 
5306 --        ) AS grouped
5307 --    FROM ( 
5308 --        SELECT source
5309 --        FROM metabib.subject_field_entry
5310 --        GROUP BY source
5311 --    ) AS groupee;
5312
5313 CREATE OR REPLACE FUNCTION money.materialized_summary_billing_del () RETURNS TRIGGER AS $$
5314 DECLARE
5315         prev_billing    money.billing%ROWTYPE;
5316         old_billing     money.billing%ROWTYPE;
5317 BEGIN
5318         SELECT * INTO prev_billing FROM money.billing WHERE xact = OLD.xact AND NOT voided ORDER BY billing_ts DESC LIMIT 1 OFFSET 1;
5319         SELECT * INTO old_billing FROM money.billing WHERE xact = OLD.xact AND NOT voided ORDER BY billing_ts DESC LIMIT 1;
5320
5321         IF OLD.id = old_billing.id THEN
5322                 UPDATE  money.materialized_billable_xact_summary
5323                   SET   last_billing_ts = prev_billing.billing_ts,
5324                         last_billing_note = prev_billing.note,
5325                         last_billing_type = prev_billing.billing_type
5326                   WHERE id = OLD.xact;
5327         END IF;
5328
5329         IF NOT OLD.voided THEN
5330                 UPDATE  money.materialized_billable_xact_summary
5331                   SET   total_owed = total_owed - OLD.amount,
5332                         balance_owed = balance_owed + OLD.amount
5333                   WHERE id = OLD.xact;
5334         END IF;
5335
5336         RETURN OLD;
5337 END;
5338 $$ LANGUAGE PLPGSQL;
5339
5340 -- ARG! need to rid ourselves of the broken table definition ... this mechanism is not ideal, sorry.
5341 DROP TABLE IF EXISTS config.index_normalizer CASCADE;
5342
5343 CREATE OR REPLACE FUNCTION public.naco_normalize( TEXT, TEXT ) RETURNS TEXT AS $func$
5344         use Unicode::Normalize;
5345         use Encode;
5346
5347         # When working with Unicode data, the first step is to decode it to
5348         # a byte string; after that, lowercasing is safe
5349         my $txt = lc(decode_utf8(shift));
5350         my $sf = shift;
5351
5352         $txt = NFD($txt);
5353         $txt =~ s/\pM+//go;     # Remove diacritics
5354
5355         $txt =~ s/\xE6/AE/go;   # Convert ae digraph
5356         $txt =~ s/\x{153}/OE/go;# Convert oe digraph
5357         $txt =~ s/\xFE/TH/go;   # Convert Icelandic thorn
5358
5359         $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
5360         $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
5361
5362         $txt =~ tr/\x{0251}\x{03B1}\x{03B2}\x{0262}\x{03B3}/AABGG/;             # Convert Latin and Greek
5363         $txt =~ tr/\x{2113}\xF0\!\"\(\)\-\{\}\<\>\;\:\.\?\xA1\xBF\/\\\@\*\%\=\xB1\+\xAE\xA9\x{2117}\$\xA3\x{FFE1}\xB0\^\_\~\`/LD /;     # Convert Misc
5364         $txt =~ tr/\'\[\]\|//d;                                                 # Remove Misc
5365
5366         if ($sf && $sf =~ /^a/o) {
5367                 my $commapos = index($txt,',');
5368                 if ($commapos > -1) {
5369                         if ($commapos != length($txt) - 1) {
5370                                 my @list = split /,/, $txt;
5371                                 my $first = shift @list;
5372                                 $txt = $first . ',' . join(' ', @list);
5373                         } else {
5374                                 $txt =~ s/,/ /go;
5375                         }
5376                 }
5377         } else {
5378                 $txt =~ s/,/ /go;
5379         }
5380
5381         $txt =~ s/\s+/ /go;     # Compress multiple spaces
5382         $txt =~ s/^\s+//o;      # Remove leading space
5383         $txt =~ s/\s+$//o;      # Remove trailing space
5384
5385         # Encoding the outgoing string is good practice, but not strictly
5386         # necessary in this case because we've stripped everything from it
5387         return encode_utf8($txt);
5388 $func$ LANGUAGE 'plperlu' STRICT IMMUTABLE;
5389
5390 -- Some handy functions, based on existing ones, to provide optional ingest normalization
5391
5392 CREATE OR REPLACE FUNCTION public.left_trunc( TEXT, INT ) RETURNS TEXT AS $func$
5393         SELECT SUBSTRING($1,$2);
5394 $func$ LANGUAGE SQL STRICT IMMUTABLE;
5395
5396 CREATE OR REPLACE FUNCTION public.right_trunc( TEXT, INT ) RETURNS TEXT AS $func$
5397         SELECT SUBSTRING($1,1,$2);
5398 $func$ LANGUAGE SQL STRICT IMMUTABLE;
5399
5400 CREATE OR REPLACE FUNCTION public.naco_normalize_keep_comma( TEXT ) RETURNS TEXT AS $func$
5401         SELECT public.naco_normalize($1,'a');
5402 $func$ LANGUAGE SQL STRICT IMMUTABLE;
5403
5404 CREATE OR REPLACE FUNCTION public.split_date_range( TEXT ) RETURNS TEXT AS $func$
5405         SELECT REGEXP_REPLACE( $1, E'(\\d{4})-(\\d{4})', E'\\1 \\2', 'g' );
5406 $func$ LANGUAGE SQL STRICT IMMUTABLE;
5407
5408 -- And ... a table in which to register them
5409
5410 CREATE TABLE config.index_normalizer (
5411         id              SERIAL  PRIMARY KEY,
5412         name            TEXT    UNIQUE NOT NULL,
5413         description     TEXT    UNIQUE NOT NULL,
5414         func            TEXT    NOT NULL,
5415         param_count     INT     NOT NULL DEFAULT 0
5416 );
5417
5418 CREATE TABLE config.metabib_field_index_norm_map (
5419         id      SERIAL  PRIMARY KEY,
5420         field   INT     NOT NULL REFERENCES config.metabib_field (id) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED,
5421         norm    INT     NOT NULL REFERENCES config.index_normalizer (id) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED,
5422         params  TEXT,
5423         pos     INT     NOT NULL DEFAULT 0
5424 );
5425
5426 INSERT INTO config.index_normalizer (name, description, func, param_count) VALUES (
5427         'NACO Normalize',
5428         'Apply NACO normalization rules to the extracted text.  See http://www.loc.gov/catdir/pcc/naco/normrule-2.html for details.',
5429         'naco_normalize',
5430         0
5431 );
5432
5433 INSERT INTO config.index_normalizer (name, description, func, param_count) VALUES (
5434         'Normalize date range',
5435         'Split date ranges in the form of "XXXX-YYYY" into "XXXX YYYY" for proper index.',
5436         'split_date_range',
5437         1
5438 );
5439
5440 INSERT INTO config.index_normalizer (name, description, func, param_count) VALUES (
5441         'NACO Normalize -- retain first comma',
5442         '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.',
5443         'naco_normalize_keep_comma',
5444         0
5445 );
5446
5447 INSERT INTO config.index_normalizer (name, description, func, param_count) VALUES (
5448         'Strip Diacritics',
5449         'Convert text to NFD form and remove non-spacing combining marks.',
5450         'remove_diacritics',
5451         0
5452 );
5453
5454 INSERT INTO config.index_normalizer (name, description, func, param_count) VALUES (
5455         'Up-case',
5456         'Convert text upper case.',
5457         'uppercase',
5458         0
5459 );
5460
5461 INSERT INTO config.index_normalizer (name, description, func, param_count) VALUES (
5462         'Down-case',
5463         'Convert text lower case.',
5464         'lowercase',
5465         0
5466 );
5467
5468 INSERT INTO config.index_normalizer (name, description, func, param_count) VALUES (
5469         'Extract Dewey-like number',
5470         'Extract a string of numeric characters ther resembles a DDC number.',
5471         'call_number_dewey',
5472         0
5473 );
5474
5475 INSERT INTO config.index_normalizer (name, description, func, param_count) VALUES (
5476         'Left truncation',
5477         'Discard the specified number of characters from the left side of the string.',
5478         'left_trunc',
5479         1
5480 );
5481
5482 INSERT INTO config.index_normalizer (name, description, func, param_count) VALUES (
5483         'Right truncation',
5484         'Include only the specified number of characters from the left side of the string.',
5485         'right_trunc',
5486         1
5487 );
5488
5489 INSERT INTO config.index_normalizer (name, description, func, param_count) VALUES (
5490         'First word',
5491         'Include only the first space-separated word of a string.',
5492         'first_word',
5493         0
5494 );
5495
5496 INSERT INTO config.metabib_field_index_norm_map (field,norm)
5497         SELECT  m.id,
5498                 i.id
5499           FROM  config.metabib_field m,
5500                 config.index_normalizer i
5501           WHERE i.func IN ('naco_normalize','split_date_range');
5502
5503 CREATE OR REPLACE FUNCTION oils_tsearch2 () RETURNS TRIGGER AS $$
5504 DECLARE
5505     normalizer      RECORD;
5506     value           TEXT := '';
5507 BEGIN
5508
5509     value := NEW.value;
5510
5511     IF TG_TABLE_NAME::TEXT ~ 'field_entry$' THEN
5512         FOR normalizer IN
5513             SELECT  n.func AS func,
5514                     n.param_count AS param_count,
5515                     m.params AS params
5516               FROM  config.index_normalizer n
5517                     JOIN config.metabib_field_index_norm_map m ON (m.norm = n.id)
5518               WHERE field = NEW.field AND m.pos < 0
5519               ORDER BY m.pos LOOP
5520                 EXECUTE 'SELECT ' || normalizer.func || '(' ||
5521                     quote_literal( value ) ||
5522                     CASE
5523                         WHEN normalizer.param_count > 0
5524                             THEN ',' || REPLACE(REPLACE(BTRIM(normalizer.params,'[]'),E'\'',E'\\\''),E'"',E'\'')
5525                             ELSE ''
5526                         END ||
5527                     ')' INTO value;
5528
5529         END LOOP;
5530
5531         NEW.value := value;
5532     END IF;
5533
5534     IF NEW.index_vector = ''::tsvector THEN
5535         RETURN NEW;
5536     END IF;
5537
5538     IF TG_TABLE_NAME::TEXT ~ 'field_entry$' THEN
5539         FOR normalizer IN
5540             SELECT  n.func AS func,
5541                     n.param_count AS param_count,
5542                     m.params AS params
5543               FROM  config.index_normalizer n
5544                     JOIN config.metabib_field_index_norm_map m ON (m.norm = n.id)
5545               WHERE field = NEW.field AND m.pos >= 0
5546               ORDER BY m.pos LOOP
5547                 EXECUTE 'SELECT ' || normalizer.func || '(' ||
5548                     quote_literal( value ) ||
5549                     CASE
5550                         WHEN normalizer.param_count > 0
5551                             THEN ',' || REPLACE(REPLACE(BTRIM(normalizer.params,'[]'),E'\'',E'\\\''),E'"',E'\'')
5552                             ELSE ''
5553                         END ||
5554                     ')' INTO value;
5555
5556         END LOOP;
5557     END IF;
5558
5559     IF REGEXP_REPLACE(VERSION(),E'^.+?(\\d+\\.\\d+).*?$',E'\\1')::FLOAT > 8.2 THEN
5560         NEW.index_vector = to_tsvector((TG_ARGV[0])::regconfig, value);
5561     ELSE
5562         NEW.index_vector = to_tsvector(TG_ARGV[0], value);
5563     END IF;
5564
5565     RETURN NEW;
5566 END;
5567 $$ LANGUAGE PLPGSQL;
5568
5569 CREATE OR REPLACE FUNCTION oils_xpath ( TEXT, TEXT, ANYARRAY ) RETURNS TEXT[] AS 'SELECT XPATH( $1, $2::XML, $3 )::TEXT[];' LANGUAGE SQL IMMUTABLE;
5570
5571 CREATE OR REPLACE FUNCTION oils_xpath ( TEXT, TEXT ) RETURNS TEXT[] AS 'SELECT XPATH( $1, $2::XML )::TEXT[];' LANGUAGE SQL IMMUTABLE;
5572
5573 CREATE OR REPLACE FUNCTION oils_xpath_string ( TEXT, TEXT, TEXT, ANYARRAY ) RETURNS TEXT AS $func$
5574     SELECT  ARRAY_TO_STRING(
5575                 oils_xpath(
5576                     $1 ||
5577                         CASE WHEN $1 ~ $re$/[^/[]*@[^]]+$$re$ OR $1 ~ $re$text\(\)$$re$ THEN '' ELSE '//text()' END,
5578                     $2,
5579                     $4
5580                 ),
5581                 $3
5582             );
5583 $func$ LANGUAGE SQL IMMUTABLE;
5584
5585 CREATE OR REPLACE FUNCTION oils_xpath_string ( TEXT, TEXT, TEXT ) RETURNS TEXT AS $func$
5586     SELECT oils_xpath_string( $1, $2, $3, '{}'::TEXT[] );
5587 $func$ LANGUAGE SQL IMMUTABLE;
5588
5589 CREATE OR REPLACE FUNCTION oils_xpath_string ( TEXT, TEXT, ANYARRAY ) RETURNS TEXT AS $func$
5590     SELECT oils_xpath_string( $1, $2, '', $3 );
5591 $func$ LANGUAGE SQL IMMUTABLE;
5592
5593 CREATE OR REPLACE FUNCTION oils_xpath_string ( TEXT, TEXT ) RETURNS TEXT AS $func$
5594     SELECT oils_xpath_string( $1, $2, '{}'::TEXT[] );
5595 $func$ LANGUAGE SQL IMMUTABLE;
5596
5597 CREATE TYPE metabib.field_entry_template AS (
5598         field_class     TEXT,
5599         field           INT,
5600         source          BIGINT,
5601         value           TEXT
5602 );
5603
5604 CREATE OR REPLACE FUNCTION oils_xslt_process(TEXT, TEXT) RETURNS TEXT AS $func$
5605   use strict;
5606
5607   use XML::LibXSLT;
5608   use XML::LibXML;
5609
5610   my $doc = shift;
5611   my $xslt = shift;
5612
5613   # The following approach uses the older XML::LibXML 1.69 / XML::LibXSLT 1.68
5614   # methods of parsing XML documents and stylesheets, in the hopes of broader
5615   # compatibility with distributions
5616   my $parser = $_SHARED{'_xslt_process'}{parsers}{xml} || XML::LibXML->new();
5617
5618   # Cache the XML parser, if we do not already have one
5619   $_SHARED{'_xslt_process'}{parsers}{xml} = $parser
5620     unless ($_SHARED{'_xslt_process'}{parsers}{xml});
5621
5622   my $xslt_parser = $_SHARED{'_xslt_process'}{parsers}{xslt} || XML::LibXSLT->new();
5623
5624   # Cache the XSLT processor, if we do not already have one
5625   $_SHARED{'_xslt_process'}{parsers}{xslt} = $xslt_parser
5626     unless ($_SHARED{'_xslt_process'}{parsers}{xslt});
5627
5628   my $stylesheet = $_SHARED{'_xslt_process'}{stylesheets}{$xslt} ||
5629     $xslt_parser->parse_stylesheet( $parser->parse_string($xslt) );
5630
5631   $_SHARED{'_xslt_process'}{stylesheets}{$xslt} = $stylesheet
5632     unless ($_SHARED{'_xslt_process'}{stylesheets}{$xslt});
5633
5634   return $stylesheet->output_string(
5635     $stylesheet->transform(
5636       $parser->parse_string($doc)
5637     )
5638   );
5639
5640 $func$ LANGUAGE 'plperlu' STRICT IMMUTABLE;
5641
5642 -- Add two columns so that the following function will compile.
5643 -- Eventually the label column will be NOT NULL, but not yet.
5644 ALTER TABLE config.metabib_field ADD COLUMN label TEXT;
5645 ALTER TABLE config.metabib_field ADD COLUMN facet_xpath TEXT;
5646
5647 CREATE OR REPLACE FUNCTION biblio.extract_metabib_field_entry ( rid BIGINT, default_joiner TEXT ) RETURNS SETOF metabib.field_entry_template AS $func$
5648 DECLARE
5649     bib     biblio.record_entry%ROWTYPE;
5650     idx     config.metabib_field%ROWTYPE;
5651     xfrm        config.xml_transform%ROWTYPE;
5652     prev_xfrm   TEXT;
5653     transformed_xml TEXT;
5654     xml_node    TEXT;
5655     xml_node_list   TEXT[];
5656     facet_text  TEXT;
5657     raw_text    TEXT;
5658     curr_text   TEXT;
5659     joiner      TEXT := default_joiner; -- XXX will index defs supply a joiner?
5660     output_row  metabib.field_entry_template%ROWTYPE;
5661 BEGIN
5662
5663     -- Get the record
5664     SELECT INTO bib * FROM biblio.record_entry WHERE id = rid;
5665
5666     -- Loop over the indexing entries
5667     FOR idx IN SELECT * FROM config.metabib_field ORDER BY format LOOP
5668
5669         SELECT INTO xfrm * from config.xml_transform WHERE name = idx.format;
5670
5671         -- See if we can skip the XSLT ... it's expensive
5672         IF prev_xfrm IS NULL OR prev_xfrm <> xfrm.name THEN
5673             -- Can't skip the transform
5674             IF xfrm.xslt <> '---' THEN
5675                 transformed_xml := oils_xslt_process(bib.marc,xfrm.xslt);
5676             ELSE
5677                 transformed_xml := bib.marc;
5678             END IF;
5679
5680             prev_xfrm := xfrm.name;
5681         END IF;
5682
5683         xml_node_list := oils_xpath( idx.xpath, transformed_xml, ARRAY[ARRAY[xfrm.prefix, xfrm.namespace_uri]] );
5684
5685         raw_text := NULL;
5686         FOR xml_node IN SELECT x FROM explode_array(xml_node_list) AS x LOOP
5687             CONTINUE WHEN xml_node !~ E'^\\s*<';
5688
5689             curr_text := ARRAY_TO_STRING(
5690                 oils_xpath( '//text()',
5691                     REGEXP_REPLACE( -- This escapes all &s not followed by "amp;".  Data ise returned from oils_xpath (above) in UTF-8, not entity encoded
5692                         REGEXP_REPLACE( -- This escapes embeded <s
5693                             xml_node,
5694                             $re$(>[^<]+)(<)([^>]+<)$re$,
5695                             E'\\1&lt;\\3',
5696                             'g'
5697                         ),
5698                         '&(?!amp;)',
5699                         '&amp;',
5700                         'g'
5701                     )
5702                 ),
5703                 ' '
5704             );
5705
5706             CONTINUE WHEN curr_text IS NULL OR curr_text = '';
5707
5708             IF raw_text IS NOT NULL THEN
5709                 raw_text := raw_text || joiner;
5710             END IF;
5711
5712             raw_text := COALESCE(raw_text,'') || curr_text;
5713
5714             -- insert raw node text for faceting
5715             IF idx.facet_field THEN
5716
5717                 IF idx.facet_xpath IS NOT NULL AND idx.facet_xpath <> '' THEN
5718                     facet_text := oils_xpath_string( idx.facet_xpath, xml_node, joiner, ARRAY[ARRAY[xfrm.prefix, xfrm.namespace_uri]] );
5719                 ELSE
5720                     facet_text := curr_text;
5721                 END IF;
5722
5723                 output_row.field_class = idx.field_class;
5724                 output_row.field = -1 * idx.id;
5725                 output_row.source = rid;
5726                 output_row.value = BTRIM(REGEXP_REPLACE(facet_text, E'\\s+', ' ', 'g'));
5727
5728                 RETURN NEXT output_row;
5729             END IF;
5730
5731         END LOOP;
5732
5733         CONTINUE WHEN raw_text IS NULL OR raw_text = '';
5734
5735         -- insert combined node text for searching
5736         IF idx.search_field THEN
5737             output_row.field_class = idx.field_class;
5738             output_row.field = idx.id;
5739             output_row.source = rid;
5740             output_row.value = BTRIM(REGEXP_REPLACE(raw_text, E'\\s+', ' ', 'g'));
5741
5742             RETURN NEXT output_row;
5743         END IF;
5744
5745     END LOOP;
5746
5747 END;
5748 $func$ LANGUAGE PLPGSQL;
5749
5750 -- default to a space joiner
5751 CREATE OR REPLACE FUNCTION biblio.extract_metabib_field_entry ( BIGINT ) RETURNS SETOF metabib.field_entry_template AS $func$
5752         SELECT * FROM biblio.extract_metabib_field_entry($1, ' ');
5753 $func$ LANGUAGE SQL;
5754
5755 CREATE OR REPLACE FUNCTION biblio.flatten_marc ( TEXT ) RETURNS SETOF metabib.full_rec AS $func$
5756
5757 use MARC::Record;
5758 use MARC::File::XML (BinaryEncoding => 'UTF-8');
5759
5760 my $xml = shift;
5761 my $r = MARC::Record->new_from_xml( $xml );
5762
5763 return_next( { tag => 'LDR', value => $r->leader } );
5764
5765 for my $f ( $r->fields ) {
5766     if ($f->is_control_field) {
5767         return_next({ tag => $f->tag, value => $f->data });
5768     } else {
5769         for my $s ($f->subfields) {
5770             return_next({
5771                 tag      => $f->tag,
5772                 ind1     => $f->indicator(1),
5773                 ind2     => $f->indicator(2),
5774                 subfield => $s->[0],
5775                 value    => $s->[1]
5776             });
5777
5778             if ( $f->tag eq '245' and $s->[0] eq 'a' ) {
5779                 my $trim = $f->indicator(2) || 0;
5780                 return_next({
5781                     tag      => 'tnf',
5782                     ind1     => $f->indicator(1),
5783                     ind2     => $f->indicator(2),
5784                     subfield => 'a',
5785                     value    => substr( $s->[1], $trim )
5786                 });
5787             }
5788         }
5789     }
5790 }
5791
5792 return undef;
5793
5794 $func$ LANGUAGE PLPERLU;
5795
5796 CREATE OR REPLACE FUNCTION biblio.flatten_marc ( rid BIGINT ) RETURNS SETOF metabib.full_rec AS $func$
5797 DECLARE
5798     bib biblio.record_entry%ROWTYPE;
5799     output  metabib.full_rec%ROWTYPE;
5800     field   RECORD;
5801 BEGIN
5802     SELECT INTO bib * FROM biblio.record_entry WHERE id = rid;
5803
5804     FOR field IN SELECT * FROM biblio.flatten_marc( bib.marc ) LOOP
5805         output.record := rid;
5806         output.ind1 := field.ind1;
5807         output.ind2 := field.ind2;
5808         output.tag := field.tag;
5809         output.subfield := field.subfield;
5810         IF field.subfield IS NOT NULL AND field.tag NOT IN ('020','022','024') THEN -- exclude standard numbers and control fields
5811             output.value := naco_normalize(field.value, field.subfield);
5812         ELSE
5813             output.value := field.value;
5814         END IF;
5815
5816         CONTINUE WHEN output.value IS NULL;
5817
5818         RETURN NEXT output;
5819     END LOOP;
5820 END;
5821 $func$ LANGUAGE PLPGSQL;
5822
5823 ALTER TABLE action.hold_request ADD COLUMN cut_in_line BOOL;
5824
5825 ALTER TABLE action.hold_request
5826 ADD COLUMN mint_condition boolean NOT NULL DEFAULT TRUE;
5827
5828 ALTER TABLE action.hold_request
5829 ADD COLUMN shelf_expire_time TIMESTAMPTZ;
5830
5831 ALTER TABLE action.hold_request DROP CONSTRAINT hold_request_current_copy_fkey;
5832
5833 ALTER TABLE action.hold_request DROP CONSTRAINT hold_request_hold_type_check;
5834
5835 UPDATE config.index_normalizer SET param_count = 0 WHERE func = 'split_date_range';
5836
5837 ALTER TABLE actor.usr ADD COLUMN
5838         claims_never_checked_out_count  INT         NOT NULL DEFAULT 0;
5839
5840 ALTER TABLE AUDITOR.actor_usr_history ADD COLUMN 
5841         claims_never_checked_out_count INT NOT NULL DEFAULT 0;
5842
5843 CREATE OR REPLACE FUNCTION action.circulation_claims_returned () RETURNS TRIGGER AS $$
5844 BEGIN
5845         IF OLD.stop_fines IS NULL OR OLD.stop_fines <> NEW.stop_fines THEN
5846                 IF NEW.stop_fines = 'CLAIMSRETURNED' THEN
5847                         UPDATE actor.usr SET claims_returned_count = claims_returned_count + 1 WHERE id = NEW.usr;
5848                 END IF;
5849                 IF NEW.stop_fines = 'CLAIMSNEVERCHECKEDOUT' THEN
5850                         UPDATE actor.usr SET claims_never_checked_out_count = claims_never_checked_out_count + 1 WHERE id = NEW.usr;
5851                 END IF;
5852                 IF NEW.stop_fines = 'LOST' THEN
5853                         UPDATE asset.copy SET status = 3 WHERE id = NEW.target_copy;
5854                 END IF;
5855         END IF;
5856         RETURN NEW;
5857 END;
5858 $$ LANGUAGE 'plpgsql';
5859
5860 -- Create new table acq.fund_allocation_percent
5861 -- Populate it from acq.fund_allocation
5862 -- Convert all percentages to amounts in acq.fund_allocation
5863
5864 CREATE TABLE acq.fund_allocation_percent
5865 (
5866     id                   SERIAL            PRIMARY KEY,
5867     funding_source       INT               NOT NULL REFERENCES acq.funding_source
5868                                                DEFERRABLE INITIALLY DEFERRED,
5869     org                  INT               NOT NULL REFERENCES actor.org_unit
5870                                                DEFERRABLE INITIALLY DEFERRED,
5871     fund_code            TEXT,
5872     percent              NUMERIC           NOT NULL,
5873     allocator            INTEGER           NOT NULL REFERENCES actor.usr
5874                                                DEFERRABLE INITIALLY DEFERRED,
5875     note                 TEXT,
5876     create_time          TIMESTAMPTZ       NOT NULL DEFAULT now(),
5877     CONSTRAINT logical_key UNIQUE( funding_source, org, fund_code ),
5878     CONSTRAINT percentage_range CHECK( percent >= 0 AND percent <= 100 )
5879 );
5880
5881 -- Trigger function to validate combination of org_unit and fund_code
5882
5883 CREATE OR REPLACE FUNCTION acq.fund_alloc_percent_val()
5884 RETURNS TRIGGER AS $$
5885 --
5886 DECLARE
5887 --
5888 dummy int := 0;
5889 --
5890 BEGIN
5891     SELECT
5892         1
5893     INTO
5894         dummy
5895     FROM
5896         acq.fund
5897     WHERE
5898         org = NEW.org
5899         AND code = NEW.fund_code
5900         LIMIT 1;
5901     --
5902     IF dummy = 1 then
5903         RETURN NEW;
5904     ELSE
5905         RAISE EXCEPTION 'No fund exists for org % and code %', NEW.org, NEW.fund_code;
5906     END IF;
5907 END;
5908 $$ LANGUAGE plpgsql;
5909
5910 CREATE TRIGGER acq_fund_alloc_percent_val_trig
5911     BEFORE INSERT OR UPDATE ON acq.fund_allocation_percent
5912     FOR EACH ROW EXECUTE PROCEDURE acq.fund_alloc_percent_val();
5913
5914 CREATE OR REPLACE FUNCTION acq.fap_limit_100()
5915 RETURNS TRIGGER AS $$
5916 DECLARE
5917 --
5918 total_percent numeric;
5919 --
5920 BEGIN
5921     SELECT
5922         sum( percent )
5923     INTO
5924         total_percent
5925     FROM
5926         acq.fund_allocation_percent AS fap
5927     WHERE
5928         fap.funding_source = NEW.funding_source;
5929     --
5930     IF total_percent > 100 THEN
5931         RAISE EXCEPTION 'Total percentages exceed 100 for funding_source %',
5932             NEW.funding_source;
5933     ELSE
5934         RETURN NEW;
5935     END IF;
5936 END;
5937 $$ LANGUAGE plpgsql;
5938
5939 CREATE TRIGGER acqfap_limit_100_trig
5940     AFTER INSERT OR UPDATE ON acq.fund_allocation_percent
5941     FOR EACH ROW EXECUTE PROCEDURE acq.fap_limit_100();
5942
5943 -- Populate new table from acq.fund_allocation
5944
5945 INSERT INTO acq.fund_allocation_percent
5946 (
5947     funding_source,
5948     org,
5949     fund_code,
5950     percent,
5951     allocator,
5952     note,
5953     create_time
5954 )
5955     SELECT
5956         fa.funding_source,
5957         fund.org,
5958         fund.code,
5959         fa.percent,
5960         fa.allocator,
5961         fa.note,
5962         fa.create_time
5963     FROM
5964         acq.fund_allocation AS fa
5965             INNER JOIN acq.fund AS fund
5966                 ON ( fa.fund = fund.id )
5967     WHERE
5968         fa.percent is not null
5969     ORDER BY
5970         fund.org;
5971
5972 -- Temporary function to convert percentages to amounts in acq.fund_allocation
5973
5974 -- Algorithm to apply to each funding source:
5975
5976 -- 1. Add up the credits.
5977 -- 2. Add up the percentages.
5978 -- 3. Multiply the sum of the percentages times the sum of the credits.  Drop any
5979 --    fractional cents from the result.  This is the total amount to be allocated.
5980 -- 4. For each allocation: multiply the percentage by the total allocation.  Drop any
5981 --    fractional cents to get a preliminary amount.
5982 -- 5. Add up the preliminary amounts for all the allocations.
5983 -- 6. Subtract the results of step 5 from the result of step 3.  The difference is the
5984 --    number of residual cents (resulting from having dropped fractional cents) that
5985 --    must be distributed across the funds in order to make the total of the amounts
5986 --    match the total allocation.
5987 -- 7. Make a second pass through the allocations, in decreasing order of the fractional
5988 --    cents that were dropped from their amounts in step 4.  Add one cent to the amount
5989 --    for each successive fund, until all the residual cents have been exhausted.
5990
5991 -- Result: the sum of the individual allocations now equals the total to be allocated,
5992 -- to the penny.  The individual amounts match the percentages as closely as possible,
5993 -- given the constraint that the total must match.
5994
5995 CREATE OR REPLACE FUNCTION acq.apply_percents()
5996 RETURNS VOID AS $$
5997 declare
5998 --
5999 tot              RECORD;
6000 fund             RECORD;
6001 tot_cents        INTEGER;
6002 src              INTEGER;
6003 id               INTEGER[];
6004 curr_id          INTEGER;
6005 pennies          NUMERIC[];
6006 curr_amount      NUMERIC;
6007 i                INTEGER;
6008 total_of_floors  INTEGER;
6009 total_percent    NUMERIC;
6010 total_allocation INTEGER;
6011 residue          INTEGER;
6012 --
6013 begin
6014         RAISE NOTICE 'Applying percents';
6015         FOR tot IN
6016                 SELECT
6017                         fsrc.funding_source,
6018                         sum( fsrc.amount ) AS total
6019                 FROM
6020                         acq.funding_source_credit AS fsrc
6021                 WHERE fsrc.funding_source IN
6022                         ( SELECT DISTINCT fa.funding_source
6023                           FROM acq.fund_allocation AS fa
6024                           WHERE fa.percent IS NOT NULL )
6025                 GROUP BY
6026                         fsrc.funding_source
6027         LOOP
6028                 tot_cents = floor( tot.total * 100 );
6029                 src = tot.funding_source;
6030                 RAISE NOTICE 'Funding source % total %',
6031                         src, tot_cents;
6032                 i := 0;
6033                 total_of_floors := 0;
6034                 total_percent := 0;
6035                 --
6036                 FOR fund in
6037                         SELECT
6038                                 fa.id,
6039                                 fa.percent,
6040                                 floor( fa.percent * tot_cents / 100 ) as floor_pennies
6041                         FROM
6042                                 acq.fund_allocation AS fa
6043                         WHERE
6044                                 fa.funding_source = src
6045                                 AND fa.percent IS NOT NULL
6046                         ORDER BY
6047                                 mod( fa.percent * tot_cents / 100, 1 ),
6048                                 fa.fund,
6049                                 fa.id
6050                 LOOP
6051                         RAISE NOTICE '   %: %',
6052                                 fund.id,
6053                                 fund.floor_pennies;
6054                         i := i + 1;
6055                         id[i] = fund.id;
6056                         pennies[i] = fund.floor_pennies;
6057                         total_percent := total_percent + fund.percent;
6058                         total_of_floors := total_of_floors + pennies[i];
6059                 END LOOP;
6060                 total_allocation := floor( total_percent * tot_cents /100 );
6061                 RAISE NOTICE 'Total before distributing residue: %', total_of_floors;
6062                 residue := total_allocation - total_of_floors;
6063                 RAISE NOTICE 'Residue: %', residue;
6064                 --
6065                 -- Post the calculated amounts, revising as needed to
6066                 -- distribute the rounding error
6067                 --
6068                 WHILE i > 0 LOOP
6069                         IF residue > 0 THEN
6070                                 pennies[i] = pennies[i] + 1;
6071                                 residue := residue - 1;
6072                         END IF;
6073                         --
6074                         -- Post amount
6075                         --
6076                         curr_id     := id[i];
6077                         curr_amount := trunc( pennies[i] / 100, 2 );
6078                         --
6079                         UPDATE
6080                                 acq.fund_allocation AS fa
6081                         SET
6082                                 amount = curr_amount,
6083                                 percent = NULL
6084                         WHERE
6085                                 fa.id = curr_id;
6086                         --
6087                         RAISE NOTICE '   ID % and amount %',
6088                                 curr_id,
6089                                 curr_amount;
6090                         i = i - 1;
6091                 END LOOP;
6092         END LOOP;
6093 end;
6094 $$ LANGUAGE 'plpgsql';
6095
6096 -- Run the temporary function
6097
6098 select * from acq.apply_percents();
6099
6100 -- Drop the temporary function now that we're done with it
6101
6102 DROP FUNCTION IF EXISTS acq.apply_percents();
6103
6104 -- Eliminate acq.fund_allocation.percent, which has been moved to the acq.fund_allocation_percent table.
6105
6106 -- If the following step fails, it's probably because there are still some non-null percent values in
6107 -- acq.fund_allocation.  They should have all been converted to amounts, and then set to null, by a
6108 -- previous upgrade step based on 0049.schema.acq_funding_allocation_percent.sql.  If there are any
6109 -- non-null values, then either that step didn't run, or it didn't work, or some non-null values
6110 -- slipped in afterwards.
6111
6112 -- To convert any remaining percents to amounts: create, run, and then drop the temporary stored
6113 -- procedure acq.apply_percents() as defined above.
6114
6115 ALTER TABLE acq.fund_allocation
6116 ALTER COLUMN amount SET NOT NULL;
6117
6118 CREATE OR REPLACE VIEW acq.fund_allocation_total AS
6119     SELECT  fund,
6120             SUM(a.amount * acq.exchange_ratio(s.currency_type, f.currency_type))::NUMERIC(100,2) AS amount
6121     FROM acq.fund_allocation a
6122          JOIN acq.fund f ON (a.fund = f.id)
6123          JOIN acq.funding_source s ON (a.funding_source = s.id)
6124     GROUP BY 1;
6125
6126 CREATE OR REPLACE VIEW acq.funding_source_allocation_total AS
6127     SELECT  funding_source,
6128             SUM(a.amount)::NUMERIC(100,2) AS amount
6129     FROM  acq.fund_allocation a
6130     GROUP BY 1;
6131
6132 ALTER TABLE acq.fund_allocation
6133 DROP COLUMN percent;
6134
6135 CREATE TABLE asset.copy_location_order
6136 (
6137         id              SERIAL           PRIMARY KEY,
6138         location        INT              NOT NULL
6139                                              REFERENCES asset.copy_location
6140                                              ON DELETE CASCADE
6141                                              DEFERRABLE INITIALLY DEFERRED,
6142         org             INT              NOT NULL
6143                                              REFERENCES actor.org_unit
6144                                              ON DELETE CASCADE
6145                                              DEFERRABLE INITIALLY DEFERRED,
6146         position        INT              NOT NULL DEFAULT 0,
6147         CONSTRAINT acplo_once_per_org UNIQUE ( location, org )
6148 );
6149
6150 ALTER TABLE money.credit_card_payment ADD COLUMN cc_processor TEXT;
6151
6152 -- If you ran this before its most recent incarnation:
6153 -- delete from config.upgrade_log where version = '0328';
6154 -- alter table money.credit_card_payment drop column cc_name;
6155
6156 ALTER TABLE money.credit_card_payment ADD COLUMN cc_first_name TEXT;
6157 ALTER TABLE money.credit_card_payment ADD COLUMN cc_last_name TEXT;
6158
6159 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$
6160 DECLARE
6161     current_group    permission.grp_tree%ROWTYPE;
6162     user_object    actor.usr%ROWTYPE;
6163     item_object    asset.copy%ROWTYPE;
6164     cn_object    asset.call_number%ROWTYPE;
6165     rec_descriptor    metabib.rec_descriptor%ROWTYPE;
6166     current_mp    config.circ_matrix_matchpoint%ROWTYPE;
6167     matchpoint    config.circ_matrix_matchpoint%ROWTYPE;
6168 BEGIN
6169     SELECT INTO user_object * FROM actor.usr WHERE id = match_user;
6170     SELECT INTO item_object * FROM asset.copy WHERE id = match_item;
6171     SELECT INTO cn_object * FROM asset.call_number WHERE id = item_object.call_number;
6172     SELECT INTO rec_descriptor r.* FROM metabib.rec_descriptor r JOIN asset.call_number c USING (record) WHERE c.id = item_object.call_number;
6173     SELECT INTO current_group * FROM permission.grp_tree WHERE id = user_object.profile;
6174
6175     LOOP
6176         -- for each potential matchpoint for this ou and group ...
6177         FOR current_mp IN
6178             SELECT  m.*
6179               FROM  config.circ_matrix_matchpoint m
6180                     JOIN actor.org_unit_ancestors( context_ou ) d ON (m.org_unit = d.id)
6181                     LEFT JOIN actor.org_unit_proximity p ON (p.from_org = context_ou AND p.to_org = d.id)
6182               WHERE m.grp = current_group.id
6183                     AND m.active
6184                     AND (m.copy_owning_lib IS NULL OR cn_object.owning_lib IN ( SELECT id FROM actor.org_unit_descendants(m.copy_owning_lib) ))
6185                     AND (m.copy_circ_lib   IS NULL OR item_object.circ_lib IN ( SELECT id FROM actor.org_unit_descendants(m.copy_circ_lib)   ))
6186               ORDER BY    CASE WHEN p.prox        IS NULL THEN 999 ELSE p.prox END,
6187                     CASE WHEN m.copy_owning_lib IS NOT NULL
6188                         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 )
6189                         ELSE 0
6190                     END +
6191                     CASE WHEN m.copy_circ_lib IS NOT NULL
6192                         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 )
6193                         ELSE 0
6194                     END +
6195                     CASE WHEN m.is_renewal = renewal        THEN 128 ELSE 0 END +
6196                     CASE WHEN m.juvenile_flag    IS NOT NULL THEN 64 ELSE 0 END +
6197                     CASE WHEN m.circ_modifier    IS NOT NULL THEN 32 ELSE 0 END +
6198                     CASE WHEN m.marc_type        IS NOT NULL THEN 16 ELSE 0 END +
6199                     CASE WHEN m.marc_form        IS NOT NULL THEN 8 ELSE 0 END +
6200                     CASE WHEN m.marc_vr_format    IS NOT NULL THEN 4 ELSE 0 END +
6201                     CASE WHEN m.ref_flag        IS NOT NULL THEN 2 ELSE 0 END +
6202                     CASE WHEN m.usr_age_lower_bound    IS NOT NULL THEN 0.5 ELSE 0 END +
6203                     CASE WHEN m.usr_age_upper_bound    IS NOT NULL THEN 0.5 ELSE 0 END DESC LOOP
6204
6205             IF current_mp.circ_modifier IS NOT NULL THEN
6206                 CONTINUE WHEN current_mp.circ_modifier <> item_object.circ_modifier OR item_object.circ_modifier IS NULL;
6207             END IF;
6208
6209             IF current_mp.marc_type IS NOT NULL THEN
6210                 IF item_object.circ_as_type IS NOT NULL THEN
6211                     CONTINUE WHEN current_mp.marc_type <> item_object.circ_as_type;
6212                 ELSE
6213                     CONTINUE WHEN current_mp.marc_type <> rec_descriptor.item_type;
6214                 END IF;
6215             END IF;
6216
6217             IF current_mp.marc_form IS NOT NULL THEN
6218                 CONTINUE WHEN current_mp.marc_form <> rec_descriptor.item_form;
6219             END IF;
6220
6221             IF current_mp.marc_vr_format IS NOT NULL THEN
6222                 CONTINUE WHEN current_mp.marc_vr_format <> rec_descriptor.vr_format;
6223             END IF;
6224
6225             IF current_mp.ref_flag IS NOT NULL THEN
6226                 CONTINUE WHEN current_mp.ref_flag <> item_object.ref;
6227             END IF;
6228
6229             IF current_mp.juvenile_flag IS NOT NULL THEN
6230                 CONTINUE WHEN current_mp.juvenile_flag <> user_object.juvenile;
6231             END IF;
6232
6233             IF current_mp.usr_age_lower_bound IS NOT NULL THEN
6234                 CONTINUE WHEN user_object.dob IS NULL OR current_mp.usr_age_lower_bound < age(user_object.dob);
6235             END IF;
6236
6237             IF current_mp.usr_age_upper_bound IS NOT NULL THEN
6238                 CONTINUE WHEN user_object.dob IS NULL OR current_mp.usr_age_upper_bound > age(user_object.dob);
6239             END IF;
6240
6241
6242             -- everything was undefined or matched
6243             matchpoint = current_mp;
6244
6245             EXIT WHEN matchpoint.id IS NOT NULL;
6246         END LOOP;
6247
6248         EXIT WHEN current_group.parent IS NULL OR matchpoint.id IS NOT NULL;
6249
6250         SELECT INTO current_group * FROM permission.grp_tree WHERE id = current_group.parent;
6251     END LOOP;
6252
6253     RETURN matchpoint;
6254 END;
6255 $func$ LANGUAGE plpgsql;
6256
6257 CREATE TYPE action.hold_stats AS (
6258     hold_count              INT,
6259     copy_count              INT,
6260     available_count         INT,
6261     total_copy_ratio        FLOAT,
6262     available_copy_ratio    FLOAT
6263 );
6264
6265 CREATE OR REPLACE FUNCTION action.copy_related_hold_stats (copy_id INT) RETURNS action.hold_stats AS $func$
6266 DECLARE
6267     output          action.hold_stats%ROWTYPE;
6268     hold_count      INT := 0;
6269     copy_count      INT := 0;
6270     available_count INT := 0;
6271     hold_map_data   RECORD;
6272 BEGIN
6273
6274     output.hold_count := 0;
6275     output.copy_count := 0;
6276     output.available_count := 0;
6277
6278     SELECT  COUNT( DISTINCT m.hold ) INTO hold_count
6279       FROM  action.hold_copy_map m
6280             JOIN action.hold_request h ON (m.hold = h.id)
6281       WHERE m.target_copy = copy_id
6282             AND NOT h.frozen;
6283
6284     output.hold_count := hold_count;
6285
6286     IF output.hold_count > 0 THEN
6287         FOR hold_map_data IN
6288             SELECT  DISTINCT m.target_copy,
6289                     acp.status
6290               FROM  action.hold_copy_map m
6291                     JOIN asset.copy acp ON (m.target_copy = acp.id)
6292                     JOIN action.hold_request h ON (m.hold = h.id)
6293               WHERE m.hold IN ( SELECT DISTINCT hold FROM action.hold_copy_map WHERE target_copy = copy_id ) AND NOT h.frozen
6294         LOOP
6295             output.copy_count := output.copy_count + 1;
6296             IF hold_map_data.status IN (0,7,12) THEN
6297                 output.available_count := output.available_count + 1;
6298             END IF;
6299         END LOOP;
6300         output.total_copy_ratio = output.copy_count::FLOAT / output.hold_count::FLOAT;
6301         output.available_copy_ratio = output.available_count::FLOAT / output.hold_count::FLOAT;
6302
6303     END IF;
6304
6305     RETURN output;
6306
6307 END;
6308 $func$ LANGUAGE PLPGSQL;
6309
6310 ALTER TABLE config.circ_matrix_matchpoint ADD COLUMN total_copy_hold_ratio FLOAT;
6311 ALTER TABLE config.circ_matrix_matchpoint ADD COLUMN available_copy_hold_ratio FLOAT;
6312
6313 ALTER TABLE config.circ_matrix_matchpoint DROP CONSTRAINT ep_once_per_grp_loc_mod_marc;
6314
6315 ALTER TABLE config.circ_matrix_matchpoint ADD COLUMN copy_circ_lib   INT REFERENCES actor.org_unit (id) DEFERRABLE INITIALLY DEFERRED;
6316 ALTER TABLE config.circ_matrix_matchpoint ADD COLUMN copy_owning_lib INT REFERENCES actor.org_unit (id) DEFERRABLE INITIALLY DEFERRED;
6317
6318 ALTER TABLE config.circ_matrix_matchpoint ADD CONSTRAINT ep_once_per_grp_loc_mod_marc UNIQUE (
6319     grp, org_unit, circ_modifier, marc_type, marc_form, marc_vr_format, ref_flag,
6320     juvenile_flag, usr_age_lower_bound, usr_age_upper_bound, is_renewal, copy_circ_lib,
6321     copy_owning_lib
6322 );
6323
6324 -- Return the correct fail_part when the item can't be found
6325 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$
6326 DECLARE
6327     user_object        actor.usr%ROWTYPE;
6328     standing_penalty    config.standing_penalty%ROWTYPE;
6329     item_object        asset.copy%ROWTYPE;
6330     item_status_object    config.copy_status%ROWTYPE;
6331     item_location_object    asset.copy_location%ROWTYPE;
6332     result            action.matrix_test_result;
6333     circ_test        config.circ_matrix_matchpoint%ROWTYPE;
6334     out_by_circ_mod        config.circ_matrix_circ_mod_test%ROWTYPE;
6335     circ_mod_map        config.circ_matrix_circ_mod_test_map%ROWTYPE;
6336     hold_ratio          action.hold_stats%ROWTYPE;
6337     penalty_type         TEXT;
6338     tmp_grp         INT;
6339     items_out        INT;
6340     context_org_list        INT[];
6341     done            BOOL := FALSE;
6342 BEGIN
6343     result.success := TRUE;
6344
6345     -- Fail if the user is BARRED
6346     SELECT INTO user_object * FROM actor.usr WHERE id = match_user;
6347
6348     -- Fail if we couldn't find the user 
6349     IF user_object.id IS NULL THEN
6350         result.fail_part := 'no_user';
6351         result.success := FALSE;
6352         done := TRUE;
6353         RETURN NEXT result;
6354         RETURN;
6355     END IF;
6356
6357     SELECT INTO item_object * FROM asset.copy WHERE id = match_item;
6358
6359     -- Fail if we couldn't find the item 
6360     IF item_object.id IS NULL THEN
6361         result.fail_part := 'no_item';
6362         result.success := FALSE;
6363         done := TRUE;
6364         RETURN NEXT result;
6365         RETURN;
6366     END IF;
6367
6368     SELECT INTO circ_test * FROM action.find_circ_matrix_matchpoint(circ_ou, match_item, match_user, renewal);
6369     result.matchpoint := circ_test.id;
6370
6371     -- Fail if we couldn't find a matchpoint
6372     IF result.matchpoint IS NULL THEN
6373         result.fail_part := 'no_matchpoint';
6374         result.success := FALSE;
6375         done := TRUE;
6376         RETURN NEXT result;
6377     END IF;
6378
6379     IF user_object.barred IS TRUE THEN
6380         result.fail_part := 'actor.usr.barred';
6381         result.success := FALSE;
6382         done := TRUE;
6383         RETURN NEXT result;
6384     END IF;
6385
6386     -- Fail if the item can't circulate
6387     IF item_object.circulate IS FALSE THEN
6388         result.fail_part := 'asset.copy.circulate';
6389         result.success := FALSE;
6390         done := TRUE;
6391         RETURN NEXT result;
6392     END IF;
6393
6394     -- Fail if the item isn't in a circulateable status on a non-renewal
6395     IF NOT renewal AND item_object.status NOT IN ( 0, 7, 8 ) THEN
6396         result.fail_part := 'asset.copy.status';
6397         result.success := FALSE;
6398         done := TRUE;
6399         RETURN NEXT result;
6400     ELSIF renewal AND item_object.status <> 1 THEN
6401         result.fail_part := 'asset.copy.status';
6402         result.success := FALSE;
6403         done := TRUE;
6404         RETURN NEXT result;
6405     END IF;
6406
6407     -- Fail if the item can't circulate because of the shelving location
6408     SELECT INTO item_location_object * FROM asset.copy_location WHERE id = item_object.location;
6409     IF item_location_object.circulate IS FALSE THEN
6410         result.fail_part := 'asset.copy_location.circulate';
6411         result.success := FALSE;
6412         done := TRUE;
6413         RETURN NEXT result;
6414     END IF;
6415
6416     SELECT INTO context_org_list ARRAY_ACCUM(id) FROM actor.org_unit_full_path( circ_test.org_unit );
6417
6418     -- Fail if the test is set to hard non-circulating
6419     IF circ_test.circulate IS FALSE THEN
6420         result.fail_part := 'config.circ_matrix_test.circulate';
6421         result.success := FALSE;
6422         done := TRUE;
6423         RETURN NEXT result;
6424     END IF;
6425
6426     -- Fail if the total copy-hold ratio is too low
6427     IF circ_test.total_copy_hold_ratio IS NOT NULL THEN
6428         SELECT INTO hold_ratio * FROM action.copy_related_hold_stats(match_item);
6429         IF hold_ratio.total_copy_ratio IS NOT NULL AND hold_ratio.total_copy_ratio < circ_test.total_copy_hold_ratio THEN
6430             result.fail_part := 'config.circ_matrix_test.total_copy_hold_ratio';
6431             result.success := FALSE;
6432             done := TRUE;
6433             RETURN NEXT result;
6434         END IF;
6435     END IF;
6436
6437     -- Fail if the available copy-hold ratio is too low
6438     IF circ_test.available_copy_hold_ratio IS NOT NULL THEN
6439         SELECT INTO hold_ratio * FROM action.copy_related_hold_stats(match_item);
6440         IF hold_ratio.available_copy_ratio IS NOT NULL AND hold_ratio.available_copy_ratio < circ_test.available_copy_hold_ratio THEN
6441             result.fail_part := 'config.circ_matrix_test.available_copy_hold_ratio';
6442             result.success := FALSE;
6443             done := TRUE;
6444             RETURN NEXT result;
6445         END IF;
6446     END IF;
6447
6448     IF renewal THEN
6449         penalty_type = '%RENEW%';
6450     ELSE
6451         penalty_type = '%CIRC%';
6452     END IF;
6453
6454     FOR standing_penalty IN
6455         SELECT  DISTINCT csp.*
6456           FROM  actor.usr_standing_penalty usp
6457                 JOIN config.standing_penalty csp ON (csp.id = usp.standing_penalty)
6458           WHERE usr = match_user
6459                 AND usp.org_unit IN ( SELECT * FROM explode_array(context_org_list) )
6460                 AND (usp.stop_date IS NULL or usp.stop_date > NOW())
6461                 AND csp.block_list LIKE penalty_type LOOP
6462
6463         result.fail_part := standing_penalty.name;
6464         result.success := FALSE;
6465         done := TRUE;
6466         RETURN NEXT result;
6467     END LOOP;
6468
6469     -- Fail if the user has too many items with specific circ_modifiers checked out
6470     FOR out_by_circ_mod IN SELECT * FROM config.circ_matrix_circ_mod_test WHERE matchpoint = circ_test.id LOOP
6471         SELECT  INTO items_out COUNT(*)
6472           FROM  action.circulation circ
6473             JOIN asset.copy cp ON (cp.id = circ.target_copy)
6474           WHERE circ.usr = match_user
6475                AND circ.circ_lib IN ( SELECT * FROM explode_array(context_org_list) )
6476             AND circ.checkin_time IS NULL
6477             AND (circ.stop_fines IN ('MAXFINES','LONGOVERDUE') OR circ.stop_fines IS NULL)
6478             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);
6479         IF items_out >= out_by_circ_mod.items_out THEN
6480             result.fail_part := 'config.circ_matrix_circ_mod_test';
6481             result.success := FALSE;
6482             done := TRUE;
6483             RETURN NEXT result;
6484         END IF;
6485     END LOOP;
6486
6487     -- If we passed everything, return the successful matchpoint id
6488     IF NOT done THEN
6489         RETURN NEXT result;
6490     END IF;
6491
6492     RETURN;
6493 END;
6494 $func$ LANGUAGE plpgsql;
6495
6496 CREATE TABLE config.remote_account (
6497     id          SERIAL  PRIMARY KEY,
6498     label       TEXT    NOT NULL,
6499     host        TEXT    NOT NULL,   -- name or IP, :port optional
6500     username    TEXT,               -- optional, since we could default to $USER
6501     password    TEXT,               -- optional, since we could use SSH keys, or anonymous login.
6502     account     TEXT,               -- aka profile or FTP "account" command
6503     path        TEXT,               -- aka directory
6504     owner       INT     NOT NULL REFERENCES actor.org_unit (id) DEFERRABLE INITIALLY DEFERRED,
6505     last_activity TIMESTAMP WITH TIME ZONE
6506 );
6507
6508 CREATE TABLE acq.edi_account (      -- similar tables can extend remote_account for other parts of EG
6509     provider    INT     NOT NULL REFERENCES acq.provider          (id) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED,
6510     in_dir      TEXT,   -- incoming messages dir (probably different than config.remote_account.path, the outgoing dir)
6511         vendcode    TEXT,
6512         vendacct    TEXT
6513
6514 ) INHERITS (config.remote_account);
6515
6516 ALTER TABLE acq.edi_account ADD PRIMARY KEY (id);
6517
6518 CREATE TABLE acq.claim_type (
6519         id             SERIAL           PRIMARY KEY,
6520         org_unit       INT              NOT NULL REFERENCES actor.org_unit(id)
6521                                                  DEFERRABLE INITIALLY DEFERRED,
6522         code           TEXT             NOT NULL,
6523         description    TEXT             NOT NULL,
6524         CONSTRAINT claim_type_once_per_org UNIQUE ( org_unit, code )
6525 );
6526
6527 CREATE TABLE acq.claim (
6528         id             SERIAL           PRIMARY KEY,
6529         type           INT              NOT NULL REFERENCES acq.claim_type
6530                                                  DEFERRABLE INITIALLY DEFERRED,
6531         lineitem_detail BIGINT          NOT NULL REFERENCES acq.lineitem_detail
6532                                                  DEFERRABLE INITIALLY DEFERRED
6533 );
6534
6535 CREATE TABLE acq.claim_policy (
6536         id              SERIAL       PRIMARY KEY,
6537         org_unit        INT          NOT NULL REFERENCES actor.org_unit
6538                                      DEFERRABLE INITIALLY DEFERRED,
6539         name            TEXT         NOT NULL,
6540         description     TEXT         NOT NULL,
6541         CONSTRAINT name_once_per_org UNIQUE (org_unit, name)
6542 );
6543
6544 -- Add a san column for EDI. 
6545 -- See: http://isbn.org/standards/home/isbn/us/san/san-qa.asp
6546
6547 ALTER TABLE acq.provider ADD COLUMN san INT;
6548
6549 ALTER TABLE acq.provider ALTER COLUMN san TYPE TEXT USING lpad(text(san), 7, '0');
6550
6551 -- null edi_default is OK... it has to be, since we have no values in acq.edi_account yet
6552 ALTER TABLE acq.provider ADD COLUMN edi_default INT REFERENCES acq.edi_account (id) DEFERRABLE INITIALLY DEFERRED;
6553
6554 ALTER TABLE acq.provider
6555         ADD COLUMN active BOOL NOT NULL DEFAULT TRUE;
6556
6557 ALTER TABLE acq.provider
6558         ADD COLUMN prepayment_required BOOLEAN NOT NULL DEFAULT FALSE;
6559
6560 ALTER TABLE acq.provider
6561         ADD COLUMN url TEXT;
6562
6563 ALTER TABLE acq.provider
6564         ADD COLUMN email TEXT;
6565
6566 ALTER TABLE acq.provider
6567         ADD COLUMN phone TEXT;
6568
6569 ALTER TABLE acq.provider
6570         ADD COLUMN fax_phone TEXT;
6571
6572 ALTER TABLE acq.provider
6573         ADD COLUMN default_claim_policy INT
6574                 REFERENCES acq.claim_policy
6575                 DEFERRABLE INITIALLY DEFERRED;
6576
6577 ALTER TABLE action.transit_copy
6578 ADD COLUMN prev_dest INTEGER REFERENCES actor.org_unit( id )
6579                                                          DEFERRABLE INITIALLY DEFERRED;
6580
6581 DROP SCHEMA IF EXISTS booking CASCADE;
6582
6583 CREATE SCHEMA booking;
6584
6585 CREATE TABLE booking.resource_type (
6586         id             SERIAL          PRIMARY KEY,
6587         name           TEXT            NOT NULL,
6588         fine_interval  INTERVAL,
6589         fine_amount    DECIMAL(8,2)    NOT NULL DEFAULT 0,
6590         owner          INT             NOT NULL
6591                                        REFERENCES actor.org_unit( id )
6592                                        DEFERRABLE INITIALLY DEFERRED,
6593         catalog_item   BOOLEAN         NOT NULL DEFAULT FALSE,
6594         transferable   BOOLEAN         NOT NULL DEFAULT FALSE,
6595     record         BIGINT          REFERENCES biblio.record_entry (id)
6596                                        DEFERRABLE INITIALLY DEFERRED,
6597     max_fine       NUMERIC(8,2),
6598     elbow_room     INTERVAL,
6599     CONSTRAINT brt_name_or_record_once_per_owner UNIQUE(owner, name, record)
6600 );
6601
6602 CREATE TABLE booking.resource (
6603         id             SERIAL           PRIMARY KEY,
6604         owner          INT              NOT NULL
6605                                         REFERENCES actor.org_unit(id)
6606                                         DEFERRABLE INITIALLY DEFERRED,
6607         type           INT              NOT NULL
6608                                         REFERENCES booking.resource_type(id)
6609                                         DEFERRABLE INITIALLY DEFERRED,
6610         overbook       BOOLEAN          NOT NULL DEFAULT FALSE,
6611         barcode        TEXT             NOT NULL,
6612         deposit        BOOLEAN          NOT NULL DEFAULT FALSE,
6613         deposit_amount DECIMAL(8,2)     NOT NULL DEFAULT 0.00,
6614         user_fee       DECIMAL(8,2)     NOT NULL DEFAULT 0.00,
6615         CONSTRAINT br_unique UNIQUE (owner, barcode)
6616 );
6617
6618 -- For non-catalog items: hijack barcode for name/description
6619
6620 CREATE TABLE booking.resource_attr (
6621         id              SERIAL          PRIMARY KEY,
6622         owner           INT             NOT NULL
6623                                         REFERENCES actor.org_unit(id)
6624                                         DEFERRABLE INITIALLY DEFERRED,
6625         name            TEXT            NOT NULL,
6626         resource_type   INT             NOT NULL
6627                                         REFERENCES booking.resource_type(id)
6628                                         ON DELETE CASCADE
6629                                         DEFERRABLE INITIALLY DEFERRED,
6630         required        BOOLEAN         NOT NULL DEFAULT FALSE,
6631         CONSTRAINT bra_name_once_per_type UNIQUE(resource_type, name)
6632 );
6633
6634 CREATE TABLE booking.resource_attr_value (
6635         id               SERIAL         PRIMARY KEY,
6636         owner            INT            NOT NULL
6637                                         REFERENCES actor.org_unit(id)
6638                                         DEFERRABLE INITIALLY DEFERRED,
6639         attr             INT            NOT NULL
6640                                         REFERENCES booking.resource_attr(id)
6641                                         DEFERRABLE INITIALLY DEFERRED,
6642         valid_value      TEXT           NOT NULL,
6643         CONSTRAINT brav_logical_key UNIQUE(owner, attr, valid_value)
6644 );
6645
6646 CREATE TABLE booking.resource_attr_map (
6647         id               SERIAL         PRIMARY KEY,
6648         resource         INT            NOT NULL
6649                                         REFERENCES booking.resource(id)
6650                                         ON DELETE CASCADE
6651                                         DEFERRABLE INITIALLY DEFERRED,
6652         resource_attr    INT            NOT NULL
6653                                         REFERENCES booking.resource_attr(id)
6654                                         ON DELETE CASCADE
6655                                         DEFERRABLE INITIALLY DEFERRED,
6656         value            INT            NOT NULL
6657                                         REFERENCES booking.resource_attr_value(id)
6658                                         DEFERRABLE INITIALLY DEFERRED,
6659         CONSTRAINT bram_one_value_per_attr UNIQUE(resource, resource_attr)
6660 );
6661
6662 CREATE TABLE booking.reservation (
6663         request_time     TIMESTAMPTZ   NOT NULL DEFAULT now(),
6664         start_time       TIMESTAMPTZ,
6665         end_time         TIMESTAMPTZ,
6666         capture_time     TIMESTAMPTZ,
6667         cancel_time      TIMESTAMPTZ,
6668         pickup_time      TIMESTAMPTZ,
6669         return_time      TIMESTAMPTZ,
6670         booking_interval INTERVAL,
6671         fine_interval    INTERVAL,
6672         fine_amount      DECIMAL(8,2),
6673         target_resource_type  INT       NOT NULL
6674                                         REFERENCES booking.resource_type(id)
6675                                         ON DELETE CASCADE
6676                                         DEFERRABLE INITIALLY DEFERRED,
6677         target_resource  INT            REFERENCES booking.resource(id)
6678                                         ON DELETE CASCADE
6679                                         DEFERRABLE INITIALLY DEFERRED,
6680         current_resource INT            REFERENCES booking.resource(id)
6681                                         ON DELETE CASCADE
6682                                         DEFERRABLE INITIALLY DEFERRED,
6683         request_lib      INT            NOT NULL
6684                                         REFERENCES actor.org_unit(id)
6685                                         DEFERRABLE INITIALLY DEFERRED,
6686         pickup_lib       INT            REFERENCES actor.org_unit(id)
6687                                         DEFERRABLE INITIALLY DEFERRED,
6688         capture_staff    INT            REFERENCES actor.usr(id)
6689                                         DEFERRABLE INITIALLY DEFERRED,
6690     max_fine         NUMERIC(8,2)
6691 ) INHERITS (money.billable_xact);
6692
6693 ALTER TABLE booking.reservation ADD PRIMARY KEY (id);
6694
6695 ALTER TABLE booking.reservation
6696         ADD CONSTRAINT booking_reservation_usr_fkey
6697         FOREIGN KEY (usr) REFERENCES actor.usr (id)
6698         DEFERRABLE INITIALLY DEFERRED;
6699
6700 CREATE TABLE booking.reservation_attr_value_map (
6701         id               SERIAL         PRIMARY KEY,
6702         reservation      INT            NOT NULL
6703                                         REFERENCES booking.reservation(id)
6704                                         ON DELETE CASCADE
6705                                         DEFERRABLE INITIALLY DEFERRED,
6706         attr_value       INT            NOT NULL
6707                                         REFERENCES booking.resource_attr_value(id)
6708                                         ON DELETE CASCADE
6709                                         DEFERRABLE INITIALLY DEFERRED,
6710         CONSTRAINT bravm_logical_key UNIQUE(reservation, attr_value)
6711 );
6712
6713 CREATE FUNCTION auditor.create_auditor_seq     ( sch TEXT, tbl TEXT ) RETURNS BOOL AS $creator$
6714 BEGIN
6715     EXECUTE $$
6716         CREATE SEQUENCE auditor.$$ || sch || $$_$$ || tbl || $$_pkey_seq;
6717     $$;
6718         RETURN TRUE;
6719 END;
6720 $creator$ LANGUAGE 'plpgsql';
6721
6722 CREATE FUNCTION auditor.create_auditor_history ( sch TEXT, tbl TEXT ) RETURNS BOOL AS $creator$
6723 BEGIN
6724     EXECUTE $$
6725         CREATE TABLE auditor.$$ || sch || $$_$$ || tbl || $$_history (
6726             audit_id    BIGINT                          PRIMARY KEY,
6727             audit_time  TIMESTAMP WITH TIME ZONE        NOT NULL,
6728             audit_action        TEXT                            NOT NULL,
6729             LIKE $$ || sch || $$.$$ || tbl || $$
6730         );
6731     $$;
6732         RETURN TRUE;
6733 END;
6734 $creator$ LANGUAGE 'plpgsql';
6735
6736 CREATE FUNCTION auditor.create_auditor_func    ( sch TEXT, tbl TEXT ) RETURNS BOOL AS $creator$
6737 BEGIN
6738     EXECUTE $$
6739         CREATE FUNCTION auditor.audit_$$ || sch || $$_$$ || tbl || $$_func ()
6740         RETURNS TRIGGER AS $func$
6741         BEGIN
6742             INSERT INTO auditor.$$ || sch || $$_$$ || tbl || $$_history
6743                 SELECT  nextval('auditor.$$ || sch || $$_$$ || tbl || $$_pkey_seq'),
6744                     now(),
6745                     SUBSTR(TG_OP,1,1),
6746                     OLD.*;
6747             RETURN NULL;
6748         END;
6749         $func$ LANGUAGE 'plpgsql';
6750     $$;
6751     RETURN TRUE;
6752 END;
6753 $creator$ LANGUAGE 'plpgsql';
6754
6755 CREATE FUNCTION auditor.create_auditor_update_trigger ( sch TEXT, tbl TEXT ) RETURNS BOOL AS $creator$
6756 BEGIN
6757     EXECUTE $$
6758         CREATE TRIGGER audit_$$ || sch || $$_$$ || tbl || $$_update_trigger
6759             AFTER UPDATE OR DELETE ON $$ || sch || $$.$$ || tbl || $$ FOR EACH ROW
6760             EXECUTE PROCEDURE auditor.audit_$$ || sch || $$_$$ || tbl || $$_func ();
6761     $$;
6762         RETURN TRUE;
6763 END;
6764 $creator$ LANGUAGE 'plpgsql';
6765
6766 CREATE FUNCTION auditor.create_auditor_lifecycle     ( sch TEXT, tbl TEXT ) RETURNS BOOL AS $creator$
6767 BEGIN
6768     EXECUTE $$
6769         CREATE VIEW auditor.$$ || sch || $$_$$ || tbl || $$_lifecycle AS
6770             SELECT      -1, now() as audit_time, '-' as audit_action, *
6771               FROM      $$ || sch || $$.$$ || tbl || $$
6772                 UNION ALL
6773             SELECT      *
6774               FROM      auditor.$$ || sch || $$_$$ || tbl || $$_history;
6775     $$;
6776         RETURN TRUE;
6777 END;
6778 $creator$ LANGUAGE 'plpgsql';
6779
6780 DROP FUNCTION IF EXISTS auditor.create_auditor (TEXT, TEXT);
6781
6782 -- The main event
6783
6784 CREATE FUNCTION auditor.create_auditor ( sch TEXT, tbl TEXT ) RETURNS BOOL AS $creator$
6785 BEGIN
6786     PERFORM auditor.create_auditor_seq(sch, tbl);
6787     PERFORM auditor.create_auditor_history(sch, tbl);
6788     PERFORM auditor.create_auditor_func(sch, tbl);
6789     PERFORM auditor.create_auditor_update_trigger(sch, tbl);
6790     PERFORM auditor.create_auditor_lifecycle(sch, tbl);
6791         RETURN TRUE;
6792 END;
6793 $creator$ LANGUAGE 'plpgsql';
6794
6795 -- represents a circ chain summary
6796 CREATE TYPE action.circ_chain_summary AS (
6797     num_circs INTEGER,
6798     start_time TIMESTAMP WITH TIME ZONE,
6799     checkout_workstation TEXT,
6800     last_renewal_time TIMESTAMP WITH TIME ZONE, -- NULL if no renewals
6801     last_stop_fines TEXT,
6802     last_stop_fines_time TIMESTAMP WITH TIME ZONE,
6803     last_renewal_workstation TEXT, -- NULL if no renewals
6804     last_checkin_workstation TEXT,
6805     last_checkin_time TIMESTAMP WITH TIME ZONE,
6806     last_checkin_scan_time TIMESTAMP WITH TIME ZONE
6807 );
6808
6809 CREATE OR REPLACE FUNCTION action.circ_chain ( ctx_circ_id INTEGER ) RETURNS SETOF action.circulation AS $$
6810 DECLARE
6811     tmp_circ action.circulation%ROWTYPE;
6812     circ_0 action.circulation%ROWTYPE;
6813 BEGIN
6814
6815     SELECT INTO tmp_circ * FROM action.circulation WHERE id = ctx_circ_id;
6816
6817     IF tmp_circ IS NULL THEN
6818         RETURN NEXT tmp_circ;
6819     END IF;
6820     circ_0 := tmp_circ;
6821
6822     -- find the front of the chain
6823     WHILE TRUE LOOP
6824         SELECT INTO tmp_circ * FROM action.circulation WHERE id = tmp_circ.parent_circ;
6825         IF tmp_circ IS NULL THEN
6826             EXIT;
6827         END IF;
6828         circ_0 := tmp_circ;
6829     END LOOP;
6830
6831     -- now send the circs to the caller, oldest to newest
6832     tmp_circ := circ_0;
6833     WHILE TRUE LOOP
6834         IF tmp_circ IS NULL THEN
6835             EXIT;
6836         END IF;
6837         RETURN NEXT tmp_circ;
6838         SELECT INTO tmp_circ * FROM action.circulation WHERE parent_circ = tmp_circ.id;
6839     END LOOP;
6840
6841 END;
6842 $$ LANGUAGE 'plpgsql';
6843
6844 CREATE OR REPLACE FUNCTION action.summarize_circ_chain ( ctx_circ_id INTEGER ) RETURNS action.circ_chain_summary AS $$
6845
6846 DECLARE
6847
6848     -- first circ in the chain
6849     circ_0 action.circulation%ROWTYPE;
6850
6851     -- last circ in the chain
6852     circ_n action.circulation%ROWTYPE;
6853
6854     -- circ chain under construction
6855     chain action.circ_chain_summary;
6856     tmp_circ action.circulation%ROWTYPE;
6857
6858 BEGIN
6859     
6860     chain.num_circs := 0;
6861     FOR tmp_circ IN SELECT * FROM action.circ_chain(ctx_circ_id) LOOP
6862
6863         IF chain.num_circs = 0 THEN
6864             circ_0 := tmp_circ;
6865         END IF;
6866
6867         chain.num_circs := chain.num_circs + 1;
6868         circ_n := tmp_circ;
6869     END LOOP;
6870
6871     chain.start_time := circ_0.xact_start;
6872     chain.last_stop_fines := circ_n.stop_fines;
6873     chain.last_stop_fines_time := circ_n.stop_fines_time;
6874     chain.last_checkin_time := circ_n.checkin_time;
6875     chain.last_checkin_scan_time := circ_n.checkin_scan_time;
6876     SELECT INTO chain.checkout_workstation name FROM actor.workstation WHERE id = circ_0.workstation;
6877     SELECT INTO chain.last_checkin_workstation name FROM actor.workstation WHERE id = circ_n.checkin_workstation;
6878
6879     IF chain.num_circs > 1 THEN
6880         chain.last_renewal_time := circ_n.xact_start;
6881         SELECT INTO chain.last_renewal_workstation name FROM actor.workstation WHERE id = circ_n.workstation;
6882     END IF;
6883
6884     RETURN chain;
6885
6886 END;
6887 $$ LANGUAGE 'plpgsql';
6888
6889 CREATE TRIGGER mat_summary_create_tgr AFTER INSERT ON booking.reservation FOR EACH ROW EXECUTE PROCEDURE money.mat_summary_create ('reservation');
6890 CREATE TRIGGER mat_summary_change_tgr AFTER UPDATE ON booking.reservation FOR EACH ROW EXECUTE PROCEDURE money.mat_summary_update ();
6891 CREATE TRIGGER mat_summary_remove_tgr AFTER DELETE ON booking.reservation FOR EACH ROW EXECUTE PROCEDURE money.mat_summary_delete ();
6892
6893 ALTER TABLE config.standing_penalty
6894         ADD COLUMN org_depth   INTEGER;
6895
6896 CREATE OR REPLACE FUNCTION actor.calculate_system_penalties( match_user INT, context_org INT ) RETURNS SETOF actor.usr_standing_penalty AS $func$
6897 DECLARE
6898     user_object         actor.usr%ROWTYPE;
6899     new_sp_row          actor.usr_standing_penalty%ROWTYPE;
6900     existing_sp_row     actor.usr_standing_penalty%ROWTYPE;
6901     collections_fines   permission.grp_penalty_threshold%ROWTYPE;
6902     max_fines           permission.grp_penalty_threshold%ROWTYPE;
6903     max_overdue         permission.grp_penalty_threshold%ROWTYPE;
6904     max_items_out       permission.grp_penalty_threshold%ROWTYPE;
6905     tmp_grp             INT;
6906     items_overdue       INT;
6907     items_out           INT;
6908     context_org_list    INT[];
6909     current_fines        NUMERIC(8,2) := 0.0;
6910     tmp_fines            NUMERIC(8,2);
6911     tmp_groc            RECORD;
6912     tmp_circ            RECORD;
6913     tmp_org             actor.org_unit%ROWTYPE;
6914     tmp_penalty         config.standing_penalty%ROWTYPE;
6915     tmp_depth           INTEGER;
6916 BEGIN
6917     SELECT INTO user_object * FROM actor.usr WHERE id = match_user;
6918
6919     -- Max fines
6920     SELECT INTO tmp_org * FROM actor.org_unit WHERE id = context_org;
6921
6922     -- Fail if the user has a high fine balance
6923     LOOP
6924         tmp_grp := user_object.profile;
6925         LOOP
6926             SELECT * INTO max_fines FROM permission.grp_penalty_threshold WHERE grp = tmp_grp AND penalty = 1 AND org_unit = tmp_org.id;
6927
6928             IF max_fines.threshold IS NULL THEN
6929                 SELECT parent INTO tmp_grp FROM permission.grp_tree WHERE id = tmp_grp;
6930             ELSE
6931                 EXIT;
6932             END IF;
6933
6934             IF tmp_grp IS NULL THEN
6935                 EXIT;
6936             END IF;
6937         END LOOP;
6938
6939         IF max_fines.threshold IS NOT NULL OR tmp_org.parent_ou IS NULL THEN
6940             EXIT;
6941         END IF;
6942
6943         SELECT * INTO tmp_org FROM actor.org_unit WHERE id = tmp_org.parent_ou;
6944
6945     END LOOP;
6946
6947     IF max_fines.threshold IS NOT NULL THEN
6948
6949         FOR existing_sp_row IN
6950                 SELECT  *
6951                   FROM  actor.usr_standing_penalty
6952                   WHERE usr = match_user
6953                         AND org_unit = max_fines.org_unit
6954                         AND (stop_date IS NULL or stop_date > NOW())
6955                         AND standing_penalty = 1
6956                 LOOP
6957             RETURN NEXT existing_sp_row;
6958         END LOOP;
6959
6960         SELECT  SUM(f.balance_owed) INTO current_fines
6961           FROM  money.materialized_billable_xact_summary f
6962                 JOIN (
6963                     SELECT  r.id
6964                       FROM  booking.reservation r
6965                             JOIN  actor.org_unit_full_path( max_fines.org_unit ) fp ON (r.pickup_lib = fp.id)
6966                       WHERE usr = match_user
6967                             AND xact_finish IS NULL
6968                                 UNION ALL
6969                     SELECT  g.id
6970                       FROM  money.grocery g
6971                             JOIN  actor.org_unit_full_path( max_fines.org_unit ) fp ON (g.billing_location = fp.id)
6972                       WHERE usr = match_user
6973                             AND xact_finish IS NULL
6974                                 UNION ALL
6975                     SELECT  circ.id
6976                       FROM  action.circulation circ
6977                             JOIN  actor.org_unit_full_path( max_fines.org_unit ) fp ON (circ.circ_lib = fp.id)
6978                       WHERE usr = match_user
6979                             AND xact_finish IS NULL ) l USING (id);
6980
6981         IF current_fines >= max_fines.threshold THEN
6982             new_sp_row.usr := match_user;
6983             new_sp_row.org_unit := max_fines.org_unit;
6984             new_sp_row.standing_penalty := 1;
6985             RETURN NEXT new_sp_row;
6986         END IF;
6987     END IF;
6988
6989     -- Start over for max overdue
6990     SELECT INTO tmp_org * FROM actor.org_unit WHERE id = context_org;
6991
6992     -- Fail if the user has too many overdue items
6993     LOOP
6994         tmp_grp := user_object.profile;
6995         LOOP
6996
6997             SELECT * INTO max_overdue FROM permission.grp_penalty_threshold WHERE grp = tmp_grp AND penalty = 2 AND org_unit = tmp_org.id;
6998
6999             IF max_overdue.threshold IS NULL THEN
7000                 SELECT parent INTO tmp_grp FROM permission.grp_tree WHERE id = tmp_grp;
7001             ELSE
7002                 EXIT;
7003             END IF;
7004
7005             IF tmp_grp IS NULL THEN
7006                 EXIT;
7007             END IF;
7008         END LOOP;
7009
7010         IF max_overdue.threshold IS NOT NULL OR tmp_org.parent_ou IS NULL THEN
7011             EXIT;
7012         END IF;
7013
7014         SELECT INTO tmp_org * FROM actor.org_unit WHERE id = tmp_org.parent_ou;
7015
7016     END LOOP;
7017
7018     IF max_overdue.threshold IS NOT NULL THEN
7019
7020         FOR existing_sp_row IN
7021                 SELECT  *
7022                   FROM  actor.usr_standing_penalty
7023                   WHERE usr = match_user
7024                         AND org_unit = max_overdue.org_unit
7025                         AND (stop_date IS NULL or stop_date > NOW())
7026                         AND standing_penalty = 2
7027                 LOOP
7028             RETURN NEXT existing_sp_row;
7029         END LOOP;
7030
7031         SELECT  INTO items_overdue COUNT(*)
7032           FROM  action.circulation circ
7033                 JOIN  actor.org_unit_full_path( max_overdue.org_unit ) fp ON (circ.circ_lib = fp.id)
7034           WHERE circ.usr = match_user
7035             AND circ.checkin_time IS NULL
7036             AND circ.due_date < NOW()
7037             AND (circ.stop_fines = 'MAXFINES' OR circ.stop_fines IS NULL);
7038
7039         IF items_overdue >= max_overdue.threshold::INT THEN
7040             new_sp_row.usr := match_user;
7041             new_sp_row.org_unit := max_overdue.org_unit;
7042             new_sp_row.standing_penalty := 2;
7043             RETURN NEXT new_sp_row;
7044         END IF;
7045     END IF;
7046
7047     -- Start over for max out
7048     SELECT INTO tmp_org * FROM actor.org_unit WHERE id = context_org;
7049
7050     -- Fail if the user has too many checked out items
7051     LOOP
7052         tmp_grp := user_object.profile;
7053         LOOP
7054             SELECT * INTO max_items_out FROM permission.grp_penalty_threshold WHERE grp = tmp_grp AND penalty = 3 AND org_unit = tmp_org.id;
7055
7056             IF max_items_out.threshold IS NULL THEN
7057                 SELECT parent INTO tmp_grp FROM permission.grp_tree WHERE id = tmp_grp;
7058             ELSE
7059                 EXIT;
7060             END IF;
7061
7062             IF tmp_grp IS NULL THEN
7063                 EXIT;
7064             END IF;
7065         END LOOP;
7066
7067         IF max_items_out.threshold IS NOT NULL OR tmp_org.parent_ou IS NULL THEN
7068             EXIT;
7069         END IF;
7070
7071         SELECT INTO tmp_org * FROM actor.org_unit WHERE id = tmp_org.parent_ou;
7072
7073     END LOOP;
7074
7075
7076     -- Fail if the user has too many items checked out
7077     IF max_items_out.threshold IS NOT NULL THEN
7078
7079         FOR existing_sp_row IN
7080                 SELECT  *
7081                   FROM  actor.usr_standing_penalty
7082                   WHERE usr = match_user
7083                         AND org_unit = max_items_out.org_unit
7084                         AND (stop_date IS NULL or stop_date > NOW())
7085                         AND standing_penalty = 3
7086                 LOOP
7087             RETURN NEXT existing_sp_row;
7088         END LOOP;
7089
7090         SELECT  INTO items_out COUNT(*)
7091           FROM  action.circulation circ
7092                 JOIN  actor.org_unit_full_path( max_items_out.org_unit ) fp ON (circ.circ_lib = fp.id)
7093           WHERE circ.usr = match_user
7094                 AND circ.checkin_time IS NULL
7095                 AND (circ.stop_fines IN ('MAXFINES','LONGOVERDUE') OR circ.stop_fines IS NULL);
7096
7097            IF items_out >= max_items_out.threshold::INT THEN
7098             new_sp_row.usr := match_user;
7099             new_sp_row.org_unit := max_items_out.org_unit;
7100             new_sp_row.standing_penalty := 3;
7101             RETURN NEXT new_sp_row;
7102            END IF;
7103     END IF;
7104
7105     -- Start over for collections warning
7106     SELECT INTO tmp_org * FROM actor.org_unit WHERE id = context_org;
7107
7108     -- Fail if the user has a collections-level fine balance
7109     LOOP
7110         tmp_grp := user_object.profile;
7111         LOOP
7112             SELECT * INTO max_fines FROM permission.grp_penalty_threshold WHERE grp = tmp_grp AND penalty = 4 AND org_unit = tmp_org.id;
7113
7114             IF max_fines.threshold IS NULL THEN
7115                 SELECT parent INTO tmp_grp FROM permission.grp_tree WHERE id = tmp_grp;
7116             ELSE
7117                 EXIT;
7118             END IF;
7119
7120             IF tmp_grp IS NULL THEN
7121                 EXIT;
7122             END IF;
7123         END LOOP;
7124
7125         IF max_fines.threshold IS NOT NULL OR tmp_org.parent_ou IS NULL THEN
7126             EXIT;
7127         END IF;
7128
7129         SELECT * INTO tmp_org FROM actor.org_unit WHERE id = tmp_org.parent_ou;
7130
7131     END LOOP;
7132
7133     IF max_fines.threshold IS NOT NULL THEN
7134
7135         FOR existing_sp_row IN
7136                 SELECT  *
7137                   FROM  actor.usr_standing_penalty
7138                   WHERE usr = match_user
7139                         AND org_unit = max_fines.org_unit
7140                         AND (stop_date IS NULL or stop_date > NOW())
7141                         AND standing_penalty = 4
7142                 LOOP
7143             RETURN NEXT existing_sp_row;
7144         END LOOP;
7145
7146         SELECT  SUM(f.balance_owed) INTO current_fines
7147           FROM  money.materialized_billable_xact_summary f
7148                 JOIN (
7149                     SELECT  r.id
7150                       FROM  booking.reservation r
7151                             JOIN  actor.org_unit_full_path( max_fines.org_unit ) fp ON (r.pickup_lib = fp.id)
7152                       WHERE usr = match_user
7153                             AND xact_finish IS NULL
7154                                 UNION ALL
7155                     SELECT  g.id
7156                       FROM  money.grocery g
7157                             JOIN  actor.org_unit_full_path( max_fines.org_unit ) fp ON (g.billing_location = fp.id)
7158                       WHERE usr = match_user
7159                             AND xact_finish IS NULL
7160                                 UNION ALL
7161                     SELECT  circ.id
7162                       FROM  action.circulation circ
7163                             JOIN  actor.org_unit_full_path( max_fines.org_unit ) fp ON (circ.circ_lib = fp.id)
7164                       WHERE usr = match_user
7165                             AND xact_finish IS NULL ) l USING (id);
7166
7167         IF current_fines >= max_fines.threshold THEN
7168             new_sp_row.usr := match_user;
7169             new_sp_row.org_unit := max_fines.org_unit;
7170             new_sp_row.standing_penalty := 4;
7171             RETURN NEXT new_sp_row;
7172         END IF;
7173     END IF;
7174
7175     -- Start over for in collections
7176     SELECT INTO tmp_org * FROM actor.org_unit WHERE id = context_org;
7177
7178     -- Remove the in-collections penalty if the user has paid down enough
7179     -- This penalty is different, because this code is not responsible for creating 
7180     -- new in-collections penalties, only for removing them
7181     LOOP
7182         tmp_grp := user_object.profile;
7183         LOOP
7184             SELECT * INTO max_fines FROM permission.grp_penalty_threshold WHERE grp = tmp_grp AND penalty = 30 AND org_unit = tmp_org.id;
7185
7186             IF max_fines.threshold IS NULL THEN
7187                 SELECT parent INTO tmp_grp FROM permission.grp_tree WHERE id = tmp_grp;
7188             ELSE
7189                 EXIT;
7190             END IF;
7191
7192             IF tmp_grp IS NULL THEN
7193                 EXIT;
7194             END IF;
7195         END LOOP;
7196
7197         IF max_fines.threshold IS NOT NULL OR tmp_org.parent_ou IS NULL THEN
7198             EXIT;
7199         END IF;
7200
7201         SELECT * INTO tmp_org FROM actor.org_unit WHERE id = tmp_org.parent_ou;
7202
7203     END LOOP;
7204
7205     IF max_fines.threshold IS NOT NULL THEN
7206
7207         -- first, see if the user had paid down to the threshold
7208         SELECT  SUM(f.balance_owed) INTO current_fines
7209           FROM  money.materialized_billable_xact_summary f
7210                 JOIN (
7211                     SELECT  r.id
7212                       FROM  booking.reservation r
7213                             JOIN  actor.org_unit_full_path( max_fines.org_unit ) fp ON (r.pickup_lib = fp.id)
7214                       WHERE usr = match_user
7215                             AND xact_finish IS NULL
7216                                 UNION ALL
7217                     SELECT  g.id
7218                       FROM  money.grocery g
7219                             JOIN  actor.org_unit_full_path( max_fines.org_unit ) fp ON (g.billing_location = fp.id)
7220                       WHERE usr = match_user
7221                             AND xact_finish IS NULL
7222                                 UNION ALL
7223                     SELECT  circ.id
7224                       FROM  action.circulation circ
7225                             JOIN  actor.org_unit_full_path( max_fines.org_unit ) fp ON (circ.circ_lib = fp.id)
7226                       WHERE usr = match_user
7227                             AND xact_finish IS NULL ) l USING (id);
7228
7229         IF current_fines IS NULL OR current_fines <= max_fines.threshold THEN
7230             -- patron has paid down enough
7231
7232             SELECT INTO tmp_penalty * FROM config.standing_penalty WHERE id = 30;
7233
7234             IF tmp_penalty.org_depth IS NOT NULL THEN
7235
7236                 -- since this code is not responsible for applying the penalty, it can't 
7237                 -- guarantee the current context org will match the org at which the penalty 
7238                 --- was applied.  search up the org tree until we hit the configured penalty depth
7239                 SELECT INTO tmp_org * FROM actor.org_unit WHERE id = context_org;
7240                 SELECT INTO tmp_depth depth FROM actor.org_unit_type WHERE id = tmp_org.ou_type;
7241
7242                 WHILE tmp_depth >= tmp_penalty.org_depth LOOP
7243
7244                     FOR existing_sp_row IN
7245                             SELECT  *
7246                             FROM  actor.usr_standing_penalty
7247                             WHERE usr = match_user
7248                                     AND org_unit = tmp_org.id
7249                                     AND (stop_date IS NULL or stop_date > NOW())
7250                                     AND standing_penalty = 30 
7251                             LOOP
7252
7253                         -- Penalty exists, return it for removal
7254                         RETURN NEXT existing_sp_row;
7255                     END LOOP;
7256
7257                     IF tmp_org.parent_ou IS NULL THEN
7258                         EXIT;
7259                     END IF;
7260
7261                     SELECT * INTO tmp_org FROM actor.org_unit WHERE id = tmp_org.parent_ou;
7262                     SELECT INTO tmp_depth depth FROM actor.org_unit_type WHERE id = tmp_org.ou_type;
7263                 END LOOP;
7264
7265             ELSE
7266
7267                 -- no penalty depth is defined, look for exact matches
7268
7269                 FOR existing_sp_row IN
7270                         SELECT  *
7271                         FROM  actor.usr_standing_penalty
7272                         WHERE usr = match_user
7273                                 AND org_unit = max_fines.org_unit
7274                                 AND (stop_date IS NULL or stop_date > NOW())
7275                                 AND standing_penalty = 30 
7276                         LOOP
7277                     -- Penalty exists, return it for removal
7278                     RETURN NEXT existing_sp_row;
7279                 END LOOP;
7280             END IF;
7281     
7282         END IF;
7283
7284     END IF;
7285
7286     RETURN;
7287 END;
7288 $func$ LANGUAGE plpgsql;
7289
7290 -- Create a default row in acq.fiscal_calendar
7291 -- Add a column in actor.org_unit to point to it
7292
7293 INSERT INTO acq.fiscal_calendar ( id, name ) VALUES ( 1, 'Default' );
7294
7295 ALTER TABLE actor.org_unit
7296 ADD COLUMN fiscal_calendar INT NOT NULL
7297         REFERENCES acq.fiscal_calendar( id )
7298         DEFERRABLE INITIALLY DEFERRED
7299         DEFAULT 1;
7300
7301 ALTER TABLE auditor.actor_org_unit_history
7302         ADD COLUMN fiscal_calendar INT;
7303
7304 ALTER TABLE acq.funding_source_credit
7305 ADD COLUMN deadline_date TIMESTAMPTZ;
7306
7307 ALTER TABLE acq.funding_source_credit
7308 ADD COLUMN effective_date TIMESTAMPTZ NOT NULL DEFAULT now();
7309
7310 INSERT INTO config.standing_penalty (id,name,label) VALUES (30,'PATRON_IN_COLLECTIONS','Patron has been referred to a collections agency');
7311
7312 CREATE TABLE acq.fund_transfer (
7313         id               SERIAL         PRIMARY KEY,
7314         src_fund         INT            NOT NULL REFERENCES acq.fund( id )
7315                                         DEFERRABLE INITIALLY DEFERRED,
7316         src_amount       NUMERIC        NOT NULL,
7317         dest_fund        INT            REFERENCES acq.fund( id )
7318                                         DEFERRABLE INITIALLY DEFERRED,
7319         dest_amount      NUMERIC,
7320         transfer_time    TIMESTAMPTZ    NOT NULL DEFAULT now(),
7321         transfer_user    INT            NOT NULL REFERENCES actor.usr( id )
7322                                         DEFERRABLE INITIALLY DEFERRED,
7323         note             TEXT,
7324     funding_source_credit INTEGER   NOT NULL
7325                                         REFERENCES acq.funding_source_credit(id)
7326                                         DEFERRABLE INITIALLY DEFERRED
7327 );
7328
7329 CREATE INDEX acqftr_usr_idx
7330 ON acq.fund_transfer( transfer_user );
7331
7332 COMMENT ON TABLE acq.fund_transfer IS $$
7333 /*
7334  * Copyright (C) 2009  Georgia Public Library Service
7335  * Scott McKellar <scott@esilibrary.com>
7336  *
7337  * Fund Transfer
7338  *
7339  * Each row represents the transfer of money from a source fund
7340  * to a destination fund.  There should be corresponding entries
7341  * in acq.fund_allocation.  The purpose of acq.fund_transfer is
7342  * to record how much money moved from which fund to which other
7343  * fund.
7344  * 
7345  * The presence of two amount fields, rather than one, reflects
7346  * the possibility that the two funds are denominated in different
7347  * currencies.  If they use the same currency type, the two
7348  * amounts should be the same.
7349  *
7350  * ****
7351  *
7352  * This program is free software; you can redistribute it and/or
7353  * modify it under the terms of the GNU General Public License
7354  * as published by the Free Software Foundation; either version 2
7355  * of the License, or (at your option) any later version.
7356  *
7357  * This program is distributed in the hope that it will be useful,
7358  * but WITHOUT ANY WARRANTY; without even the implied warranty of
7359  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
7360  * GNU General Public License for more details.
7361  */
7362 $$;
7363
7364 CREATE TABLE acq.claim_event_type (
7365         id             SERIAL           PRIMARY KEY,
7366         org_unit       INT              NOT NULL REFERENCES actor.org_unit(id)
7367                                                  DEFERRABLE INITIALLY DEFERRED,
7368         code           TEXT             NOT NULL,
7369         description    TEXT             NOT NULL,
7370         library_initiated BOOL          NOT NULL DEFAULT FALSE,
7371         CONSTRAINT event_type_once_per_org UNIQUE ( org_unit, code )
7372 );
7373
7374 CREATE TABLE acq.claim_event (
7375         id             BIGSERIAL        PRIMARY KEY,
7376         type           INT              NOT NULL REFERENCES acq.claim_event_type
7377                                                  DEFERRABLE INITIALLY DEFERRED,
7378         claim          SERIAL           NOT NULL REFERENCES acq.claim
7379                                                  DEFERRABLE INITIALLY DEFERRED,
7380         event_date     TIMESTAMPTZ      NOT NULL DEFAULT now(),
7381         creator        INT              NOT NULL REFERENCES actor.usr
7382                                                  DEFERRABLE INITIALLY DEFERRED,
7383         note           TEXT
7384 );
7385
7386 CREATE INDEX claim_event_claim_date_idx ON acq.claim_event( claim, event_date );
7387
7388 CREATE OR REPLACE FUNCTION actor.usr_purge_data(
7389         src_usr  IN INTEGER,
7390         dest_usr IN INTEGER
7391 ) RETURNS VOID AS $$
7392 DECLARE
7393         suffix TEXT;
7394         renamable_row RECORD;
7395 BEGIN
7396
7397         UPDATE actor.usr SET
7398                 active = FALSE,
7399                 card = NULL,
7400                 mailing_address = NULL,
7401                 billing_address = NULL
7402         WHERE id = src_usr;
7403
7404         -- acq.*
7405         UPDATE acq.fund_allocation SET allocator = dest_usr WHERE allocator = src_usr;
7406         UPDATE acq.lineitem SET creator = dest_usr WHERE creator = src_usr;
7407         UPDATE acq.lineitem SET editor = dest_usr WHERE editor = src_usr;
7408         UPDATE acq.lineitem SET selector = dest_usr WHERE selector = src_usr;
7409         UPDATE acq.lineitem_note SET creator = dest_usr WHERE creator = src_usr;
7410         UPDATE acq.lineitem_note SET editor = dest_usr WHERE editor = src_usr;
7411         DELETE FROM acq.lineitem_usr_attr_definition WHERE usr = src_usr;
7412
7413         -- Update with a rename to avoid collisions
7414         FOR renamable_row in
7415                 SELECT id, name
7416                 FROM   acq.picklist
7417                 WHERE  owner = src_usr
7418         LOOP
7419                 suffix := ' (' || src_usr || ')';
7420                 LOOP
7421                         BEGIN
7422                                 UPDATE  acq.picklist
7423                                 SET     owner = dest_usr, name = name || suffix
7424                                 WHERE   id = renamable_row.id;
7425                         EXCEPTION WHEN unique_violation THEN
7426                                 suffix := suffix || ' ';
7427                                 CONTINUE;
7428                         END;
7429                         EXIT;
7430                 END LOOP;
7431         END LOOP;
7432
7433         UPDATE acq.picklist SET creator = dest_usr WHERE creator = src_usr;
7434         UPDATE acq.picklist SET editor = dest_usr WHERE editor = src_usr;
7435         UPDATE acq.po_note SET creator = dest_usr WHERE creator = src_usr;
7436         UPDATE acq.po_note SET editor = dest_usr WHERE editor = src_usr;
7437         UPDATE acq.purchase_order SET owner = dest_usr WHERE owner = src_usr;
7438         UPDATE acq.purchase_order SET creator = dest_usr WHERE creator = src_usr;
7439         UPDATE acq.purchase_order SET editor = dest_usr WHERE editor = src_usr;
7440         UPDATE acq.claim_event SET creator = dest_usr WHERE creator = src_usr;
7441
7442         -- action.*
7443         DELETE FROM action.circulation WHERE usr = src_usr;
7444         UPDATE action.circulation SET circ_staff = dest_usr WHERE circ_staff = src_usr;
7445         UPDATE action.circulation SET checkin_staff = dest_usr WHERE checkin_staff = src_usr;
7446         UPDATE action.hold_notification SET notify_staff = dest_usr WHERE notify_staff = src_usr;
7447         UPDATE action.hold_request SET fulfillment_staff = dest_usr WHERE fulfillment_staff = src_usr;
7448         UPDATE action.hold_request SET requestor = dest_usr WHERE requestor = src_usr;
7449         DELETE FROM action.hold_request WHERE usr = src_usr;
7450         UPDATE action.in_house_use SET staff = dest_usr WHERE staff = src_usr;
7451         UPDATE action.non_cat_in_house_use SET staff = dest_usr WHERE staff = src_usr;
7452         DELETE FROM action.non_cataloged_circulation WHERE patron = src_usr;
7453         UPDATE action.non_cataloged_circulation SET staff = dest_usr WHERE staff = src_usr;
7454         DELETE FROM action.survey_response WHERE usr = src_usr;
7455         UPDATE action.fieldset SET owner = dest_usr WHERE owner = src_usr;
7456
7457         -- actor.*
7458         DELETE FROM actor.card WHERE usr = src_usr;
7459         DELETE FROM actor.stat_cat_entry_usr_map WHERE target_usr = src_usr;
7460
7461         -- The following update is intended to avoid transient violations of a foreign
7462         -- key constraint, whereby actor.usr_address references itself.  It may not be
7463         -- necessary, but it does no harm.
7464         UPDATE actor.usr_address SET replaces = NULL
7465                 WHERE usr = src_usr AND replaces IS NOT NULL;
7466         DELETE FROM actor.usr_address WHERE usr = src_usr;
7467         DELETE FROM actor.usr_note WHERE usr = src_usr;
7468         UPDATE actor.usr_note SET creator = dest_usr WHERE creator = src_usr;
7469         DELETE FROM actor.usr_org_unit_opt_in WHERE usr = src_usr;
7470         UPDATE actor.usr_org_unit_opt_in SET staff = dest_usr WHERE staff = src_usr;
7471         DELETE FROM actor.usr_setting WHERE usr = src_usr;
7472         DELETE FROM actor.usr_standing_penalty WHERE usr = src_usr;
7473         UPDATE actor.usr_standing_penalty SET staff = dest_usr WHERE staff = src_usr;
7474
7475         -- asset.*
7476         UPDATE asset.call_number SET creator = dest_usr WHERE creator = src_usr;
7477         UPDATE asset.call_number SET editor = dest_usr WHERE editor = src_usr;
7478         UPDATE asset.call_number_note SET creator = dest_usr WHERE creator = src_usr;
7479         UPDATE asset.copy SET creator = dest_usr WHERE creator = src_usr;
7480         UPDATE asset.copy SET editor = dest_usr WHERE editor = src_usr;
7481         UPDATE asset.copy_note SET creator = dest_usr WHERE creator = src_usr;
7482
7483         -- auditor.*
7484         DELETE FROM auditor.actor_usr_address_history WHERE id = src_usr;
7485         DELETE FROM auditor.actor_usr_history WHERE id = src_usr;
7486         UPDATE auditor.asset_call_number_history SET creator = dest_usr WHERE creator = src_usr;
7487         UPDATE auditor.asset_call_number_history SET editor  = dest_usr WHERE editor  = src_usr;
7488         UPDATE auditor.asset_copy_history SET creator = dest_usr WHERE creator = src_usr;
7489         UPDATE auditor.asset_copy_history SET editor  = dest_usr WHERE editor  = src_usr;
7490         UPDATE auditor.biblio_record_entry_history SET creator = dest_usr WHERE creator = src_usr;
7491         UPDATE auditor.biblio_record_entry_history SET editor  = dest_usr WHERE editor  = src_usr;
7492
7493         -- biblio.*
7494         UPDATE biblio.record_entry SET creator = dest_usr WHERE creator = src_usr;
7495         UPDATE biblio.record_entry SET editor = dest_usr WHERE editor = src_usr;
7496         UPDATE biblio.record_note SET creator = dest_usr WHERE creator = src_usr;
7497         UPDATE biblio.record_note SET editor = dest_usr WHERE editor = src_usr;
7498
7499         -- container.*
7500         -- Update buckets with a rename to avoid collisions
7501         FOR renamable_row in
7502                 SELECT id, name
7503                 FROM   container.biblio_record_entry_bucket
7504                 WHERE  owner = src_usr
7505         LOOP
7506                 suffix := ' (' || src_usr || ')';
7507                 LOOP
7508                         BEGIN
7509                                 UPDATE  container.biblio_record_entry_bucket
7510                                 SET     owner = dest_usr, name = name || suffix
7511                                 WHERE   id = renamable_row.id;
7512                         EXCEPTION WHEN unique_violation THEN
7513                                 suffix := suffix || ' ';
7514                                 CONTINUE;
7515                         END;
7516                         EXIT;
7517                 END LOOP;
7518         END LOOP;
7519
7520         FOR renamable_row in
7521                 SELECT id, name
7522                 FROM   container.call_number_bucket
7523                 WHERE  owner = src_usr
7524         LOOP
7525                 suffix := ' (' || src_usr || ')';
7526                 LOOP
7527                         BEGIN
7528                                 UPDATE  container.call_number_bucket
7529                                 SET     owner = dest_usr, name = name || suffix
7530                                 WHERE   id = renamable_row.id;
7531                         EXCEPTION WHEN unique_violation THEN
7532                                 suffix := suffix || ' ';
7533                                 CONTINUE;
7534                         END;
7535                         EXIT;
7536                 END LOOP;
7537         END LOOP;
7538
7539         FOR renamable_row in
7540                 SELECT id, name
7541                 FROM   container.copy_bucket
7542                 WHERE  owner = src_usr
7543         LOOP
7544                 suffix := ' (' || src_usr || ')';
7545                 LOOP
7546                         BEGIN
7547                                 UPDATE  container.copy_bucket
7548                                 SET     owner = dest_usr, name = name || suffix
7549                                 WHERE   id = renamable_row.id;
7550                         EXCEPTION WHEN unique_violation THEN
7551                                 suffix := suffix || ' ';
7552                                 CONTINUE;
7553                         END;
7554                         EXIT;
7555                 END LOOP;
7556         END LOOP;
7557
7558         FOR renamable_row in
7559                 SELECT id, name
7560                 FROM   container.user_bucket
7561                 WHERE  owner = src_usr
7562         LOOP
7563                 suffix := ' (' || src_usr || ')';
7564                 LOOP
7565                         BEGIN
7566                                 UPDATE  container.user_bucket
7567                                 SET     owner = dest_usr, name = name || suffix
7568                                 WHERE   id = renamable_row.id;
7569                         EXCEPTION WHEN unique_violation THEN
7570                                 suffix := suffix || ' ';
7571                                 CONTINUE;
7572                         END;
7573                         EXIT;
7574                 END LOOP;
7575         END LOOP;
7576
7577         DELETE FROM container.user_bucket_item WHERE target_user = src_usr;
7578
7579         -- money.*
7580         DELETE FROM money.billable_xact WHERE usr = src_usr;
7581         DELETE FROM money.collections_tracker WHERE usr = src_usr;
7582         UPDATE money.collections_tracker SET collector = dest_usr WHERE collector = src_usr;
7583
7584         -- permission.*
7585         DELETE FROM permission.usr_grp_map WHERE usr = src_usr;
7586         DELETE FROM permission.usr_object_perm_map WHERE usr = src_usr;
7587         DELETE FROM permission.usr_perm_map WHERE usr = src_usr;
7588         DELETE FROM permission.usr_work_ou_map WHERE usr = src_usr;
7589
7590         -- reporter.*
7591         -- Update with a rename to avoid collisions
7592         BEGIN
7593                 FOR renamable_row in
7594                         SELECT id, name
7595                         FROM   reporter.output_folder
7596                         WHERE  owner = src_usr
7597                 LOOP
7598                         suffix := ' (' || src_usr || ')';
7599                         LOOP
7600                                 BEGIN
7601                                         UPDATE  reporter.output_folder
7602                                         SET     owner = dest_usr, name = name || suffix
7603                                         WHERE   id = renamable_row.id;
7604                                 EXCEPTION WHEN unique_violation THEN
7605                                         suffix := suffix || ' ';
7606                                         CONTINUE;
7607                                 END;
7608                                 EXIT;
7609                         END LOOP;
7610                 END LOOP;
7611         EXCEPTION WHEN undefined_table THEN
7612                 -- do nothing
7613         END;
7614
7615         BEGIN
7616                 UPDATE reporter.report SET owner = dest_usr WHERE owner = src_usr;
7617         EXCEPTION WHEN undefined_table THEN
7618                 -- do nothing
7619         END;
7620
7621         -- Update with a rename to avoid collisions
7622         BEGIN
7623                 FOR renamable_row in
7624                         SELECT id, name
7625                         FROM   reporter.report_folder
7626                         WHERE  owner = src_usr
7627                 LOOP
7628                         suffix := ' (' || src_usr || ')';
7629                         LOOP
7630                                 BEGIN
7631                                         UPDATE  reporter.report_folder
7632                                         SET     owner = dest_usr, name = name || suffix
7633                                         WHERE   id = renamable_row.id;
7634                                 EXCEPTION WHEN unique_violation THEN
7635                                         suffix := suffix || ' ';
7636                                         CONTINUE;
7637                                 END;
7638                                 EXIT;
7639                         END LOOP;
7640                 END LOOP;
7641         EXCEPTION WHEN undefined_table THEN
7642                 -- do nothing
7643         END;
7644
7645         BEGIN
7646                 UPDATE reporter.schedule SET runner = dest_usr WHERE runner = src_usr;
7647         EXCEPTION WHEN undefined_table THEN
7648                 -- do nothing
7649         END;
7650
7651         BEGIN
7652                 UPDATE reporter.template SET owner = dest_usr WHERE owner = src_usr;
7653         EXCEPTION WHEN undefined_table THEN
7654                 -- do nothing
7655         END;
7656
7657         -- Update with a rename to avoid collisions
7658         BEGIN
7659                 FOR renamable_row in
7660                         SELECT id, name
7661                         FROM   reporter.template_folder
7662                         WHERE  owner = src_usr
7663                 LOOP
7664                         suffix := ' (' || src_usr || ')';
7665                         LOOP
7666                                 BEGIN
7667                                         UPDATE  reporter.template_folder
7668                                         SET     owner = dest_usr, name = name || suffix
7669                                         WHERE   id = renamable_row.id;
7670                                 EXCEPTION WHEN unique_violation THEN
7671                                         suffix := suffix || ' ';
7672                                         CONTINUE;
7673                                 END;
7674                                 EXIT;
7675                         END LOOP;
7676                 END LOOP;
7677         EXCEPTION WHEN undefined_table THEN
7678         -- do nothing
7679         END;
7680
7681         -- vandelay.*
7682         -- Update with a rename to avoid collisions
7683         FOR renamable_row in
7684                 SELECT id, name
7685                 FROM   vandelay.queue
7686                 WHERE  owner = src_usr
7687         LOOP
7688                 suffix := ' (' || src_usr || ')';
7689                 LOOP
7690                         BEGIN
7691                                 UPDATE  vandelay.queue
7692                                 SET     owner = dest_usr, name = name || suffix
7693                                 WHERE   id = renamable_row.id;
7694                         EXCEPTION WHEN unique_violation THEN
7695                                 suffix := suffix || ' ';
7696                                 CONTINUE;
7697                         END;
7698                         EXIT;
7699                 END LOOP;
7700         END LOOP;
7701
7702 END;
7703 $$ LANGUAGE plpgsql;
7704
7705 -- INSERT INTO config.copy_status (id,name) VALUES (15,oils_i18n_gettext(15, 'On reservation shelf', 'ccs', 'name'));
7706
7707 ALTER TABLE acq.fund
7708 ADD COLUMN rollover BOOL NOT NULL DEFAULT FALSE;
7709
7710 ALTER TABLE acq.fund
7711         ADD COLUMN propagate BOOLEAN NOT NULL DEFAULT TRUE;
7712
7713 -- A fund can't roll over if it doesn't propagate from one year to the next
7714
7715 ALTER TABLE acq.fund
7716         ADD CONSTRAINT acq_fund_rollover_implies_propagate CHECK
7717         ( propagate OR NOT rollover );
7718
7719 ALTER TABLE acq.fund
7720         ADD COLUMN active BOOL NOT NULL DEFAULT TRUE;
7721
7722 ALTER TABLE acq.fund
7723     ADD COLUMN balance_warning_percent INT
7724     CONSTRAINT balance_warning_percent_limit
7725         CHECK( balance_warning_percent <= 100 );
7726
7727 ALTER TABLE acq.fund
7728     ADD COLUMN balance_stop_percent INT
7729     CONSTRAINT balance_stop_percent_limit
7730         CHECK( balance_stop_percent <= 100 );
7731
7732 CREATE VIEW acq.ordered_funding_source_credit AS
7733         SELECT
7734                 CASE WHEN deadline_date IS NULL THEN
7735                         2
7736                 ELSE
7737                         1
7738                 END AS sort_priority,
7739                 CASE WHEN deadline_date IS NULL THEN
7740                         effective_date
7741                 ELSE
7742                         deadline_date
7743                 END AS sort_date,
7744                 id,
7745                 funding_source,
7746                 amount,
7747                 note
7748         FROM
7749                 acq.funding_source_credit;
7750
7751 COMMENT ON VIEW acq.ordered_funding_source_credit IS $$
7752 /*
7753  * Copyright (C) 2009  Georgia Public Library Service
7754  * Scott McKellar <scott@gmail.com>
7755  *
7756  * The acq.ordered_funding_source_credit view is a prioritized
7757  * ordering of funding source credits.  When ordered by the first
7758  * three columns, this view defines the order in which the various
7759  * credits are to be tapped for spending, subject to the allocations
7760  * in the acq.fund_allocation table.
7761  *
7762  * The first column reflects the principle that we should spend
7763  * money with deadlines before spending money without deadlines.
7764  *
7765  * The second column reflects the principle that we should spend the
7766  * oldest money first.  For money with deadlines, that means that we
7767  * spend first from the credit with the earliest deadline.  For
7768  * money without deadlines, we spend first from the credit with the
7769  * earliest effective date.  
7770  *
7771  * The third column is a tie breaker to ensure a consistent
7772  * ordering.
7773  *
7774  * ****
7775  *
7776  * This program is free software; you can redistribute it and/or
7777  * modify it under the terms of the GNU General Public License
7778  * as published by the Free Software Foundation; either version 2
7779  * of the License, or (at your option) any later version.
7780  *
7781  * This program is distributed in the hope that it will be useful,
7782  * but WITHOUT ANY WARRANTY; without even the implied warranty of
7783  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
7784  * GNU General Public License for more details.
7785  */
7786 $$;
7787
7788 CREATE OR REPLACE VIEW money.billable_xact_summary_location_view AS
7789     SELECT  m.*, COALESCE(c.circ_lib, g.billing_location, r.pickup_lib) AS billing_location
7790       FROM  money.materialized_billable_xact_summary m
7791             LEFT JOIN action.circulation c ON (c.id = m.id)
7792             LEFT JOIN money.grocery g ON (g.id = m.id)
7793             LEFT JOIN booking.reservation r ON (r.id = m.id);
7794
7795 CREATE TABLE config.marc21_rec_type_map (
7796     code        TEXT    PRIMARY KEY,
7797     type_val    TEXT    NOT NULL,
7798     blvl_val    TEXT    NOT NULL
7799 );
7800
7801 CREATE TABLE config.marc21_ff_pos_map (
7802     id          SERIAL  PRIMARY KEY,
7803     fixed_field TEXT    NOT NULL,
7804     tag         TEXT    NOT NULL,
7805     rec_type    TEXT    NOT NULL,
7806     start_pos   INT     NOT NULL,
7807     length      INT     NOT NULL,
7808     default_val TEXT    NOT NULL DEFAULT ' '
7809 );
7810
7811 CREATE TABLE config.marc21_physical_characteristic_type_map (
7812     ptype_key   TEXT    PRIMARY KEY,
7813     label       TEXT    NOT NULL -- I18N
7814 );
7815
7816 CREATE TABLE config.marc21_physical_characteristic_subfield_map (
7817     id          SERIAL  PRIMARY KEY,
7818     ptype_key   TEXT    NOT NULL REFERENCES config.marc21_physical_characteristic_type_map (ptype_key) ON DELETE CASCADE ON UPDATE CASCADE,
7819     subfield    TEXT    NOT NULL,
7820     start_pos   INT     NOT NULL,
7821     length      INT     NOT NULL,
7822     label       TEXT    NOT NULL -- I18N
7823 );
7824
7825 CREATE TABLE config.marc21_physical_characteristic_value_map (
7826     id              SERIAL  PRIMARY KEY,
7827     value           TEXT    NOT NULL,
7828     ptype_subfield  INT     NOT NULL REFERENCES config.marc21_physical_characteristic_subfield_map (id),
7829     label           TEXT    NOT NULL -- I18N
7830 );
7831
7832 ----------------------------------
7833 -- MARC21 record structure data --
7834 ----------------------------------
7835
7836 -- Record type map
7837 INSERT INTO config.marc21_rec_type_map (code, type_val, blvl_val) VALUES ('BKS','at','acdm');
7838 INSERT INTO config.marc21_rec_type_map (code, type_val, blvl_val) VALUES ('SER','a','bsi');
7839 INSERT INTO config.marc21_rec_type_map (code, type_val, blvl_val) VALUES ('VIS','gkro','abcdmsi');
7840 INSERT INTO config.marc21_rec_type_map (code, type_val, blvl_val) VALUES ('MIX','p','cdi');
7841 INSERT INTO config.marc21_rec_type_map (code, type_val, blvl_val) VALUES ('MAP','ef','abcdmsi');
7842 INSERT INTO config.marc21_rec_type_map (code, type_val, blvl_val) VALUES ('SCO','cd','abcdmsi');
7843 INSERT INTO config.marc21_rec_type_map (code, type_val, blvl_val) VALUES ('REC','ij','abcdmsi');
7844 INSERT INTO config.marc21_rec_type_map (code, type_val, blvl_val) VALUES ('COM','m','abcdmsi');
7845
7846 ------ Physical Characteristics
7847
7848 -- Map
7849 INSERT INTO config.marc21_physical_characteristic_type_map (ptype_key, label) VALUES ('a','Map');
7850 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('a','b','1','1','SMD');
7851 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('d',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Atlas');
7852 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('g',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Diagram');
7853 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('j',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Map');
7854 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('k',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Profile');
7855 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('q',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Model');
7856 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');
7857 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('s',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Section');
7858 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unspecified');
7859 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('y',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'View');
7860 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
7861 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('a','d','3','1','Color');
7862 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');
7863 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('c',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Multicolored');
7864 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('a','e','4','1','Physical medium');
7865 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('a',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Paper');
7866 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('b',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Wood');
7867 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('c',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Stone');
7868 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('d',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Metal');
7869 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('e',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Synthetics');
7870 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('f',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Skins');
7871 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('g',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Textile');
7872 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('p',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Plaster');
7873 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');
7874 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');
7875 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');
7876 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');
7877 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
7878 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');
7879 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
7880 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('a','f','5','1','Type of reproduction');
7881 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('f',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Facsimile');
7882 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');
7883 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
7884 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
7885 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('a','g','6','1','Production/reproduction details');
7886 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');
7887 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('b',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Photocopy');
7888 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');
7889 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('d',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Film');
7890 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
7891 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
7892 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('a','h','7','1','Positive/negative');
7893 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('a',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Positive');
7894 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('b',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Negative');
7895 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('m',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Mixed');
7896 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');
7897
7898 -- Electronic Resource
7899 INSERT INTO config.marc21_physical_characteristic_type_map (ptype_key, label) VALUES ('c','Electronic Resource');
7900 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('c','b','1','1','SMD');
7901 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');
7902 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');
7903 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');
7904 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');
7905 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');
7906 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');
7907 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');
7908 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');
7909 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('r',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Remote');
7910 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unspecified');
7911 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
7912 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('c','d','3','1','Color');
7913 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');
7914 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');
7915 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('c',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Multicolored');
7916 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');
7917 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('m',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Mixed');
7918 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');
7919 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
7920 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
7921 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('c','e','4','1','Dimensions');
7922 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.');
7923 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.');
7924 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.');
7925 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.');
7926 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.');
7927 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');
7928 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.');
7929 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
7930 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.');
7931 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
7932 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('c','f','5','1','Sound');
7933 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)');
7934 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('a',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Sound');
7935 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
7936 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('c','g','6','3','Image bit depth');
7937 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('---',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
7938 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('mmm',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Multiple');
7939 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');
7940 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('c','h','9','1','File formats');
7941 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');
7942 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');
7943 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
7944 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('c','i','10','1','Quality assurance target(s)');
7945 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('a',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Absent');
7946 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');
7947 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('p',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Present');
7948 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
7949 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('c','j','11','1','Antecedent/Source');
7950 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');
7951 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');
7952 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');
7953 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)');
7954 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('m',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Mixed');
7955 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');
7956 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
7957 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('c','k','12','1','Level of compression');
7958 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('a',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Uncompressed');
7959 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('b',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Lossless');
7960 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('d',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Lossy');
7961 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('m',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Mixed');
7962 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
7963 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('c','l','13','1','Reformatting quality');
7964 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('a',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Access');
7965 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');
7966 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('p',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Preservation');
7967 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('r',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Replacement');
7968 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
7969
7970 -- Globe
7971 INSERT INTO config.marc21_physical_characteristic_type_map (ptype_key, label) VALUES ('d','Globe');
7972 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('d','b','1','1','SMD');
7973 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');
7974 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');
7975 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');
7976 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');
7977 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unspecified');
7978 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
7979 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('d','d','3','1','Color');
7980 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');
7981 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('c',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Multicolored');
7982 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('d','e','4','1','Physical medium');
7983 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('a',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Paper');
7984 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('b',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Wood');
7985 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('c',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Stone');
7986 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('d',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Metal');
7987 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('e',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Synthetics');
7988 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('f',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Skins');
7989 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('g',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Textile');
7990 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('p',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Plaster');
7991 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
7992 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
7993 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('d','f','5','1','Type of reproduction');
7994 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('f',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Facsimile');
7995 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');
7996 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
7997 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
7998
7999 -- Tactile Material
8000 INSERT INTO config.marc21_physical_characteristic_type_map (ptype_key, label) VALUES ('f','Tactile Material');
8001 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('f','b','1','1','SMD');
8002 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('a',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Moon');
8003 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('b',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Braille');
8004 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('c',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Combination');
8005 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');
8006 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unspecified');
8007 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
8008 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('f','d','3','2','Class of braille writing');
8009 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');
8010 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');
8011 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');
8012 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');
8013 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');
8014 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');
8015 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');
8016 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
8017 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
8018 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('f','e','4','1','Level of contraction');
8019 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('a',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Uncontracted');
8020 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('b',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Contracted');
8021 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('m',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Combination');
8022 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');
8023 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
8024 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
8025 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('f','f','6','3','Braille music format');
8026 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');
8027 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');
8028 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');
8029 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('d',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Paragraph');
8030 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');
8031 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');
8032 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');
8033 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');
8034 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');
8035 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');
8036 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('k',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Outline');
8037 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');
8038 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');
8039 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
8040 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
8041 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('f','g','9','1','Special physical characteristics');
8042 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');
8043 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');
8044 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');
8045 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
8046 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
8047
8048 -- Projected Graphic
8049 INSERT INTO config.marc21_physical_characteristic_type_map (ptype_key, label) VALUES ('g','Projected Graphic');
8050 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('g','b','1','1','SMD');
8051 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');
8052 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('d',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Filmstrip');
8053 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');
8054 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');
8055 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('s',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Slide');
8056 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('t',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Transparency');
8057 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
8058 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('g','d','3','1','Color');
8059 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');
8060 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('c',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Multicolored');
8061 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');
8062 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('m',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Mixed');
8063 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');
8064 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
8065 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
8066 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('g','e','4','1','Base of emulsion');
8067 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('d',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Glass');
8068 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('e',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Synthetics');
8069 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');
8070 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');
8071 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');
8072 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('o',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Paper');
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 ('g','f','5','1','Sound on medium or separate');
8076 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');
8077 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');
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_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('g','g','6','1','Medium for sound');
8080 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');
8081 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');
8082 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');
8083 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');
8084 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');
8085 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');
8086 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');
8087 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('h',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Videotape');
8088 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('i',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Videodisc');
8089 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
8090 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
8091 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('g','h','7','1','Dimensions');
8092 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.');
8093 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.');
8094 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.');
8095 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.');
8096 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.');
8097 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.');
8098 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.');
8099 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.)');
8100 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.)');
8101 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.)');
8102 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.)');
8103 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
8104 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.)');
8105 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.)');
8106 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.)');
8107 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.)');
8108 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
8109 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('g','i','8','1','Secondary support material');
8110 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('c',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Cardboard');
8111 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('d',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Glass');
8112 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('e',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Synthetics');
8113 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('h',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'metal');
8114 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');
8115 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');
8116 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');
8117 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
8118 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
8119
8120 -- Microform
8121 INSERT INTO config.marc21_physical_characteristic_type_map (ptype_key, label) VALUES ('h','Microform');
8122 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('h','b','1','1','SMD');
8123 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');
8124 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');
8125 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');
8126 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');
8127 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('e',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Microfiche');
8128 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');
8129 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('g',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Microopaque');
8130 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unspecified');
8131 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
8132 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('h','d','3','1','Positive/negative');
8133 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('a',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Positive');
8134 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('b',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Negative');
8135 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('m',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Mixed');
8136 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
8137 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('h','e','4','1','Dimensions');
8138 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.');
8139 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.');
8140 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.');
8141 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('g',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'70mm.');
8142 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.');
8143 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.)');
8144 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.)');
8145 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.)');
8146 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.)');
8147 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
8148 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
8149 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');
8150 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)');
8151 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)');
8152 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)');
8153 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)');
8154 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-)');
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 ('v',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Reduction ratio varies');
8157 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('h','g','9','1','Color');
8158 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');
8159 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('c',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Multicolored');
8160 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('m',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Mixed');
8161 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
8162 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
8163 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('h','h','10','1','Emulsion on film');
8164 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');
8165 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('b',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Diazo');
8166 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('c',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Vesicular');
8167 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('m',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Mixed');
8168 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');
8169 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
8170 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
8171 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('h','i','11','1','Quality assurance target(s)');
8172 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');
8173 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');
8174 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');
8175 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');
8176 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
8177 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('h','j','12','1','Base of film');
8178 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');
8179 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');
8180 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');
8181 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');
8182 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');
8183 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');
8184 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');
8185 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');
8186 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');
8187 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
8188 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
8189
8190 -- Non-projected Graphic
8191 INSERT INTO config.marc21_physical_characteristic_type_map (ptype_key, label) VALUES ('k','Non-projected Graphic');
8192 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('k','b','1','1','SMD');
8193 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('c',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Collage');
8194 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('d',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Drawing');
8195 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('e',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Painting');
8196 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');
8197 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('g',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Photonegative');
8198 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('h',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Photoprint');
8199 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('i',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Picture');
8200 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('j',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Print');
8201 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');
8202 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('n',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Chart');
8203 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');
8204 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unspecified');
8205 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
8206 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('k','d','3','1','Color');
8207 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');
8208 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');
8209 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('c',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Multicolored');
8210 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');
8211 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('m',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Mixed');
8212 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
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 ('k','e','4','1','Primary support material');
8215 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('a',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Canvas');
8216 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');
8217 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');
8218 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('d',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Glass');
8219 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('e',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Synthetics');
8220 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('f',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Skins');
8221 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('g',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Textile');
8222 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('h',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Metal');
8223 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');
8224 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('o',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Paper');
8225 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('p',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Plaster');
8226 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('q',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Hardboard');
8227 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('r',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Porcelain');
8228 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('s',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Stone');
8229 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('t',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Wood');
8230 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
8231 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
8232 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('k','f','5','1','Secondary support material');
8233 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('a',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Canvas');
8234 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');
8235 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');
8236 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('d',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Glass');
8237 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('e',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Synthetics');
8238 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('f',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Skins');
8239 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('g',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Textile');
8240 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('h',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Metal');
8241 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');
8242 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('o',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Paper');
8243 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('p',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Plaster');
8244 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('q',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Hardboard');
8245 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('r',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Porcelain');
8246 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('s',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Stone');
8247 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('t',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Wood');
8248 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
8249 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
8250
8251 -- Motion Picture
8252 INSERT INTO config.marc21_physical_characteristic_type_map (ptype_key, label) VALUES ('m','Motion Picture');
8253 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('m','b','1','1','SMD');
8254 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');
8255 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');
8256 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');
8257 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unspecified');
8258 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
8259 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('m','d','3','1','Color');
8260 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');
8261 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('c',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Multicolored');
8262 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');
8263 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('m',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Mixed');
8264 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
8265 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
8266 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('m','e','4','1','Motion picture presentation format');
8267 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');
8268 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)');
8269 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('c',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'3D');
8270 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)');
8271 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');
8272 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');
8273 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
8274 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
8275 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');
8276 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');
8277 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');
8278 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
8279 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('m','g','6','1','Medium for sound');
8280 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');
8281 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');
8282 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');
8283 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');
8284 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');
8285 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');
8286 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');
8287 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('h',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Videotape');
8288 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('i',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Videodisc');
8289 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
8290 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
8291 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('m','h','7','1','Dimensions');
8292 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.');
8293 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.');
8294 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.');
8295 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.');
8296 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.');
8297 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.');
8298 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.');
8299 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
8300 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
8301 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('m','i','8','1','Configuration of playback channels');
8302 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('k',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Mixed');
8303 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('m',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Monaural');
8304 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');
8305 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');
8306 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('s',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Stereophonic');
8307 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
8308 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
8309 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('m','j','9','1','Production elements');
8310 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');
8311 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('b',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Trims');
8312 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('c',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Outtakes');
8313 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('d',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Rushes');
8314 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');
8315 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');
8316 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');
8317 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');
8318 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
8319
8320 -- Remote-sensing Image
8321 INSERT INTO config.marc21_physical_characteristic_type_map (ptype_key, label) VALUES ('r','Remote-sensing Image');
8322 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('r','b','1','1','SMD');
8323 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unspecified');
8324 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('r','d','3','1','Altitude of sensor');
8325 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('a',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Surface');
8326 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('b',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Airborne');
8327 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('c',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Spaceborne');
8328 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');
8329 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
8330 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
8331 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('r','e','4','1','Attitude of sensor');
8332 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');
8333 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');
8334 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('c',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Vertical');
8335 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');
8336 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
8337 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('r','f','5','1','Cloud cover');
8338 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%');
8339 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%');
8340 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%');
8341 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%');
8342 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%');
8343 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%');
8344 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%');
8345 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%');
8346 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%');
8347 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%');
8348 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');
8349 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
8350 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('r','g','6','1','Platform construction type');
8351 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('a',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Balloon');
8352 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');
8353 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');
8354 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');
8355 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');
8356 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');
8357 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');
8358 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');
8359 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');
8360 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');
8361 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
8362 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
8363 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('r','h','7','1','Platform use category');
8364 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('a',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Meteorological');
8365 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');
8366 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');
8367 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');
8368 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');
8369 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
8370 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
8371 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('r','i','8','1','Sensor type');
8372 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('a',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Active');
8373 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('b',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Passive');
8374 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
8375 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
8376 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('r','j','9','2','Data type');
8377 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');
8378 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');
8379 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');
8380 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');
8381 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');
8382 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)');
8383 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');
8384 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('dv',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Combinations');
8385 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');
8386 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)');
8387 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)');
8388 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)');
8389 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');
8390 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');
8391 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');
8392 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');
8393 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');
8394 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');
8395 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');
8396 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');
8397 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');
8398 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');
8399 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');
8400 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');
8401 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');
8402 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');
8403 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');
8404 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');
8405 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');
8406 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');
8407 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');
8408 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');
8409 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');
8410 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)');
8411 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');
8412 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('rc',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Bouger');
8413 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('rd',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Isostatic');
8414 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');
8415 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');
8416 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('uu',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
8417 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('zz',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
8418
8419 -- Sound Recording
8420 INSERT INTO config.marc21_physical_characteristic_type_map (ptype_key, label) VALUES ('s','Sound Recording');
8421 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('s','b','1','1','SMD');
8422 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');
8423 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('e',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Cylinder');
8424 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');
8425 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');
8426 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('q',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Roll');
8427 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');
8428 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');
8429 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unspecified');
8430 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');
8431 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
8432 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('s','d','3','1','Speed');
8433 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');
8434 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');
8435 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');
8436 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');
8437 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');
8438 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');
8439 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');
8440 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');
8441 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');
8442 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');
8443 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');
8444 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');
8445 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');
8446 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');
8447 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
8448 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
8449 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('s','e','4','1','Configuration of playback channels');
8450 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('m',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Monaural');
8451 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('q',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Quadraphonic');
8452 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('s',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Stereophonic');
8453 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
8454 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
8455 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('s','f','5','1','Groove width or pitch');
8456 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');
8457 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');
8458 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');
8459 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
8460 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
8461 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('s','g','6','1','Dimensions');
8462 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.');
8463 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.');
8464 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.');
8465 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.');
8466 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.');
8467 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.');
8468 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.)');
8469 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.');
8470 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');
8471 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.');
8472 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.');
8473 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
8474 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
8475 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('s','h','7','1','Tape width');
8476 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.');
8477 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.');
8478 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');
8479 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.');
8480 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.');
8481 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
8482 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
8483 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('s','i','8','1','Tape configuration ');
8484 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');
8485 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');
8486 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');
8487 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');
8488 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');
8489 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');
8490 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');
8491 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
8492 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
8493 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('s','m','12','1','Special playback');
8494 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');
8495 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');
8496 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');
8497 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');
8498 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');
8499 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');
8500 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');
8501 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');
8502 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');
8503 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
8504 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
8505 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('s','n','13','1','Capture and storage');
8506 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');
8507 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');
8508 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');
8509 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');
8510 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
8511 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
8512
8513 -- Videorecording
8514 INSERT INTO config.marc21_physical_characteristic_type_map (ptype_key, label) VALUES ('v','Videorecording');
8515 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('v','b','1','1','SMD');
8516 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('c',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Videocartridge');
8517 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('d',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Videodisc');
8518 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('f',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Videocassette');
8519 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('r',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Videoreel');
8520 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unspecified');
8521 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
8522 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('v','d','3','1','Color');
8523 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');
8524 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('c',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Multicolored');
8525 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('m',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Mixed');
8526 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');
8527 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
8528 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
8529 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('v','e','4','1','Videorecording format');
8530 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('a',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Beta');
8531 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('b',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'VHS');
8532 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');
8533 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('d',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'EIAJ');
8534 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');
8535 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('f',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Quadruplex');
8536 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('g',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Laserdisc');
8537 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('h',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'CED');
8538 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('i',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Betacam');
8539 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');
8540 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');
8541 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');
8542 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');
8543 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.');
8544 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.');
8545 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
8546 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('v',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'DVD');
8547 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
8548 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');
8549 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');
8550 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');
8551 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
8552 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('v','g','6','1','Medium for sound');
8553 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');
8554 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');
8555 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');
8556 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');
8557 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');
8558 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');
8559 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');
8560 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('h',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Videotape');
8561 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('i',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Videodisc');
8562 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
8563 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
8564 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('v','h','7','1','Dimensions');
8565 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.');
8566 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.');
8567 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.');
8568 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.');
8569 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.');
8570 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.');
8571 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
8572 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
8573 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('v','i','8','1','Configuration of playback channel');
8574 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('k',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Mixed');
8575 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('m',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Monaural');
8576 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');
8577 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');
8578 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('s',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Stereophonic');
8579 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
8580 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
8581
8582 -- Fixed Field position data -- 0-based!
8583 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Alph', '006', 'SER', 16, 1, ' ');
8584 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Alph', '008', 'SER', 33, 1, ' ');
8585 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Audn', '006', 'BKS', 5, 1, ' ');
8586 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Audn', '006', 'COM', 5, 1, ' ');
8587 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Audn', '006', 'REC', 5, 1, ' ');
8588 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Audn', '006', 'SCO', 5, 1, ' ');
8589 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Audn', '006', 'SER', 5, 1, ' ');
8590 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Audn', '006', 'VIS', 5, 1, ' ');
8591 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Audn', '008', 'BKS', 22, 1, ' ');
8592 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Audn', '008', 'COM', 22, 1, ' ');
8593 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Audn', '008', 'REC', 22, 1, ' ');
8594 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Audn', '008', 'SCO', 22, 1, ' ');
8595 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Audn', '008', 'SER', 22, 1, ' ');
8596 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Audn', '008', 'VIS', 22, 1, ' ');
8597 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('BLvl', 'ldr', 'BKS', 7, 1, 'm');
8598 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('BLvl', 'ldr', 'COM', 7, 1, 'm');
8599 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('BLvl', 'ldr', 'MAP', 7, 1, 'm');
8600 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('BLvl', 'ldr', 'MIX', 7, 1, 'c');
8601 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('BLvl', 'ldr', 'REC', 7, 1, 'm');
8602 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('BLvl', 'ldr', 'SCO', 7, 1, 'm');
8603 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('BLvl', 'ldr', 'SER', 7, 1, 's');
8604 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('BLvl', 'ldr', 'VIS', 7, 1, 'm');
8605 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Biog', '006', 'BKS', 17, 1, ' ');
8606 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Biog', '008', 'BKS', 34, 1, ' ');
8607 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Conf', '006', 'BKS', 7, 4, ' ');
8608 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Conf', '006', 'SER', 8, 3, ' ');
8609 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Conf', '008', 'BKS', 24, 4, ' ');
8610 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Conf', '008', 'SER', 25, 3, ' ');
8611 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Ctrl', 'ldr', 'BKS', 8, 1, ' ');
8612 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Ctrl', 'ldr', 'COM', 8, 1, ' ');
8613 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Ctrl', 'ldr', 'MAP', 8, 1, ' ');
8614 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Ctrl', 'ldr', 'MIX', 8, 1, ' ');
8615 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Ctrl', 'ldr', 'REC', 8, 1, ' ');
8616 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Ctrl', 'ldr', 'SCO', 8, 1, ' ');
8617 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Ctrl', 'ldr', 'SER', 8, 1, ' ');
8618 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Ctrl', 'ldr', 'VIS', 8, 1, ' ');
8619 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Ctry', '008', 'BKS', 15, 3, ' ');
8620 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Ctry', '008', 'COM', 15, 3, ' ');
8621 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Ctry', '008', 'MAP', 15, 3, ' ');
8622 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Ctry', '008', 'MIX', 15, 3, ' ');
8623 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Ctry', '008', 'REC', 15, 3, ' ');
8624 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Ctry', '008', 'SCO', 15, 3, ' ');
8625 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Ctry', '008', 'SER', 15, 3, ' ');
8626 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Ctry', '008', 'VIS', 15, 3, ' ');
8627 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Date1', '008', 'BKS', 7, 4, ' ');
8628 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Date1', '008', 'COM', 7, 4, ' ');
8629 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Date1', '008', 'MAP', 7, 4, ' ');
8630 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Date1', '008', 'MIX', 7, 4, ' ');
8631 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Date1', '008', 'REC', 7, 4, ' ');
8632 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Date1', '008', 'SCO', 7, 4, ' ');
8633 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Date1', '008', 'SER', 7, 4, ' ');
8634 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Date1', '008', 'VIS', 7, 4, ' ');
8635 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Date2', '008', 'BKS', 11, 4, ' ');
8636 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Date2', '008', 'COM', 11, 4, ' ');
8637 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Date2', '008', 'MAP', 11, 4, ' ');
8638 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Date2', '008', 'MIX', 11, 4, ' ');
8639 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Date2', '008', 'REC', 11, 4, ' ');
8640 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Date2', '008', 'SCO', 11, 4, ' ');
8641 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Date2', '008', 'SER', 11, 4, '9');
8642 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Date2', '008', 'VIS', 11, 4, ' ');
8643 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Desc', 'ldr', 'BKS', 18, 1, ' ');
8644 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Desc', 'ldr', 'COM', 18, 1, ' ');
8645 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Desc', 'ldr', 'MAP', 18, 1, ' ');
8646 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Desc', 'ldr', 'MIX', 18, 1, ' ');
8647 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Desc', 'ldr', 'REC', 18, 1, ' ');
8648 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Desc', 'ldr', 'SCO', 18, 1, ' ');
8649 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Desc', 'ldr', 'SER', 18, 1, ' ');
8650 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Desc', 'ldr', 'VIS', 18, 1, ' ');
8651 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('DtSt', '008', 'BKS', 6, 1, ' ');
8652 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('DtSt', '008', 'COM', 6, 1, ' ');
8653 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('DtSt', '008', 'MAP', 6, 1, ' ');
8654 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('DtSt', '008', 'MIX', 6, 1, ' ');
8655 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('DtSt', '008', 'REC', 6, 1, ' ');
8656 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('DtSt', '008', 'SCO', 6, 1, ' ');
8657 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('DtSt', '008', 'SER', 6, 1, 'c');
8658 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('DtSt', '008', 'VIS', 6, 1, ' ');
8659 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('ELvl', 'ldr', 'BKS', 17, 1, ' ');
8660 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('ELvl', 'ldr', 'COM', 17, 1, ' ');
8661 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('ELvl', 'ldr', 'MAP', 17, 1, ' ');
8662 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('ELvl', 'ldr', 'MIX', 17, 1, ' ');
8663 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('ELvl', 'ldr', 'REC', 17, 1, ' ');
8664 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('ELvl', 'ldr', 'SCO', 17, 1, ' ');
8665 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('ELvl', 'ldr', 'SER', 17, 1, ' ');
8666 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('ELvl', 'ldr', 'VIS', 17, 1, ' ');
8667 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Fest', '006', 'BKS', 13, 1, '0');
8668 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Fest', '008', 'BKS', 30, 1, '0');
8669 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Form', '006', 'BKS', 6, 1, ' ');
8670 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Form', '006', 'MAP', 12, 1, ' ');
8671 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Form', '006', 'MIX', 6, 1, ' ');
8672 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Form', '006', 'REC', 6, 1, ' ');
8673 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Form', '006', 'SCO', 6, 1, ' ');
8674 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Form', '006', 'SER', 6, 1, ' ');
8675 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Form', '006', 'VIS', 12, 1, ' ');
8676 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Form', '008', 'BKS', 23, 1, ' ');
8677 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Form', '008', 'MAP', 29, 1, ' ');
8678 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Form', '008', 'MIX', 23, 1, ' ');
8679 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Form', '008', 'REC', 23, 1, ' ');
8680 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Form', '008', 'SCO', 23, 1, ' ');
8681 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Form', '008', 'SER', 23, 1, ' ');
8682 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Form', '008', 'VIS', 29, 1, ' ');
8683 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('GPub', '006', 'BKS', 11, 1, ' ');
8684 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('GPub', '006', 'COM', 11, 1, ' ');
8685 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('GPub', '006', 'MAP', 11, 1, ' ');
8686 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('GPub', '006', 'SER', 11, 1, ' ');
8687 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('GPub', '006', 'VIS', 11, 1, ' ');
8688 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('GPub', '008', 'BKS', 28, 1, ' ');
8689 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('GPub', '008', 'COM', 28, 1, ' ');
8690 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('GPub', '008', 'MAP', 28, 1, ' ');
8691 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('GPub', '008', 'SER', 28, 1, ' ');
8692 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('GPub', '008', 'VIS', 28, 1, ' ');
8693 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Ills', '006', 'BKS', 1, 4, ' ');
8694 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Ills', '008', 'BKS', 18, 4, ' ');
8695 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Indx', '006', 'BKS', 14, 1, '0');
8696 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Indx', '006', 'MAP', 14, 1, '0');
8697 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Indx', '008', 'BKS', 31, 1, '0');
8698 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Indx', '008', 'MAP', 31, 1, '0');
8699 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Lang', '008', 'BKS', 35, 3, ' ');
8700 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Lang', '008', 'COM', 35, 3, ' ');
8701 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Lang', '008', 'MAP', 35, 3, ' ');
8702 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Lang', '008', 'MIX', 35, 3, ' ');
8703 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Lang', '008', 'REC', 35, 3, ' ');
8704 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Lang', '008', 'SCO', 35, 3, ' ');
8705 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Lang', '008', 'SER', 35, 3, ' ');
8706 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Lang', '008', 'VIS', 35, 3, ' ');
8707 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('LitF', '006', 'BKS', 16, 1, '0');
8708 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('LitF', '008', 'BKS', 33, 1, '0');
8709 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('MRec', '008', 'BKS', 38, 1, ' ');
8710 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('MRec', '008', 'COM', 38, 1, ' ');
8711 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('MRec', '008', 'MAP', 38, 1, ' ');
8712 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('MRec', '008', 'MIX', 38, 1, ' ');
8713 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('MRec', '008', 'REC', 38, 1, ' ');
8714 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('MRec', '008', 'SCO', 38, 1, ' ');
8715 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('MRec', '008', 'SER', 38, 1, ' ');
8716 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('MRec', '008', 'VIS', 38, 1, ' ');
8717 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');
8718 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');
8719 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('TMat', '006', 'VIS', 16, 1, ' ');
8720 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('TMat', '008', 'VIS', 33, 1, ' ');
8721 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Type', 'ldr', 'BKS', 6, 1, 'a');
8722 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Type', 'ldr', 'COM', 6, 1, 'm');
8723 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Type', 'ldr', 'MAP', 6, 1, 'e');
8724 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Type', 'ldr', 'MIX', 6, 1, 'p');
8725 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Type', 'ldr', 'REC', 6, 1, 'i');
8726 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Type', 'ldr', 'SCO', 6, 1, 'c');
8727 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Type', 'ldr', 'SER', 6, 1, 'a');
8728 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Type', 'ldr', 'VIS', 6, 1, 'g');
8729
8730 CREATE OR REPLACE FUNCTION biblio.marc21_record_type( rid BIGINT ) RETURNS config.marc21_rec_type_map AS $func$
8731 DECLARE
8732         ldr         RECORD;
8733         tval        TEXT;
8734         tval_rec    RECORD;
8735         bval        TEXT;
8736         bval_rec    RECORD;
8737     retval      config.marc21_rec_type_map%ROWTYPE;
8738 BEGIN
8739     SELECT * INTO ldr FROM metabib.full_rec WHERE record = rid AND tag = 'LDR' LIMIT 1;
8740
8741     IF ldr.id IS NULL THEN
8742         SELECT * INTO retval FROM config.marc21_rec_type_map WHERE code = 'BKS';
8743         RETURN retval;
8744     END IF;
8745
8746     SELECT * INTO tval_rec FROM config.marc21_ff_pos_map WHERE fixed_field = 'Type' LIMIT 1; -- They're all the same
8747     SELECT * INTO bval_rec FROM config.marc21_ff_pos_map WHERE fixed_field = 'BLvl' LIMIT 1; -- They're all the same
8748
8749
8750     tval := SUBSTRING( ldr.value, tval_rec.start_pos + 1, tval_rec.length );
8751     bval := SUBSTRING( ldr.value, bval_rec.start_pos + 1, bval_rec.length );
8752
8753     -- RAISE NOTICE 'type %, blvl %, ldr %', tval, bval, ldr.value;
8754
8755     SELECT * INTO retval FROM config.marc21_rec_type_map WHERE type_val LIKE '%' || tval || '%' AND blvl_val LIKE '%' || bval || '%';
8756
8757
8758     IF retval.code IS NULL THEN
8759         SELECT * INTO retval FROM config.marc21_rec_type_map WHERE code = 'BKS';
8760     END IF;
8761
8762     RETURN retval;
8763 END;
8764 $func$ LANGUAGE PLPGSQL;
8765
8766 CREATE OR REPLACE FUNCTION biblio.marc21_extract_fixed_field( rid BIGINT, ff TEXT ) RETURNS TEXT AS $func$
8767 DECLARE
8768     rtype       TEXT;
8769     ff_pos      RECORD;
8770     tag_data    RECORD;
8771     val         TEXT;
8772 BEGIN
8773     rtype := (biblio.marc21_record_type( rid )).code;
8774     FOR ff_pos IN SELECT * FROM config.marc21_ff_pos_map WHERE fixed_field = ff AND rec_type = rtype ORDER BY tag DESC LOOP
8775         FOR tag_data IN SELECT * FROM metabib.full_rec WHERE tag = UPPER(ff_pos.tag) AND record = rid LOOP
8776             val := SUBSTRING( tag_data.value, ff_pos.start_pos + 1, ff_pos.length );
8777             RETURN val;
8778         END LOOP;
8779         val := REPEAT( ff_pos.default_val, ff_pos.length );
8780         RETURN val;
8781     END LOOP;
8782
8783     RETURN NULL;
8784 END;
8785 $func$ LANGUAGE PLPGSQL;
8786
8787 CREATE TYPE biblio.marc21_physical_characteristics AS ( id INT, record BIGINT, ptype TEXT, subfield INT, value INT );
8788 CREATE OR REPLACE FUNCTION biblio.marc21_physical_characteristics( rid BIGINT ) RETURNS SETOF biblio.marc21_physical_characteristics AS $func$
8789 DECLARE
8790     rowid   INT := 0;
8791     _007    RECORD;
8792     ptype   config.marc21_physical_characteristic_type_map%ROWTYPE;
8793     psf     config.marc21_physical_characteristic_subfield_map%ROWTYPE;
8794     pval    config.marc21_physical_characteristic_value_map%ROWTYPE;
8795     retval  biblio.marc21_physical_characteristics%ROWTYPE;
8796 BEGIN
8797
8798     SELECT * INTO _007 FROM metabib.full_rec WHERE record = rid AND tag = '007' LIMIT 1;
8799
8800     IF _007.id IS NOT NULL THEN
8801         SELECT * INTO ptype FROM config.marc21_physical_characteristic_type_map WHERE ptype_key = SUBSTRING( _007.value, 1, 1 );
8802
8803         IF ptype.ptype_key IS NOT NULL THEN
8804             FOR psf IN SELECT * FROM config.marc21_physical_characteristic_subfield_map WHERE ptype_key = ptype.ptype_key LOOP
8805                 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 );
8806
8807                 IF pval.id IS NOT NULL THEN
8808                     rowid := rowid + 1;
8809                     retval.id := rowid;
8810                     retval.record := rid;
8811                     retval.ptype := ptype.ptype_key;
8812                     retval.subfield := psf.id;
8813                     retval.value := pval.id;
8814                     RETURN NEXT retval;
8815                 END IF;
8816
8817             END LOOP;
8818         END IF;
8819     END IF;
8820
8821     RETURN;
8822 END;
8823 $func$ LANGUAGE PLPGSQL;
8824
8825 DROP VIEW IF EXISTS money.open_usr_circulation_summary;
8826 DROP VIEW IF EXISTS money.open_usr_summary;
8827 DROP VIEW IF EXISTS money.open_billable_xact_summary;
8828
8829 -- The view should supply defaults for numeric (amount) columns
8830 CREATE OR REPLACE VIEW money.billable_xact_summary AS
8831     SELECT  xact.id,
8832         xact.usr,
8833         xact.xact_start,
8834         xact.xact_finish,
8835         COALESCE(credit.amount, 0.0::numeric) AS total_paid,
8836         credit.payment_ts AS last_payment_ts,
8837         credit.note AS last_payment_note,
8838         credit.payment_type AS last_payment_type,
8839         COALESCE(debit.amount, 0.0::numeric) AS total_owed,
8840         debit.billing_ts AS last_billing_ts,
8841         debit.note AS last_billing_note,
8842         debit.billing_type AS last_billing_type,
8843         COALESCE(debit.amount, 0.0::numeric) - COALESCE(credit.amount, 0.0::numeric) AS balance_owed,
8844         p.relname AS xact_type
8845       FROM  money.billable_xact xact
8846         JOIN pg_class p ON xact.tableoid = p.oid
8847         LEFT JOIN (
8848             SELECT  billing.xact,
8849                 sum(billing.amount) AS amount,
8850                 max(billing.billing_ts) AS billing_ts,
8851                 last(billing.note) AS note,
8852                 last(billing.billing_type) AS billing_type
8853               FROM  money.billing
8854               WHERE billing.voided IS FALSE
8855               GROUP BY billing.xact
8856             ) debit ON xact.id = debit.xact
8857         LEFT JOIN (
8858             SELECT  payment_view.xact,
8859                 sum(payment_view.amount) AS amount,
8860                 max(payment_view.payment_ts) AS payment_ts,
8861                 last(payment_view.note) AS note,
8862                 last(payment_view.payment_type) AS payment_type
8863               FROM  money.payment_view
8864               WHERE payment_view.voided IS FALSE
8865               GROUP BY payment_view.xact
8866             ) credit ON xact.id = credit.xact
8867       ORDER BY debit.billing_ts, credit.payment_ts;
8868
8869 CREATE OR REPLACE VIEW money.open_billable_xact_summary AS 
8870     SELECT * FROM money.billable_xact_summary_location_view
8871     WHERE xact_finish IS NULL;
8872
8873 CREATE OR REPLACE VIEW money.open_usr_circulation_summary AS
8874     SELECT 
8875         usr,
8876         SUM(total_paid) AS total_paid,
8877         SUM(total_owed) AS total_owed,
8878         SUM(balance_owed) AS balance_owed
8879     FROM  money.materialized_billable_xact_summary
8880     WHERE xact_type = 'circulation' AND xact_finish IS NULL
8881     GROUP BY usr;
8882
8883 CREATE OR REPLACE VIEW money.usr_summary AS
8884     SELECT 
8885         usr, 
8886         sum(total_paid) AS total_paid, 
8887         sum(total_owed) AS total_owed, 
8888         sum(balance_owed) AS balance_owed
8889     FROM money.materialized_billable_xact_summary
8890     GROUP BY usr;
8891
8892 CREATE OR REPLACE VIEW money.open_usr_summary AS
8893     SELECT 
8894         usr, 
8895         sum(total_paid) AS total_paid, 
8896         sum(total_owed) AS total_owed, 
8897         sum(balance_owed) AS balance_owed
8898     FROM money.materialized_billable_xact_summary
8899     WHERE xact_finish IS NULL
8900     GROUP BY usr;
8901
8902 -- 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;
8903
8904 CREATE TABLE config.biblio_fingerprint (
8905         id                      SERIAL  PRIMARY KEY,
8906         name            TEXT    NOT NULL, 
8907         xpath           TEXT    NOT NULL,
8908     first_word  BOOL    NOT NULL DEFAULT FALSE,
8909         format          TEXT    NOT NULL DEFAULT 'marcxml'
8910 );
8911
8912 INSERT INTO config.biblio_fingerprint (name, xpath, format)
8913     VALUES (
8914         'Title',
8915         '//marc:datafield[@tag="700"]/marc:subfield[@code="t"]|' ||
8916             '//marc:datafield[@tag="240"]/marc:subfield[@code="a"]|' ||
8917             '//marc:datafield[@tag="242"]/marc:subfield[@code="a"]|' ||
8918             '//marc:datafield[@tag="246"]/marc:subfield[@code="a"]|' ||
8919             '//marc:datafield[@tag="245"]/marc:subfield[@code="a"]',
8920         'marcxml'
8921     );
8922
8923 INSERT INTO config.biblio_fingerprint (name, xpath, format, first_word)
8924     VALUES (
8925         'Author',
8926         '//marc:datafield[@tag="700" and ./*[@code="t"]]/marc:subfield[@code="a"]|'
8927             '//marc:datafield[@tag="100"]/marc:subfield[@code="a"]|'
8928             '//marc:datafield[@tag="110"]/marc:subfield[@code="a"]|'
8929             '//marc:datafield[@tag="111"]/marc:subfield[@code="a"]|'
8930             '//marc:datafield[@tag="260"]/marc:subfield[@code="b"]',
8931         'marcxml',
8932         TRUE
8933     );
8934
8935 CREATE OR REPLACE FUNCTION biblio.extract_quality ( marc TEXT, best_lang TEXT, best_type TEXT ) RETURNS INT AS $func$
8936 DECLARE
8937     qual        INT;
8938     ldr         TEXT;
8939     tval        TEXT;
8940     tval_rec    RECORD;
8941     bval        TEXT;
8942     bval_rec    RECORD;
8943     type_map    RECORD;
8944     ff_pos      RECORD;
8945     ff_tag_data TEXT;
8946 BEGIN
8947
8948     IF marc IS NULL OR marc = '' THEN
8949         RETURN NULL;
8950     END IF;
8951
8952     -- First, the count of tags
8953     qual := ARRAY_UPPER(oils_xpath('*[local-name()="datafield"]', marc), 1);
8954
8955     -- now go through a bunch of pain to get the record type
8956     IF best_type IS NOT NULL THEN
8957         ldr := (oils_xpath('//*[local-name()="leader"]/text()', marc))[1];
8958
8959         IF ldr IS NOT NULL THEN
8960             SELECT * INTO tval_rec FROM config.marc21_ff_pos_map WHERE fixed_field = 'Type' LIMIT 1; -- They're all the same
8961             SELECT * INTO bval_rec FROM config.marc21_ff_pos_map WHERE fixed_field = 'BLvl' LIMIT 1; -- They're all the same
8962
8963
8964             tval := SUBSTRING( ldr, tval_rec.start_pos + 1, tval_rec.length );
8965             bval := SUBSTRING( ldr, bval_rec.start_pos + 1, bval_rec.length );
8966
8967             -- RAISE NOTICE 'type %, blvl %, ldr %', tval, bval, ldr;
8968
8969             SELECT * INTO type_map FROM config.marc21_rec_type_map WHERE type_val LIKE '%' || tval || '%' AND blvl_val LIKE '%' || bval || '%';
8970
8971             IF type_map.code IS NOT NULL THEN
8972                 IF best_type = type_map.code THEN
8973                     qual := qual + qual / 2;
8974                 END IF;
8975
8976                 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
8977                     ff_tag_data := SUBSTRING((oils_xpath('//*[@tag="' || ff_pos.tag || '"]/text()',marc))[1], ff_pos.start_pos + 1, ff_pos.length);
8978                     IF ff_tag_data = best_lang THEN
8979                             qual := qual + 100;
8980                     END IF;
8981                 END LOOP;
8982             END IF;
8983         END IF;
8984     END IF;
8985
8986     -- Now look for some quality metrics
8987     -- DCL record?
8988     IF ARRAY_UPPER(oils_xpath('//*[@tag="040"]/*[@code="a" and contains(.,"DLC")]', marc), 1) = 1 THEN
8989         qual := qual + 10;
8990     END IF;
8991
8992     -- From OCLC?
8993     IF (oils_xpath('//*[@tag="003"]/text()', marc))[1] ~* E'oclo?c' THEN
8994         qual := qual + 10;
8995     END IF;
8996
8997     RETURN qual;
8998
8999 END;
9000 $func$ LANGUAGE PLPGSQL;
9001
9002 CREATE OR REPLACE FUNCTION biblio.extract_fingerprint ( marc text ) RETURNS TEXT AS $func$
9003 DECLARE
9004     idx     config.biblio_fingerprint%ROWTYPE;
9005     xfrm        config.xml_transform%ROWTYPE;
9006     prev_xfrm   TEXT;
9007     transformed_xml TEXT;
9008     xml_node    TEXT;
9009     xml_node_list   TEXT[];
9010     raw_text    TEXT;
9011     output_text TEXT := '';
9012 BEGIN
9013
9014     IF marc IS NULL OR marc = '' THEN
9015         RETURN NULL;
9016     END IF;
9017
9018     -- Loop over the indexing entries
9019     FOR idx IN SELECT * FROM config.biblio_fingerprint ORDER BY format, id LOOP
9020
9021         SELECT INTO xfrm * from config.xml_transform WHERE name = idx.format;
9022
9023         -- See if we can skip the XSLT ... it's expensive
9024         IF prev_xfrm IS NULL OR prev_xfrm <> xfrm.name THEN
9025             -- Can't skip the transform
9026             IF xfrm.xslt <> '---' THEN
9027                 transformed_xml := oils_xslt_process(marc,xfrm.xslt);
9028             ELSE
9029                 transformed_xml := marc;
9030             END IF;
9031
9032             prev_xfrm := xfrm.name;
9033         END IF;
9034
9035         raw_text := COALESCE(
9036             naco_normalize(
9037                 ARRAY_TO_STRING(
9038                     oils_xpath(
9039                         '//text()',
9040                         (oils_xpath(
9041                             idx.xpath,
9042                             transformed_xml,
9043                             ARRAY[ARRAY[xfrm.prefix, xfrm.namespace_uri]]
9044                         ))[1]
9045                     ),
9046                     ''
9047                 )
9048             ),
9049             ''
9050         );
9051
9052         raw_text := REGEXP_REPLACE(raw_text, E'\\[.+?\\]', E'');
9053         raw_text := REGEXP_REPLACE(raw_text, E'\\mthe\\M|\\man?d?d\\M', E'', 'g'); -- arg! the pain!
9054
9055         IF idx.first_word IS TRUE THEN
9056             raw_text := REGEXP_REPLACE(raw_text, E'^(\\w+).*?$', E'\\1');
9057         END IF;
9058
9059         output_text := output_text || REGEXP_REPLACE(raw_text, E'\\s+', '', 'g');
9060
9061     END LOOP;
9062
9063     RETURN output_text;
9064
9065 END;
9066 $func$ LANGUAGE PLPGSQL;
9067
9068 -- BEFORE UPDATE OR INSERT trigger for biblio.record_entry
9069 CREATE OR REPLACE FUNCTION biblio.fingerprint_trigger () RETURNS TRIGGER AS $func$
9070 BEGIN
9071
9072     -- For TG_ARGV, first param is language (like 'eng'), second is record type (like 'BKS')
9073
9074     IF NEW.deleted IS TRUE THEN -- we don't much care, then, do we?
9075         RETURN NEW;
9076     END IF;
9077
9078     NEW.fingerprint := biblio.extract_fingerprint(NEW.marc);
9079     NEW.quality := biblio.extract_quality(NEW.marc, TG_ARGV[0], TG_ARGV[1]);
9080
9081     RETURN NEW;
9082
9083 END;
9084 $func$ LANGUAGE PLPGSQL;
9085
9086 CREATE TABLE config.internal_flag (
9087     name    TEXT    PRIMARY KEY,
9088     value   TEXT,
9089     enabled BOOL    NOT NULL DEFAULT FALSE
9090 );
9091 INSERT INTO config.internal_flag (name) VALUES ('ingest.metarecord_mapping.skip_on_insert');
9092 INSERT INTO config.internal_flag (name) VALUES ('ingest.reingest.force_on_same_marc');
9093 INSERT INTO config.internal_flag (name) VALUES ('ingest.reingest.skip_located_uri');
9094 INSERT INTO config.internal_flag (name) VALUES ('ingest.disable_located_uri');
9095 INSERT INTO config.internal_flag (name) VALUES ('ingest.disable_metabib_full_rec');
9096 INSERT INTO config.internal_flag (name) VALUES ('ingest.disable_metabib_rec_descriptor');
9097 INSERT INTO config.internal_flag (name) VALUES ('ingest.disable_metabib_field_entry');
9098 INSERT INTO config.internal_flag (name) VALUES ('ingest.disable_authority_linking');
9099
9100 CREATE TABLE authority.bib_linking (
9101     id          BIGSERIAL   PRIMARY KEY,
9102     bib         BIGINT      NOT NULL REFERENCES biblio.record_entry (id),
9103     authority   BIGINT      NOT NULL REFERENCES authority.record_entry (id)
9104 );
9105 CREATE INDEX authority_bl_bib_idx ON authority.bib_linking ( bib );
9106 CREATE UNIQUE INDEX authority_bl_bib_authority_once_idx ON authority.bib_linking ( authority, bib );
9107
9108 CREATE OR REPLACE FUNCTION public.remove_paren_substring( TEXT ) RETURNS TEXT AS $func$
9109     SELECT regexp_replace($1, $$\([^)]+\)$$, '', 'g');
9110 $func$ LANGUAGE SQL STRICT IMMUTABLE;
9111
9112 CREATE OR REPLACE FUNCTION biblio.map_authority_linking (bibid BIGINT, marc TEXT) RETURNS BIGINT AS $func$
9113     DELETE FROM authority.bib_linking WHERE bib = $1;
9114     INSERT INTO authority.bib_linking (bib, authority)
9115         SELECT  y.bib,
9116                 y.authority
9117           FROM (    SELECT  DISTINCT $1 AS bib,
9118                             BTRIM(remove_paren_substring(txt))::BIGINT AS authority
9119                       FROM  explode_array(oils_xpath('//*[@code="0"]/text()',$2)) x(txt)
9120                       WHERE BTRIM(remove_paren_substring(txt)) ~ $re$^\d+$$re$
9121                 ) y JOIN authority.record_entry r ON r.id = y.authority;
9122     SELECT $1;
9123 $func$ LANGUAGE SQL;
9124
9125 CREATE OR REPLACE FUNCTION metabib.reingest_metabib_rec_descriptor( bib_id BIGINT ) RETURNS VOID AS $func$
9126 BEGIN
9127     DELETE FROM metabib.rec_descriptor WHERE record = bib_id;
9128     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)
9129         SELECT  bib_id,
9130                 biblio.marc21_extract_fixed_field( bib_id, 'Type' ),
9131                 biblio.marc21_extract_fixed_field( bib_id, 'Form' ),
9132                 biblio.marc21_extract_fixed_field( bib_id, 'BLvl' ),
9133                 biblio.marc21_extract_fixed_field( bib_id, 'Ctrl' ),
9134                 biblio.marc21_extract_fixed_field( bib_id, 'ELvl' ),
9135                 biblio.marc21_extract_fixed_field( bib_id, 'Audn' ),
9136                 biblio.marc21_extract_fixed_field( bib_id, 'LitF' ),
9137                 biblio.marc21_extract_fixed_field( bib_id, 'TMat' ),
9138                 biblio.marc21_extract_fixed_field( bib_id, 'Desc' ),
9139                 biblio.marc21_extract_fixed_field( bib_id, 'DtSt' ),
9140                 biblio.marc21_extract_fixed_field( bib_id, 'Lang' ),
9141                 (   SELECT  v.value
9142                       FROM  biblio.marc21_physical_characteristics( bib_id) p
9143                             JOIN config.marc21_physical_characteristic_subfield_map s ON (s.id = p.subfield)
9144                             JOIN config.marc21_physical_characteristic_value_map v ON (v.id = p.value)
9145                       WHERE p.ptype = 'v' AND s.subfield = 'e'    ),
9146                 biblio.marc21_extract_fixed_field( bib_id, 'Date1'),
9147                 biblio.marc21_extract_fixed_field( bib_id, 'Date2');
9148
9149     RETURN;
9150 END;
9151 $func$ LANGUAGE PLPGSQL;
9152
9153 CREATE TABLE config.metabib_class (
9154     name    TEXT    PRIMARY KEY,
9155     label   TEXT    NOT NULL UNIQUE
9156 );
9157
9158 INSERT INTO config.metabib_class ( name, label ) VALUES ( 'keyword', oils_i18n_gettext('keyword', 'Keyword', 'cmc', 'label') );
9159 INSERT INTO config.metabib_class ( name, label ) VALUES ( 'title', oils_i18n_gettext('title', 'Title', 'cmc', 'label') );
9160 INSERT INTO config.metabib_class ( name, label ) VALUES ( 'author', oils_i18n_gettext('author', 'Author', 'cmc', 'label') );
9161 INSERT INTO config.metabib_class ( name, label ) VALUES ( 'subject', oils_i18n_gettext('subject', 'Subject', 'cmc', 'label') );
9162 INSERT INTO config.metabib_class ( name, label ) VALUES ( 'series', oils_i18n_gettext('series', 'Series', 'cmc', 'label') );
9163
9164 CREATE TABLE metabib.facet_entry (
9165         id              BIGSERIAL       PRIMARY KEY,
9166         source          BIGINT          NOT NULL,
9167         field           INT             NOT NULL,
9168         value           TEXT            NOT NULL
9169 );
9170
9171 CREATE OR REPLACE FUNCTION metabib.reingest_metabib_field_entries( bib_id BIGINT ) RETURNS VOID AS $func$
9172 DECLARE
9173     fclass          RECORD;
9174     ind_data        metabib.field_entry_template%ROWTYPE;
9175 BEGIN
9176     FOR fclass IN SELECT * FROM config.metabib_class LOOP
9177         -- RAISE NOTICE 'Emptying out %', fclass.name;
9178         EXECUTE $$DELETE FROM metabib.$$ || fclass.name || $$_field_entry WHERE source = $$ || bib_id;
9179     END LOOP;
9180
9181     DELETE FROM metabib.facet_entry WHERE source = bib_id;
9182
9183     FOR ind_data IN SELECT * FROM biblio.extract_metabib_field_entry( bib_id ) LOOP
9184         IF ind_data.field < 0 THEN
9185             ind_data.field = -1 * ind_data.field;
9186             INSERT INTO metabib.facet_entry (field, source, value)
9187                 VALUES (ind_data.field, ind_data.source, ind_data.value);
9188         ELSE
9189             EXECUTE $$
9190                 INSERT INTO metabib.$$ || ind_data.field_class || $$_field_entry (field, source, value)
9191                     VALUES ($$ ||
9192                         quote_literal(ind_data.field) || $$, $$ ||
9193                         quote_literal(ind_data.source) || $$, $$ ||
9194                         quote_literal(ind_data.value) ||
9195                     $$);$$;
9196         END IF;
9197
9198     END LOOP;
9199
9200     RETURN;
9201 END;
9202 $func$ LANGUAGE PLPGSQL;
9203
9204 CREATE OR REPLACE FUNCTION biblio.extract_located_uris( bib_id BIGINT, marcxml TEXT, editor_id INT ) RETURNS VOID AS $func$
9205 DECLARE
9206     uris            TEXT[];
9207     uri_xml         TEXT;
9208     uri_label       TEXT;
9209     uri_href        TEXT;
9210     uri_use         TEXT;
9211     uri_owner       TEXT;
9212     uri_owner_id    INT;
9213     uri_id          INT;
9214     uri_cn_id       INT;
9215     uri_map_id      INT;
9216 BEGIN
9217
9218     uris := oils_xpath('//*[@tag="856" and (@ind1="4" or @ind1="1") and (@ind2="0" or @ind2="1")]',marcxml);
9219     IF ARRAY_UPPER(uris,1) > 0 THEN
9220         FOR i IN 1 .. ARRAY_UPPER(uris, 1) LOOP
9221             -- First we pull info out of the 856
9222             uri_xml     := uris[i];
9223
9224             uri_href    := (oils_xpath('//*[@code="u"]/text()',uri_xml))[1];
9225             CONTINUE WHEN uri_href IS NULL;
9226
9227             uri_label   := (oils_xpath('//*[@code="y"]/text()|//*[@code="3"]/text()|//*[@code="u"]/text()',uri_xml))[1];
9228             CONTINUE WHEN uri_label IS NULL;
9229
9230             uri_owner   := (oils_xpath('//*[@code="9"]/text()|//*[@code="w"]/text()|//*[@code="n"]/text()',uri_xml))[1];
9231             CONTINUE WHEN uri_owner IS NULL;
9232
9233             uri_use     := (oils_xpath('//*[@code="z"]/text()|//*[@code="2"]/text()|//*[@code="n"]/text()',uri_xml))[1];
9234
9235             uri_owner := REGEXP_REPLACE(uri_owner, $re$^.*?\((\w+)\).*$$re$, E'\\1');
9236
9237             SELECT id INTO uri_owner_id FROM actor.org_unit WHERE shortname = uri_owner;
9238             CONTINUE WHEN NOT FOUND;
9239
9240             -- now we look for a matching uri
9241             SELECT id INTO uri_id FROM asset.uri WHERE label = uri_label AND href = uri_href AND use_restriction = uri_use AND active;
9242             IF NOT FOUND THEN -- create one
9243                 INSERT INTO asset.uri (label, href, use_restriction) VALUES (uri_label, uri_href, uri_use);
9244                 SELECT id INTO uri_id FROM asset.uri WHERE label = uri_label AND href = uri_href AND use_restriction = uri_use AND active;
9245             END IF;
9246
9247             -- we need a call number to link through
9248             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;
9249             IF NOT FOUND THEN
9250                 INSERT INTO asset.call_number (owning_lib, record, create_date, edit_date, creator, editor, label)
9251                     VALUES (uri_owner_id, bib_id, 'now', 'now', editor_id, editor_id, '##URI##');
9252                 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;
9253             END IF;
9254
9255             -- now, link them if they're not already
9256             SELECT id INTO uri_map_id FROM asset.uri_call_number_map WHERE call_number = uri_cn_id AND uri = uri_id;
9257             IF NOT FOUND THEN
9258                 INSERT INTO asset.uri_call_number_map (call_number, uri) VALUES (uri_cn_id, uri_id);
9259             END IF;
9260
9261         END LOOP;
9262     END IF;
9263
9264     RETURN;
9265 END;
9266 $func$ LANGUAGE PLPGSQL;
9267
9268 CREATE OR REPLACE FUNCTION metabib.remap_metarecord_for_bib( bib_id BIGINT, fp TEXT ) RETURNS BIGINT AS $func$
9269 DECLARE
9270     source_count    INT;
9271     old_mr          BIGINT;
9272     tmp_mr          metabib.metarecord%ROWTYPE;
9273     deleted_mrs     BIGINT[];
9274 BEGIN
9275
9276     DELETE FROM metabib.metarecord_source_map WHERE source = bib_id; -- Rid ourselves of the search-estimate-killing linkage
9277
9278     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
9279
9280         IF old_mr IS NULL AND fp = tmp_mr.fingerprint THEN -- Find the first fingerprint-matching
9281             old_mr := tmp_mr.id;
9282         ELSE
9283             SELECT COUNT(*) INTO source_count FROM metabib.metarecord_source_map WHERE metarecord = tmp_mr.id;
9284             IF source_count = 0 THEN -- No other records
9285                 deleted_mrs := ARRAY_APPEND(deleted_mrs, tmp_mr.id);
9286                 DELETE FROM metabib.metarecord WHERE id = tmp_mr.id;
9287             END IF;
9288         END IF;
9289
9290     END LOOP;
9291
9292     IF old_mr IS NULL THEN -- we found no suitable, preexisting MR based on old source maps
9293         SELECT id INTO old_mr FROM metabib.metarecord WHERE fingerprint = fp; -- is there one for our current fingerprint?
9294         IF old_mr IS NULL THEN -- nope, create one and grab its id
9295             INSERT INTO metabib.metarecord ( fingerprint, master_record ) VALUES ( fp, bib_id );
9296             SELECT id INTO old_mr FROM metabib.metarecord WHERE fingerprint = fp;
9297         ELSE -- indeed there is. update it with a null cache and recalcualated master record
9298             UPDATE  metabib.metarecord
9299               SET   mods = NULL,
9300                     master_record = ( SELECT id FROM biblio.record_entry WHERE fingerprint = fp ORDER BY quality DESC LIMIT 1)
9301               WHERE id = old_mr;
9302         END IF;
9303     ELSE -- there was one we already attached to, update its mods cache and master_record
9304         UPDATE  metabib.metarecord
9305           SET   mods = NULL,
9306                 master_record = ( SELECT id FROM biblio.record_entry WHERE fingerprint = fp ORDER BY quality DESC LIMIT 1)
9307           WHERE id = old_mr;
9308     END IF;
9309
9310     INSERT INTO metabib.metarecord_source_map (metarecord, source) VALUES (old_mr, bib_id); -- new source mapping
9311
9312     IF ARRAY_UPPER(deleted_mrs,1) > 0 THEN
9313         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
9314     END IF;
9315
9316     RETURN old_mr;
9317
9318 END;
9319 $func$ LANGUAGE PLPGSQL;
9320
9321 CREATE OR REPLACE FUNCTION metabib.reingest_metabib_full_rec( bib_id BIGINT ) RETURNS VOID AS $func$
9322 BEGIN
9323     DELETE FROM metabib.real_full_rec WHERE record = bib_id;
9324     INSERT INTO metabib.real_full_rec (record, tag, ind1, ind2, subfield, value)
9325         SELECT record, tag, ind1, ind2, subfield, value FROM biblio.flatten_marc( bib_id );
9326
9327     RETURN;
9328 END;
9329 $func$ LANGUAGE PLPGSQL;
9330
9331 -- AFTER UPDATE OR INSERT trigger for biblio.record_entry
9332 CREATE OR REPLACE FUNCTION biblio.indexing_ingest_or_delete () RETURNS TRIGGER AS $func$
9333 BEGIN
9334
9335     IF NEW.deleted IS TRUE THEN -- If this bib is deleted
9336         DELETE FROM metabib.metarecord_source_map WHERE source = NEW.id; -- Rid ourselves of the search-estimate-killing linkage
9337         DELETE FROM authority.bib_linking WHERE bib = NEW.id; -- Avoid updating fields in bibs that are no longer visible
9338         RETURN NEW; -- and we're done
9339     END IF;
9340
9341     IF TG_OP = 'UPDATE' THEN -- re-ingest?
9342         PERFORM * FROM config.internal_flag WHERE name = 'ingest.reingest.force_on_same_marc' AND enabled;
9343
9344         IF NOT FOUND AND OLD.marc = NEW.marc THEN -- don't do anything if the MARC didn't change
9345             RETURN NEW;
9346         END IF;
9347     END IF;
9348
9349     -- Record authority linking
9350     PERFORM * FROM config.internal_flag WHERE name = 'ingest.disable_authority_linking' AND enabled;
9351     IF NOT FOUND THEN
9352         PERFORM biblio.map_authority_linking( NEW.id, NEW.marc );
9353     END IF;
9354
9355     -- Flatten and insert the mfr data
9356     PERFORM * FROM config.internal_flag WHERE name = 'ingest.disable_metabib_full_rec' AND enabled;
9357     IF NOT FOUND THEN
9358         PERFORM metabib.reingest_metabib_full_rec(NEW.id);
9359         PERFORM * FROM config.internal_flag WHERE name = 'ingest.disable_metabib_rec_descriptor' AND enabled;
9360         IF NOT FOUND THEN
9361             PERFORM metabib.reingest_metabib_rec_descriptor(NEW.id);
9362         END IF;
9363     END IF;
9364
9365     -- Gather and insert the field entry data
9366     PERFORM metabib.reingest_metabib_field_entries(NEW.id);
9367
9368     -- Located URI magic
9369     IF TG_OP = 'INSERT' THEN
9370         PERFORM * FROM config.internal_flag WHERE name = 'ingest.disable_located_uri' AND enabled;
9371         IF NOT FOUND THEN
9372             PERFORM biblio.extract_located_uris( NEW.id, NEW.marc, NEW.editor );
9373         END IF;
9374     ELSE
9375         PERFORM * FROM config.internal_flag WHERE name = 'ingest.disable_located_uri' AND enabled;
9376         IF NOT FOUND THEN
9377             PERFORM biblio.extract_located_uris( NEW.id, NEW.marc, NEW.editor );
9378         END IF;
9379     END IF;
9380
9381     -- (re)map metarecord-bib linking
9382     IF TG_OP = 'INSERT' THEN -- if not deleted and performing an insert, check for the flag
9383         PERFORM * FROM config.internal_flag WHERE name = 'ingest.metarecord_mapping.skip_on_insert' AND enabled;
9384         IF NOT FOUND THEN
9385             PERFORM metabib.remap_metarecord_for_bib( NEW.id, NEW.fingerprint );
9386         END IF;
9387     ELSE -- we're doing an update, and we're not deleted, remap
9388         PERFORM metabib.remap_metarecord_for_bib( NEW.id, NEW.fingerprint );
9389     END IF;
9390
9391     RETURN NEW;
9392 END;
9393 $func$ LANGUAGE PLPGSQL;
9394
9395 CREATE TRIGGER fingerprint_tgr BEFORE INSERT OR UPDATE ON biblio.record_entry FOR EACH ROW EXECUTE PROCEDURE biblio.fingerprint_trigger ('eng','BKS');
9396 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 ();
9397
9398 DROP TRIGGER IF EXISTS zzz_update_materialized_simple_rec_delete_tgr ON biblio.record_entry;
9399
9400 CREATE OR REPLACE FUNCTION oils_xpath_table ( key TEXT, document_field TEXT, relation_name TEXT, xpaths TEXT, criteria TEXT )
9401 RETURNS SETOF RECORD AS $func$
9402 DECLARE
9403     xpath_list  TEXT[];
9404     select_list TEXT[];
9405     where_list  TEXT[];
9406     q           TEXT;
9407     out_record  RECORD;
9408     empty_test  RECORD;
9409 BEGIN
9410     xpath_list := STRING_TO_ARRAY( xpaths, '|' );
9411
9412     select_list := ARRAY_APPEND( select_list, key || '::INT AS key' );
9413
9414     FOR i IN 1 .. ARRAY_UPPER(xpath_list,1) LOOP
9415         select_list := ARRAY_APPEND(
9416             select_list,
9417             $sel$
9418             EXPLODE_ARRAY(
9419                 COALESCE(
9420                     NULLIF(
9421                         oils_xpath(
9422                             $sel$ ||
9423                                 quote_literal(
9424                                     CASE
9425                                         WHEN xpath_list[i] ~ $re$/[^/[]*@[^/]+$$re$ OR xpath_list[i] ~ $re$text\(\)$$re$ THEN xpath_list[i]
9426                                         ELSE xpath_list[i] || '//text()'
9427                                     END
9428                                 ) ||
9429                             $sel$,
9430                             $sel$ || document_field || $sel$
9431                         ),
9432                        '{}'::TEXT[]
9433                     ),
9434                     '{NULL}'::TEXT[]
9435                 )
9436             ) AS c_$sel$ || i
9437         );
9438         where_list := ARRAY_APPEND(
9439             where_list,
9440             'c_' || i || ' IS NOT NULL'
9441         );
9442     END LOOP;
9443
9444     q := $q$
9445 SELECT * FROM (
9446     SELECT $q$ || ARRAY_TO_STRING( select_list, ', ' ) || $q$ FROM $q$ || relation_name || $q$ WHERE ($q$ || criteria || $q$)
9447 )x WHERE $q$ || ARRAY_TO_STRING( where_list, ' AND ' );
9448     -- RAISE NOTICE 'query: %', q;
9449
9450     FOR out_record IN EXECUTE q LOOP
9451         RETURN NEXT out_record;
9452     END LOOP;
9453
9454     RETURN;
9455 END;
9456 $func$ LANGUAGE PLPGSQL;
9457
9458 CREATE OR REPLACE FUNCTION vandelay.ingest_items ( import_id BIGINT, attr_def_id BIGINT ) RETURNS SETOF vandelay.import_item AS $$
9459 DECLARE
9460
9461     owning_lib      TEXT;
9462     circ_lib        TEXT;
9463     call_number     TEXT;
9464     copy_number     TEXT;
9465     status          TEXT;
9466     location        TEXT;
9467     circulate       TEXT;
9468     deposit         TEXT;
9469     deposit_amount  TEXT;
9470     ref             TEXT;
9471     holdable        TEXT;
9472     price           TEXT;
9473     barcode         TEXT;
9474     circ_modifier   TEXT;
9475     circ_as_type    TEXT;
9476     alert_message   TEXT;
9477     opac_visible    TEXT;
9478     pub_note        TEXT;
9479     priv_note       TEXT;
9480
9481     attr_def        RECORD;
9482     tmp_attr_set    RECORD;
9483     attr_set        vandelay.import_item%ROWTYPE;
9484
9485     xpath           TEXT;
9486
9487 BEGIN
9488
9489     SELECT * INTO attr_def FROM vandelay.import_item_attr_definition WHERE id = attr_def_id;
9490
9491     IF FOUND THEN
9492
9493         attr_set.definition := attr_def.id; 
9494     
9495         -- Build the combined XPath
9496     
9497         owning_lib :=
9498             CASE
9499                 WHEN attr_def.owning_lib IS NULL THEN 'null()'
9500                 WHEN LENGTH( attr_def.owning_lib ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.owning_lib || '"]'
9501                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.owning_lib
9502             END;
9503     
9504         circ_lib :=
9505             CASE
9506                 WHEN attr_def.circ_lib IS NULL THEN 'null()'
9507                 WHEN LENGTH( attr_def.circ_lib ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.circ_lib || '"]'
9508                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.circ_lib
9509             END;
9510     
9511         call_number :=
9512             CASE
9513                 WHEN attr_def.call_number IS NULL THEN 'null()'
9514                 WHEN LENGTH( attr_def.call_number ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.call_number || '"]'
9515                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.call_number
9516             END;
9517     
9518         copy_number :=
9519             CASE
9520                 WHEN attr_def.copy_number IS NULL THEN 'null()'
9521                 WHEN LENGTH( attr_def.copy_number ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.copy_number || '"]'
9522                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.copy_number
9523             END;
9524     
9525         status :=
9526             CASE
9527                 WHEN attr_def.status IS NULL THEN 'null()'
9528                 WHEN LENGTH( attr_def.status ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.status || '"]'
9529                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.status
9530             END;
9531     
9532         location :=
9533             CASE
9534                 WHEN attr_def.location IS NULL THEN 'null()'
9535                 WHEN LENGTH( attr_def.location ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.location || '"]'
9536                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.location
9537             END;
9538     
9539         circulate :=
9540             CASE
9541                 WHEN attr_def.circulate IS NULL THEN 'null()'
9542                 WHEN LENGTH( attr_def.circulate ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.circulate || '"]'
9543                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.circulate
9544             END;
9545     
9546         deposit :=
9547             CASE
9548                 WHEN attr_def.deposit IS NULL THEN 'null()'
9549                 WHEN LENGTH( attr_def.deposit ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.deposit || '"]'
9550                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.deposit
9551             END;
9552     
9553         deposit_amount :=
9554             CASE
9555                 WHEN attr_def.deposit_amount IS NULL THEN 'null()'
9556                 WHEN LENGTH( attr_def.deposit_amount ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.deposit_amount || '"]'
9557                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.deposit_amount
9558             END;
9559     
9560         ref :=
9561             CASE
9562                 WHEN attr_def.ref IS NULL THEN 'null()'
9563                 WHEN LENGTH( attr_def.ref ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.ref || '"]'
9564                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.ref
9565             END;
9566     
9567         holdable :=
9568             CASE
9569                 WHEN attr_def.holdable IS NULL THEN 'null()'
9570                 WHEN LENGTH( attr_def.holdable ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.holdable || '"]'
9571                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.holdable
9572             END;
9573     
9574         price :=
9575             CASE
9576                 WHEN attr_def.price IS NULL THEN 'null()'
9577                 WHEN LENGTH( attr_def.price ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.price || '"]'
9578                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.price
9579             END;
9580     
9581         barcode :=
9582             CASE
9583                 WHEN attr_def.barcode IS NULL THEN 'null()'
9584                 WHEN LENGTH( attr_def.barcode ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.barcode || '"]'
9585                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.barcode
9586             END;
9587     
9588         circ_modifier :=
9589             CASE
9590                 WHEN attr_def.circ_modifier IS NULL THEN 'null()'
9591                 WHEN LENGTH( attr_def.circ_modifier ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.circ_modifier || '"]'
9592                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.circ_modifier
9593             END;
9594     
9595         circ_as_type :=
9596             CASE
9597                 WHEN attr_def.circ_as_type IS NULL THEN 'null()'
9598                 WHEN LENGTH( attr_def.circ_as_type ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.circ_as_type || '"]'
9599                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.circ_as_type
9600             END;
9601     
9602         alert_message :=
9603             CASE
9604                 WHEN attr_def.alert_message IS NULL THEN 'null()'
9605                 WHEN LENGTH( attr_def.alert_message ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.alert_message || '"]'
9606                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.alert_message
9607             END;
9608     
9609         opac_visible :=
9610             CASE
9611                 WHEN attr_def.opac_visible IS NULL THEN 'null()'
9612                 WHEN LENGTH( attr_def.opac_visible ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.opac_visible || '"]'
9613                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.opac_visible
9614             END;
9615
9616         pub_note :=
9617             CASE
9618                 WHEN attr_def.pub_note IS NULL THEN 'null()'
9619                 WHEN LENGTH( attr_def.pub_note ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.pub_note || '"]'
9620                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.pub_note
9621             END;
9622         priv_note :=
9623             CASE
9624                 WHEN attr_def.priv_note IS NULL THEN 'null()'
9625                 WHEN LENGTH( attr_def.priv_note ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.priv_note || '"]'
9626                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.priv_note
9627             END;
9628     
9629     
9630         xpath := 
9631             owning_lib      || '|' || 
9632             circ_lib        || '|' || 
9633             call_number     || '|' || 
9634             copy_number     || '|' || 
9635             status          || '|' || 
9636             location        || '|' || 
9637             circulate       || '|' || 
9638             deposit         || '|' || 
9639             deposit_amount  || '|' || 
9640             ref             || '|' || 
9641             holdable        || '|' || 
9642             price           || '|' || 
9643             barcode         || '|' || 
9644             circ_modifier   || '|' || 
9645             circ_as_type    || '|' || 
9646             alert_message   || '|' || 
9647             pub_note        || '|' || 
9648             priv_note       || '|' || 
9649             opac_visible;
9650
9651         -- RAISE NOTICE 'XPath: %', xpath;
9652         
9653         FOR tmp_attr_set IN
9654                 SELECT  *
9655                   FROM  oils_xpath_table( 'id', 'marc', 'vandelay.queued_bib_record', xpath, 'id = ' || import_id )
9656                             AS t( id INT, ol TEXT, clib TEXT, cn TEXT, cnum TEXT, cs TEXT, cl TEXT, circ TEXT,
9657                                   dep TEXT, dep_amount TEXT, r TEXT, hold TEXT, pr TEXT, bc TEXT, circ_mod TEXT,
9658                                   circ_as TEXT, amessage TEXT, note TEXT, pnote TEXT, opac_vis TEXT )
9659         LOOP
9660     
9661             tmp_attr_set.pr = REGEXP_REPLACE(tmp_attr_set.pr, E'[^0-9\\.]', '', 'g');
9662             tmp_attr_set.dep_amount = REGEXP_REPLACE(tmp_attr_set.dep_amount, E'[^0-9\\.]', '', 'g');
9663
9664             tmp_attr_set.pr := NULLIF( tmp_attr_set.pr, '' );
9665             tmp_attr_set.dep_amount := NULLIF( tmp_attr_set.dep_amount, '' );
9666     
9667             SELECT id INTO attr_set.owning_lib FROM actor.org_unit WHERE shortname = UPPER(tmp_attr_set.ol); -- INT
9668             SELECT id INTO attr_set.circ_lib FROM actor.org_unit WHERE shortname = UPPER(tmp_attr_set.clib); -- INT
9669             SELECT id INTO attr_set.status FROM config.copy_status WHERE LOWER(name) = LOWER(tmp_attr_set.cs); -- INT
9670     
9671             SELECT  id INTO attr_set.location
9672               FROM  asset.copy_location
9673               WHERE LOWER(name) = LOWER(tmp_attr_set.cl)
9674                     AND asset.copy_location.owning_lib = COALESCE(attr_set.owning_lib, attr_set.circ_lib); -- INT
9675     
9676             attr_set.circulate      :=
9677                 LOWER( SUBSTRING( tmp_attr_set.circ, 1, 1)) IN ('t','y','1')
9678                 OR LOWER(tmp_attr_set.circ) = 'circulating'; -- BOOL
9679
9680             attr_set.deposit        :=
9681                 LOWER( SUBSTRING( tmp_attr_set.dep, 1, 1 ) ) IN ('t','y','1')
9682                 OR LOWER(tmp_attr_set.dep) = 'deposit'; -- BOOL
9683
9684             attr_set.holdable       :=
9685                 LOWER( SUBSTRING( tmp_attr_set.hold, 1, 1 ) ) IN ('t','y','1')
9686                 OR LOWER(tmp_attr_set.hold) = 'holdable'; -- BOOL
9687
9688             attr_set.opac_visible   :=
9689                 LOWER( SUBSTRING( tmp_attr_set.opac_vis, 1, 1 ) ) IN ('t','y','1')
9690                 OR LOWER(tmp_attr_set.opac_vis) = 'visible'; -- BOOL
9691
9692             attr_set.ref            :=
9693                 LOWER( SUBSTRING( tmp_attr_set.r, 1, 1 ) ) IN ('t','y','1')
9694                 OR LOWER(tmp_attr_set.r) = 'reference'; -- BOOL
9695     
9696             attr_set.copy_number    := tmp_attr_set.cnum::INT; -- INT,
9697             attr_set.deposit_amount := tmp_attr_set.dep_amount::NUMERIC(6,2); -- NUMERIC(6,2),
9698             attr_set.price          := tmp_attr_set.pr::NUMERIC(8,2); -- NUMERIC(8,2),
9699     
9700             attr_set.call_number    := tmp_attr_set.cn; -- TEXT
9701             attr_set.barcode        := tmp_attr_set.bc; -- TEXT,
9702             attr_set.circ_modifier  := tmp_attr_set.circ_mod; -- TEXT,
9703             attr_set.circ_as_type   := tmp_attr_set.circ_as; -- TEXT,
9704             attr_set.alert_message  := tmp_attr_set.amessage; -- TEXT,
9705             attr_set.pub_note       := tmp_attr_set.note; -- TEXT,
9706             attr_set.priv_note      := tmp_attr_set.pnote; -- TEXT,
9707             attr_set.alert_message  := tmp_attr_set.amessage; -- TEXT,
9708     
9709             RETURN NEXT attr_set;
9710     
9711         END LOOP;
9712     
9713     END IF;
9714
9715     RETURN;
9716
9717 END;
9718 $$ LANGUAGE PLPGSQL;
9719
9720 CREATE OR REPLACE FUNCTION vandelay.ingest_bib_items ( ) RETURNS TRIGGER AS $func$
9721 DECLARE
9722     attr_def    BIGINT;
9723     item_data   vandelay.import_item%ROWTYPE;
9724 BEGIN
9725
9726     SELECT item_attr_def INTO attr_def FROM vandelay.bib_queue WHERE id = NEW.queue;
9727
9728     FOR item_data IN SELECT * FROM vandelay.ingest_items( NEW.id::BIGINT, attr_def ) LOOP
9729         INSERT INTO vandelay.import_item (
9730             record,
9731             definition,
9732             owning_lib,
9733             circ_lib,
9734             call_number,
9735             copy_number,
9736             status,
9737             location,
9738             circulate,
9739             deposit,
9740             deposit_amount,
9741             ref,
9742             holdable,
9743             price,
9744             barcode,
9745             circ_modifier,
9746             circ_as_type,
9747             alert_message,
9748             pub_note,
9749             priv_note,
9750             opac_visible
9751         ) VALUES (
9752             NEW.id,
9753             item_data.definition,
9754             item_data.owning_lib,
9755             item_data.circ_lib,
9756             item_data.call_number,
9757             item_data.copy_number,
9758             item_data.status,
9759             item_data.location,
9760             item_data.circulate,
9761             item_data.deposit,
9762             item_data.deposit_amount,
9763             item_data.ref,
9764             item_data.holdable,
9765             item_data.price,
9766             item_data.barcode,
9767             item_data.circ_modifier,
9768             item_data.circ_as_type,
9769             item_data.alert_message,
9770             item_data.pub_note,
9771             item_data.priv_note,
9772             item_data.opac_visible
9773         );
9774     END LOOP;
9775
9776     RETURN NULL;
9777 END;
9778 $func$ LANGUAGE PLPGSQL;
9779
9780 CREATE OR REPLACE FUNCTION acq.create_acq_seq     ( sch TEXT, tbl TEXT ) RETURNS BOOL AS $creator$
9781 BEGIN
9782     EXECUTE $$
9783         CREATE SEQUENCE acq.$$ || sch || $$_$$ || tbl || $$_pkey_seq;
9784     $$;
9785         RETURN TRUE;
9786 END;
9787 $creator$ LANGUAGE 'plpgsql';
9788
9789 CREATE OR REPLACE FUNCTION acq.create_acq_history ( sch TEXT, tbl TEXT ) RETURNS BOOL AS $creator$
9790 BEGIN
9791     EXECUTE $$
9792         CREATE TABLE acq.$$ || sch || $$_$$ || tbl || $$_history (
9793             audit_id    BIGINT                          PRIMARY KEY,
9794             audit_time  TIMESTAMP WITH TIME ZONE        NOT NULL,
9795             audit_action        TEXT                            NOT NULL,
9796             LIKE $$ || sch || $$.$$ || tbl || $$
9797         );
9798     $$;
9799         RETURN TRUE;
9800 END;
9801 $creator$ LANGUAGE 'plpgsql';
9802
9803 CREATE OR REPLACE FUNCTION acq.create_acq_func    ( sch TEXT, tbl TEXT ) RETURNS BOOL AS $creator$
9804 BEGIN
9805     EXECUTE $$
9806         CREATE OR REPLACE FUNCTION acq.audit_$$ || sch || $$_$$ || tbl || $$_func ()
9807         RETURNS TRIGGER AS $func$
9808         BEGIN
9809             INSERT INTO acq.$$ || sch || $$_$$ || tbl || $$_history
9810                 SELECT  nextval('acq.$$ || sch || $$_$$ || tbl || $$_pkey_seq'),
9811                     now(),
9812                     SUBSTR(TG_OP,1,1),
9813                     OLD.*;
9814             RETURN NULL;
9815         END;
9816         $func$ LANGUAGE 'plpgsql';
9817     $$;
9818         RETURN TRUE;
9819 END;
9820 $creator$ LANGUAGE 'plpgsql';
9821
9822 CREATE OR REPLACE FUNCTION acq.create_acq_update_trigger ( sch TEXT, tbl TEXT ) RETURNS BOOL AS $creator$
9823 BEGIN
9824     EXECUTE $$
9825         CREATE TRIGGER audit_$$ || sch || $$_$$ || tbl || $$_update_trigger
9826             AFTER UPDATE OR DELETE ON $$ || sch || $$.$$ || tbl || $$ FOR EACH ROW
9827             EXECUTE PROCEDURE acq.audit_$$ || sch || $$_$$ || tbl || $$_func ();
9828     $$;
9829         RETURN TRUE;
9830 END;
9831 $creator$ LANGUAGE 'plpgsql';
9832
9833 CREATE OR REPLACE FUNCTION acq.create_acq_lifecycle     ( sch TEXT, tbl TEXT ) RETURNS BOOL AS $creator$
9834 BEGIN
9835     EXECUTE $$
9836         CREATE OR REPLACE VIEW acq.$$ || sch || $$_$$ || tbl || $$_lifecycle AS
9837             SELECT      -1, now() as audit_time, '-' as audit_action, *
9838               FROM      $$ || sch || $$.$$ || tbl || $$
9839                 UNION ALL
9840             SELECT      *
9841               FROM      acq.$$ || sch || $$_$$ || tbl || $$_history;
9842     $$;
9843         RETURN TRUE;
9844 END;
9845 $creator$ LANGUAGE 'plpgsql';
9846
9847 -- The main event
9848
9849 CREATE OR REPLACE FUNCTION acq.create_acq_auditor ( sch TEXT, tbl TEXT ) RETURNS BOOL AS $creator$
9850 BEGIN
9851     PERFORM acq.create_acq_seq(sch, tbl);
9852     PERFORM acq.create_acq_history(sch, tbl);
9853     PERFORM acq.create_acq_func(sch, tbl);
9854     PERFORM acq.create_acq_update_trigger(sch, tbl);
9855     PERFORM acq.create_acq_lifecycle(sch, tbl);
9856     RETURN TRUE;
9857 END;
9858 $creator$ LANGUAGE 'plpgsql';
9859
9860 SELECT acq.create_acq_auditor ( 'acq', 'purchase_order' );
9861 CREATE INDEX acq_po_hist_id_idx            ON acq.acq_purchase_order_history( id );
9862
9863 SELECT acq.create_acq_auditor ( 'acq', 'lineitem' );
9864 CREATE INDEX acq_lineitem_hist_id_idx            ON acq.acq_lineitem_history( id );
9865
9866 CREATE OR REPLACE VIEW acq.fund_debit_total AS
9867     SELECT  fund.id AS fund,
9868             fund_debit.encumbrance AS encumbrance,
9869             SUM( COALESCE( fund_debit.amount, 0 ) ) AS amount
9870       FROM acq.fund AS fund
9871                         LEFT JOIN acq.fund_debit AS fund_debit
9872                                 ON ( fund.id = fund_debit.fund )
9873       GROUP BY 1,2;
9874
9875 CREATE TABLE acq.debit_attribution (
9876         id                     INT         NOT NULL PRIMARY KEY,
9877         fund_debit             INT         NOT NULL
9878                                            REFERENCES acq.fund_debit
9879                                            DEFERRABLE INITIALLY DEFERRED,
9880     debit_amount           NUMERIC     NOT NULL,
9881         funding_source_credit  INT         REFERENCES acq.funding_source_credit
9882                                            DEFERRABLE INITIALLY DEFERRED,
9883     credit_amount          NUMERIC
9884 );
9885
9886 CREATE INDEX acq_attribution_debit_idx
9887         ON acq.debit_attribution( fund_debit );
9888
9889 CREATE INDEX acq_attribution_credit_idx
9890         ON acq.debit_attribution( funding_source_credit );
9891
9892 CREATE OR REPLACE FUNCTION acq.attribute_debits() RETURNS VOID AS $$
9893 /*
9894 Function to attribute expenditures and encumbrances to funding source credits,
9895 and thereby to funding sources.
9896
9897 Read the debits in chonological order, attributing each one to one or
9898 more funding source credits.  Constraints:
9899
9900 1. Don't attribute more to a credit than the amount of the credit.
9901
9902 2. For a given fund, don't attribute more to a funding source than the
9903 source has allocated to that fund.
9904
9905 3. Attribute debits to credits with deadlines before attributing them to
9906 credits without deadlines.  Otherwise attribute to the earliest credits
9907 first, based on the deadline date when present, or on the effective date
9908 when there is no deadline.  Use funding_source_credit.id as a tie-breaker.
9909 This ordering is defined by an ORDER BY clause on the view
9910 acq.ordered_funding_source_credit.
9911
9912 Start by truncating the table acq.debit_attribution.  Then insert a row
9913 into that table for each attribution.  If a debit cannot be fully
9914 attributed, insert a row for the unattributable balance, with the 
9915 funding_source_credit and credit_amount columns NULL.
9916 */
9917 DECLARE
9918         curr_fund_source_bal RECORD;
9919         seqno                INT;     -- sequence num for credits applicable to a fund
9920         fund_credit          RECORD;  -- current row in temp t_fund_credit table
9921         fc                   RECORD;  -- used for loading t_fund_credit table
9922         sc                   RECORD;  -- used for loading t_fund_credit table
9923         --
9924         -- Used exclusively in the main loop:
9925         --
9926         deb                 RECORD;   -- current row from acq.fund_debit table
9927         curr_credit_bal     RECORD;   -- current row from temp t_credit table
9928         debit_balance       NUMERIC;  -- amount left to attribute for current debit
9929         conv_debit_balance  NUMERIC;  -- debit balance in currency of the fund
9930         attr_amount         NUMERIC;  -- amount being attributed, in currency of debit
9931         conv_attr_amount    NUMERIC;  -- amount being attributed, in currency of source
9932         conv_cred_balance   NUMERIC;  -- credit_balance in the currency of the fund
9933         conv_alloc_balance  NUMERIC;  -- allocated balance in the currency of the fund
9934         attrib_count        INT;      -- populates id of acq.debit_attribution
9935 BEGIN
9936         --
9937         -- Load a temporary table.  For each combination of fund and funding source,
9938         -- load an entry with the total amount allocated to that fund by that source.
9939         -- This sum may reflect transfers as well as original allocations.  We will
9940         -- reduce this balance whenever we attribute debits to it.
9941         --
9942         CREATE TEMP TABLE t_fund_source_bal
9943         ON COMMIT DROP AS
9944                 SELECT
9945                         fund AS fund,
9946                         funding_source AS source,
9947                         sum( amount ) AS balance
9948                 FROM
9949                         acq.fund_allocation
9950                 GROUP BY
9951                         fund,
9952                         funding_source
9953                 HAVING
9954                         sum( amount ) > 0;
9955         --
9956         CREATE INDEX t_fund_source_bal_idx
9957                 ON t_fund_source_bal( fund, source );
9958         -------------------------------------------------------------------------------
9959         --
9960         -- Load another temporary table.  For each fund, load zero or more
9961         -- funding source credits from which that fund can get money.
9962         --
9963         CREATE TEMP TABLE t_fund_credit (
9964                 fund        INT,
9965                 seq         INT,
9966                 credit      INT
9967         ) ON COMMIT DROP;
9968         --
9969         FOR fc IN
9970                 SELECT DISTINCT fund
9971                 FROM acq.fund_allocation
9972                 ORDER BY fund
9973         LOOP                  -- Loop over the funds
9974                 seqno := 1;
9975                 FOR sc IN
9976                         SELECT
9977                                 ofsc.id
9978                         FROM
9979                                 acq.ordered_funding_source_credit AS ofsc
9980                         WHERE
9981                                 ofsc.funding_source IN
9982                                 (
9983                                         SELECT funding_source
9984                                         FROM acq.fund_allocation
9985                                         WHERE fund = fc.fund
9986                                 )
9987                 ORDER BY
9988                     ofsc.sort_priority,
9989                     ofsc.sort_date,
9990                     ofsc.id
9991                 LOOP                        -- Add each credit to the list
9992                         INSERT INTO t_fund_credit (
9993                                 fund,
9994                                 seq,
9995                                 credit
9996                         ) VALUES (
9997                                 fc.fund,
9998                                 seqno,
9999                                 sc.id
10000                         );
10001                         --RAISE NOTICE 'Fund % credit %', fc.fund, sc.id;
10002                         seqno := seqno + 1;
10003                 END LOOP;     -- Loop over credits for a given fund
10004         END LOOP;         -- Loop over funds
10005         --
10006         CREATE INDEX t_fund_credit_idx
10007                 ON t_fund_credit( fund, seq );
10008         -------------------------------------------------------------------------------
10009         --
10010         -- Load yet another temporary table.  This one is a list of funding source
10011         -- credits, with their balances.  We shall reduce those balances as we
10012         -- attribute debits to them.
10013         --
10014         CREATE TEMP TABLE t_credit
10015         ON COMMIT DROP AS
10016         SELECT
10017             fsc.id AS credit,
10018             fsc.funding_source AS source,
10019             fsc.amount AS balance,
10020             fs.currency_type AS currency_type
10021         FROM
10022             acq.funding_source_credit AS fsc,
10023             acq.funding_source fs
10024         WHERE
10025             fsc.funding_source = fs.id
10026                         AND fsc.amount > 0;
10027         --
10028         CREATE INDEX t_credit_idx
10029                 ON t_credit( credit );
10030         --
10031         -------------------------------------------------------------------------------
10032         --
10033         -- Now that we have loaded the lookup tables: loop through the debits,
10034         -- attributing each one to one or more funding source credits.
10035         -- 
10036         truncate table acq.debit_attribution;
10037         --
10038         attrib_count := 0;
10039         FOR deb in
10040                 SELECT
10041                         fd.id,
10042                         fd.fund,
10043                         fd.amount,
10044                         f.currency_type,
10045                         fd.encumbrance
10046                 FROM
10047                         acq.fund_debit fd,
10048                         acq.fund f
10049                 WHERE
10050                         fd.fund = f.id
10051                 ORDER BY
10052                         fd.id
10053         LOOP
10054                 --RAISE NOTICE 'Debit %, fund %', deb.id, deb.fund;
10055                 --
10056                 debit_balance := deb.amount;
10057                 --
10058                 -- Loop over the funding source credits that are eligible
10059                 -- to pay for this debit
10060                 --
10061                 FOR fund_credit IN
10062                         SELECT
10063                                 credit
10064                         FROM
10065                                 t_fund_credit
10066                         WHERE
10067                                 fund = deb.fund
10068                         ORDER BY
10069                                 seq
10070                 LOOP
10071                         --RAISE NOTICE '   Examining credit %', fund_credit.credit;
10072                         --
10073                         -- Look up the balance for this credit.  If it's zero, then
10074                         -- it's not useful, so treat it as if you didn't find it.
10075                         -- (Actually there shouldn't be any zero balances in the table,
10076                         -- but we check just to make sure.)
10077                         --
10078                         SELECT *
10079                         INTO curr_credit_bal
10080                         FROM t_credit
10081                         WHERE
10082                                 credit = fund_credit.credit
10083                                 AND balance > 0;
10084                         --
10085                         IF curr_credit_bal IS NULL THEN
10086                                 --
10087                                 -- This credit is exhausted; try the next one.
10088                                 --
10089                                 CONTINUE;
10090                         END IF;
10091                         --
10092                         --
10093                         -- At this point we have an applicable credit with some money left.
10094                         -- Now see if the relevant funding_source has any money left.
10095                         --
10096                         -- Look up the balance of the allocation for this combination of
10097                         -- fund and source.  If you find such an entry, but it has a zero
10098                         -- balance, then it's not useful, so treat it as unfound.
10099                         -- (Actually there shouldn't be any zero balances in the table,
10100                         -- but we check just to make sure.)
10101                         --
10102                         SELECT *
10103                         INTO curr_fund_source_bal
10104                         FROM t_fund_source_bal
10105                         WHERE
10106                                 fund = deb.fund
10107                                 AND source = curr_credit_bal.source
10108                                 AND balance > 0;
10109                         --
10110                         IF curr_fund_source_bal IS NULL THEN
10111                                 --
10112                                 -- This fund/source doesn't exist or is already exhausted,
10113                                 -- so we can't use this credit.  Go on to the next one.
10114                                 --
10115                                 CONTINUE;
10116                         END IF;
10117                         --
10118                         -- Convert the available balances to the currency of the fund
10119                         --
10120                         conv_alloc_balance := curr_fund_source_bal.balance * acq.exchange_ratio(
10121                                 curr_credit_bal.currency_type, deb.currency_type );
10122                         conv_cred_balance := curr_credit_bal.balance * acq.exchange_ratio(
10123                                 curr_credit_bal.currency_type, deb.currency_type );
10124                         --
10125                         -- Determine how much we can attribute to this credit: the minimum
10126                         -- of the debit amount, the fund/source balance, and the
10127                         -- credit balance
10128                         --
10129                         --RAISE NOTICE '   deb bal %', debit_balance;
10130                         --RAISE NOTICE '      source % balance %', curr_credit_bal.source, conv_alloc_balance;
10131                         --RAISE NOTICE '      credit % balance %', curr_credit_bal.credit, conv_cred_balance;
10132                         --
10133                         conv_attr_amount := NULL;
10134                         attr_amount := debit_balance;
10135                         --
10136                         IF attr_amount > conv_alloc_balance THEN
10137                                 attr_amount := conv_alloc_balance;
10138                                 conv_attr_amount := curr_fund_source_bal.balance;
10139                         END IF;
10140                         IF attr_amount > conv_cred_balance THEN
10141                                 attr_amount := conv_cred_balance;
10142                                 conv_attr_amount := curr_credit_bal.balance;
10143                         END IF;
10144                         --
10145                         -- If we're attributing all of one of the balances, then that's how
10146                         -- much we will deduct from the balances, and we already captured
10147                         -- that amount above.  Otherwise we must convert the amount of the
10148                         -- attribution from the currency of the fund back to the currency of
10149                         -- the funding source.
10150                         --
10151                         IF conv_attr_amount IS NULL THEN
10152                                 conv_attr_amount := attr_amount * acq.exchange_ratio(
10153                                         deb.currency_type, curr_credit_bal.currency_type );
10154                         END IF;
10155                         --
10156                         -- Insert a row to record the attribution
10157                         --
10158                         attrib_count := attrib_count + 1;
10159                         INSERT INTO acq.debit_attribution (
10160                                 id,
10161                                 fund_debit,
10162                                 debit_amount,
10163                                 funding_source_credit,
10164                                 credit_amount
10165                         ) VALUES (
10166                                 attrib_count,
10167                                 deb.id,
10168                                 attr_amount,
10169                                 curr_credit_bal.credit,
10170                                 conv_attr_amount
10171                         );
10172                         --
10173                         -- Subtract the attributed amount from the various balances
10174                         --
10175                         debit_balance := debit_balance - attr_amount;
10176                         curr_fund_source_bal.balance := curr_fund_source_bal.balance - conv_attr_amount;
10177                         --
10178                         IF curr_fund_source_bal.balance <= 0 THEN
10179                                 --
10180                                 -- This allocation is exhausted.  Delete it so
10181                                 -- that we don't waste time looking at it again.
10182                                 --
10183                                 DELETE FROM t_fund_source_bal
10184                                 WHERE
10185                                         fund = curr_fund_source_bal.fund
10186                                         AND source = curr_fund_source_bal.source;
10187                         ELSE
10188                                 UPDATE t_fund_source_bal
10189                                 SET balance = balance - conv_attr_amount
10190                                 WHERE
10191                                         fund = curr_fund_source_bal.fund
10192                                         AND source = curr_fund_source_bal.source;
10193                         END IF;
10194                         --
10195                         IF curr_credit_bal.balance <= 0 THEN
10196                                 --
10197                                 -- This funding source credit is exhausted.  Delete it
10198                                 -- so that we don't waste time looking at it again.
10199                                 --
10200                                 --DELETE FROM t_credit
10201                                 --WHERE
10202                                 --      credit = curr_credit_bal.credit;
10203                                 --
10204                                 DELETE FROM t_fund_credit
10205                                 WHERE
10206                                         credit = curr_credit_bal.credit;
10207                         ELSE
10208                                 UPDATE t_credit
10209                                 SET balance = curr_credit_bal.balance
10210                                 WHERE
10211                                         credit = curr_credit_bal.credit;
10212                         END IF;
10213                         --
10214                         -- Are we done with this debit yet?
10215                         --
10216                         IF debit_balance <= 0 THEN
10217                                 EXIT;       -- We've fully attributed this debit; stop looking at credits.
10218                         END IF;
10219                 END LOOP;       -- End loop over credits
10220                 --
10221                 IF debit_balance <> 0 THEN
10222                         --
10223                         -- We weren't able to attribute this debit, or at least not
10224                         -- all of it.  Insert a row for the unattributed balance.
10225                         --
10226                         attrib_count := attrib_count + 1;
10227                         INSERT INTO acq.debit_attribution (
10228                                 id,
10229                                 fund_debit,
10230                                 debit_amount,
10231                                 funding_source_credit,
10232                                 credit_amount
10233                         ) VALUES (
10234                                 attrib_count,
10235                                 deb.id,
10236                                 debit_balance,
10237                                 NULL,
10238                                 NULL
10239                         );
10240                 END IF;
10241         END LOOP;   -- End of loop over debits
10242 END;
10243 $$ LANGUAGE 'plpgsql';
10244
10245 CREATE OR REPLACE FUNCTION extract_marc_field ( TEXT, BIGINT, TEXT, TEXT ) RETURNS TEXT AS $$
10246 DECLARE
10247     query TEXT;
10248     output TEXT;
10249 BEGIN
10250     query := $q$
10251         SELECT  regexp_replace(
10252                     oils_xpath_string(
10253                         $q$ || quote_literal($3) || $q$,
10254                         marc,
10255                         ' '
10256                     ),
10257                     $q$ || quote_literal($4) || $q$,
10258                     '',
10259                     'g')
10260           FROM  $q$ || $1 || $q$
10261           WHERE id = $q$ || $2;
10262
10263     EXECUTE query INTO output;
10264
10265     -- RAISE NOTICE 'query: %, output; %', query, output;
10266
10267     RETURN output;
10268 END;
10269 $$ LANGUAGE PLPGSQL IMMUTABLE;
10270
10271 CREATE OR REPLACE FUNCTION extract_marc_field ( TEXT, BIGINT, TEXT ) RETURNS TEXT AS $$
10272     SELECT extract_marc_field($1,$2,$3,'');
10273 $$ LANGUAGE SQL IMMUTABLE;
10274
10275 CREATE OR REPLACE FUNCTION asset.merge_record_assets( target_record BIGINT, source_record BIGINT ) RETURNS INT AS $func$
10276 DECLARE
10277     moved_objects INT := 0;
10278     source_cn     asset.call_number%ROWTYPE;
10279     target_cn     asset.call_number%ROWTYPE;
10280     metarec       metabib.metarecord%ROWTYPE;
10281     hold          action.hold_request%ROWTYPE;
10282     ser_rec       serial.record_entry%ROWTYPE;
10283     uri_count     INT := 0;
10284     counter       INT := 0;
10285     uri_datafield TEXT;
10286     uri_text      TEXT := '';
10287 BEGIN
10288
10289     -- move any 856 entries on records that have at least one MARC-mapped URI entry
10290     SELECT  INTO uri_count COUNT(*)
10291       FROM  asset.uri_call_number_map m
10292             JOIN asset.call_number cn ON (m.call_number = cn.id)
10293       WHERE cn.record = source_record;
10294
10295     IF uri_count > 0 THEN
10296
10297         SELECT  COUNT(*) INTO counter
10298           FROM  oils_xpath_table(
10299                     'id',
10300                     'marc',
10301                     'biblio.record_entry',
10302                     '//*[@tag="856"]',
10303                     'id=' || source_record
10304                 ) as t(i int,c text);
10305
10306         FOR i IN 1 .. counter LOOP
10307             SELECT  '<datafield xmlns="http://www.loc.gov/MARC21/slim"' ||
10308                         ' tag="856"' || 
10309                         ' ind1="' || FIRST(ind1) || '"'  || 
10310                         ' ind2="' || FIRST(ind2) || '">' || 
10311                         array_to_string(
10312                             array_accum(
10313                                 '<subfield code="' || subfield || '">' ||
10314                                 regexp_replace(
10315                                     regexp_replace(
10316                                         regexp_replace(data,'&','&amp;','g'),
10317                                         '>', '&gt;', 'g'
10318                                     ),
10319                                     '<', '&lt;', 'g'
10320                                 ) || '</subfield>'
10321                             ), ''
10322                         ) || '</datafield>' INTO uri_datafield
10323               FROM  oils_xpath_table(
10324                         'id',
10325                         'marc',
10326                         'biblio.record_entry',
10327                         '//*[@tag="856"][position()=' || i || ']/@ind1|' || 
10328                         '//*[@tag="856"][position()=' || i || ']/@ind2|' || 
10329                         '//*[@tag="856"][position()=' || i || ']/*/@code|' ||
10330                         '//*[@tag="856"][position()=' || i || ']/*[@code]',
10331                         'id=' || source_record
10332                     ) as t(id int,ind1 text, ind2 text,subfield text,data text);
10333
10334             uri_text := uri_text || uri_datafield;
10335         END LOOP;
10336
10337         IF uri_text <> '' THEN
10338             UPDATE  biblio.record_entry
10339               SET   marc = regexp_replace(marc,'(</[^>]*record>)', uri_text || E'\\1')
10340               WHERE id = target_record;
10341         END IF;
10342
10343     END IF;
10344
10345     -- Find and move metarecords to the target record
10346     SELECT  INTO metarec *
10347       FROM  metabib.metarecord
10348       WHERE master_record = source_record;
10349
10350     IF FOUND THEN
10351         UPDATE  metabib.metarecord
10352           SET   master_record = target_record,
10353             mods = NULL
10354           WHERE id = metarec.id;
10355
10356         moved_objects := moved_objects + 1;
10357     END IF;
10358
10359     -- Find call numbers attached to the source ...
10360     FOR source_cn IN SELECT * FROM asset.call_number WHERE record = source_record LOOP
10361
10362         SELECT  INTO target_cn *
10363           FROM  asset.call_number
10364           WHERE label = source_cn.label
10365             AND owning_lib = source_cn.owning_lib
10366             AND record = target_record;
10367
10368         -- ... and if there's a conflicting one on the target ...
10369         IF FOUND THEN
10370
10371             -- ... move the copies to that, and ...
10372             UPDATE  asset.copy
10373               SET   call_number = target_cn.id
10374               WHERE call_number = source_cn.id;
10375
10376             -- ... move V holds to the move-target call number
10377             FOR hold IN SELECT * FROM action.hold_request WHERE target = source_cn.id AND hold_type = 'V' LOOP
10378
10379                 UPDATE  action.hold_request
10380                   SET   target = target_cn.id
10381                   WHERE id = hold.id;
10382
10383                 moved_objects := moved_objects + 1;
10384             END LOOP;
10385
10386         -- ... if not ...
10387         ELSE
10388             -- ... just move the call number to the target record
10389             UPDATE  asset.call_number
10390               SET   record = target_record
10391               WHERE id = source_cn.id;
10392         END IF;
10393
10394         moved_objects := moved_objects + 1;
10395     END LOOP;
10396
10397     -- Find T holds targeting the source record ...
10398     FOR hold IN SELECT * FROM action.hold_request WHERE target = source_record AND hold_type = 'T' LOOP
10399
10400         -- ... and move them to the target record
10401         UPDATE  action.hold_request
10402           SET   target = target_record
10403           WHERE id = hold.id;
10404
10405         moved_objects := moved_objects + 1;
10406     END LOOP;
10407
10408     -- Find serial records targeting the source record ...
10409     FOR ser_rec IN SELECT * FROM serial.record_entry WHERE record = source_record LOOP
10410         -- ... and move them to the target record
10411         UPDATE  serial.record_entry
10412           SET   record = target_record
10413           WHERE id = ser_rec.id;
10414
10415         moved_objects := moved_objects + 1;
10416     END LOOP;
10417
10418     -- Finally, "delete" the source record
10419     DELETE FROM biblio.record_entry WHERE id = source_record;
10420
10421     -- That's all, folks!
10422     RETURN moved_objects;
10423 END;
10424 $func$ LANGUAGE plpgsql;
10425
10426 CREATE OR REPLACE FUNCTION acq.transfer_fund(
10427         old_fund   IN INT,
10428         old_amount IN NUMERIC,     -- in currency of old fund
10429         new_fund   IN INT,
10430         new_amount IN NUMERIC,     -- in currency of new fund
10431         user_id    IN INT,
10432         xfer_note  IN TEXT         -- to be recorded in acq.fund_transfer
10433         -- ,funding_source_in IN INT  -- if user wants to specify a funding source (see notes)
10434 ) RETURNS VOID AS $$
10435 /* -------------------------------------------------------------------------------
10436
10437 Function to transfer money from one fund to another.
10438
10439 A transfer is represented as a pair of entries in acq.fund_allocation, with a
10440 negative amount for the old (losing) fund and a positive amount for the new
10441 (gaining) fund.  In some cases there may be more than one such pair of entries
10442 in order to pull the money from different funding sources, or more specifically
10443 from different funding source credits.  For each such pair there is also an
10444 entry in acq.fund_transfer.
10445
10446 Since funding_source is a non-nullable column in acq.fund_allocation, we must
10447 choose a funding source for the transferred money to come from.  This choice
10448 must meet two constraints, so far as possible:
10449
10450 1. The amount transferred from a given funding source must not exceed the
10451 amount allocated to the old fund by the funding source.  To that end we
10452 compare the amount being transferred to the amount allocated.
10453
10454 2. We shouldn't transfer money that has already been spent or encumbered, as
10455 defined by the funding attribution process.  We attribute expenses to the
10456 oldest funding source credits first.  In order to avoid transferring that
10457 attributed money, we reverse the priority, transferring from the newest funding
10458 source credits first.  There can be no guarantee that this approach will
10459 avoid overcommitting a fund, but no other approach can do any better.
10460
10461 In this context the age of a funding source credit is defined by the
10462 deadline_date for credits with deadline_dates, and by the effective_date for
10463 credits without deadline_dates, with the proviso that credits with deadline_dates
10464 are all considered "older" than those without.
10465
10466 ----------
10467
10468 In the signature for this function, there is one last parameter commented out,
10469 named "funding_source_in".  Correspondingly, the WHERE clause for the query
10470 driving the main loop has an OR clause commented out, which references the
10471 funding_source_in parameter.
10472
10473 If these lines are uncommented, this function will allow the user optionally to
10474 restrict a fund transfer to a specified funding source.  If the source
10475 parameter is left NULL, then there will be no such restriction.
10476
10477 ------------------------------------------------------------------------------- */ 
10478 DECLARE
10479         same_currency      BOOLEAN;
10480         currency_ratio     NUMERIC;
10481         old_fund_currency  TEXT;
10482         old_remaining      NUMERIC;  -- in currency of old fund
10483         new_fund_currency  TEXT;
10484         new_fund_active    BOOLEAN;
10485         new_remaining      NUMERIC;  -- in currency of new fund
10486         curr_old_amt       NUMERIC;  -- in currency of old fund
10487         curr_new_amt       NUMERIC;  -- in currency of new fund
10488         source_addition    NUMERIC;  -- in currency of funding source
10489         source_deduction   NUMERIC;  -- in currency of funding source
10490         orig_allocated_amt NUMERIC;  -- in currency of funding source
10491         allocated_amt      NUMERIC;  -- in currency of fund
10492         source             RECORD;
10493 BEGIN
10494         --
10495         -- Sanity checks
10496         --
10497         IF old_fund IS NULL THEN
10498                 RAISE EXCEPTION 'acq.transfer_fund: old fund id is NULL';
10499         END IF;
10500         --
10501         IF old_amount IS NULL THEN
10502                 RAISE EXCEPTION 'acq.transfer_fund: amount to transfer is NULL';
10503         END IF;
10504         --
10505         -- The new fund and its amount must be both NULL or both not NULL.
10506         --
10507         IF new_fund IS NOT NULL AND new_amount IS NULL THEN
10508                 RAISE EXCEPTION 'acq.transfer_fund: amount to transfer to receiving fund is NULL';
10509         END IF;
10510         --
10511         IF new_fund IS NULL AND new_amount IS NOT NULL THEN
10512                 RAISE EXCEPTION 'acq.transfer_fund: receiving fund is NULL, its amount is not NULL';
10513         END IF;
10514         --
10515         IF user_id IS NULL THEN
10516                 RAISE EXCEPTION 'acq.transfer_fund: user id is NULL';
10517         END IF;
10518         --
10519         -- Initialize the amounts to be transferred, each denominated
10520         -- in the currency of its respective fund.  They will be
10521         -- reduced on each iteration of the loop.
10522         --
10523         old_remaining := old_amount;
10524         new_remaining := new_amount;
10525         --
10526         -- RAISE NOTICE 'Transferring % in fund % to % in fund %',
10527         --      old_amount, old_fund, new_amount, new_fund;
10528         --
10529         -- Get the currency types of the old and new funds.
10530         --
10531         SELECT
10532                 currency_type
10533         INTO
10534                 old_fund_currency
10535         FROM
10536                 acq.fund
10537         WHERE
10538                 id = old_fund;
10539         --
10540         IF old_fund_currency IS NULL THEN
10541                 RAISE EXCEPTION 'acq.transfer_fund: old fund id % is not defined', old_fund;
10542         END IF;
10543         --
10544         IF new_fund IS NOT NULL THEN
10545                 SELECT
10546                         currency_type,
10547                         active
10548                 INTO
10549                         new_fund_currency,
10550                         new_fund_active
10551                 FROM
10552                         acq.fund
10553                 WHERE
10554                         id = new_fund;
10555                 --
10556                 IF new_fund_currency IS NULL THEN
10557                         RAISE EXCEPTION 'acq.transfer_fund: new fund id % is not defined', new_fund;
10558                 ELSIF NOT new_fund_active THEN
10559                         --
10560                         -- No point in putting money into a fund from whence you can't spend it
10561                         --
10562                         RAISE EXCEPTION 'acq.transfer_fund: new fund id % is inactive', new_fund;
10563                 END IF;
10564                 --
10565                 IF new_amount = old_amount THEN
10566                         same_currency := true;
10567                         currency_ratio := 1;
10568                 ELSE
10569                         --
10570                         -- We'll have to translate currency between funds.  We presume that
10571                         -- the calling code has already applied an appropriate exchange rate,
10572                         -- so we'll apply the same conversion to each sub-transfer.
10573                         --
10574                         same_currency := false;
10575                         currency_ratio := new_amount / old_amount;
10576                 END IF;
10577         END IF;
10578         --
10579         -- Identify the funding source(s) from which we want to transfer the money.
10580         -- The principle is that we want to transfer the newest money first, because
10581         -- we spend the oldest money first.  The priority for spending is defined
10582         -- by a sort of the view acq.ordered_funding_source_credit.
10583         --
10584         FOR source in
10585                 SELECT
10586                         ofsc.id,
10587                         ofsc.funding_source,
10588                         ofsc.amount,
10589                         ofsc.amount * acq.exchange_ratio( fs.currency_type, old_fund_currency )
10590                                 AS converted_amt,
10591                         fs.currency_type
10592                 FROM
10593                         acq.ordered_funding_source_credit AS ofsc,
10594                         acq.funding_source fs
10595                 WHERE
10596                         ofsc.funding_source = fs.id
10597                         and ofsc.funding_source IN
10598                         (
10599                                 SELECT funding_source
10600                                 FROM acq.fund_allocation
10601                                 WHERE fund = old_fund
10602                         )
10603                         -- and
10604                         -- (
10605                         --      ofsc.funding_source = funding_source_in
10606                         --      OR funding_source_in IS NULL
10607                         -- )
10608                 ORDER BY
10609                         ofsc.sort_priority desc,
10610                         ofsc.sort_date desc,
10611                         ofsc.id desc
10612         LOOP
10613                 --
10614                 -- Determine how much money the old fund got from this funding source,
10615                 -- denominated in the currency types of the source and of the fund.
10616                 -- This result may reflect transfers from previous iterations.
10617                 --
10618                 SELECT
10619                         COALESCE( sum( amount ), 0 ),
10620                         COALESCE( sum( amount )
10621                                 * acq.exchange_ratio( source.currency_type, old_fund_currency ), 0 )
10622                 INTO
10623                         orig_allocated_amt,     -- in currency of the source
10624                         allocated_amt           -- in currency of the old fund
10625                 FROM
10626                         acq.fund_allocation
10627                 WHERE
10628                         fund = old_fund
10629                         and funding_source = source.funding_source;
10630                 --      
10631                 -- Determine how much to transfer from this credit, in the currency
10632                 -- of the fund.   Begin with the amount remaining to be attributed:
10633                 --
10634                 curr_old_amt := old_remaining;
10635                 --
10636                 -- Can't attribute more than was allocated from the fund:
10637                 --
10638                 IF curr_old_amt > allocated_amt THEN
10639                         curr_old_amt := allocated_amt;
10640                 END IF;
10641                 --
10642                 -- Can't attribute more than the amount of the current credit:
10643                 --
10644                 IF curr_old_amt > source.converted_amt THEN
10645                         curr_old_amt := source.converted_amt;
10646                 END IF;
10647                 --
10648                 curr_old_amt := trunc( curr_old_amt, 2 );
10649                 --
10650                 old_remaining := old_remaining - curr_old_amt;
10651                 --
10652                 -- Determine the amount to be deducted, if any,
10653                 -- from the old allocation.
10654                 --
10655                 IF old_remaining > 0 THEN
10656                         --
10657                         -- In this case we're using the whole allocation, so use that
10658                         -- amount directly instead of applying a currency translation
10659                         -- and thereby inviting round-off errors.
10660                         --
10661                         source_deduction := - orig_allocated_amt;
10662                 ELSE 
10663                         source_deduction := trunc(
10664                                 ( - curr_old_amt ) *
10665                                         acq.exchange_ratio( old_fund_currency, source.currency_type ),
10666                                 2 );
10667                 END IF;
10668                 --
10669                 IF source_deduction <> 0 THEN
10670                         --
10671                         -- Insert negative allocation for old fund in fund_allocation,
10672                         -- converted into the currency of the funding source
10673                         --
10674                         INSERT INTO acq.fund_allocation (
10675                                 funding_source,
10676                                 fund,
10677                                 amount,
10678                                 allocator,
10679                                 note
10680                         ) VALUES (
10681                                 source.funding_source,
10682                                 old_fund,
10683                                 source_deduction,
10684                                 user_id,
10685                                 'Transfer to fund ' || new_fund
10686                         );
10687                 END IF;
10688                 --
10689                 IF new_fund IS NOT NULL THEN
10690                         --
10691                         -- Determine how much to add to the new fund, in
10692                         -- its currency, and how much remains to be added:
10693                         --
10694                         IF same_currency THEN
10695                                 curr_new_amt := curr_old_amt;
10696                         ELSE
10697                                 IF old_remaining = 0 THEN
10698                                         --
10699                                         -- This is the last iteration, so nothing should be left
10700                                         --
10701                                         curr_new_amt := new_remaining;
10702                                         new_remaining := 0;
10703                                 ELSE
10704                                         curr_new_amt := trunc( curr_old_amt * currency_ratio, 2 );
10705                                         new_remaining := new_remaining - curr_new_amt;
10706                                 END IF;
10707                         END IF;
10708                         --
10709                         -- Determine how much to add, if any,
10710                         -- to the new fund's allocation.
10711                         --
10712                         IF old_remaining > 0 THEN
10713                                 --
10714                                 -- In this case we're using the whole allocation, so use that amount
10715                                 -- amount directly instead of applying a currency translation and
10716                                 -- thereby inviting round-off errors.
10717                                 --
10718                                 source_addition := orig_allocated_amt;
10719                         ELSIF source.currency_type = old_fund_currency THEN
10720                                 --
10721                                 -- In this case we don't need a round trip currency translation,
10722                                 -- thereby inviting round-off errors:
10723                                 --
10724                                 source_addition := curr_old_amt;
10725                         ELSE 
10726                                 source_addition := trunc(
10727                                         curr_new_amt *
10728                                                 acq.exchange_ratio( new_fund_currency, source.currency_type ),
10729                                         2 );
10730                         END IF;
10731                         --
10732                         IF source_addition <> 0 THEN
10733                                 --
10734                                 -- Insert positive allocation for new fund in fund_allocation,
10735                                 -- converted to the currency of the founding source
10736                                 --
10737                                 INSERT INTO acq.fund_allocation (
10738                                         funding_source,
10739                                         fund,
10740                                         amount,
10741                                         allocator,
10742                                         note
10743                                 ) VALUES (
10744                                         source.funding_source,
10745                                         new_fund,
10746                                         source_addition,
10747                                         user_id,
10748                                         'Transfer from fund ' || old_fund
10749                                 );
10750                         END IF;
10751                 END IF;
10752                 --
10753                 IF trunc( curr_old_amt, 2 ) <> 0
10754                 OR trunc( curr_new_amt, 2 ) <> 0 THEN
10755                         --
10756                         -- Insert row in fund_transfer, using amounts in the currency of the funds
10757                         --
10758                         INSERT INTO acq.fund_transfer (
10759                                 src_fund,
10760                                 src_amount,
10761                                 dest_fund,
10762                                 dest_amount,
10763                                 transfer_user,
10764                                 note,
10765                                 funding_source_credit
10766                         ) VALUES (
10767                                 old_fund,
10768                                 trunc( curr_old_amt, 2 ),
10769                                 new_fund,
10770                                 trunc( curr_new_amt, 2 ),
10771                                 user_id,
10772                                 xfer_note,
10773                                 source.id
10774                         );
10775                 END IF;
10776                 --
10777                 if old_remaining <= 0 THEN
10778                         EXIT;                   -- Nothing more to be transferred
10779                 END IF;
10780         END LOOP;
10781 END;
10782 $$ LANGUAGE plpgsql;
10783
10784 CREATE OR REPLACE FUNCTION acq.propagate_funds_by_org_unit(
10785         old_year INTEGER,
10786         user_id INTEGER,
10787         org_unit_id INTEGER
10788 ) RETURNS VOID AS $$
10789 DECLARE
10790 --
10791 new_id      INT;
10792 old_fund    RECORD;
10793 org_found   BOOLEAN;
10794 --
10795 BEGIN
10796         --
10797         -- Sanity checks
10798         --
10799         IF old_year IS NULL THEN
10800                 RAISE EXCEPTION 'Input year argument is NULL';
10801         ELSIF old_year NOT BETWEEN 2008 and 2200 THEN
10802                 RAISE EXCEPTION 'Input year is out of range';
10803         END IF;
10804         --
10805         IF user_id IS NULL THEN
10806                 RAISE EXCEPTION 'Input user id argument is NULL';
10807         END IF;
10808         --
10809         IF org_unit_id IS NULL THEN
10810                 RAISE EXCEPTION 'Org unit id argument is NULL';
10811         ELSE
10812                 SELECT TRUE INTO org_found
10813                 FROM actor.org_unit
10814                 WHERE id = org_unit_id;
10815                 --
10816                 IF org_found IS NULL THEN
10817                         RAISE EXCEPTION 'Org unit id is invalid';
10818                 END IF;
10819         END IF;
10820         --
10821         -- Loop over the applicable funds
10822         --
10823         FOR old_fund in SELECT * FROM acq.fund
10824         WHERE
10825                 year = old_year
10826                 AND propagate
10827                 AND org = org_unit_id
10828         LOOP
10829                 BEGIN
10830                         INSERT INTO acq.fund (
10831                                 org,
10832                                 name,
10833                                 year,
10834                                 currency_type,
10835                                 code,
10836                                 rollover,
10837                                 propagate,
10838                                 balance_warning_percent,
10839                                 balance_stop_percent
10840                         ) VALUES (
10841                                 old_fund.org,
10842                                 old_fund.name,
10843                                 old_year + 1,
10844                                 old_fund.currency_type,
10845                                 old_fund.code,
10846                                 old_fund.rollover,
10847                                 true,
10848                                 old_fund.balance_warning_percent,
10849                                 old_fund.balance_stop_percent
10850                         )
10851                         RETURNING id INTO new_id;
10852                 EXCEPTION
10853                         WHEN unique_violation THEN
10854                                 --RAISE NOTICE 'Fund % already propagated', old_fund.id;
10855                                 CONTINUE;
10856                 END;
10857                 --RAISE NOTICE 'Propagating fund % to fund %',
10858                 --      old_fund.code, new_id;
10859         END LOOP;
10860 END;
10861 $$ LANGUAGE plpgsql;
10862
10863 CREATE OR REPLACE FUNCTION acq.propagate_funds_by_org_tree(
10864         old_year INTEGER,
10865         user_id INTEGER,
10866         org_unit_id INTEGER
10867 ) RETURNS VOID AS $$
10868 DECLARE
10869 --
10870 new_id      INT;
10871 old_fund    RECORD;
10872 org_found   BOOLEAN;
10873 --
10874 BEGIN
10875         --
10876         -- Sanity checks
10877         --
10878         IF old_year IS NULL THEN
10879                 RAISE EXCEPTION 'Input year argument is NULL';
10880         ELSIF old_year NOT BETWEEN 2008 and 2200 THEN
10881                 RAISE EXCEPTION 'Input year is out of range';
10882         END IF;
10883         --
10884         IF user_id IS NULL THEN
10885                 RAISE EXCEPTION 'Input user id argument is NULL';
10886         END IF;
10887         --
10888         IF org_unit_id IS NULL THEN
10889                 RAISE EXCEPTION 'Org unit id argument is NULL';
10890         ELSE
10891                 SELECT TRUE INTO org_found
10892                 FROM actor.org_unit
10893                 WHERE id = org_unit_id;
10894                 --
10895                 IF org_found IS NULL THEN
10896                         RAISE EXCEPTION 'Org unit id is invalid';
10897                 END IF;
10898         END IF;
10899         --
10900         -- Loop over the applicable funds
10901         --
10902         FOR old_fund in SELECT * FROM acq.fund
10903         WHERE
10904                 year = old_year
10905                 AND propagate
10906                 AND org in (
10907                         SELECT id FROM actor.org_unit_descendants( org_unit_id )
10908                 )
10909         LOOP
10910                 BEGIN
10911                         INSERT INTO acq.fund (
10912                                 org,
10913                                 name,
10914                                 year,
10915                                 currency_type,
10916                                 code,
10917                                 rollover,
10918                                 propagate,
10919                                 balance_warning_percent,
10920                                 balance_stop_percent
10921                         ) VALUES (
10922                                 old_fund.org,
10923                                 old_fund.name,
10924                                 old_year + 1,
10925                                 old_fund.currency_type,
10926                                 old_fund.code,
10927                                 old_fund.rollover,
10928                                 true,
10929                                 old_fund.balance_warning_percent,
10930                                 old_fund.balance_stop_percent
10931                         )
10932                         RETURNING id INTO new_id;
10933                 EXCEPTION
10934                         WHEN unique_violation THEN
10935                                 --RAISE NOTICE 'Fund % already propagated', old_fund.id;
10936                                 CONTINUE;
10937                 END;
10938                 --RAISE NOTICE 'Propagating fund % to fund %',
10939                 --      old_fund.code, new_id;
10940         END LOOP;
10941 END;
10942 $$ LANGUAGE plpgsql;
10943
10944 CREATE OR REPLACE FUNCTION acq.rollover_funds_by_org_unit(
10945         old_year INTEGER,
10946         user_id INTEGER,
10947         org_unit_id INTEGER
10948 ) RETURNS VOID AS $$
10949 DECLARE
10950 --
10951 new_fund    INT;
10952 new_year    INT := old_year + 1;
10953 org_found   BOOL;
10954 xfer_amount NUMERIC;
10955 roll_fund   RECORD;
10956 deb         RECORD;
10957 detail      RECORD;
10958 --
10959 BEGIN
10960         --
10961         -- Sanity checks
10962         --
10963         IF old_year IS NULL THEN
10964                 RAISE EXCEPTION 'Input year argument is NULL';
10965     ELSIF old_year NOT BETWEEN 2008 and 2200 THEN
10966         RAISE EXCEPTION 'Input year is out of range';
10967         END IF;
10968         --
10969         IF user_id IS NULL THEN
10970                 RAISE EXCEPTION 'Input user id argument is NULL';
10971         END IF;
10972         --
10973         IF org_unit_id IS NULL THEN
10974                 RAISE EXCEPTION 'Org unit id argument is NULL';
10975         ELSE
10976                 --
10977                 -- Validate the org unit
10978                 --
10979                 SELECT TRUE
10980                 INTO org_found
10981                 FROM actor.org_unit
10982                 WHERE id = org_unit_id;
10983                 --
10984                 IF org_found IS NULL THEN
10985                         RAISE EXCEPTION 'Org unit id % is invalid', org_unit_id;
10986                 END IF;
10987         END IF;
10988         --
10989         -- Loop over the propagable funds to identify the details
10990         -- from the old fund plus the id of the new one, if it exists.
10991         --
10992         FOR roll_fund in
10993         SELECT
10994             oldf.id AS old_fund,
10995             oldf.org,
10996             oldf.name,
10997             oldf.currency_type,
10998             oldf.code,
10999                 oldf.rollover,
11000             newf.id AS new_fund_id
11001         FROM
11002         acq.fund AS oldf
11003         LEFT JOIN acq.fund AS newf
11004                 ON ( oldf.code = newf.code )
11005         WHERE
11006                     oldf.org = org_unit_id
11007                 and oldf.year = old_year
11008                 and oldf.propagate
11009         and newf.year = new_year
11010         LOOP
11011                 --RAISE NOTICE 'Processing fund %', roll_fund.old_fund;
11012                 --
11013                 IF roll_fund.new_fund_id IS NULL THEN
11014                         --
11015                         -- The old fund hasn't been propagated yet.  Propagate it now.
11016                         --
11017                         INSERT INTO acq.fund (
11018                                 org,
11019                                 name,
11020                                 year,
11021                                 currency_type,
11022                                 code,
11023                                 rollover,
11024                                 propagate,
11025                                 balance_warning_percent,
11026                                 balance_stop_percent
11027                         ) VALUES (
11028                                 roll_fund.org,
11029                                 roll_fund.name,
11030                                 new_year,
11031                                 roll_fund.currency_type,
11032                                 roll_fund.code,
11033                                 true,
11034                                 true,
11035                                 roll_fund.balance_warning_percent,
11036                                 roll_fund.balance_stop_percent
11037                         )
11038                         RETURNING id INTO new_fund;
11039                 ELSE
11040                         new_fund = roll_fund.new_fund_id;
11041                 END IF;
11042                 --
11043                 -- Determine the amount to transfer
11044                 --
11045                 SELECT amount
11046                 INTO xfer_amount
11047                 FROM acq.fund_spent_balance
11048                 WHERE fund = roll_fund.old_fund;
11049                 --
11050                 IF xfer_amount <> 0 THEN
11051                         IF roll_fund.rollover THEN
11052                                 --
11053                                 -- Transfer balance from old fund to new
11054                                 --
11055                                 --RAISE NOTICE 'Transferring % from fund % to %', xfer_amount, roll_fund.old_fund, new_fund;
11056                                 --
11057                                 PERFORM acq.transfer_fund(
11058                                         roll_fund.old_fund,
11059                                         xfer_amount,
11060                                         new_fund,
11061                                         xfer_amount,
11062                                         user_id,
11063                                         'Rollover'
11064                                 );
11065                         ELSE
11066                                 --
11067                                 -- Transfer balance from old fund to the void
11068                                 --
11069                                 -- RAISE NOTICE 'Transferring % from fund % to the void', xfer_amount, roll_fund.old_fund;
11070                                 --
11071                                 PERFORM acq.transfer_fund(
11072                                         roll_fund.old_fund,
11073                                         xfer_amount,
11074                                         NULL,
11075                                         NULL,
11076                                         user_id,
11077                                         'Rollover'
11078                                 );
11079                         END IF;
11080                 END IF;
11081                 --
11082                 IF roll_fund.rollover THEN
11083                         --
11084                         -- Move any lineitems from the old fund to the new one
11085                         -- where the associated debit is an encumbrance.
11086                         --
11087                         -- Any other tables tying expenditure details to funds should
11088                         -- receive similar treatment.  At this writing there are none.
11089                         --
11090                         UPDATE acq.lineitem_detail
11091                         SET fund = new_fund
11092                         WHERE
11093                         fund = roll_fund.old_fund -- this condition may be redundant
11094                         AND fund_debit in
11095                         (
11096                                 SELECT id
11097                                 FROM acq.fund_debit
11098                                 WHERE
11099                                 fund = roll_fund.old_fund
11100                                 AND encumbrance
11101                         );
11102                         --
11103                         -- Move encumbrance debits from the old fund to the new fund
11104                         --
11105                         UPDATE acq.fund_debit
11106                         SET fund = new_fund
11107                         wHERE
11108                                 fund = roll_fund.old_fund
11109                                 AND encumbrance;
11110                 END IF;
11111                 --
11112                 -- Mark old fund as inactive, now that we've closed it
11113                 --
11114                 UPDATE acq.fund
11115                 SET active = FALSE
11116                 WHERE id = roll_fund.old_fund;
11117         END LOOP;
11118 END;
11119 $$ LANGUAGE plpgsql;
11120
11121 CREATE OR REPLACE FUNCTION acq.rollover_funds_by_org_tree(
11122         old_year INTEGER,
11123         user_id INTEGER,
11124         org_unit_id INTEGER
11125 ) RETURNS VOID AS $$
11126 DECLARE
11127 --
11128 new_fund    INT;
11129 new_year    INT := old_year + 1;
11130 org_found   BOOL;
11131 xfer_amount NUMERIC;
11132 roll_fund   RECORD;
11133 deb         RECORD;
11134 detail      RECORD;
11135 --
11136 BEGIN
11137         --
11138         -- Sanity checks
11139         --
11140         IF old_year IS NULL THEN
11141                 RAISE EXCEPTION 'Input year argument is NULL';
11142     ELSIF old_year NOT BETWEEN 2008 and 2200 THEN
11143         RAISE EXCEPTION 'Input year is out of range';
11144         END IF;
11145         --
11146         IF user_id IS NULL THEN
11147                 RAISE EXCEPTION 'Input user id argument is NULL';
11148         END IF;
11149         --
11150         IF org_unit_id IS NULL THEN
11151                 RAISE EXCEPTION 'Org unit id argument is NULL';
11152         ELSE
11153                 --
11154                 -- Validate the org unit
11155                 --
11156                 SELECT TRUE
11157                 INTO org_found
11158                 FROM actor.org_unit
11159                 WHERE id = org_unit_id;
11160                 --
11161                 IF org_found IS NULL THEN
11162                         RAISE EXCEPTION 'Org unit id % is invalid', org_unit_id;
11163                 END IF;
11164         END IF;
11165         --
11166         -- Loop over the propagable funds to identify the details
11167         -- from the old fund plus the id of the new one, if it exists.
11168         --
11169         FOR roll_fund in
11170         SELECT
11171             oldf.id AS old_fund,
11172             oldf.org,
11173             oldf.name,
11174             oldf.currency_type,
11175             oldf.code,
11176                 oldf.rollover,
11177             newf.id AS new_fund_id
11178         FROM
11179         acq.fund AS oldf
11180         LEFT JOIN acq.fund AS newf
11181                 ON ( oldf.code = newf.code )
11182         WHERE
11183                     oldf.year = old_year
11184                 AND oldf.propagate
11185         AND newf.year = new_year
11186                 AND oldf.org in (
11187                         SELECT id FROM actor.org_unit_descendants( org_unit_id )
11188                 )
11189         LOOP
11190                 --RAISE NOTICE 'Processing fund %', roll_fund.old_fund;
11191                 --
11192                 IF roll_fund.new_fund_id IS NULL THEN
11193                         --
11194                         -- The old fund hasn't been propagated yet.  Propagate it now.
11195                         --
11196                         INSERT INTO acq.fund (
11197                                 org,
11198                                 name,
11199                                 year,
11200                                 currency_type,
11201                                 code,
11202                                 rollover,
11203                                 propagate,
11204                                 balance_warning_percent,
11205                                 balance_stop_percent
11206                         ) VALUES (
11207                                 roll_fund.org,
11208                                 roll_fund.name,
11209                                 new_year,
11210                                 roll_fund.currency_type,
11211                                 roll_fund.code,
11212                                 true,
11213                                 true,
11214                                 roll_fund.balance_warning_percent,
11215                                 roll_fund.balance_stop_percent
11216                         )
11217                         RETURNING id INTO new_fund;
11218                 ELSE
11219                         new_fund = roll_fund.new_fund_id;
11220                 END IF;
11221                 --
11222                 -- Determine the amount to transfer
11223                 --
11224                 SELECT amount
11225                 INTO xfer_amount
11226                 FROM acq.fund_spent_balance
11227                 WHERE fund = roll_fund.old_fund;
11228                 --
11229                 IF xfer_amount <> 0 THEN
11230                         IF roll_fund.rollover THEN
11231                                 --
11232                                 -- Transfer balance from old fund to new
11233                                 --
11234                                 --RAISE NOTICE 'Transferring % from fund % to %', xfer_amount, roll_fund.old_fund, new_fund;
11235                                 --
11236                                 PERFORM acq.transfer_fund(
11237                                         roll_fund.old_fund,
11238                                         xfer_amount,
11239                                         new_fund,
11240                                         xfer_amount,
11241                                         user_id,
11242                                         'Rollover'
11243                                 );
11244                         ELSE
11245                                 --
11246                                 -- Transfer balance from old fund to the void
11247                                 --
11248                                 -- RAISE NOTICE 'Transferring % from fund % to the void', xfer_amount, roll_fund.old_fund;
11249                                 --
11250                                 PERFORM acq.transfer_fund(
11251                                         roll_fund.old_fund,
11252                                         xfer_amount,
11253                                         NULL,
11254                                         NULL,
11255                                         user_id,
11256                                         'Rollover'
11257                                 );
11258                         END IF;
11259                 END IF;
11260                 --
11261                 IF roll_fund.rollover THEN
11262                         --
11263                         -- Move any lineitems from the old fund to the new one
11264                         -- where the associated debit is an encumbrance.
11265                         --
11266                         -- Any other tables tying expenditure details to funds should
11267                         -- receive similar treatment.  At this writing there are none.
11268                         --
11269                         UPDATE acq.lineitem_detail
11270                         SET fund = new_fund
11271                         WHERE
11272                         fund = roll_fund.old_fund -- this condition may be redundant
11273                         AND fund_debit in
11274                         (
11275                                 SELECT id
11276                                 FROM acq.fund_debit
11277                                 WHERE
11278                                 fund = roll_fund.old_fund
11279                                 AND encumbrance
11280                         );
11281                         --
11282                         -- Move encumbrance debits from the old fund to the new fund
11283                         --
11284                         UPDATE acq.fund_debit
11285                         SET fund = new_fund
11286                         wHERE
11287                                 fund = roll_fund.old_fund
11288                                 AND encumbrance;
11289                 END IF;
11290                 --
11291                 -- Mark old fund as inactive, now that we've closed it
11292                 --
11293                 UPDATE acq.fund
11294                 SET active = FALSE
11295                 WHERE id = roll_fund.old_fund;
11296         END LOOP;
11297 END;
11298 $$ LANGUAGE plpgsql;
11299
11300 CREATE OR REPLACE FUNCTION public.remove_commas( TEXT ) RETURNS TEXT AS $$
11301     SELECT regexp_replace($1, ',', '', 'g');
11302 $$ LANGUAGE SQL STRICT IMMUTABLE;
11303
11304 CREATE OR REPLACE FUNCTION public.remove_whitespace( TEXT ) RETURNS TEXT AS $$
11305     SELECT regexp_replace(normalize_space($1), E'\\s+', '', 'g');
11306 $$ LANGUAGE SQL STRICT IMMUTABLE;
11307
11308 CREATE TABLE acq.distribution_formula_application (
11309     id BIGSERIAL PRIMARY KEY,
11310     creator INT NOT NULL REFERENCES actor.usr(id) DEFERRABLE INITIALLY DEFERRED,
11311     create_time TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(),
11312     formula INT NOT NULL
11313         REFERENCES acq.distribution_formula(id) DEFERRABLE INITIALLY DEFERRED,
11314     lineitem INT NOT NULL
11315         REFERENCES acq.lineitem( id )
11316                 ON DELETE CASCADE
11317                 DEFERRABLE INITIALLY DEFERRED
11318 );
11319
11320 CREATE INDEX acqdfa_df_idx
11321     ON acq.distribution_formula_application(formula);
11322 CREATE INDEX acqdfa_li_idx
11323     ON acq.distribution_formula_application(lineitem);
11324 CREATE INDEX acqdfa_creator_idx
11325     ON acq.distribution_formula_application(creator);
11326
11327 CREATE TABLE acq.user_request_type (
11328     id      SERIAL  PRIMARY KEY,
11329     label   TEXT    NOT NULL UNIQUE -- i18n-ize
11330 );
11331
11332 INSERT INTO acq.user_request_type (id,label) VALUES (1, oils_i18n_gettext('1', 'Books', 'aurt', 'label'));
11333 INSERT INTO acq.user_request_type (id,label) VALUES (2, oils_i18n_gettext('2', 'Journal/Magazine & Newspaper Articles', 'aurt', 'label'));
11334 INSERT INTO acq.user_request_type (id,label) VALUES (3, oils_i18n_gettext('3', 'Audiobooks', 'aurt', 'label'));
11335 INSERT INTO acq.user_request_type (id,label) VALUES (4, oils_i18n_gettext('4', 'Music', 'aurt', 'label'));
11336 INSERT INTO acq.user_request_type (id,label) VALUES (5, oils_i18n_gettext('5', 'DVDs', 'aurt', 'label'));
11337
11338 SELECT SETVAL('acq.user_request_type_id_seq'::TEXT, 6);
11339
11340 CREATE TABLE acq.cancel_reason (
11341         id            SERIAL            PRIMARY KEY,
11342         org_unit      INTEGER           NOT NULL REFERENCES actor.org_unit( id )
11343                                         DEFERRABLE INITIALLY DEFERRED,
11344         label         TEXT              NOT NULL,
11345         description   TEXT              NOT NULL,
11346         keep_debits   BOOL              NOT NULL DEFAULT FALSE,
11347         CONSTRAINT acq_cancel_reason_one_per_org_unit UNIQUE( org_unit, label )
11348 );
11349
11350 -- Reserve ids 1-999 for stock reasons
11351 -- Reserve ids 1000-1999 for EDI reasons
11352 -- 2000+ are available for staff to create
11353
11354 SELECT SETVAL('acq.cancel_reason_id_seq'::TEXT, 2000);
11355
11356 CREATE TABLE acq.user_request (
11357     id                  SERIAL  PRIMARY KEY,
11358     usr                 INT     NOT NULL REFERENCES actor.usr (id), -- requesting user
11359     hold                BOOL    NOT NULL DEFAULT TRUE,
11360
11361     pickup_lib          INT     NOT NULL REFERENCES actor.org_unit (id), -- pickup lib
11362     holdable_formats    TEXT,           -- nullable, for use in hold creation
11363     phone_notify        TEXT,
11364     email_notify        BOOL    NOT NULL DEFAULT TRUE,
11365     lineitem            INT     REFERENCES acq.lineitem (id) ON DELETE CASCADE,
11366     eg_bib              BIGINT  REFERENCES biblio.record_entry (id) ON DELETE CASCADE,
11367     request_date        TIMESTAMPTZ NOT NULL DEFAULT NOW(), -- when they requested it
11368     need_before         TIMESTAMPTZ,    -- don't create holds after this
11369     max_fee             TEXT,
11370
11371     request_type        INT     NOT NULL REFERENCES acq.user_request_type (id), 
11372     isxn                TEXT,
11373     title               TEXT,
11374     volume              TEXT,
11375     author              TEXT,
11376     article_title       TEXT,
11377     article_pages       TEXT,
11378     publisher           TEXT,
11379     location            TEXT,
11380     pubdate             TEXT,
11381     mentioned           TEXT,
11382     other_info          TEXT,
11383         cancel_reason       INT              REFERENCES acq.cancel_reason( id )
11384                                              DEFERRABLE INITIALLY DEFERRED
11385 );
11386
11387 CREATE TABLE acq.lineitem_alert_text (
11388         id               SERIAL         PRIMARY KEY,
11389         code             TEXT           UNIQUE NOT NULL,
11390         description      TEXT,
11391         owning_lib       INT            NOT NULL
11392                                         REFERENCES actor.org_unit(id)
11393                                         DEFERRABLE INITIALLY DEFERRED,
11394         CONSTRAINT alert_one_code_per_org UNIQUE (code, owning_lib)
11395 );
11396
11397 ALTER TABLE acq.lineitem_note
11398         ADD COLUMN alert_text    INT     REFERENCES acq.lineitem_alert_text(id)
11399                                          DEFERRABLE INITIALLY DEFERRED;
11400
11401 -- add ON DELETE CASCADE clause
11402
11403 ALTER TABLE acq.lineitem_note
11404         DROP CONSTRAINT lineitem_note_lineitem_fkey;
11405
11406 ALTER TABLE acq.lineitem_note
11407         ADD FOREIGN KEY (lineitem) REFERENCES acq.lineitem( id )
11408                 ON DELETE CASCADE
11409                 DEFERRABLE INITIALLY DEFERRED;
11410
11411 ALTER TABLE acq.lineitem_note
11412         ADD COLUMN vendor_public BOOLEAN NOT NULL DEFAULT FALSE;
11413
11414 CREATE TABLE acq.invoice_method (
11415     code    TEXT    PRIMARY KEY,
11416     name    TEXT    NOT NULL -- i18n-ize
11417 );
11418 INSERT INTO acq.invoice_method (code,name) VALUES ('EDI',oils_i18n_gettext('EDI', 'EDI', 'acqim', 'name'));
11419 INSERT INTO acq.invoice_method (code,name) VALUES ('PPR',oils_i18n_gettext('PPR', 'Paper', 'acqit', 'name'));
11420
11421 CREATE TABLE acq.invoice_payment_method (
11422         code      TEXT     PRIMARY KEY,
11423         name      TEXT     NOT NULL
11424 );
11425
11426 CREATE TABLE acq.invoice (
11427     id             SERIAL      PRIMARY KEY,
11428     receiver       INT         NOT NULL REFERENCES actor.org_unit (id),
11429     provider       INT         NOT NULL REFERENCES acq.provider (id),
11430     shipper        INT         NOT NULL REFERENCES acq.provider (id),
11431     recv_date      TIMESTAMPTZ NOT NULL DEFAULT NOW(),
11432     recv_method    TEXT        NOT NULL REFERENCES acq.invoice_method (code) DEFAULT 'EDI',
11433     inv_type       TEXT,       -- A "type" field is desired, but no idea what goes here
11434     inv_ident      TEXT        NOT NULL, -- vendor-supplied invoice id/number
11435         payment_auth   TEXT,
11436         payment_method TEXT        REFERENCES acq.invoice_payment_method (code)
11437                                    DEFERRABLE INITIALLY DEFERRED,
11438         note           TEXT,
11439     complete       BOOL        NOT NULL DEFAULT FALSE,
11440     CONSTRAINT inv_ident_once_per_provider UNIQUE(provider, inv_ident)
11441 );
11442
11443 CREATE TABLE acq.invoice_entry (
11444     id              SERIAL      PRIMARY KEY,
11445     invoice         INT         NOT NULL REFERENCES acq.invoice (id) ON DELETE CASCADE,
11446     purchase_order  INT         REFERENCES acq.purchase_order (id) ON UPDATE CASCADE ON DELETE SET NULL,
11447     lineitem        INT         REFERENCES acq.lineitem (id) ON UPDATE CASCADE ON DELETE SET NULL,
11448     inv_item_count  INT         NOT NULL, -- How many acqlids did they say they sent
11449     phys_item_count INT, -- and how many did staff count
11450     note            TEXT,
11451     billed_per_item BOOL,
11452     cost_billed     NUMERIC(8,2),
11453     actual_cost     NUMERIC(8,2),
11454         amount_paid     NUMERIC (8,2)
11455 );
11456
11457 CREATE TABLE acq.invoice_item_type (
11458     code    TEXT    PRIMARY KEY,
11459     name    TEXT    NOT NULL, -- i18n-ize
11460         prorate BOOL    NOT NULL DEFAULT FALSE
11461 );
11462
11463 INSERT INTO acq.invoice_item_type (code,name) VALUES ('TAX',oils_i18n_gettext('TAX', 'Tax', 'aiit', 'name'));
11464 INSERT INTO acq.invoice_item_type (code,name) VALUES ('PRO',oils_i18n_gettext('PRO', 'Processing Fee', 'aiit', 'name'));
11465 INSERT INTO acq.invoice_item_type (code,name) VALUES ('SHP',oils_i18n_gettext('SHP', 'Shipping Charge', 'aiit', 'name'));
11466 INSERT INTO acq.invoice_item_type (code,name) VALUES ('HND',oils_i18n_gettext('HND', 'Handling Charge', 'aiit', 'name'));
11467 INSERT INTO acq.invoice_item_type (code,name) VALUES ('ITM',oils_i18n_gettext('ITM', 'Non-library Item', 'aiit', 'name'));
11468 INSERT INTO acq.invoice_item_type (code,name) VALUES ('SUB',oils_i18n_gettext('SUB', 'Searial Subscription', 'aiit', 'name'));
11469
11470 CREATE TABLE acq.po_item (
11471         id              SERIAL      PRIMARY KEY,
11472         purchase_order  INT         REFERENCES acq.purchase_order (id)
11473                                     ON UPDATE CASCADE ON DELETE SET NULL
11474                                     DEFERRABLE INITIALLY DEFERRED,
11475         fund_debit      INT         REFERENCES acq.fund_debit (id)
11476                                     DEFERRABLE INITIALLY DEFERRED,
11477         inv_item_type   TEXT        NOT NULL
11478                                     REFERENCES acq.invoice_item_type (code)
11479                                     DEFERRABLE INITIALLY DEFERRED,
11480         title           TEXT,
11481         author          TEXT,
11482         note            TEXT,
11483         estimated_cost  NUMERIC(8,2),
11484         fund            INT         REFERENCES acq.fund (id)
11485                                     DEFERRABLE INITIALLY DEFERRED,
11486         target          BIGINT
11487 );
11488
11489 CREATE TABLE acq.invoice_item ( -- for invoice-only debits: taxes/fees/non-bib items/etc
11490     id              SERIAL      PRIMARY KEY,
11491     invoice         INT         NOT NULL REFERENCES acq.invoice (id) ON UPDATE CASCADE ON DELETE CASCADE,
11492     purchase_order  INT         REFERENCES acq.purchase_order (id) ON UPDATE CASCADE ON DELETE SET NULL,
11493     fund_debit      INT         REFERENCES acq.fund_debit (id),
11494     inv_item_type   TEXT        NOT NULL REFERENCES acq.invoice_item_type (code),
11495     title           TEXT,
11496     author          TEXT,
11497     note            TEXT,
11498     cost_billed     NUMERIC(8,2),
11499     actual_cost     NUMERIC(8,2),
11500     fund            INT         REFERENCES acq.fund (id)
11501                                 DEFERRABLE INITIALLY DEFERRED,
11502     amount_paid     NUMERIC (8,2),
11503     po_item         INT         REFERENCES acq.po_item (id)
11504                                 DEFERRABLE INITIALLY DEFERRED,
11505     target          BIGINT
11506 );
11507
11508 CREATE TABLE acq.edi_message (
11509     id               SERIAL          PRIMARY KEY,
11510     account          INTEGER         REFERENCES acq.edi_account(id)
11511                                      DEFERRABLE INITIALLY DEFERRED,
11512     remote_file      TEXT,
11513     create_time      TIMESTAMPTZ     NOT NULL DEFAULT now(),
11514     translate_time   TIMESTAMPTZ,
11515     process_time     TIMESTAMPTZ,
11516     error_time       TIMESTAMPTZ,
11517     status           TEXT            NOT NULL DEFAULT 'new'
11518                                      CONSTRAINT status_value CHECK
11519                                      ( status IN (
11520                                         'new',          -- needs to be translated
11521                                         'translated',   -- needs to be processed
11522                                         'trans_error',  -- error in translation step
11523                                         'processed',    -- needs to have remote_file deleted
11524                                         'proc_error',   -- error in processing step
11525                                         'delete_error', -- error in deletion
11526                                         'retry',        -- need to retry
11527                                         'complete'      -- done
11528                                      )),
11529     edi              TEXT,
11530     jedi             TEXT,
11531     error            TEXT,
11532     purchase_order   INT             REFERENCES acq.purchase_order
11533                                      DEFERRABLE INITIALLY DEFERRED,
11534     message_type     TEXT            NOT NULL CONSTRAINT valid_message_type
11535                                      CHECK ( message_type IN (
11536                                         'ORDERS',
11537                                         'ORDRSP',
11538                                         'INVOIC',
11539                                         'OSTENQ',
11540                                         'OSTRPT'
11541                                      ))
11542 );
11543
11544 ALTER TABLE actor.org_address ADD COLUMN san TEXT;
11545
11546 ALTER TABLE acq.provider_address
11547         ADD COLUMN fax_phone TEXT;
11548
11549 ALTER TABLE acq.provider_contact_address
11550         ADD COLUMN fax_phone TEXT;
11551
11552 CREATE TABLE acq.provider_note (
11553     id      SERIAL              PRIMARY KEY,
11554     provider    INT             NOT NULL REFERENCES acq.provider (id) DEFERRABLE INITIALLY DEFERRED,
11555     creator     INT             NOT NULL REFERENCES actor.usr (id) DEFERRABLE INITIALLY DEFERRED,
11556     editor      INT             NOT NULL REFERENCES actor.usr (id) DEFERRABLE INITIALLY DEFERRED,
11557     create_time TIMESTAMP WITH TIME ZONE    NOT NULL DEFAULT NOW(),
11558     edit_time   TIMESTAMP WITH TIME ZONE    NOT NULL DEFAULT NOW(),
11559     value       TEXT            NOT NULL
11560 );
11561 CREATE INDEX acq_pro_note_pro_idx      ON acq.provider_note ( provider );
11562 CREATE INDEX acq_pro_note_creator_idx  ON acq.provider_note ( creator );
11563 CREATE INDEX acq_pro_note_editor_idx   ON acq.provider_note ( editor );
11564
11565 -- For each fund: the total allocation from all sources, in the
11566 -- currency of the fund (or 0 if there are no allocations)
11567
11568 CREATE VIEW acq.all_fund_allocation_total AS
11569 SELECT
11570     f.id AS fund,
11571     COALESCE( SUM( a.amount * acq.exchange_ratio(
11572         s.currency_type, f.currency_type))::numeric(100,2), 0 )
11573     AS amount
11574 FROM
11575     acq.fund f
11576         LEFT JOIN acq.fund_allocation a
11577             ON a.fund = f.id
11578         LEFT JOIN acq.funding_source s
11579             ON a.funding_source = s.id
11580 GROUP BY
11581     f.id;
11582
11583 -- For every fund: the total encumbrances (or 0 if none),
11584 -- in the currency of the fund.
11585
11586 CREATE VIEW acq.all_fund_encumbrance_total AS
11587 SELECT
11588         f.id AS fund,
11589         COALESCE( encumb.amount, 0 ) AS amount
11590 FROM
11591         acq.fund AS f
11592                 LEFT JOIN (
11593                         SELECT
11594                                 fund,
11595                                 sum( amount ) AS amount
11596                         FROM
11597                                 acq.fund_debit
11598                         WHERE
11599                                 encumbrance
11600                         GROUP BY fund
11601                 ) AS encumb
11602                         ON f.id = encumb.fund;
11603
11604 -- For every fund: the total spent (or 0 if none),
11605 -- in the currency of the fund.
11606
11607 CREATE VIEW acq.all_fund_spent_total AS
11608 SELECT
11609     f.id AS fund,
11610     COALESCE( spent.amount, 0 ) AS amount
11611 FROM
11612     acq.fund AS f
11613         LEFT JOIN (
11614             SELECT
11615                 fund,
11616                 sum( amount ) AS amount
11617             FROM
11618                 acq.fund_debit
11619             WHERE
11620                 NOT encumbrance
11621             GROUP BY fund
11622         ) AS spent
11623             ON f.id = spent.fund;
11624
11625 -- For each fund: the amount not yet spent, in the currency
11626 -- of the fund.  May include encumbrances.
11627
11628 CREATE VIEW acq.all_fund_spent_balance AS
11629 SELECT
11630         c.fund,
11631         c.amount - d.amount AS amount
11632 FROM acq.all_fund_allocation_total c
11633     LEFT JOIN acq.all_fund_spent_total d USING (fund);
11634
11635 -- For each fund: the amount neither spent nor encumbered,
11636 -- in the currency of the fund
11637
11638 CREATE VIEW acq.all_fund_combined_balance AS
11639 SELECT
11640      a.fund,
11641      a.amount - COALESCE( c.amount, 0 ) AS amount
11642 FROM
11643      acq.all_fund_allocation_total a
11644         LEFT OUTER JOIN (
11645             SELECT
11646                 fund,
11647                 SUM( amount ) AS amount
11648             FROM
11649                 acq.fund_debit
11650             GROUP BY
11651                 fund
11652         ) AS c USING ( fund );
11653
11654 CREATE OR REPLACE FUNCTION actor.usr_merge(
11655         src_usr INT,
11656         dest_usr INT,
11657         del_addrs BOOLEAN,
11658         del_cards BOOLEAN,
11659         deactivate_cards BOOLEAN
11660 ) RETURNS VOID AS $$
11661 DECLARE
11662         suffix TEXT;
11663         bucket_row RECORD;
11664         picklist_row RECORD;
11665         queue_row RECORD;
11666         folder_row RECORD;
11667 BEGIN
11668
11669     -- do some initial cleanup 
11670     UPDATE actor.usr SET card = NULL WHERE id = src_usr;
11671     UPDATE actor.usr SET mailing_address = NULL WHERE id = src_usr;
11672     UPDATE actor.usr SET billing_address = NULL WHERE id = src_usr;
11673
11674     -- actor.*
11675     IF del_cards THEN
11676         DELETE FROM actor.card where usr = src_usr;
11677     ELSE
11678         IF deactivate_cards THEN
11679             UPDATE actor.card SET active = 'f' WHERE usr = src_usr;
11680         END IF;
11681         UPDATE actor.card SET usr = dest_usr WHERE usr = src_usr;
11682     END IF;
11683
11684
11685     IF del_addrs THEN
11686         DELETE FROM actor.usr_address WHERE usr = src_usr;
11687     ELSE
11688         UPDATE actor.usr_address SET usr = dest_usr WHERE usr = src_usr;
11689     END IF;
11690
11691     UPDATE actor.usr_note SET usr = dest_usr WHERE usr = src_usr;
11692     -- dupes are technically OK in actor.usr_standing_penalty, should manually delete them...
11693     UPDATE actor.usr_standing_penalty SET usr = dest_usr WHERE usr = src_usr;
11694     PERFORM actor.usr_merge_rows('actor.usr_org_unit_opt_in', 'usr', src_usr, dest_usr);
11695     PERFORM actor.usr_merge_rows('actor.usr_setting', 'usr', src_usr, dest_usr);
11696
11697     -- permission.*
11698     PERFORM actor.usr_merge_rows('permission.usr_perm_map', 'usr', src_usr, dest_usr);
11699     PERFORM actor.usr_merge_rows('permission.usr_object_perm_map', 'usr', src_usr, dest_usr);
11700     PERFORM actor.usr_merge_rows('permission.usr_grp_map', 'usr', src_usr, dest_usr);
11701     PERFORM actor.usr_merge_rows('permission.usr_work_ou_map', 'usr', src_usr, dest_usr);
11702
11703
11704     -- container.*
11705         
11706         -- For each *_bucket table: transfer every bucket belonging to src_usr
11707         -- into the custody of dest_usr.
11708         --
11709         -- In order to avoid colliding with an existing bucket owned by
11710         -- the destination user, append the source user's id (in parenthesese)
11711         -- to the name.  If you still get a collision, add successive
11712         -- spaces to the name and keep trying until you succeed.
11713         --
11714         FOR bucket_row in
11715                 SELECT id, name
11716                 FROM   container.biblio_record_entry_bucket
11717                 WHERE  owner = src_usr
11718         LOOP
11719                 suffix := ' (' || src_usr || ')';
11720                 LOOP
11721                         BEGIN
11722                                 UPDATE  container.biblio_record_entry_bucket
11723                                 SET     owner = dest_usr, name = name || suffix
11724                                 WHERE   id = bucket_row.id;
11725                         EXCEPTION WHEN unique_violation THEN
11726                                 suffix := suffix || ' ';
11727                                 CONTINUE;
11728                         END;
11729                         EXIT;
11730                 END LOOP;
11731         END LOOP;
11732
11733         FOR bucket_row in
11734                 SELECT id, name
11735                 FROM   container.call_number_bucket
11736                 WHERE  owner = src_usr
11737         LOOP
11738                 suffix := ' (' || src_usr || ')';
11739                 LOOP
11740                         BEGIN
11741                                 UPDATE  container.call_number_bucket
11742                                 SET     owner = dest_usr, name = name || suffix
11743                                 WHERE   id = bucket_row.id;
11744                         EXCEPTION WHEN unique_violation THEN
11745                                 suffix := suffix || ' ';
11746                                 CONTINUE;
11747                         END;
11748                         EXIT;
11749                 END LOOP;
11750         END LOOP;
11751
11752         FOR bucket_row in
11753                 SELECT id, name
11754                 FROM   container.copy_bucket
11755                 WHERE  owner = src_usr
11756         LOOP
11757                 suffix := ' (' || src_usr || ')';
11758                 LOOP
11759                         BEGIN
11760                                 UPDATE  container.copy_bucket
11761                                 SET     owner = dest_usr, name = name || suffix
11762                                 WHERE   id = bucket_row.id;
11763                         EXCEPTION WHEN unique_violation THEN
11764                                 suffix := suffix || ' ';
11765                                 CONTINUE;
11766                         END;
11767                         EXIT;
11768                 END LOOP;
11769         END LOOP;
11770
11771         FOR bucket_row in
11772                 SELECT id, name
11773                 FROM   container.user_bucket
11774                 WHERE  owner = src_usr
11775         LOOP
11776                 suffix := ' (' || src_usr || ')';
11777                 LOOP
11778                         BEGIN
11779                                 UPDATE  container.user_bucket
11780                                 SET     owner = dest_usr, name = name || suffix
11781                                 WHERE   id = bucket_row.id;
11782                         EXCEPTION WHEN unique_violation THEN
11783                                 suffix := suffix || ' ';
11784                                 CONTINUE;
11785                         END;
11786                         EXIT;
11787                 END LOOP;
11788         END LOOP;
11789
11790         UPDATE container.user_bucket_item SET target_user = dest_usr WHERE target_user = src_usr;
11791
11792     -- vandelay.*
11793         -- transfer queues the same way we transfer buckets (see above)
11794         FOR queue_row in
11795                 SELECT id, name
11796                 FROM   vandelay.queue
11797                 WHERE  owner = src_usr
11798         LOOP
11799                 suffix := ' (' || src_usr || ')';
11800                 LOOP
11801                         BEGIN
11802                                 UPDATE  vandelay.queue
11803                                 SET     owner = dest_usr, name = name || suffix
11804                                 WHERE   id = queue_row.id;
11805                         EXCEPTION WHEN unique_violation THEN
11806                                 suffix := suffix || ' ';
11807                                 CONTINUE;
11808                         END;
11809                         EXIT;
11810                 END LOOP;
11811         END LOOP;
11812
11813     -- money.*
11814     PERFORM actor.usr_merge_rows('money.collections_tracker', 'usr', src_usr, dest_usr);
11815     PERFORM actor.usr_merge_rows('money.collections_tracker', 'collector', src_usr, dest_usr);
11816     UPDATE money.billable_xact SET usr = dest_usr WHERE usr = src_usr;
11817     UPDATE money.billing SET voider = dest_usr WHERE voider = src_usr;
11818     UPDATE money.bnm_payment SET accepting_usr = dest_usr WHERE accepting_usr = src_usr;
11819
11820     -- action.*
11821     UPDATE action.circulation SET usr = dest_usr WHERE usr = src_usr;
11822     UPDATE action.circulation SET circ_staff = dest_usr WHERE circ_staff = src_usr;
11823     UPDATE action.circulation SET checkin_staff = dest_usr WHERE checkin_staff = src_usr;
11824
11825     UPDATE action.hold_request SET usr = dest_usr WHERE usr = src_usr;
11826     UPDATE action.hold_request SET fulfillment_staff = dest_usr WHERE fulfillment_staff = src_usr;
11827     UPDATE action.hold_request SET requestor = dest_usr WHERE requestor = src_usr;
11828     UPDATE action.hold_notification SET notify_staff = dest_usr WHERE notify_staff = src_usr;
11829
11830     UPDATE action.in_house_use SET staff = dest_usr WHERE staff = src_usr;
11831     UPDATE action.non_cataloged_circulation SET staff = dest_usr WHERE staff = src_usr;
11832     UPDATE action.non_cataloged_circulation SET patron = dest_usr WHERE patron = src_usr;
11833     UPDATE action.non_cat_in_house_use SET staff = dest_usr WHERE staff = src_usr;
11834     UPDATE action.survey_response SET usr = dest_usr WHERE usr = src_usr;
11835
11836     -- acq.*
11837     UPDATE acq.fund_allocation SET allocator = dest_usr WHERE allocator = src_usr;
11838     UPDATE acq.fund_transfer SET transfer_user = dest_usr WHERE transfer_user = src_usr;
11839
11840         -- transfer picklists the same way we transfer buckets (see above)
11841         FOR picklist_row in
11842                 SELECT id, name
11843                 FROM   acq.picklist
11844                 WHERE  owner = src_usr
11845         LOOP
11846                 suffix := ' (' || src_usr || ')';
11847                 LOOP
11848                         BEGIN
11849                                 UPDATE  acq.picklist
11850                                 SET     owner = dest_usr, name = name || suffix
11851                                 WHERE   id = picklist_row.id;
11852                         EXCEPTION WHEN unique_violation THEN
11853                                 suffix := suffix || ' ';
11854                                 CONTINUE;
11855                         END;
11856                         EXIT;
11857                 END LOOP;
11858         END LOOP;
11859
11860     UPDATE acq.purchase_order SET owner = dest_usr WHERE owner = src_usr;
11861     UPDATE acq.po_note SET creator = dest_usr WHERE creator = src_usr;
11862     UPDATE acq.po_note SET editor = dest_usr WHERE editor = src_usr;
11863         UPDATE acq.provider_note SET creator = dest_usr WHERE creator = src_usr;
11864         UPDATE acq.provider_note SET editor = dest_usr WHERE editor = src_usr;
11865     UPDATE acq.lineitem_note SET creator = dest_usr WHERE creator = src_usr;
11866     UPDATE acq.lineitem_note SET editor = dest_usr WHERE editor = src_usr;
11867     UPDATE acq.lineitem_usr_attr_definition SET usr = dest_usr WHERE usr = src_usr;
11868
11869     -- asset.*
11870     UPDATE asset.copy SET creator = dest_usr WHERE creator = src_usr;
11871     UPDATE asset.copy SET editor = dest_usr WHERE editor = src_usr;
11872     UPDATE asset.copy_note SET creator = dest_usr WHERE creator = src_usr;
11873     UPDATE asset.call_number SET creator = dest_usr WHERE creator = src_usr;
11874     UPDATE asset.call_number SET editor = dest_usr WHERE editor = src_usr;
11875     UPDATE asset.call_number_note SET creator = dest_usr WHERE creator = src_usr;
11876
11877     -- serial.*
11878     UPDATE serial.record_entry SET creator = dest_usr WHERE creator = src_usr;
11879     UPDATE serial.record_entry SET editor = dest_usr WHERE editor = src_usr;
11880
11881     -- reporter.*
11882     -- It's not uncommon to define the reporter schema in a replica 
11883     -- DB only, so don't assume these tables exist in the write DB.
11884     BEGIN
11885         UPDATE reporter.template SET owner = dest_usr WHERE owner = src_usr;
11886     EXCEPTION WHEN undefined_table THEN
11887         -- do nothing
11888     END;
11889     BEGIN
11890         UPDATE reporter.report SET owner = dest_usr WHERE owner = src_usr;
11891     EXCEPTION WHEN undefined_table THEN
11892         -- do nothing
11893     END;
11894     BEGIN
11895         UPDATE reporter.schedule SET runner = dest_usr WHERE runner = src_usr;
11896     EXCEPTION WHEN undefined_table THEN
11897         -- do nothing
11898     END;
11899     BEGIN
11900                 -- transfer folders the same way we transfer buckets (see above)
11901                 FOR folder_row in
11902                         SELECT id, name
11903                         FROM   reporter.template_folder
11904                         WHERE  owner = src_usr
11905                 LOOP
11906                         suffix := ' (' || src_usr || ')';
11907                         LOOP
11908                                 BEGIN
11909                                         UPDATE  reporter.template_folder
11910                                         SET     owner = dest_usr, name = name || suffix
11911                                         WHERE   id = folder_row.id;
11912                                 EXCEPTION WHEN unique_violation THEN
11913                                         suffix := suffix || ' ';
11914                                         CONTINUE;
11915                                 END;
11916                                 EXIT;
11917                         END LOOP;
11918                 END LOOP;
11919     EXCEPTION WHEN undefined_table THEN
11920         -- do nothing
11921     END;
11922     BEGIN
11923                 -- transfer folders the same way we transfer buckets (see above)
11924                 FOR folder_row in
11925                         SELECT id, name
11926                         FROM   reporter.report_folder
11927                         WHERE  owner = src_usr
11928                 LOOP
11929                         suffix := ' (' || src_usr || ')';
11930                         LOOP
11931                                 BEGIN
11932                                         UPDATE  reporter.report_folder
11933                                         SET     owner = dest_usr, name = name || suffix
11934                                         WHERE   id = folder_row.id;
11935                                 EXCEPTION WHEN unique_violation THEN
11936                                         suffix := suffix || ' ';
11937                                         CONTINUE;
11938                                 END;
11939                                 EXIT;
11940                         END LOOP;
11941                 END LOOP;
11942     EXCEPTION WHEN undefined_table THEN
11943         -- do nothing
11944     END;
11945     BEGIN
11946                 -- transfer folders the same way we transfer buckets (see above)
11947                 FOR folder_row in
11948                         SELECT id, name
11949                         FROM   reporter.output_folder
11950                         WHERE  owner = src_usr
11951                 LOOP
11952                         suffix := ' (' || src_usr || ')';
11953                         LOOP
11954                                 BEGIN
11955                                         UPDATE  reporter.output_folder
11956                                         SET     owner = dest_usr, name = name || suffix
11957                                         WHERE   id = folder_row.id;
11958                                 EXCEPTION WHEN unique_violation THEN
11959                                         suffix := suffix || ' ';
11960                                         CONTINUE;
11961                                 END;
11962                                 EXIT;
11963                         END LOOP;
11964                 END LOOP;
11965     EXCEPTION WHEN undefined_table THEN
11966         -- do nothing
11967     END;
11968
11969     -- Finally, delete the source user
11970     DELETE FROM actor.usr WHERE id = src_usr;
11971
11972 END;
11973 $$ LANGUAGE plpgsql;
11974
11975 -- The "add" trigger functions should protect against existing NULLed values, just in case
11976 CREATE OR REPLACE FUNCTION money.materialized_summary_billing_add () RETURNS TRIGGER AS $$
11977 BEGIN
11978     IF NOT NEW.voided THEN
11979         UPDATE  money.materialized_billable_xact_summary
11980           SET   total_owed = COALESCE(total_owed, 0.0::numeric) + NEW.amount,
11981             last_billing_ts = NEW.billing_ts,
11982             last_billing_note = NEW.note,
11983             last_billing_type = NEW.billing_type,
11984             balance_owed = balance_owed + NEW.amount
11985           WHERE id = NEW.xact;
11986     END IF;
11987
11988     RETURN NEW;
11989 END;
11990 $$ LANGUAGE PLPGSQL;
11991
11992 CREATE OR REPLACE FUNCTION money.materialized_summary_payment_add () RETURNS TRIGGER AS $$
11993 BEGIN
11994     IF NOT NEW.voided THEN
11995         UPDATE  money.materialized_billable_xact_summary
11996           SET   total_paid = COALESCE(total_paid, 0.0::numeric) + NEW.amount,
11997             last_payment_ts = NEW.payment_ts,
11998             last_payment_note = NEW.note,
11999             last_payment_type = TG_ARGV[0],
12000             balance_owed = balance_owed - NEW.amount
12001           WHERE id = NEW.xact;
12002     END IF;
12003
12004     RETURN NEW;
12005 END;
12006 $$ LANGUAGE PLPGSQL;
12007
12008 -- Refresh the mat view with the corrected underlying view
12009 TRUNCATE money.materialized_billable_xact_summary;
12010 INSERT INTO money.materialized_billable_xact_summary SELECT * FROM money.billable_xact_summary;
12011
12012 CREATE OR REPLACE FUNCTION permission.usr_has_perm_at_nd(
12013     user_id    IN INTEGER,
12014     perm_code  IN TEXT
12015 )
12016 RETURNS SETOF INTEGER AS $$
12017 --
12018 -- Return a set of all the org units for which a given user has a given
12019 -- permission, granted directly (not through inheritance from a parent
12020 -- org unit).
12021 --
12022 -- The permissions apply to a minimum depth of the org unit hierarchy,
12023 -- for the org unit(s) to which the user is assigned.  (They also apply
12024 -- to the subordinates of those org units, but we don't report the
12025 -- subordinates here.)
12026 --
12027 -- For purposes of this function, the permission.usr_work_ou_map table
12028 -- defines which users belong to which org units.  I.e. we ignore the
12029 -- home_ou column of actor.usr.
12030 --
12031 -- The result set may contain duplicates, which should be eliminated
12032 -- by a DISTINCT clause.
12033 --
12034 DECLARE
12035     b_super       BOOLEAN;
12036     n_perm        INTEGER;
12037     n_min_depth   INTEGER;
12038     n_work_ou     INTEGER;
12039     n_curr_ou     INTEGER;
12040     n_depth       INTEGER;
12041     n_curr_depth  INTEGER;
12042 BEGIN
12043     --
12044     -- Check for superuser
12045     --
12046     SELECT INTO b_super
12047         super_user
12048     FROM
12049         actor.usr
12050     WHERE
12051         id = user_id;
12052     --
12053     IF NOT FOUND THEN
12054         return;             -- No user?  No permissions.
12055     ELSIF b_super THEN
12056         --
12057         -- Super user has all permissions everywhere
12058         --
12059         FOR n_work_ou IN
12060             SELECT
12061                 id
12062             FROM
12063                 actor.org_unit
12064             WHERE
12065                 parent_ou IS NULL
12066         LOOP
12067             RETURN NEXT n_work_ou;
12068         END LOOP;
12069         RETURN;
12070     END IF;
12071     --
12072     -- Translate the permission name
12073     -- to a numeric permission id
12074     --
12075     SELECT INTO n_perm
12076         id
12077     FROM
12078         permission.perm_list
12079     WHERE
12080         code = perm_code;
12081     --
12082     IF NOT FOUND THEN
12083         RETURN;               -- No such permission
12084     END IF;
12085     --
12086     -- Find the highest-level org unit (i.e. the minimum depth)
12087     -- to which the permission is applied for this user
12088     --
12089     -- This query is modified from the one in permission.usr_perms().
12090     --
12091     SELECT INTO n_min_depth
12092         min( depth )
12093     FROM    (
12094         SELECT depth
12095           FROM permission.usr_perm_map upm
12096          WHERE upm.usr = user_id
12097            AND (upm.perm = n_perm OR upm.perm = -1)
12098                     UNION
12099         SELECT  gpm.depth
12100           FROM  permission.grp_perm_map gpm
12101           WHERE (gpm.perm = n_perm OR gpm.perm = -1)
12102             AND gpm.grp IN (
12103                SELECT   (permission.grp_ancestors(
12104                     (SELECT profile FROM actor.usr WHERE id = user_id)
12105                 )).id
12106             )
12107                     UNION
12108         SELECT  p.depth
12109           FROM  permission.grp_perm_map p
12110           WHERE (p.perm = n_perm OR p.perm = -1)
12111             AND p.grp IN (
12112                 SELECT (permission.grp_ancestors(m.grp)).id
12113                 FROM   permission.usr_grp_map m
12114                 WHERE  m.usr = user_id
12115             )
12116     ) AS x;
12117     --
12118     IF NOT FOUND THEN
12119         RETURN;                -- No such permission for this user
12120     END IF;
12121     --
12122     -- Identify the org units to which the user is assigned.  Note that
12123     -- we pay no attention to the home_ou column in actor.usr.
12124     --
12125     FOR n_work_ou IN
12126         SELECT
12127             work_ou
12128         FROM
12129             permission.usr_work_ou_map
12130         WHERE
12131             usr = user_id
12132     LOOP            -- For each org unit to which the user is assigned
12133         --
12134         -- Determine the level of the org unit by a lookup in actor.org_unit_type.
12135         -- We take it on faith that this depth agrees with the actual hierarchy
12136         -- defined in actor.org_unit.
12137         --
12138         SELECT INTO n_depth
12139             type.depth
12140         FROM
12141             actor.org_unit_type type
12142                 INNER JOIN actor.org_unit ou
12143                     ON ( ou.ou_type = type.id )
12144         WHERE
12145             ou.id = n_work_ou;
12146         --
12147         IF NOT FOUND THEN
12148             CONTINUE;        -- Maybe raise exception?
12149         END IF;
12150         --
12151         -- Compare the depth of the work org unit to the
12152         -- minimum depth, and branch accordingly
12153         --
12154         IF n_depth = n_min_depth THEN
12155             --
12156             -- The org unit is at the right depth, so return it.
12157             --
12158             RETURN NEXT n_work_ou;
12159         ELSIF n_depth > n_min_depth THEN
12160             --
12161             -- Traverse the org unit tree toward the root,
12162             -- until you reach the minimum depth determined above
12163             --
12164             n_curr_depth := n_depth;
12165             n_curr_ou := n_work_ou;
12166             WHILE n_curr_depth > n_min_depth LOOP
12167                 SELECT INTO n_curr_ou
12168                     parent_ou
12169                 FROM
12170                     actor.org_unit
12171                 WHERE
12172                     id = n_curr_ou;
12173                 --
12174                 IF FOUND THEN
12175                     n_curr_depth := n_curr_depth - 1;
12176                 ELSE
12177                     --
12178                     -- This can happen only if the hierarchy defined in
12179                     -- actor.org_unit is corrupted, or out of sync with
12180                     -- the depths defined in actor.org_unit_type.
12181                     -- Maybe we should raise an exception here, instead
12182                     -- of silently ignoring the problem.
12183                     --
12184                     n_curr_ou = NULL;
12185                     EXIT;
12186                 END IF;
12187             END LOOP;
12188             --
12189             IF n_curr_ou IS NOT NULL THEN
12190                 RETURN NEXT n_curr_ou;
12191             END IF;
12192         ELSE
12193             --
12194             -- The permission applies only at a depth greater than the work org unit.
12195             -- Use connectby() to find all dependent org units at the specified depth.
12196             --
12197             FOR n_curr_ou IN
12198                 SELECT ou::INTEGER
12199                 FROM connectby(
12200                         'actor.org_unit',         -- table name
12201                         'id',                     -- key column
12202                         'parent_ou',              -- recursive foreign key
12203                         n_work_ou::TEXT,          -- id of starting point
12204                         (n_min_depth - n_depth)   -- max depth to search, relative
12205                     )                             --   to starting point
12206                     AS t(
12207                         ou text,            -- dependent org unit
12208                         parent_ou text,     -- (ignore)
12209                         level int           -- depth relative to starting point
12210                     )
12211                 WHERE
12212                     level = n_min_depth - n_depth
12213             LOOP
12214                 RETURN NEXT n_curr_ou;
12215             END LOOP;
12216         END IF;
12217         --
12218     END LOOP;
12219     --
12220     RETURN;
12221     --
12222 END;
12223 $$ LANGUAGE 'plpgsql';
12224
12225 ALTER TABLE acq.purchase_order
12226         ADD COLUMN cancel_reason        INT REFERENCES acq.cancel_reason( id )
12227                                             DEFERRABLE INITIALLY DEFERRED;
12228
12229 ALTER TABLE acq.acq_purchase_order_history
12230         ADD COLUMN cancel_reason INTEGER;
12231
12232 ALTER TABLE acq.purchase_order
12233         ADD COLUMN prepayment_required BOOLEAN NOT NULL DEFAULT FALSE;
12234
12235 ALTER TABLE acq.acq_purchase_order_history
12236         ADD COLUMN prepayment_required BOOLEAN NOT NULL DEFAULT FALSE;
12237
12238 ALTER TABLE acq.lineitem
12239         ADD COLUMN cancel_reason        INT REFERENCES acq.cancel_reason( id )
12240                                             DEFERRABLE INITIALLY DEFERRED;
12241
12242 ALTER TABLE acq.acq_lineitem_history
12243         ADD COLUMN cancel_reason INTEGER;
12244
12245 ALTER TABLE acq.lineitem
12246         ADD COLUMN estimated_unit_price NUMERIC;
12247
12248 ALTER TABLE acq.acq_lineitem_history
12249         ADD COLUMN estimated_unit_price NUMERIC;
12250
12251 ALTER TABLE acq.lineitem
12252         ADD COLUMN claim_policy INT
12253                 REFERENCES acq.claim_policy
12254                 DEFERRABLE INITIALLY DEFERRED;
12255
12256 ALTER TABLE acq.acq_lineitem_history
12257         ADD COLUMN claim_policy INT
12258                 REFERENCES acq.claim_policy
12259                 DEFERRABLE INITIALLY DEFERRED;
12260
12261 ALTER TABLE acq.lineitem_detail
12262         ADD COLUMN cancel_reason        INT REFERENCES acq.cancel_reason( id )
12263                                             DEFERRABLE INITIALLY DEFERRED;
12264
12265 ALTER TABLE acq.lineitem_detail
12266         DROP CONSTRAINT lineitem_detail_lineitem_fkey;
12267
12268 ALTER TABLE acq.lineitem_detail
12269         ADD FOREIGN KEY (lineitem) REFERENCES acq.lineitem( id )
12270                 ON DELETE CASCADE
12271                 DEFERRABLE INITIALLY DEFERRED;
12272
12273 ALTER TABLE acq.lineitem_detail DROP CONSTRAINT lineitem_detail_eg_copy_id_fkey;
12274
12275 INSERT INTO acq.cancel_reason ( id, org_unit, label, description ) VALUES (
12276         1, 1, 'invalid_isbn', oils_i18n_gettext( 1, 'ISBN is unrecognizable', 'acqcr', 'label' ));
12277
12278 INSERT INTO acq.cancel_reason ( id, org_unit, label, description ) VALUES (
12279         2, 1, 'postpone', oils_i18n_gettext( 2, 'Title has been postponed', 'acqcr', 'label' ));
12280
12281 CREATE OR REPLACE FUNCTION vandelay.add_field ( target_xml TEXT, source_xml TEXT, field TEXT ) RETURNS TEXT AS $_$
12282
12283     use MARC::Record;
12284     use MARC::File::XML (BinaryEncoding => 'UTF-8');
12285     use strict;
12286
12287     my $target_xml = shift;
12288     my $source_xml = shift;
12289     my $field_spec = shift;
12290
12291     my $target_r = MARC::Record->new_from_xml( $target_xml );
12292     my $source_r = MARC::Record->new_from_xml( $source_xml );
12293
12294     return $target_xml unless ($target_r && $source_r);
12295
12296     my @field_list = split(',', $field_spec);
12297
12298     my %fields;
12299     for my $f (@field_list) {
12300         $f =~ s/^\s*//; $f =~ s/\s*$//;
12301         if ($f =~ /^(.{3})(\w*)(?:\[([^]]*)\])?$/) {
12302             my $field = $1;
12303             $field =~ s/\s+//;
12304             my $sf = $2;
12305             $sf =~ s/\s+//;
12306             my $match = $3;
12307             $match =~ s/^\s*//; $match =~ s/\s*$//;
12308             $fields{$field} = { sf => [ split('', $sf) ] };
12309             if ($match) {
12310                 my ($msf,$mre) = split('~', $match);
12311                 if (length($msf) > 0 and length($mre) > 0) {
12312                     $msf =~ s/^\s*//; $msf =~ s/\s*$//;
12313                     $mre =~ s/^\s*//; $mre =~ s/\s*$//;
12314                     $fields{$field}{match} = { sf => $msf, re => qr/$mre/ };
12315                 }
12316             }
12317         }
12318     }
12319
12320     for my $f ( keys %fields) {
12321         if ( @{$fields{$f}{sf}} ) {
12322             for my $from_field ($source_r->field( $f )) {
12323                 for my $to_field ($target_r->field( $f )) {
12324                     if (exists($fields{$f}{match})) {
12325                         next unless (grep { $_ =~ $fields{$f}{match}{re} } $to_field->subfield($fields{$f}{match}{sf}));
12326                     }
12327                     my @new_sf = map { ($_ => $from_field->subfield($_)) } @{$fields{$f}{sf}};
12328                     $to_field->add_subfields( @new_sf );
12329                 }
12330             }
12331         } else {
12332             my @new_fields = map { $_->clone } $source_r->field( $f );
12333             $target_r->insert_fields_ordered( @new_fields );
12334         }
12335     }
12336
12337     $target_xml = $target_r->as_xml_record;
12338     $target_xml =~ s/^<\?.+?\?>$//mo;
12339     $target_xml =~ s/\n//sgo;
12340     $target_xml =~ s/>\s+</></sgo;
12341
12342     return $target_xml;
12343
12344 $_$ LANGUAGE PLPERLU;
12345
12346 CREATE OR REPLACE FUNCTION vandelay.strip_field ( xml TEXT, field TEXT ) RETURNS TEXT AS $_$
12347
12348     use MARC::Record;
12349     use MARC::File::XML (BinaryEncoding => 'UTF-8');
12350     use strict;
12351
12352     my $xml = shift;
12353     my $r = MARC::Record->new_from_xml( $xml );
12354
12355     return $xml unless ($r);
12356
12357     my $field_spec = shift;
12358     my @field_list = split(',', $field_spec);
12359
12360     my %fields;
12361     for my $f (@field_list) {
12362         $f =~ s/^\s*//; $f =~ s/\s*$//;
12363         if ($f =~ /^(.{3})(\w*)(?:\[([^]]*)\])?$/) {
12364             my $field = $1;
12365             $field =~ s/\s+//;
12366             my $sf = $2;
12367             $sf =~ s/\s+//;
12368             my $match = $3;
12369             $match =~ s/^\s*//; $match =~ s/\s*$//;
12370             $fields{$field} = { sf => [ split('', $sf) ] };
12371             if ($match) {
12372                 my ($msf,$mre) = split('~', $match);
12373                 if (length($msf) > 0 and length($mre) > 0) {
12374                     $msf =~ s/^\s*//; $msf =~ s/\s*$//;
12375                     $mre =~ s/^\s*//; $mre =~ s/\s*$//;
12376                     $fields{$field}{match} = { sf => $msf, re => qr/$mre/ };
12377                 }
12378             }
12379         }
12380     }
12381
12382     for my $f ( keys %fields) {
12383         for my $to_field ($r->field( $f )) {
12384             if (exists($fields{$f}{match})) {
12385                 next unless (grep { $_ =~ $fields{$f}{match}{re} } $to_field->subfield($fields{$f}{match}{sf}));
12386             }
12387
12388             if ( @{$fields{$f}{sf}} ) {
12389                 $to_field->delete_subfield(code => $fields{$f}{sf});
12390             } else {
12391                 $r->delete_field( $to_field );
12392             }
12393         }
12394     }
12395
12396     $xml = $r->as_xml_record;
12397     $xml =~ s/^<\?.+?\?>$//mo;
12398     $xml =~ s/\n//sgo;
12399     $xml =~ s/>\s+</></sgo;
12400
12401     return $xml;
12402
12403 $_$ LANGUAGE PLPERLU;
12404
12405 CREATE OR REPLACE FUNCTION vandelay.replace_field ( target_xml TEXT, source_xml TEXT, field TEXT ) RETURNS TEXT AS $_$
12406     SELECT vandelay.add_field( vandelay.strip_field( $1, $3), $2, $3 );
12407 $_$ LANGUAGE SQL;
12408
12409 CREATE OR REPLACE FUNCTION vandelay.preserve_field ( incumbent_xml TEXT, incoming_xml TEXT, field TEXT ) RETURNS TEXT AS $_$
12410     SELECT vandelay.add_field( vandelay.strip_field( $2, $3), $1, $3 );
12411 $_$ LANGUAGE SQL;
12412
12413 CREATE VIEW action.unfulfilled_hold_max_loop AS
12414         SELECT  hold,
12415                 max(count) AS max
12416         FROM    action.unfulfilled_hold_loops
12417         GROUP BY 1;
12418
12419 ALTER TABLE acq.lineitem_attr
12420         DROP CONSTRAINT lineitem_attr_lineitem_fkey;
12421
12422 ALTER TABLE acq.lineitem_attr
12423         ADD FOREIGN KEY (lineitem) REFERENCES acq.lineitem( id )
12424                 ON DELETE CASCADE
12425                 DEFERRABLE INITIALLY DEFERRED;
12426
12427 ALTER TABLE acq.po_note
12428         ADD COLUMN vendor_public BOOLEAN NOT NULL DEFAULT FALSE;
12429
12430 CREATE TABLE vandelay.merge_profile (
12431     id              BIGSERIAL   PRIMARY KEY,
12432     owner           INT         NOT NULL REFERENCES actor.org_unit (id) ON UPDATE CASCADE ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED,
12433     name            TEXT        NOT NULL,
12434     add_spec        TEXT,
12435     replace_spec    TEXT,
12436     strip_spec      TEXT,
12437     preserve_spec   TEXT,
12438     CONSTRAINT vand_merge_prof_owner_name_idx UNIQUE (owner,name),
12439     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))
12440 );
12441
12442 CREATE OR REPLACE FUNCTION vandelay.match_bib_record ( ) RETURNS TRIGGER AS $func$
12443 DECLARE
12444     attr        RECORD;
12445     attr_def    RECORD;
12446     eg_rec      RECORD;
12447     id_value    TEXT;
12448     exact_id    BIGINT;
12449 BEGIN
12450
12451     DELETE FROM vandelay.bib_match WHERE queued_record = NEW.id;
12452
12453     SELECT * INTO attr_def FROM vandelay.bib_attr_definition WHERE xpath = '//*[@tag="901"]/*[@code="c"]' ORDER BY id LIMIT 1;
12454
12455     IF attr_def IS NOT NULL AND attr_def.id IS NOT NULL THEN
12456         id_value := extract_marc_field('vandelay.queued_bib_record', NEW.id, attr_def.xpath, attr_def.remove);
12457
12458         IF id_value IS NOT NULL AND id_value <> '' AND id_value ~ $r$^\d+$$r$ THEN
12459             SELECT id INTO exact_id FROM biblio.record_entry WHERE id = id_value::BIGINT AND NOT deleted;
12460             SELECT * INTO attr FROM vandelay.queued_bib_record_attr WHERE record = NEW.id and field = attr_def.id LIMIT 1;
12461             IF exact_id IS NOT NULL THEN
12462                 INSERT INTO vandelay.bib_match (field_type, matched_attr, queued_record, eg_record) VALUES ('id', attr.id, NEW.id, exact_id);
12463             END IF;
12464         END IF;
12465     END IF;
12466
12467     IF exact_id IS NULL THEN
12468         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
12469
12470             -- All numbers? check for an id match
12471             IF (attr.attr_value ~ $r$^\d+$$r$) THEN
12472                 FOR eg_rec IN SELECT * FROM biblio.record_entry WHERE id = attr.attr_value::BIGINT AND deleted IS FALSE LOOP
12473                     INSERT INTO vandelay.bib_match (field_type, matched_attr, queued_record, eg_record) VALUES ('id', attr.id, NEW.id, eg_rec.id);
12474                 END LOOP;
12475             END IF;
12476
12477             -- Looks like an ISBN? check for an isbn match
12478             IF (attr.attr_value ~* $r$^[0-9x]+$$r$ AND character_length(attr.attr_value) IN (10,13)) THEN
12479                 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
12480                     PERFORM id FROM biblio.record_entry WHERE id = eg_rec.record AND deleted IS FALSE;
12481                     IF FOUND THEN
12482                         INSERT INTO vandelay.bib_match (field_type, matched_attr, queued_record, eg_record) VALUES ('isbn', attr.id, NEW.id, eg_rec.record);
12483                     END IF;
12484                 END LOOP;
12485
12486                 -- subcheck for isbn-as-tcn
12487                 FOR eg_rec IN SELECT * FROM biblio.record_entry WHERE tcn_value = 'i' || attr.attr_value AND deleted IS FALSE LOOP
12488                     INSERT INTO vandelay.bib_match (field_type, matched_attr, queued_record, eg_record) VALUES ('tcn_value', attr.id, NEW.id, eg_rec.id);
12489                 END LOOP;
12490             END IF;
12491
12492             -- check for an OCLC tcn_value match
12493             IF (attr.attr_value ~ $r$^o\d+$$r$) THEN
12494                 FOR eg_rec IN SELECT * FROM biblio.record_entry WHERE tcn_value = regexp_replace(attr.attr_value,'^o','ocm') AND deleted IS FALSE LOOP
12495                     INSERT INTO vandelay.bib_match (field_type, matched_attr, queued_record, eg_record) VALUES ('tcn_value', attr.id, NEW.id, eg_rec.id);
12496                 END LOOP;
12497             END IF;
12498
12499             -- check for a direct tcn_value match
12500             FOR eg_rec IN SELECT * FROM biblio.record_entry WHERE tcn_value = attr.attr_value AND deleted IS FALSE LOOP
12501                 INSERT INTO vandelay.bib_match (field_type, matched_attr, queued_record, eg_record) VALUES ('tcn_value', attr.id, NEW.id, eg_rec.id);
12502             END LOOP;
12503
12504             -- check for a direct item barcode match
12505             FOR eg_rec IN
12506                     SELECT  DISTINCT b.*
12507                       FROM  biblio.record_entry b
12508                             JOIN asset.call_number cn ON (cn.record = b.id)
12509                             JOIN asset.copy cp ON (cp.call_number = cn.id)
12510                       WHERE cp.barcode = attr.attr_value AND cp.deleted IS FALSE
12511             LOOP
12512                 INSERT INTO vandelay.bib_match (field_type, matched_attr, queued_record, eg_record) VALUES ('id', attr.id, NEW.id, eg_rec.id);
12513             END LOOP;
12514
12515         END LOOP;
12516     END IF;
12517
12518     RETURN NULL;
12519 END;
12520 $func$ LANGUAGE PLPGSQL;
12521
12522 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 $_$
12523     SELECT vandelay.replace_field( vandelay.add_field( vandelay.strip_field( $1, $5) , $2, $3 ), $2, $4);
12524 $_$ LANGUAGE SQL;
12525
12526 CREATE TYPE vandelay.compile_profile AS (add_rule TEXT, replace_rule TEXT, preserve_rule TEXT, strip_rule TEXT);
12527 CREATE OR REPLACE FUNCTION vandelay.compile_profile ( incoming_xml TEXT ) RETURNS vandelay.compile_profile AS $_$
12528 DECLARE
12529     output              vandelay.compile_profile%ROWTYPE;
12530     profile             vandelay.merge_profile%ROWTYPE;
12531     profile_tmpl        TEXT;
12532     profile_tmpl_owner  TEXT;
12533     add_rule            TEXT := '';
12534     strip_rule          TEXT := '';
12535     replace_rule        TEXT := '';
12536     preserve_rule       TEXT := '';
12537
12538 BEGIN
12539
12540     profile_tmpl := (oils_xpath('//*[@tag="905"]/*[@code="t"]/text()',incoming_xml))[1];
12541     profile_tmpl_owner := (oils_xpath('//*[@tag="905"]/*[@code="o"]/text()',incoming_xml))[1];
12542
12543     IF profile_tmpl IS NOT NULL AND profile_tmpl <> '' AND profile_tmpl_owner IS NOT NULL AND profile_tmpl_owner <> '' THEN
12544         SELECT  p.* INTO profile
12545           FROM  vandelay.merge_profile p
12546                 JOIN actor.org_unit u ON (u.id = p.owner)
12547           WHERE p.name = profile_tmpl
12548                 AND u.shortname = profile_tmpl_owner;
12549
12550         IF profile.id IS NOT NULL THEN
12551             add_rule := COALESCE(profile.add_spec,'');
12552             strip_rule := COALESCE(profile.strip_spec,'');
12553             replace_rule := COALESCE(profile.replace_spec,'');
12554             preserve_rule := COALESCE(profile.preserve_spec,'');
12555         END IF;
12556     END IF;
12557
12558     add_rule := add_rule || ',' || COALESCE(ARRAY_TO_STRING(oils_xpath('//*[@tag="905"]/*[@code="a"]/text()',incoming_xml),''),'');
12559     strip_rule := strip_rule || ',' || COALESCE(ARRAY_TO_STRING(oils_xpath('//*[@tag="905"]/*[@code="d"]/text()',incoming_xml),''),'');
12560     replace_rule := replace_rule || ',' || COALESCE(ARRAY_TO_STRING(oils_xpath('//*[@tag="905"]/*[@code="r"]/text()',incoming_xml),''),'');
12561     preserve_rule := preserve_rule || ',' || COALESCE(ARRAY_TO_STRING(oils_xpath('//*[@tag="905"]/*[@code="p"]/text()',incoming_xml),''),'');
12562
12563     output.add_rule := BTRIM(add_rule,',');
12564     output.replace_rule := BTRIM(replace_rule,',');
12565     output.strip_rule := BTRIM(strip_rule,',');
12566     output.preserve_rule := BTRIM(preserve_rule,',');
12567
12568     RETURN output;
12569 END;
12570 $_$ LANGUAGE PLPGSQL;
12571
12572 -- Template-based marc munging functions
12573 CREATE OR REPLACE FUNCTION vandelay.template_overlay_bib_record ( v_marc TEXT, eg_id BIGINT, merge_profile_id INT ) RETURNS BOOL AS $$
12574 DECLARE
12575     merge_profile   vandelay.merge_profile%ROWTYPE;
12576     dyn_profile     vandelay.compile_profile%ROWTYPE;
12577     editor_string   TEXT;
12578     editor_id       INT;
12579     source_marc     TEXT;
12580     target_marc     TEXT;
12581     eg_marc         TEXT;
12582     replace_rule    TEXT;
12583     match_count     INT;
12584 BEGIN
12585
12586     SELECT  b.marc INTO eg_marc
12587       FROM  biblio.record_entry b
12588       WHERE b.id = eg_id
12589       LIMIT 1;
12590
12591     IF eg_marc IS NULL OR v_marc IS NULL THEN
12592         -- RAISE NOTICE 'no marc for template or bib record';
12593         RETURN FALSE;
12594     END IF;
12595
12596     dyn_profile := vandelay.compile_profile( v_marc );
12597
12598     IF merge_profile_id IS NOT NULL THEN
12599         SELECT * INTO merge_profile FROM vandelay.merge_profile WHERE id = merge_profile_id;
12600         IF FOUND THEN
12601             dyn_profile.add_rule := BTRIM( dyn_profile.add_rule || ',' || COALESCE(merge_profile.add_spec,''), ',');
12602             dyn_profile.strip_rule := BTRIM( dyn_profile.strip_rule || ',' || COALESCE(merge_profile.strip_spec,''), ',');
12603             dyn_profile.replace_rule := BTRIM( dyn_profile.replace_rule || ',' || COALESCE(merge_profile.replace_spec,''), ',');
12604             dyn_profile.preserve_rule := BTRIM( dyn_profile.preserve_rule || ',' || COALESCE(merge_profile.preserve_spec,''), ',');
12605         END IF;
12606     END IF;
12607
12608     IF dyn_profile.replace_rule <> '' AND dyn_profile.preserve_rule <> '' THEN
12609         -- RAISE NOTICE 'both replace [%] and preserve [%] specified', dyn_profile.replace_rule, dyn_profile.preserve_rule;
12610         RETURN FALSE;
12611     END IF;
12612
12613     IF dyn_profile.replace_rule <> '' THEN
12614         source_marc = v_marc;
12615         target_marc = eg_marc;
12616         replace_rule = dyn_profile.replace_rule;
12617     ELSE
12618         source_marc = eg_marc;
12619         target_marc = v_marc;
12620         replace_rule = dyn_profile.preserve_rule;
12621     END IF;
12622
12623     UPDATE  biblio.record_entry
12624       SET   marc = vandelay.merge_record_xml( target_marc, source_marc, dyn_profile.add_rule, replace_rule, dyn_profile.strip_rule )
12625       WHERE id = eg_id;
12626
12627     IF NOT FOUND THEN
12628         -- RAISE NOTICE 'update of biblio.record_entry failed';
12629         RETURN FALSE;
12630     END IF;
12631
12632     RETURN TRUE;
12633
12634 END;
12635 $$ LANGUAGE PLPGSQL;
12636
12637 CREATE OR REPLACE FUNCTION vandelay.template_overlay_bib_record ( v_marc TEXT, eg_id BIGINT) RETURNS BOOL AS $$
12638     SELECT vandelay.template_overlay_bib_record( $1, $2, NULL);
12639 $$ LANGUAGE SQL;
12640
12641 CREATE OR REPLACE FUNCTION vandelay.overlay_bib_record ( import_id BIGINT, eg_id BIGINT, merge_profile_id INT ) RETURNS BOOL AS $$
12642 DECLARE
12643     merge_profile   vandelay.merge_profile%ROWTYPE;
12644     dyn_profile     vandelay.compile_profile%ROWTYPE;
12645     editor_string   TEXT;
12646     editor_id       INT;
12647     source_marc     TEXT;
12648     target_marc     TEXT;
12649     eg_marc         TEXT;
12650     v_marc          TEXT;
12651     replace_rule    TEXT;
12652     match_count     INT;
12653 BEGIN
12654
12655     SELECT  q.marc INTO v_marc
12656       FROM  vandelay.queued_record q
12657             JOIN vandelay.bib_match m ON (m.queued_record = q.id AND q.id = import_id)
12658       LIMIT 1;
12659
12660     IF v_marc IS NULL THEN
12661         -- RAISE NOTICE 'no marc for vandelay or bib record';
12662         RETURN FALSE;
12663     END IF;
12664
12665     IF vandelay.template_overlay_bib_record( v_marc, eg_id, merge_profile_id) THEN
12666         UPDATE  vandelay.queued_bib_record
12667           SET   imported_as = eg_id,
12668                 import_time = NOW()
12669           WHERE id = import_id;
12670
12671         editor_string := (oils_xpath('//*[@tag="905"]/*[@code="u"]/text()',v_marc))[1];
12672
12673         IF editor_string IS NOT NULL AND editor_string <> '' THEN
12674             SELECT usr INTO editor_id FROM actor.card WHERE barcode = editor_string;
12675
12676             IF editor_id IS NULL THEN
12677                 SELECT id INTO editor_id FROM actor.usr WHERE usrname = editor_string;
12678             END IF;
12679
12680             IF editor_id IS NOT NULL THEN
12681                 UPDATE biblio.record_entry SET editor = editor_id WHERE id = eg_id;
12682             END IF;
12683         END IF;
12684
12685         RETURN TRUE;
12686     END IF;
12687
12688     -- RAISE NOTICE 'update of biblio.record_entry failed';
12689
12690     RETURN FALSE;
12691
12692 END;
12693 $$ LANGUAGE PLPGSQL;
12694
12695 CREATE OR REPLACE FUNCTION vandelay.auto_overlay_bib_record ( import_id BIGINT, merge_profile_id INT ) RETURNS BOOL AS $$
12696 DECLARE
12697     eg_id           BIGINT;
12698     match_count     INT;
12699     match_attr      vandelay.bib_attr_definition%ROWTYPE;
12700 BEGIN
12701
12702     PERFORM * FROM vandelay.queued_bib_record WHERE import_time IS NOT NULL AND id = import_id;
12703
12704     IF FOUND THEN
12705         -- RAISE NOTICE 'already imported, cannot auto-overlay'
12706         RETURN FALSE;
12707     END IF;
12708
12709     SELECT COUNT(*) INTO match_count FROM vandelay.bib_match WHERE queued_record = import_id;
12710
12711     IF match_count <> 1 THEN
12712         -- RAISE NOTICE 'not an exact match';
12713         RETURN FALSE;
12714     END IF;
12715
12716     SELECT  d.* INTO match_attr
12717       FROM  vandelay.bib_attr_definition d
12718             JOIN vandelay.queued_bib_record_attr a ON (a.field = d.id)
12719             JOIN vandelay.bib_match m ON (m.matched_attr = a.id)
12720       WHERE m.queued_record = import_id;
12721
12722     IF NOT (match_attr.xpath ~ '@tag="901"' AND match_attr.xpath ~ '@code="c"') THEN
12723         -- RAISE NOTICE 'not a 901c match: %', match_attr.xpath;
12724         RETURN FALSE;
12725     END IF;
12726
12727     SELECT  m.eg_record INTO eg_id
12728       FROM  vandelay.bib_match m
12729       WHERE m.queued_record = import_id
12730       LIMIT 1;
12731
12732     IF eg_id IS NULL THEN
12733         RETURN FALSE;
12734     END IF;
12735
12736     RETURN vandelay.overlay_bib_record( import_id, eg_id, merge_profile_id );
12737 END;
12738 $$ LANGUAGE PLPGSQL;
12739
12740 CREATE OR REPLACE FUNCTION vandelay.auto_overlay_bib_queue ( queue_id BIGINT, merge_profile_id INT ) RETURNS SETOF BIGINT AS $$
12741 DECLARE
12742     queued_record   vandelay.queued_bib_record%ROWTYPE;
12743 BEGIN
12744
12745     FOR queued_record IN SELECT * FROM vandelay.queued_bib_record WHERE queue = queue_id AND import_time IS NULL LOOP
12746
12747         IF vandelay.auto_overlay_bib_record( queued_record.id, merge_profile_id ) THEN
12748             RETURN NEXT queued_record.id;
12749         END IF;
12750
12751     END LOOP;
12752
12753     RETURN;
12754
12755 END;
12756 $$ LANGUAGE PLPGSQL;
12757
12758 CREATE OR REPLACE FUNCTION vandelay.auto_overlay_bib_queue ( queue_id BIGINT ) RETURNS SETOF BIGINT AS $$
12759     SELECT * FROM vandelay.auto_overlay_bib_queue( $1, NULL );
12760 $$ LANGUAGE SQL;
12761
12762 CREATE OR REPLACE FUNCTION vandelay.overlay_authority_record ( import_id BIGINT, eg_id BIGINT, merge_profile_id INT ) RETURNS BOOL AS $$
12763 DECLARE
12764     merge_profile   vandelay.merge_profile%ROWTYPE;
12765     dyn_profile     vandelay.compile_profile%ROWTYPE;
12766     source_marc     TEXT;
12767     target_marc     TEXT;
12768     eg_marc         TEXT;
12769     v_marc          TEXT;
12770     replace_rule    TEXT;
12771     match_count     INT;
12772 BEGIN
12773
12774     SELECT  b.marc INTO eg_marc
12775       FROM  authority.record_entry b
12776             JOIN vandelay.authority_match m ON (m.eg_record = b.id AND m.queued_record = import_id)
12777       LIMIT 1;
12778
12779     SELECT  q.marc INTO v_marc
12780       FROM  vandelay.queued_record q
12781             JOIN vandelay.authority_match m ON (m.queued_record = q.id AND q.id = import_id)
12782       LIMIT 1;
12783
12784     IF eg_marc IS NULL OR v_marc IS NULL THEN
12785         -- RAISE NOTICE 'no marc for vandelay or authority record';
12786         RETURN FALSE;
12787     END IF;
12788
12789     dyn_profile := vandelay.compile_profile( v_marc );
12790
12791     IF merge_profile_id IS NOT NULL THEN
12792         SELECT * INTO merge_profile FROM vandelay.merge_profile WHERE id = merge_profile_id;
12793         IF FOUND THEN
12794             dyn_profile.add_rule := BTRIM( dyn_profile.add_rule || ',' || COALESCE(merge_profile.add_spec,''), ',');
12795             dyn_profile.strip_rule := BTRIM( dyn_profile.strip_rule || ',' || COALESCE(merge_profile.strip_spec,''), ',');
12796             dyn_profile.replace_rule := BTRIM( dyn_profile.replace_rule || ',' || COALESCE(merge_profile.replace_spec,''), ',');
12797             dyn_profile.preserve_rule := BTRIM( dyn_profile.preserve_rule || ',' || COALESCE(merge_profile.preserve_spec,''), ',');
12798         END IF;
12799     END IF;
12800
12801     IF dyn_profile.replace_rule <> '' AND dyn_profile.preserve_rule <> '' THEN
12802         -- RAISE NOTICE 'both replace [%] and preserve [%] specified', dyn_profile.replace_rule, dyn_profile.preserve_rule;
12803         RETURN FALSE;
12804     END IF;
12805
12806     IF dyn_profile.replace_rule <> '' THEN
12807         source_marc = v_marc;
12808         target_marc = eg_marc;
12809         replace_rule = dyn_profile.replace_rule;
12810     ELSE
12811         source_marc = eg_marc;
12812         target_marc = v_marc;
12813         replace_rule = dyn_profile.preserve_rule;
12814     END IF;
12815
12816     UPDATE  authority.record_entry
12817       SET   marc = vandelay.merge_record_xml( target_marc, source_marc, dyn_profile.add_rule, replace_rule, dyn_profile.strip_rule )
12818       WHERE id = eg_id;
12819
12820     IF FOUND THEN
12821         UPDATE  vandelay.queued_authority_record
12822           SET   imported_as = eg_id,
12823                 import_time = NOW()
12824           WHERE id = import_id;
12825         RETURN TRUE;
12826     END IF;
12827
12828     -- RAISE NOTICE 'update of authority.record_entry failed';
12829
12830     RETURN FALSE;
12831
12832 END;
12833 $$ LANGUAGE PLPGSQL;
12834
12835 CREATE OR REPLACE FUNCTION vandelay.auto_overlay_authority_record ( import_id BIGINT, merge_profile_id INT ) RETURNS BOOL AS $$
12836 DECLARE
12837     eg_id           BIGINT;
12838     match_count     INT;
12839 BEGIN
12840     SELECT COUNT(*) INTO match_count FROM vandelay.authority_match WHERE queued_record = import_id;
12841
12842     IF match_count <> 1 THEN
12843         -- RAISE NOTICE 'not an exact match';
12844         RETURN FALSE;
12845     END IF;
12846
12847     SELECT  m.eg_record INTO eg_id
12848       FROM  vandelay.authority_match m
12849       WHERE m.queued_record = import_id
12850       LIMIT 1;
12851
12852     IF eg_id IS NULL THEN
12853         RETURN FALSE;
12854     END IF;
12855
12856     RETURN vandelay.overlay_authority_record( import_id, eg_id, merge_profile_id );
12857 END;
12858 $$ LANGUAGE PLPGSQL;
12859
12860 CREATE OR REPLACE FUNCTION vandelay.auto_overlay_authority_queue ( queue_id BIGINT, merge_profile_id INT ) RETURNS SETOF BIGINT AS $$
12861 DECLARE
12862     queued_record   vandelay.queued_authority_record%ROWTYPE;
12863 BEGIN
12864
12865     FOR queued_record IN SELECT * FROM vandelay.queued_authority_record WHERE queue = queue_id AND import_time IS NULL LOOP
12866
12867         IF vandelay.auto_overlay_authority_record( queued_record.id, merge_profile_id ) THEN
12868             RETURN NEXT queued_record.id;
12869         END IF;
12870
12871     END LOOP;
12872
12873     RETURN;
12874
12875 END;
12876 $$ LANGUAGE PLPGSQL;
12877
12878 CREATE OR REPLACE FUNCTION vandelay.auto_overlay_authority_queue ( queue_id BIGINT ) RETURNS SETOF BIGINT AS $$
12879     SELECT * FROM vandelay.auto_overlay_authority_queue( $1, NULL );
12880 $$ LANGUAGE SQL;
12881
12882 CREATE TYPE vandelay.tcn_data AS (tcn TEXT, tcn_source TEXT, used BOOL);
12883 CREATE OR REPLACE FUNCTION vandelay.find_bib_tcn_data ( xml TEXT ) RETURNS SETOF vandelay.tcn_data AS $_$
12884 DECLARE
12885     eg_tcn          TEXT;
12886     eg_tcn_source   TEXT;
12887     output          vandelay.tcn_data%ROWTYPE;
12888 BEGIN
12889
12890     -- 001/003
12891     eg_tcn := BTRIM((oils_xpath('//*[@tag="001"]/text()',xml))[1]);
12892     IF eg_tcn IS NOT NULL AND eg_tcn <> '' THEN
12893
12894         eg_tcn_source := BTRIM((oils_xpath('//*[@tag="003"]/text()',xml))[1]);
12895         IF eg_tcn_source IS NULL OR eg_tcn_source = '' THEN
12896             eg_tcn_source := 'System Local';
12897         END IF;
12898
12899         PERFORM id FROM biblio.record_entry WHERE tcn_value = eg_tcn  AND NOT deleted;
12900
12901         IF NOT FOUND THEN
12902             output.used := FALSE;
12903         ELSE
12904             output.used := TRUE;
12905         END IF;
12906
12907         output.tcn := eg_tcn;
12908         output.tcn_source := eg_tcn_source;
12909         RETURN NEXT output;
12910
12911     END IF;
12912
12913     -- 901 ab
12914     eg_tcn := BTRIM((oils_xpath('//*[@tag="901"]/*[@code="a"]/text()',xml))[1]);
12915     IF eg_tcn IS NOT NULL AND eg_tcn <> '' THEN
12916
12917         eg_tcn_source := BTRIM((oils_xpath('//*[@tag="901"]/*[@code="b"]/text()',xml))[1]);
12918         IF eg_tcn_source IS NULL OR eg_tcn_source = '' THEN
12919             eg_tcn_source := 'System Local';
12920         END IF;
12921
12922         PERFORM id FROM biblio.record_entry WHERE tcn_value = eg_tcn  AND NOT deleted;
12923
12924         IF NOT FOUND THEN
12925             output.used := FALSE;
12926         ELSE
12927             output.used := TRUE;
12928         END IF;
12929
12930         output.tcn := eg_tcn;
12931         output.tcn_source := eg_tcn_source;
12932         RETURN NEXT output;
12933
12934     END IF;
12935
12936     -- 039 ab
12937     eg_tcn := BTRIM((oils_xpath('//*[@tag="039"]/*[@code="a"]/text()',xml))[1]);
12938     IF eg_tcn IS NOT NULL AND eg_tcn <> '' THEN
12939
12940         eg_tcn_source := BTRIM((oils_xpath('//*[@tag="039"]/*[@code="b"]/text()',xml))[1]);
12941         IF eg_tcn_source IS NULL OR eg_tcn_source = '' THEN
12942             eg_tcn_source := 'System Local';
12943         END IF;
12944
12945         PERFORM id FROM biblio.record_entry WHERE tcn_value = eg_tcn  AND NOT deleted;
12946
12947         IF NOT FOUND THEN
12948             output.used := FALSE;
12949         ELSE
12950             output.used := TRUE;
12951         END IF;
12952
12953         output.tcn := eg_tcn;
12954         output.tcn_source := eg_tcn_source;
12955         RETURN NEXT output;
12956
12957     END IF;
12958
12959     -- 020 a
12960     eg_tcn := REGEXP_REPLACE((oils_xpath('//*[@tag="020"]/*[@code="a"]/text()',xml))[1], $re$^(\w+).*?$$re$, $re$\1$re$);
12961     IF eg_tcn IS NOT NULL AND eg_tcn <> '' THEN
12962
12963         eg_tcn_source := 'ISBN';
12964
12965         PERFORM id FROM biblio.record_entry WHERE tcn_value = eg_tcn  AND NOT deleted;
12966
12967         IF NOT FOUND THEN
12968             output.used := FALSE;
12969         ELSE
12970             output.used := TRUE;
12971         END IF;
12972
12973         output.tcn := eg_tcn;
12974         output.tcn_source := eg_tcn_source;
12975         RETURN NEXT output;
12976
12977     END IF;
12978
12979     -- 022 a
12980     eg_tcn := REGEXP_REPLACE((oils_xpath('//*[@tag="022"]/*[@code="a"]/text()',xml))[1], $re$^(\w+).*?$$re$, $re$\1$re$);
12981     IF eg_tcn IS NOT NULL AND eg_tcn <> '' THEN
12982
12983         eg_tcn_source := 'ISSN';
12984
12985         PERFORM id FROM biblio.record_entry WHERE tcn_value = eg_tcn  AND NOT deleted;
12986
12987         IF NOT FOUND THEN
12988             output.used := FALSE;
12989         ELSE
12990             output.used := TRUE;
12991         END IF;
12992
12993         output.tcn := eg_tcn;
12994         output.tcn_source := eg_tcn_source;
12995         RETURN NEXT output;
12996
12997     END IF;
12998
12999     -- 010 a
13000     eg_tcn := REGEXP_REPLACE((oils_xpath('//*[@tag="010"]/*[@code="a"]/text()',xml))[1], $re$^(\w+).*?$$re$, $re$\1$re$);
13001     IF eg_tcn IS NOT NULL AND eg_tcn <> '' THEN
13002
13003         eg_tcn_source := 'LCCN';
13004
13005         PERFORM id FROM biblio.record_entry WHERE tcn_value = eg_tcn  AND NOT deleted;
13006
13007         IF NOT FOUND THEN
13008             output.used := FALSE;
13009         ELSE
13010             output.used := TRUE;
13011         END IF;
13012
13013         output.tcn := eg_tcn;
13014         output.tcn_source := eg_tcn_source;
13015         RETURN NEXT output;
13016
13017     END IF;
13018
13019     -- 035 a
13020     eg_tcn := REGEXP_REPLACE((oils_xpath('//*[@tag="035"]/*[@code="a"]/text()',xml))[1], $re$^.*?(\w+)$$re$, $re$\1$re$);
13021     IF eg_tcn IS NOT NULL AND eg_tcn <> '' THEN
13022
13023         eg_tcn_source := 'System Legacy';
13024
13025         PERFORM id FROM biblio.record_entry WHERE tcn_value = eg_tcn  AND NOT deleted;
13026
13027         IF NOT FOUND THEN
13028             output.used := FALSE;
13029         ELSE
13030             output.used := TRUE;
13031         END IF;
13032
13033         output.tcn := eg_tcn;
13034         output.tcn_source := eg_tcn_source;
13035         RETURN NEXT output;
13036
13037     END IF;
13038
13039     RETURN;
13040 END;
13041 $_$ LANGUAGE PLPGSQL;
13042
13043 CREATE INDEX claim_lid_idx ON acq.claim( lineitem_detail );
13044
13045 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);
13046
13047 UPDATE biblio.record_entry SET marc = '<record xmlns="http://www.loc.gov/MARC21/slim"/>' WHERE id = -1;
13048
13049 CREATE INDEX metabib_title_field_entry_value_idx ON metabib.title_field_entry (SUBSTRING(value,1,1024)) WHERE index_vector = ''::TSVECTOR;
13050 CREATE INDEX metabib_author_field_entry_value_idx ON metabib.author_field_entry (SUBSTRING(value,1,1024)) WHERE index_vector = ''::TSVECTOR;
13051 CREATE INDEX metabib_subject_field_entry_value_idx ON metabib.subject_field_entry (SUBSTRING(value,1,1024)) WHERE index_vector = ''::TSVECTOR;
13052 CREATE INDEX metabib_keyword_field_entry_value_idx ON metabib.keyword_field_entry (SUBSTRING(value,1,1024)) WHERE index_vector = ''::TSVECTOR;
13053 CREATE INDEX metabib_series_field_entry_value_idx ON metabib.series_field_entry (SUBSTRING(value,1,1024)) WHERE index_vector = ''::TSVECTOR;
13054
13055 CREATE INDEX metabib_author_field_entry_source_idx ON metabib.author_field_entry (source);
13056 CREATE INDEX metabib_keyword_field_entry_source_idx ON metabib.keyword_field_entry (source);
13057 CREATE INDEX metabib_title_field_entry_source_idx ON metabib.title_field_entry (source);
13058 CREATE INDEX metabib_series_field_entry_source_idx ON metabib.series_field_entry (source);
13059
13060 CREATE TABLE acq.claim_policy_action (
13061         id              SERIAL       PRIMARY KEY,
13062         claim_policy    INT          NOT NULL REFERENCES acq.claim_policy
13063                                  ON DELETE CASCADE
13064                                      DEFERRABLE INITIALLY DEFERRED,
13065         action_interval INTERVAL     NOT NULL,
13066         action          INT          NOT NULL REFERENCES acq.claim_event_type
13067                                      DEFERRABLE INITIALLY DEFERRED,
13068         CONSTRAINT action_sequence UNIQUE (claim_policy, action_interval)
13069 );
13070
13071 CREATE OR REPLACE FUNCTION public.ingest_acq_marc ( ) RETURNS TRIGGER AS $function$
13072 DECLARE
13073     value       TEXT;
13074     atype       TEXT;
13075     prov        INT;
13076     pos         INT;
13077     adef        RECORD;
13078     xpath_string    TEXT;
13079 BEGIN
13080     FOR adef IN SELECT *,tableoid FROM acq.lineitem_attr_definition LOOP
13081  
13082         SELECT relname::TEXT INTO atype FROM pg_class WHERE oid = adef.tableoid;
13083  
13084         IF (atype NOT IN ('lineitem_usr_attr_definition','lineitem_local_attr_definition')) THEN
13085             IF (atype = 'lineitem_provider_attr_definition') THEN
13086                 SELECT provider INTO prov FROM acq.lineitem_provider_attr_definition WHERE id = adef.id;
13087                 CONTINUE WHEN NEW.provider IS NULL OR prov <> NEW.provider;
13088             END IF;
13089  
13090             IF (atype = 'lineitem_provider_attr_definition') THEN
13091                 SELECT xpath INTO xpath_string FROM acq.lineitem_provider_attr_definition WHERE id = adef.id;
13092             ELSIF (atype = 'lineitem_marc_attr_definition') THEN
13093                 SELECT xpath INTO xpath_string FROM acq.lineitem_marc_attr_definition WHERE id = adef.id;
13094             ELSIF (atype = 'lineitem_generated_attr_definition') THEN
13095                 SELECT xpath INTO xpath_string FROM acq.lineitem_generated_attr_definition WHERE id = adef.id;
13096             END IF;
13097  
13098             xpath_string := REGEXP_REPLACE(xpath_string,$re$//?text\(\)$$re$,'');
13099  
13100             pos := 1;
13101  
13102             LOOP
13103                 SELECT extract_acq_marc_field(id, xpath_string || '[' || pos || ']', adef.remove) INTO value FROM acq.lineitem WHERE id = NEW.id;
13104  
13105                 IF (value IS NOT NULL AND value <> '') THEN
13106                     INSERT INTO acq.lineitem_attr (lineitem, definition, attr_type, attr_name, attr_value)
13107                         VALUES (NEW.id, adef.id, atype, adef.code, value);
13108                 ELSE
13109                     EXIT;
13110                 END IF;
13111  
13112                 pos := pos + 1;
13113             END LOOP;
13114  
13115         END IF;
13116  
13117     END LOOP;
13118  
13119     RETURN NULL;
13120 END;
13121 $function$ LANGUAGE PLPGSQL;
13122
13123 UPDATE config.metabib_field SET label = name;
13124 ALTER TABLE config.metabib_field ALTER COLUMN label SET NOT NULL;
13125
13126 ALTER TABLE config.metabib_field ADD CONSTRAINT field_class_fkey FOREIGN KEY (field_class) REFERENCES config.metabib_class (name);
13127
13128 ALTER TABLE config.metabib_field DROP CONSTRAINT metabib_field_field_class_check;
13129
13130 ALTER TABLE config.metabib_field ADD CONSTRAINT metabib_field_format_fkey FOREIGN KEY (format) REFERENCES config.xml_transform (name);
13131
13132 CREATE TABLE config.metabib_search_alias (
13133     alias       TEXT    PRIMARY KEY,
13134     field_class TEXT    NOT NULL REFERENCES config.metabib_class (name),
13135     field       INT     REFERENCES config.metabib_field (id)
13136 );
13137
13138 INSERT INTO config.metabib_search_alias (alias,field_class) VALUES ('kw','keyword');
13139 INSERT INTO config.metabib_search_alias (alias,field_class) VALUES ('eg.keyword','keyword');
13140 INSERT INTO config.metabib_search_alias (alias,field_class) VALUES ('dc.publisher','keyword');
13141 INSERT INTO config.metabib_search_alias (alias,field_class) VALUES ('dc.identifier','keyword');
13142 INSERT INTO config.metabib_search_alias (alias,field_class) VALUES ('bib.subjecttitle','keyword');
13143 INSERT INTO config.metabib_search_alias (alias,field_class) VALUES ('bib.genre','keyword');
13144 INSERT INTO config.metabib_search_alias (alias,field_class) VALUES ('bib.edition','keyword');
13145 INSERT INTO config.metabib_search_alias (alias,field_class) VALUES ('srw.serverchoice','keyword');
13146
13147 INSERT INTO config.metabib_search_alias (alias,field_class) VALUES ('au','author');
13148 INSERT INTO config.metabib_search_alias (alias,field_class) VALUES ('name','author');
13149 INSERT INTO config.metabib_search_alias (alias,field_class) VALUES ('creator','author');
13150 INSERT INTO config.metabib_search_alias (alias,field_class) VALUES ('eg.author','author');
13151 INSERT INTO config.metabib_search_alias (alias,field_class) VALUES ('eg.name','author');
13152 INSERT INTO config.metabib_search_alias (alias,field_class) VALUES ('dc.creator','author');
13153 INSERT INTO config.metabib_search_alias (alias,field_class) VALUES ('dc.contributor','author');
13154 INSERT INTO config.metabib_search_alias (alias,field_class) VALUES ('bib.name','author');
13155 INSERT INTO config.metabib_search_alias (alias,field_class,field) VALUES ('bib.namepersonal','author',8);
13156 INSERT INTO config.metabib_search_alias (alias,field_class,field) VALUES ('bib.namepersonalfamily','author',8);
13157 INSERT INTO config.metabib_search_alias (alias,field_class,field) VALUES ('bib.namepersonalgiven','author',8);
13158 INSERT INTO config.metabib_search_alias (alias,field_class,field) VALUES ('bib.namecorporate','author',7);
13159 INSERT INTO config.metabib_search_alias (alias,field_class,field) VALUES ('bib.nameconference','author',9);
13160
13161 INSERT INTO config.metabib_search_alias (alias,field_class) VALUES ('ti','title');
13162 INSERT INTO config.metabib_search_alias (alias,field_class) VALUES ('eg.title','title');
13163 INSERT INTO config.metabib_search_alias (alias,field_class) VALUES ('dc.title','title');
13164 INSERT INTO config.metabib_search_alias (alias,field_class,field) VALUES ('bib.titleabbreviated','title',2);
13165 INSERT INTO config.metabib_search_alias (alias,field_class,field) VALUES ('bib.titleuniform','title',5);
13166 INSERT INTO config.metabib_search_alias (alias,field_class,field) VALUES ('bib.titletranslated','title',3);
13167 INSERT INTO config.metabib_search_alias (alias,field_class,field) VALUES ('bib.titlealternative','title',4);
13168 INSERT INTO config.metabib_search_alias (alias,field_class,field) VALUES ('bib.title','title',2);
13169
13170 INSERT INTO config.metabib_search_alias (alias,field_class) VALUES ('su','subject');
13171 INSERT INTO config.metabib_search_alias (alias,field_class) VALUES ('eg.subject','subject');
13172 INSERT INTO config.metabib_search_alias (alias,field_class) VALUES ('dc.subject','subject');
13173 INSERT INTO config.metabib_search_alias (alias,field_class,field) VALUES ('bib.subjectplace','subject',11);
13174 INSERT INTO config.metabib_search_alias (alias,field_class,field) VALUES ('bib.subjectname','subject',12);
13175 INSERT INTO config.metabib_search_alias (alias,field_class,field) VALUES ('bib.subjectoccupation','subject',16);
13176
13177 INSERT INTO config.metabib_search_alias (alias,field_class) VALUES ('se','series');
13178 INSERT INTO config.metabib_search_alias (alias,field_class) VALUES ('eg.series','series');
13179 INSERT INTO config.metabib_search_alias (alias,field_class,field) VALUES ('bib.titleseries','series',1);
13180
13181 UPDATE config.metabib_field SET facet_field=TRUE WHERE id = 1;
13182 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;
13183 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;
13184 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;
13185 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;
13186
13187 UPDATE config.metabib_field SET facet_field=TRUE WHERE id = 11;
13188 UPDATE config.metabib_field SET facet_field=TRUE , facet_xpath=$$*[local-name()='namePart']$$ WHERE id = 12;
13189 UPDATE config.metabib_field SET facet_field=TRUE WHERE id = 13;
13190 UPDATE config.metabib_field SET facet_field=TRUE WHERE id = 14;
13191
13192 CREATE INDEX metabib_rec_descriptor_item_type_idx ON metabib.rec_descriptor (item_type);
13193 CREATE INDEX metabib_rec_descriptor_item_form_idx ON metabib.rec_descriptor (item_form);
13194 CREATE INDEX metabib_rec_descriptor_bib_level_idx ON metabib.rec_descriptor (bib_level);
13195 CREATE INDEX metabib_rec_descriptor_control_type_idx ON metabib.rec_descriptor (control_type);
13196 CREATE INDEX metabib_rec_descriptor_char_encoding_idx ON metabib.rec_descriptor (char_encoding);
13197 CREATE INDEX metabib_rec_descriptor_enc_level_idx ON metabib.rec_descriptor (enc_level);
13198 CREATE INDEX metabib_rec_descriptor_audience_idx ON metabib.rec_descriptor (audience);
13199 CREATE INDEX metabib_rec_descriptor_lit_form_idx ON metabib.rec_descriptor (lit_form);
13200 CREATE INDEX metabib_rec_descriptor_cat_form_idx ON metabib.rec_descriptor (cat_form);
13201 CREATE INDEX metabib_rec_descriptor_pub_status_idx ON metabib.rec_descriptor (pub_status);
13202 CREATE INDEX metabib_rec_descriptor_item_lang_idx ON metabib.rec_descriptor (item_lang);
13203 CREATE INDEX metabib_rec_descriptor_vr_format_idx ON metabib.rec_descriptor (vr_format);
13204 CREATE INDEX metabib_rec_descriptor_date1_idx ON metabib.rec_descriptor (date1);
13205 CREATE INDEX metabib_rec_descriptor_dates_idx ON metabib.rec_descriptor (date1,date2);
13206
13207 CREATE TABLE asset.opac_visible_copies (
13208   id        BIGINT primary key, -- copy id
13209   record    BIGINT,
13210   circ_lib  INTEGER
13211 );
13212 COMMENT ON TABLE asset.opac_visible_copies IS $$
13213 Materialized view of copies that are visible in the OPAC, used by
13214 search.query_parser_fts() to speed up OPAC visibility checks on large
13215 databases.  Contents are maintained by a set of triggers.
13216 $$;
13217 CREATE INDEX opac_visible_copies_idx1 on asset.opac_visible_copies (record, circ_lib);
13218
13219 CREATE OR REPLACE FUNCTION search.query_parser_fts (
13220
13221     param_search_ou INT,
13222     param_depth     INT,
13223     param_query     TEXT,
13224     param_statuses  INT[],
13225     param_locations INT[],
13226     param_offset    INT,
13227     param_check     INT,
13228     param_limit     INT,
13229     metarecord      BOOL,
13230     staff           BOOL
13231  
13232 ) RETURNS SETOF search.search_result AS $func$
13233 DECLARE
13234
13235     current_res         search.search_result%ROWTYPE;
13236     search_org_list     INT[];
13237
13238     check_limit         INT;
13239     core_limit          INT;
13240     core_offset         INT;
13241     tmp_int             INT;
13242
13243     core_result         RECORD;
13244     core_cursor         REFCURSOR;
13245     core_rel_query      TEXT;
13246
13247     total_count         INT := 0;
13248     check_count         INT := 0;
13249     deleted_count       INT := 0;
13250     visible_count       INT := 0;
13251     excluded_count      INT := 0;
13252
13253 BEGIN
13254
13255     check_limit := COALESCE( param_check, 1000 );
13256     core_limit  := COALESCE( param_limit, 25000 );
13257     core_offset := COALESCE( param_offset, 0 );
13258
13259     -- core_skip_chk := COALESCE( param_skip_chk, 1 );
13260
13261     IF param_search_ou > 0 THEN
13262         IF param_depth IS NOT NULL THEN
13263             SELECT array_accum(distinct id) INTO search_org_list FROM actor.org_unit_descendants( param_search_ou, param_depth );
13264         ELSE
13265             SELECT array_accum(distinct id) INTO search_org_list FROM actor.org_unit_descendants( param_search_ou );
13266         END IF;
13267     ELSIF param_search_ou < 0 THEN
13268         SELECT array_accum(distinct org_unit) INTO search_org_list FROM actor.org_lasso_map WHERE lasso = -param_search_ou;
13269     ELSIF param_search_ou = 0 THEN
13270         -- reserved for user lassos (ou_buckets/type='lasso') with ID passed in depth ... hack? sure.
13271     END IF;
13272
13273     OPEN core_cursor FOR EXECUTE param_query;
13274
13275     LOOP
13276
13277         FETCH core_cursor INTO core_result;
13278         EXIT WHEN NOT FOUND;
13279         EXIT WHEN total_count >= core_limit;
13280
13281         total_count := total_count + 1;
13282
13283         CONTINUE WHEN total_count NOT BETWEEN  core_offset + 1 AND check_limit + core_offset;
13284
13285         check_count := check_count + 1;
13286
13287         PERFORM 1 FROM biblio.record_entry b WHERE NOT b.deleted AND b.id IN ( SELECT * FROM search.explode_array( core_result.records ) );
13288         IF NOT FOUND THEN
13289             -- RAISE NOTICE ' % were all deleted ... ', core_result.records;
13290             deleted_count := deleted_count + 1;
13291             CONTINUE;
13292         END IF;
13293
13294         PERFORM 1
13295           FROM  biblio.record_entry b
13296                 JOIN config.bib_source s ON (b.source = s.id)
13297           WHERE s.transcendant
13298                 AND b.id IN ( SELECT * FROM search.explode_array( core_result.records ) );
13299
13300         IF FOUND THEN
13301             -- RAISE NOTICE ' % were all transcendant ... ', core_result.records;
13302             visible_count := visible_count + 1;
13303
13304             current_res.id = core_result.id;
13305             current_res.rel = core_result.rel;
13306
13307             tmp_int := 1;
13308             IF metarecord THEN
13309                 SELECT COUNT(DISTINCT s.source) INTO tmp_int FROM metabib.metarecord_source_map s WHERE s.metarecord = core_result.id;
13310             END IF;
13311
13312             IF tmp_int = 1 THEN
13313                 current_res.record = core_result.records[1];
13314             ELSE
13315                 current_res.record = NULL;
13316             END IF;
13317
13318             RETURN NEXT current_res;
13319
13320             CONTINUE;
13321         END IF;
13322
13323         PERFORM 1
13324           FROM  asset.call_number cn
13325                 JOIN asset.uri_call_number_map map ON (map.call_number = cn.id)
13326                 JOIN asset.uri uri ON (map.uri = uri.id)
13327           WHERE NOT cn.deleted
13328                 AND cn.label = '##URI##'
13329                 AND uri.active
13330                 AND ( param_locations IS NULL OR array_upper(param_locations, 1) IS NULL )
13331                 AND cn.record IN ( SELECT * FROM search.explode_array( core_result.records ) )
13332                 AND cn.owning_lib IN ( SELECT * FROM search.explode_array( search_org_list ) )
13333           LIMIT 1;
13334
13335         IF FOUND THEN
13336             -- RAISE NOTICE ' % have at least one URI ... ', core_result.records;
13337             visible_count := visible_count + 1;
13338
13339             current_res.id = core_result.id;
13340             current_res.rel = core_result.rel;
13341
13342             tmp_int := 1;
13343             IF metarecord THEN
13344                 SELECT COUNT(DISTINCT s.source) INTO tmp_int FROM metabib.metarecord_source_map s WHERE s.metarecord = core_result.id;
13345             END IF;
13346
13347             IF tmp_int = 1 THEN
13348                 current_res.record = core_result.records[1];
13349             ELSE
13350                 current_res.record = NULL;
13351             END IF;
13352
13353             RETURN NEXT current_res;
13354
13355             CONTINUE;
13356         END IF;
13357
13358         IF param_statuses IS NOT NULL AND array_upper(param_statuses, 1) > 0 THEN
13359
13360             PERFORM 1
13361               FROM  asset.call_number cn
13362                     JOIN asset.copy cp ON (cp.call_number = cn.id)
13363               WHERE NOT cn.deleted
13364                     AND NOT cp.deleted
13365                     AND cp.status IN ( SELECT * FROM search.explode_array( param_statuses ) )
13366                     AND cn.record IN ( SELECT * FROM search.explode_array( core_result.records ) )
13367                     AND cp.circ_lib IN ( SELECT * FROM search.explode_array( search_org_list ) )
13368               LIMIT 1;
13369
13370             IF NOT FOUND THEN
13371                 -- RAISE NOTICE ' % were all status-excluded ... ', core_result.records;
13372                 excluded_count := excluded_count + 1;
13373                 CONTINUE;
13374             END IF;
13375
13376         END IF;
13377
13378         IF param_locations IS NOT NULL AND array_upper(param_locations, 1) > 0 THEN
13379
13380             PERFORM 1
13381               FROM  asset.call_number cn
13382                     JOIN asset.copy cp ON (cp.call_number = cn.id)
13383               WHERE NOT cn.deleted
13384                     AND NOT cp.deleted
13385                     AND cp.location IN ( SELECT * FROM search.explode_array( param_locations ) )
13386                     AND cn.record IN ( SELECT * FROM search.explode_array( core_result.records ) )
13387                     AND cp.circ_lib IN ( SELECT * FROM search.explode_array( search_org_list ) )
13388               LIMIT 1;
13389
13390             IF NOT FOUND THEN
13391                 -- RAISE NOTICE ' % were all copy_location-excluded ... ', core_result.records;
13392                 excluded_count := excluded_count + 1;
13393                 CONTINUE;
13394             END IF;
13395
13396         END IF;
13397
13398         IF staff IS NULL OR NOT staff THEN
13399
13400             PERFORM 1
13401               FROM  asset.opac_visible_copies
13402               WHERE circ_lib IN ( SELECT * FROM search.explode_array( search_org_list ) )
13403                     AND record IN ( SELECT * FROM search.explode_array( core_result.records ) )
13404               LIMIT 1;
13405
13406             IF NOT FOUND THEN
13407                 -- RAISE NOTICE ' % were all visibility-excluded ... ', core_result.records;
13408                 excluded_count := excluded_count + 1;
13409                 CONTINUE;
13410             END IF;
13411
13412         ELSE
13413
13414             PERFORM 1
13415               FROM  asset.call_number cn
13416                     JOIN asset.copy cp ON (cp.call_number = cn.id)
13417                     JOIN actor.org_unit a ON (cp.circ_lib = a.id)
13418               WHERE NOT cn.deleted
13419                     AND NOT cp.deleted
13420                     AND cp.circ_lib IN ( SELECT * FROM search.explode_array( search_org_list ) )
13421                     AND cn.record IN ( SELECT * FROM search.explode_array( core_result.records ) )
13422               LIMIT 1;
13423
13424             IF NOT FOUND THEN
13425
13426                 PERFORM 1
13427                   FROM  asset.call_number cn
13428                   WHERE cn.record IN ( SELECT * FROM search.explode_array( core_result.records ) )
13429                   LIMIT 1;
13430
13431                 IF FOUND THEN
13432                     -- RAISE NOTICE ' % were all visibility-excluded ... ', core_result.records;
13433                     excluded_count := excluded_count + 1;
13434                     CONTINUE;
13435                 END IF;
13436
13437             END IF;
13438
13439         END IF;
13440
13441         visible_count := visible_count + 1;
13442
13443         current_res.id = core_result.id;
13444         current_res.rel = core_result.rel;
13445
13446         tmp_int := 1;
13447         IF metarecord THEN
13448             SELECT COUNT(DISTINCT s.source) INTO tmp_int FROM metabib.metarecord_source_map s WHERE s.metarecord = core_result.id;
13449         END IF;
13450
13451         IF tmp_int = 1 THEN
13452             current_res.record = core_result.records[1];
13453         ELSE
13454             current_res.record = NULL;
13455         END IF;
13456
13457         RETURN NEXT current_res;
13458
13459         IF visible_count % 1000 = 0 THEN
13460             -- RAISE NOTICE ' % visible so far ... ', visible_count;
13461         END IF;
13462
13463     END LOOP;
13464
13465     current_res.id = NULL;
13466     current_res.rel = NULL;
13467     current_res.record = NULL;
13468     current_res.total = total_count;
13469     current_res.checked = check_count;
13470     current_res.deleted = deleted_count;
13471     current_res.visible = visible_count;
13472     current_res.excluded = excluded_count;
13473
13474     CLOSE core_cursor;
13475
13476     RETURN NEXT current_res;
13477
13478 END;
13479 $func$ LANGUAGE PLPGSQL;
13480
13481 ALTER TABLE biblio.record_entry ADD COLUMN owner INT REFERENCES actor.org_unit (id);
13482 ALTER TABLE biblio.record_entry ADD COLUMN share_depth INT;
13483
13484 ALTER TABLE auditor.biblio_record_entry_history ADD COLUMN owner INT;
13485 ALTER TABLE auditor.biblio_record_entry_history ADD COLUMN share_depth INT;
13486
13487 CREATE OR REPLACE FUNCTION public.first_word ( TEXT ) RETURNS TEXT AS $$
13488         SELECT COALESCE(SUBSTRING( $1 FROM $_$^\S+$_$), '');
13489 $$ LANGUAGE SQL STRICT IMMUTABLE;
13490
13491 CREATE OR REPLACE FUNCTION public.normalize_space( TEXT ) RETURNS TEXT AS $$
13492     SELECT regexp_replace(regexp_replace(regexp_replace($1, E'\\n', ' ', 'g'), E'(?:^\\s+)|(\\s+$)', '', 'g'), E'\\s+', ' ', 'g');
13493 $$ LANGUAGE SQL STRICT IMMUTABLE;
13494
13495 CREATE OR REPLACE FUNCTION public.lowercase( TEXT ) RETURNS TEXT AS $$
13496     return lc(shift);
13497 $$ LANGUAGE PLPERLU STRICT IMMUTABLE;
13498
13499 CREATE OR REPLACE FUNCTION public.uppercase( TEXT ) RETURNS TEXT AS $$
13500     return uc(shift);
13501 $$ LANGUAGE PLPERLU STRICT IMMUTABLE;
13502
13503 CREATE OR REPLACE FUNCTION public.remove_diacritics( TEXT ) RETURNS TEXT AS $$
13504     use Unicode::Normalize;
13505
13506     my $x = NFD(shift);
13507     $x =~ s/\pM+//go;
13508     return $x;
13509
13510 $$ LANGUAGE PLPERLU STRICT IMMUTABLE;
13511
13512 CREATE OR REPLACE FUNCTION public.entityize( TEXT ) RETURNS TEXT AS $$
13513     use Unicode::Normalize;
13514
13515     my $x = NFC(shift);
13516     $x =~ s/([\x{0080}-\x{fffd}])/sprintf('&#x%X;',ord($1))/sgoe;
13517     return $x;
13518
13519 $$ LANGUAGE PLPERLU STRICT IMMUTABLE;
13520
13521 CREATE OR REPLACE FUNCTION actor.org_unit_ancestor_setting( setting_name TEXT, org_id INT ) RETURNS SETOF actor.org_unit_setting AS $$
13522 DECLARE
13523     setting RECORD;
13524     cur_org INT;
13525 BEGIN
13526     cur_org := org_id;
13527     LOOP
13528         SELECT INTO setting * FROM actor.org_unit_setting WHERE org_unit = cur_org AND name = setting_name;
13529         IF FOUND THEN
13530             RETURN NEXT setting;
13531         END IF;
13532         SELECT INTO cur_org parent_ou FROM actor.org_unit WHERE id = cur_org;
13533         EXIT WHEN cur_org IS NULL;
13534     END LOOP;
13535     RETURN;
13536 END;
13537 $$ LANGUAGE plpgsql STABLE;
13538
13539 CREATE OR REPLACE FUNCTION acq.extract_holding_attr_table (lineitem int, tag text) RETURNS SETOF acq.flat_lineitem_holding_subfield AS $$
13540 DECLARE
13541     counter INT;
13542     lida    acq.flat_lineitem_holding_subfield%ROWTYPE;
13543 BEGIN
13544
13545     SELECT  COUNT(*) INTO counter
13546       FROM  oils_xpath_table(
13547                 'id',
13548                 'marc',
13549                 'acq.lineitem',
13550                 '//*[@tag="' || tag || '"]',
13551                 'id=' || lineitem
13552             ) as t(i int,c text);
13553
13554     FOR i IN 1 .. counter LOOP
13555         FOR lida IN
13556             SELECT  *
13557               FROM  (   SELECT  id,i,t,v
13558                           FROM  oils_xpath_table(
13559                                     'id',
13560                                     'marc',
13561                                     'acq.lineitem',
13562                                     '//*[@tag="' || tag || '"][position()=' || i || ']/*/@code|' ||
13563                                         '//*[@tag="' || tag || '"][position()=' || i || ']/*[@code]',
13564                                     'id=' || lineitem
13565                                 ) as t(id int,t text,v text)
13566                     )x
13567         LOOP
13568             RETURN NEXT lida;
13569         END LOOP;
13570     END LOOP;
13571
13572     RETURN;
13573 END;
13574 $$ LANGUAGE PLPGSQL;
13575
13576 CREATE OR REPLACE FUNCTION oils_i18n_xlate ( keytable TEXT, keyclass TEXT, keycol TEXT, identcol TEXT, keyvalue TEXT, raw_locale TEXT ) RETURNS TEXT AS $func$
13577 DECLARE
13578     locale      TEXT := REGEXP_REPLACE( REGEXP_REPLACE( raw_locale, E'[;, ].+$', '' ), E'_', '-', 'g' );
13579     language    TEXT := REGEXP_REPLACE( locale, E'-.+$', '' );
13580     result      config.i18n_core%ROWTYPE;
13581     fallback    TEXT;
13582     keyfield    TEXT := keyclass || '.' || keycol;
13583 BEGIN
13584
13585     -- Try the full locale
13586     SELECT  * INTO result
13587       FROM  config.i18n_core
13588       WHERE fq_field = keyfield
13589             AND identity_value = keyvalue
13590             AND translation = locale;
13591
13592     -- Try just the language
13593     IF NOT FOUND THEN
13594         SELECT  * INTO result
13595           FROM  config.i18n_core
13596           WHERE fq_field = keyfield
13597                 AND identity_value = keyvalue
13598                 AND translation = language;
13599     END IF;
13600
13601     -- Fall back to the string we passed in in the first place
13602     IF NOT FOUND THEN
13603     EXECUTE
13604             'SELECT ' ||
13605                 keycol ||
13606             ' FROM ' || keytable ||
13607             ' WHERE ' || identcol || ' = ' || quote_literal(keyvalue)
13608                 INTO fallback;
13609         RETURN fallback;
13610     END IF;
13611
13612     RETURN result.string;
13613 END;
13614 $func$ LANGUAGE PLPGSQL STABLE;
13615
13616 SELECT auditor.create_auditor ( 'acq', 'invoice' );
13617
13618 SELECT auditor.create_auditor ( 'acq', 'invoice_item' );
13619
13620 SELECT auditor.create_auditor ( 'acq', 'invoice_entry' );
13621
13622 INSERT INTO acq.cancel_reason ( id, org_unit, label, description, keep_debits ) VALUES (
13623     3, 1, 'delivered_but_lost',
13624     oils_i18n_gettext( 2, 'Delivered but not received; presumed lost', 'acqcr', 'label' ), TRUE );
13625
13626 CREATE TABLE config.global_flag (
13627     label   TEXT    NOT NULL
13628 ) INHERITS (config.internal_flag);
13629 ALTER TABLE config.global_flag ADD PRIMARY KEY (name);
13630
13631 INSERT INTO config.global_flag (name, label) -- defaults to enabled=FALSE
13632     VALUES (
13633         'cat.bib.use_id_for_tcn',
13634         oils_i18n_gettext(
13635             'cat.bib.use_id_for_tcn',
13636             'Cat: Use Internal ID for TCN Value',
13637             'cgf', 
13638             'label'
13639         )
13640     );
13641
13642 -- resolves performance issue noted by EG Indiana
13643
13644 CREATE INDEX scecm_owning_copy_idx ON asset.stat_cat_entry_copy_map(owning_copy);
13645
13646 INSERT INTO config.metabib_class ( name, label ) VALUES ( 'identifier', oils_i18n_gettext('identifier', 'Identifier', 'cmc', 'name') );
13647
13648 INSERT INTO config.metabib_field ( id, field_class, name, label, format, xpath, facet_field ) VALUES
13649     (17, 'identifier', 'accession', oils_i18n_gettext(17, 'Accession Number', 'cmf', 'label'), 'marcxml', $$//marcxml:datafield[tag="001"]/text()$$, TRUE );
13650 INSERT INTO config.metabib_field ( id, field_class, name, label, format, xpath, facet_field ) VALUES
13651     (18, 'identifier', 'isbn', oils_i18n_gettext(18, 'ISBN', 'cmf', 'label'), 'marcxml', $$//marcxml:datafield[tag="020"]/marcxml:subfield[code="a" or code="z"]/text()$$, TRUE );
13652 INSERT INTO config.metabib_field ( id, field_class, name, label, format, xpath, facet_field ) VALUES
13653     (19, 'identifier', 'issn', oils_i18n_gettext(19, 'ISSN', 'cmf', 'label'), 'marcxml', $$//marcxml:datafield[tag="022"]/marcxml:subfield[code="a" or code="z"]/text()$$, TRUE );
13654 INSERT INTO config.metabib_field ( id, field_class, name, label, format, xpath, facet_field ) VALUES
13655     (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 );
13656 INSERT INTO config.metabib_field ( id, field_class, name, label, format, xpath, facet_field ) VALUES
13657     (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 );
13658 INSERT INTO config.metabib_field ( id, field_class, name, label, format, xpath, facet_field ) VALUES
13659     (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 );
13660 INSERT INTO config.metabib_field ( id, field_class, name, label, format, xpath, facet_field ) VALUES
13661     (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 );
13662 INSERT INTO config.metabib_field ( id, field_class, name, label, format, xpath, facet_field ) VALUES
13663     (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 );
13664 INSERT INTO config.metabib_field ( id, field_class, name, label, format, xpath, facet_field ) VALUES
13665     (25, 'identifier', 'bibcn', oils_i18n_gettext(25, 'Local Free-Text Call Number', 'cmf', 'label'), 'marcxml', $$//marcxml:datafield[tag="099"]//text()$$, TRUE );
13666
13667 SELECT SETVAL('config.metabib_field_id_seq'::TEXT, (SELECT MAX(id) FROM config.metabib_field), TRUE);
13668  
13669
13670 DELETE FROM config.metabib_search_alias WHERE alias = 'dc.identifier';
13671
13672 INSERT INTO config.metabib_search_alias (alias,field_class) VALUES ('id','identifier');
13673 INSERT INTO config.metabib_search_alias (alias,field_class) VALUES ('dc.identifier','identifier');
13674 INSERT INTO config.metabib_search_alias (alias,field_class,field) VALUES ('eg.isbn','identifier', 18);
13675 INSERT INTO config.metabib_search_alias (alias,field_class,field) VALUES ('eg.issn','identifier', 19);
13676 INSERT INTO config.metabib_search_alias (alias,field_class,field) VALUES ('eg.upc','identifier', 20);
13677 INSERT INTO config.metabib_search_alias (alias,field_class,field) VALUES ('eg.callnumber','identifier', 25);
13678
13679 CREATE TABLE metabib.identifier_field_entry (
13680         id              BIGSERIAL       PRIMARY KEY,
13681         source          BIGINT          NOT NULL,
13682         field           INT             NOT NULL,
13683         value           TEXT            NOT NULL,
13684         index_vector    tsvector        NOT NULL
13685 );
13686 CREATE TRIGGER metabib_identifier_field_entry_fti_trigger
13687         BEFORE UPDATE OR INSERT ON metabib.identifier_field_entry
13688         FOR EACH ROW EXECUTE PROCEDURE oils_tsearch2('keyword');
13689
13690 CREATE INDEX metabib_identifier_field_entry_index_vector_idx ON metabib.identifier_field_entry USING GIST (index_vector);
13691 CREATE INDEX metabib_identifier_field_entry_value_idx ON metabib.identifier_field_entry
13692     (SUBSTRING(value,1,1024)) WHERE index_vector = ''::TSVECTOR;
13693 CREATE INDEX metabib_identifier_field_entry_source_idx ON metabib.identifier_field_entry (source);
13694
13695 ALTER TABLE metabib.identifier_field_entry ADD CONSTRAINT metabib_identifier_field_entry_source_pkey
13696     FOREIGN KEY (source) REFERENCES biblio.record_entry (id) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED;
13697 ALTER TABLE metabib.identifier_field_entry ADD CONSTRAINT metabib_identifier_field_entry_field_pkey
13698     FOREIGN KEY (field) REFERENCES config.metabib_field (id) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED;
13699
13700 CREATE OR REPLACE FUNCTION public.translate_isbn1013( TEXT ) RETURNS TEXT AS $func$
13701     use Business::ISBN;
13702     use strict;
13703     use warnings;
13704
13705     # For each ISBN found in a single string containing a set of ISBNs:
13706     #   * Normalize an incoming ISBN to have the correct checksum and no hyphens
13707     #   * Convert an incoming ISBN10 or ISBN13 to its counterpart and return
13708
13709     my $input = shift;
13710     my $output = '';
13711
13712     foreach my $word (split(/\s/, $input)) {
13713         my $isbn = Business::ISBN->new($word);
13714
13715         # First check the checksum; if it is not valid, fix it and add the original
13716         # bad-checksum ISBN to the output
13717         if ($isbn && $isbn->is_valid_checksum() == Business::ISBN::BAD_CHECKSUM) {
13718             $output .= $isbn->isbn() . " ";
13719             $isbn->fix_checksum();
13720         }
13721
13722         # If we now have a valid ISBN, convert it to its counterpart ISBN10/ISBN13
13723         # and add the normalized original ISBN to the output
13724         if ($isbn && $isbn->is_valid()) {
13725             my $isbn_xlated = ($isbn->type eq "ISBN13") ? $isbn->as_isbn10 : $isbn->as_isbn13;
13726             $output .= $isbn->isbn . " ";
13727
13728             # If we successfully converted the ISBN to its counterpart, add the
13729             # converted ISBN to the output as well
13730             $output .= ($isbn_xlated->isbn . " ") if ($isbn_xlated);
13731         }
13732     }
13733     return $output if $output;
13734
13735     # If there were no valid ISBNs, just return the raw input
13736     return $input;
13737 $func$ LANGUAGE PLPERLU;
13738
13739 COMMENT ON FUNCTION public.translate_isbn1013(TEXT) IS $$
13740 /*
13741  * Copyright (C) 2010 Merrimack Valley Library Consortium
13742  * Jason Stephenson <jstephenson@mvlc.org>
13743  * Copyright (C) 2010 Laurentian University
13744  * Dan Scott <dscott@laurentian.ca>
13745  *
13746  * The translate_isbn1013 function takes an input ISBN and returns the
13747  * following in a single space-delimited string if the input ISBN is valid:
13748  *   - The normalized input ISBN (hyphens stripped)
13749  *   - The normalized input ISBN with a fixed checksum if the checksum was bad
13750  *   - The ISBN converted to its ISBN10 or ISBN13 counterpart, if possible
13751  */
13752 $$;
13753
13754 UPDATE config.metabib_field SET facet_field = FALSE WHERE id BETWEEN 17 AND 25;
13755 UPDATE config.metabib_field SET xpath = REPLACE(xpath,'marcxml','marc') WHERE id BETWEEN 17 AND 25;
13756 UPDATE config.metabib_field SET xpath = REPLACE(xpath,'tag','@tag') WHERE id BETWEEN 17 AND 25;
13757 UPDATE config.metabib_field SET xpath = REPLACE(xpath,'code','@code') WHERE id BETWEEN 17 AND 25;
13758 UPDATE config.metabib_field SET xpath = REPLACE(xpath,'"',E'\'') WHERE id BETWEEN 17 AND 25;
13759 UPDATE config.metabib_field SET xpath = REPLACE(xpath,'/text()','') WHERE id BETWEEN 17 AND 24;
13760
13761 INSERT INTO config.index_normalizer (name, description, func, param_count) VALUES (
13762         'ISBN 10/13 conversion',
13763         'Translate ISBN10 to ISBN13, and vice versa, for indexing purposes.',
13764         'translate_isbn1013',
13765         0
13766 );
13767
13768 INSERT INTO config.index_normalizer (name, description, func, param_count) VALUES (
13769         'Replace',
13770         'Replace all occurances of first parameter in the string with the second parameter.',
13771         'replace',
13772         2
13773 );
13774
13775 INSERT INTO config.metabib_field_index_norm_map (field,norm,pos)
13776     SELECT  m.id, i.id, 1
13777       FROM  config.metabib_field m,
13778             config.index_normalizer i
13779       WHERE i.func IN ('first_word')
13780             AND m.id IN (18);
13781
13782 INSERT INTO config.metabib_field_index_norm_map (field,norm,pos)
13783     SELECT  m.id, i.id, 2
13784       FROM  config.metabib_field m,
13785             config.index_normalizer i
13786       WHERE i.func IN ('translate_isbn1013')
13787             AND m.id IN (18);
13788
13789 INSERT INTO config.metabib_field_index_norm_map (field,norm,params)
13790     SELECT  m.id, i.id, $$['-','']$$
13791       FROM  config.metabib_field m,
13792             config.index_normalizer i
13793       WHERE i.func IN ('replace')
13794             AND m.id IN (19);
13795
13796 INSERT INTO config.metabib_field_index_norm_map (field,norm,params)
13797     SELECT  m.id, i.id, $$[' ','']$$
13798       FROM  config.metabib_field m,
13799             config.index_normalizer i
13800       WHERE i.func IN ('replace')
13801             AND m.id IN (19);
13802
13803 DELETE FROM config.metabib_field_index_norm_map WHERE norm IN (1,2) and field > 16;
13804
13805 UPDATE  config.metabib_field_index_norm_map
13806   SET   params = REPLACE(params,E'\'','"')
13807   WHERE params IS NOT NULL AND params <> '';
13808
13809 DROP TRIGGER IF EXISTS metabib_identifier_field_entry_fti_trigger ON metabib.identifier_field_entry;
13810
13811 CREATE TEXT SEARCH CONFIGURATION identifier ( COPY = title );
13812
13813 ALTER TABLE config.circ_modifier
13814         ADD COLUMN avg_wait_time INTERVAL;
13815
13816 --CREATE TABLE actor.usr_password_reset (
13817 --  id SERIAL PRIMARY KEY,
13818 --  uuid TEXT NOT NULL, 
13819 --  usr BIGINT NOT NULL REFERENCES actor.usr(id) DEFERRABLE INITIALLY DEFERRED, 
13820 --  request_time TIMESTAMP NOT NULL DEFAULT NOW(), 
13821 --  has_been_reset BOOL NOT NULL DEFAULT false
13822 --);
13823 --COMMENT ON TABLE actor.usr_password_reset IS $$
13824 --/*
13825 -- * Copyright (C) 2010 Laurentian University
13826 -- * Dan Scott <dscott@laurentian.ca>
13827 -- *
13828 -- * Self-serve password reset requests
13829 -- *
13830 -- * ****
13831 -- *
13832 -- * This program is free software; you can redistribute it and/or
13833 -- * modify it under the terms of the GNU General Public License
13834 -- * as published by the Free Software Foundation; either version 2
13835 -- * of the License, or (at your option) any later version.
13836 -- *
13837 -- * This program is distributed in the hope that it will be useful,
13838 -- * but WITHOUT ANY WARRANTY; without even the implied warranty of
13839 -- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13840 -- * GNU General Public License for more details.
13841 -- */
13842 --$$;
13843 --CREATE UNIQUE INDEX actor_usr_password_reset_uuid_idx ON actor.usr_password_reset (uuid);
13844 --CREATE INDEX actor_usr_password_reset_usr_idx ON actor.usr_password_reset (usr);
13845 --CREATE INDEX actor_usr_password_reset_request_time_idx ON actor.usr_password_reset (request_time);
13846 --CREATE INDEX actor_usr_password_reset_has_been_reset_idx ON actor.usr_password_reset (has_been_reset);
13847
13848 -- Use the identifier search class tsconfig
13849 DROP TRIGGER IF EXISTS metabib_identifier_field_entry_fti_trigger ON metabib.identifier_field_entry;
13850 CREATE TRIGGER metabib_identifier_field_entry_fti_trigger
13851     BEFORE INSERT OR UPDATE ON metabib.identifier_field_entry
13852     FOR EACH ROW
13853     EXECUTE PROCEDURE public.oils_tsearch2('identifier');
13854
13855 INSERT INTO config.global_flag (name,label,enabled)
13856     VALUES ('history.circ.retention_age',oils_i18n_gettext('history.circ.retention_age', 'Historical Circulation Retention Age', 'cgf', 'label'), TRUE);
13857 INSERT INTO config.global_flag (name,label,enabled)
13858     VALUES ('history.circ.retention_count',oils_i18n_gettext('history.circ.retention_count', 'Historical Circulations per Copy', 'cgf', 'label'), TRUE);
13859
13860 -- turn a JSON scalar into an SQL TEXT value
13861 CREATE OR REPLACE FUNCTION oils_json_to_text( TEXT ) RETURNS TEXT AS $f$
13862     use JSON::XS;                    
13863     my $json = shift();
13864     my $txt;
13865     eval { $txt = JSON::XS->new->allow_nonref->decode( $json ) };   
13866     return undef if ($@);
13867     return $txt
13868 $f$ LANGUAGE PLPERLU;
13869
13870 -- Return the list of circ chain heads in xact_start order that the user has chosen to "retain"
13871 CREATE OR REPLACE FUNCTION action.usr_visible_circs (usr_id INT) RETURNS SETOF action.circulation AS $func$
13872 DECLARE
13873     c               action.circulation%ROWTYPE;
13874     view_age        INTERVAL;
13875     usr_view_age    actor.usr_setting%ROWTYPE;
13876     usr_view_start  actor.usr_setting%ROWTYPE;
13877 BEGIN
13878     SELECT * INTO usr_view_age FROM actor.usr_setting WHERE usr = usr_id AND name = 'history.circ.retention_age';
13879     SELECT * INTO usr_view_start FROM actor.usr_setting WHERE usr = usr_id AND name = 'history.circ.retention_start';
13880
13881     IF usr_view_age.value IS NOT NULL AND usr_view_start.value IS NOT NULL THEN
13882         -- User opted in and supplied a retention age
13883         IF oils_json_to_text(usr_view_age.value)::INTERVAL > AGE(NOW(), oils_json_to_text(usr_view_start.value)::TIMESTAMPTZ) THEN
13884             view_age := AGE(NOW(), oils_json_to_text(usr_view_start.value)::TIMESTAMPTZ);
13885         ELSE
13886             view_age := oils_json_to_text(usr_view_age.value)::INTERVAL;
13887         END IF;
13888     ELSIF usr_view_start.value IS NOT NULL THEN
13889         -- User opted in
13890         view_age := AGE(NOW(), oils_json_to_text(usr_view_start.value)::TIMESTAMPTZ);
13891     ELSE
13892         -- User did not opt in
13893         RETURN;
13894     END IF;
13895
13896     FOR c IN
13897         SELECT  *
13898           FROM  action.circulation
13899           WHERE usr = usr_id
13900                 AND parent_circ IS NULL
13901                 AND xact_start > NOW() - view_age
13902           ORDER BY xact_start
13903     LOOP
13904         RETURN NEXT c;
13905     END LOOP;
13906
13907     RETURN;
13908 END;
13909 $func$ LANGUAGE PLPGSQL;
13910
13911 CREATE OR REPLACE FUNCTION action.purge_circulations () RETURNS INT AS $func$
13912 DECLARE
13913     usr_keep_age    actor.usr_setting%ROWTYPE;
13914     usr_keep_start  actor.usr_setting%ROWTYPE;
13915     org_keep_age    INTERVAL;
13916     org_keep_count  INT;
13917
13918     keep_age        INTERVAL;
13919
13920     target_acp      RECORD;
13921     circ_chain_head action.circulation%ROWTYPE;
13922     circ_chain_tail action.circulation%ROWTYPE;
13923
13924     purge_position  INT;
13925     count_purged    INT;
13926 BEGIN
13927
13928     count_purged := 0;
13929
13930     SELECT value::INTERVAL INTO org_keep_age FROM config.global_flag WHERE name = 'history.circ.retention_age' AND enabled;
13931
13932     SELECT value::INT INTO org_keep_count FROM config.global_flag WHERE name = 'history.circ.retention_count' AND enabled;
13933     IF org_keep_count IS NULL THEN
13934         RETURN count_purged; -- Gimme a count to keep, or I keep them all, forever
13935     END IF;
13936
13937     -- First, find copies with more than keep_count non-renewal circs
13938     FOR target_acp IN
13939         SELECT  target_copy,
13940                 COUNT(*) AS total_real_circs
13941           FROM  action.circulation
13942           WHERE parent_circ IS NULL
13943                 AND xact_finish IS NOT NULL
13944           GROUP BY target_copy
13945           HAVING COUNT(*) > org_keep_count
13946     LOOP
13947         purge_position := 0;
13948         -- And, for those, select circs that are finished and older than keep_age
13949         FOR circ_chain_head IN
13950             SELECT  *
13951               FROM  action.circulation
13952               WHERE target_copy = target_acp.target_copy
13953                     AND parent_circ IS NULL
13954               ORDER BY xact_start
13955         LOOP
13956
13957             -- Stop once we've purged enough circs to hit org_keep_count
13958             EXIT WHEN target_acp.total_real_circs - purge_position <= org_keep_count;
13959
13960             SELECT * INTO circ_chain_tail FROM action.circ_chain(circ_chain_head.id) ORDER BY xact_start DESC LIMIT 1;
13961             EXIT WHEN circ_chain_tail.xact_finish IS NULL;
13962
13963             -- Now get the user setings, if any, to block purging if the user wants to keep more circs
13964             usr_keep_age.value := NULL;
13965             SELECT * INTO usr_keep_age FROM actor.usr_setting WHERE usr = circ_chain_head.usr AND name = 'history.circ.retention_age';
13966
13967             usr_keep_start.value := NULL;
13968             SELECT * INTO usr_keep_start FROM actor.usr_setting WHERE usr = circ_chain_head.usr AND name = 'history.circ.retention_start_date';
13969
13970             IF usr_keep_age.value IS NOT NULL AND usr_keep_start.value IS NOT NULL THEN
13971                 IF oils_json_to_string(usr_keep_age.value)::INTERVAL > AGE(NOW(), oils_json_to_string(usr_keep_start.value)::TIMESTAMPTZ) THEN
13972                     keep_age := AGE(NOW(), oils_json_to_string(usr_keep_start.value)::TIMESTAMPTZ);
13973                 ELSE
13974                     keep_age := oils_json_to_string(usr_keep_age.value)::INTERVAL;
13975                 END IF;
13976             ELSIF usr_keep_start.value IS NOT NULL THEN
13977                 keep_age := AGE(NOW(), oils_json_to_string(usr_keep_start.value)::TIMESTAMPTZ);
13978             ELSE
13979                 keep_age := COALESCE( org_keep_age::INTERVAL, '2000 years'::INTEVAL );
13980             END IF;
13981
13982             EXIT WHEN AGE(NOW(), circ_chain_tail.xact_finish) < keep_age;
13983
13984             -- We've passed the purging tests, purge the circ chain starting at the end
13985             DELETE FROM action.circulation WHERE id = circ_chain_tail.id;
13986             WHILE circ_chain_tail.parent_circ IS NOT NULL LOOP
13987                 SELECT * INTO circ_chain_tail FROM action.circulation WHERE id = circ_chain_tail.parent_circ;
13988                 DELETE FROM action.circulation WHERE id = circ_chain_tail.id;
13989             END LOOP;
13990
13991             count_purged := count_purged + 1;
13992             purge_position := purge_position + 1;
13993
13994         END LOOP;
13995     END LOOP;
13996 END;
13997 $func$ LANGUAGE PLPGSQL;
13998
13999 CREATE OR REPLACE FUNCTION action.usr_visible_holds (usr_id INT) RETURNS SETOF action.hold_request AS $func$
14000 DECLARE
14001     h               action.hold_request%ROWTYPE;
14002     view_age        INTERVAL;
14003     view_count      INT;
14004     usr_view_count  actor.usr_setting%ROWTYPE;
14005     usr_view_age    actor.usr_setting%ROWTYPE;
14006     usr_view_start  actor.usr_setting%ROWTYPE;
14007 BEGIN
14008     SELECT * INTO usr_view_count FROM actor.usr_setting WHERE usr = usr_id AND name = 'history.hold.retention_count';
14009     SELECT * INTO usr_view_age FROM actor.usr_setting WHERE usr = usr_id AND name = 'history.hold.retention_age';
14010     SELECT * INTO usr_view_start FROM actor.usr_setting WHERE usr = usr_id AND name = 'history.hold.retention_start';
14011
14012     FOR h IN
14013         SELECT  *
14014           FROM  action.hold_request
14015           WHERE usr = usr_id
14016                 AND fulfillment_time IS NULL
14017                 AND cancel_time IS NULL
14018           ORDER BY request_time DESC
14019     LOOP
14020         RETURN NEXT h;
14021     END LOOP;
14022
14023     IF usr_view_start.value IS NULL THEN
14024         RETURN;
14025     END IF;
14026
14027     IF usr_view_age.value IS NOT NULL THEN
14028         -- User opted in and supplied a retention age
14029         IF oils_json_to_string(usr_view_age.value)::INTERVAL > AGE(NOW(), oils_json_to_string(usr_view_start.value)::TIMESTAMPTZ) THEN
14030             view_age := AGE(NOW(), oils_json_to_string(usr_view_start.value)::TIMESTAMPTZ);
14031         ELSE
14032             view_age := oils_json_to_string(usr_view_age.value)::INTERVAL;
14033         END IF;
14034     ELSE
14035         -- User opted in
14036         view_age := AGE(NOW(), oils_json_to_string(usr_view_start.value)::TIMESTAMPTZ);
14037     END IF;
14038
14039     IF usr_view_count.value IS NOT NULL THEN
14040         view_count := oils_json_to_text(usr_view_count.value)::INT;
14041     ELSE
14042         view_count := 1000;
14043     END IF;
14044
14045     -- show some fulfilled/canceled holds
14046     FOR h IN
14047         SELECT  *
14048           FROM  action.hold_request
14049           WHERE usr = usr_id
14050                 AND ( fulfillment_time IS NOT NULL OR cancel_time IS NOT NULL )
14051                 AND request_time > NOW() - view_age
14052           ORDER BY request_time DESC
14053           LIMIT view_count
14054     LOOP
14055         RETURN NEXT h;
14056     END LOOP;
14057
14058     RETURN;
14059 END;
14060 $func$ LANGUAGE PLPGSQL;
14061
14062 DROP TABLE IF EXISTS serial.bib_summary CASCADE;
14063
14064 DROP TABLE IF EXISTS serial.index_summary CASCADE;
14065
14066 DROP TABLE IF EXISTS serial.sup_summary CASCADE;
14067
14068 DROP TABLE IF EXISTS serial.issuance CASCADE;
14069
14070 DROP TABLE IF EXISTS serial.binding_unit CASCADE;
14071
14072 DROP TABLE IF EXISTS serial.subscription CASCADE;
14073
14074 CREATE TABLE asset.copy_template (
14075         id             SERIAL   PRIMARY KEY,
14076         owning_lib     INT      NOT NULL
14077                                 REFERENCES actor.org_unit (id)
14078                                 DEFERRABLE INITIALLY DEFERRED,
14079         creator        BIGINT   NOT NULL
14080                                 REFERENCES actor.usr (id)
14081                                 DEFERRABLE INITIALLY DEFERRED,
14082         editor         BIGINT   NOT NULL
14083                                 REFERENCES actor.usr (id)
14084                                 DEFERRABLE INITIALLY DEFERRED,
14085         create_date    TIMESTAMP WITH TIME ZONE    DEFAULT NOW(),
14086         edit_date      TIMESTAMP WITH TIME ZONE    DEFAULT NOW(),
14087         name           TEXT     NOT NULL,
14088         -- columns above this point are attributes of the template itself
14089         -- columns after this point are attributes of the copy this template modifies/creates
14090         circ_lib       INT      REFERENCES actor.org_unit (id)
14091                                 DEFERRABLE INITIALLY DEFERRED,
14092         status         INT      REFERENCES config.copy_status (id)
14093                                 DEFERRABLE INITIALLY DEFERRED,
14094         location       INT      REFERENCES asset.copy_location (id)
14095                                 DEFERRABLE INITIALLY DEFERRED,
14096         loan_duration  INT      CONSTRAINT valid_loan_duration CHECK (
14097                                     loan_duration IS NULL OR loan_duration IN (1,2,3)),
14098         fine_level     INT      CONSTRAINT valid_fine_level CHECK (
14099                                     fine_level IS NULL OR loan_duration IN (1,2,3)),
14100         age_protect    INT,
14101         circulate      BOOL,
14102         deposit        BOOL,
14103         ref            BOOL,
14104         holdable       BOOL,
14105         deposit_amount NUMERIC(6,2),
14106         price          NUMERIC(8,2),
14107         circ_modifier  TEXT,
14108         circ_as_type   TEXT,
14109         alert_message  TEXT,
14110         opac_visible   BOOL,
14111         floating       BOOL,
14112         mint_condition BOOL
14113 );
14114
14115 CREATE TABLE serial.subscription (
14116         id                     SERIAL       PRIMARY KEY,
14117         start_date             TIMESTAMP WITH TIME ZONE     NOT NULL,
14118         end_date               TIMESTAMP WITH TIME ZONE,    -- interpret NULL as current subscription
14119         record_entry           BIGINT       REFERENCES serial.record_entry (id)
14120                                             DEFERRABLE INITIALLY DEFERRED,
14121         expected_date_offset   INTERVAL,
14122         owning_lib             INT          NOT NULL DEFAULT 1
14123                                             REFERENCES actor.org_unit (id)
14124                                             ON DELETE SET NULL
14125                                             DEFERRABLE INITIALLY DEFERRED
14126         -- acquisitions/business-side tables link to here
14127 );
14128
14129 --at least one distribution per org_unit holding issues
14130 CREATE TABLE serial.distribution (
14131         id                    SERIAL  PRIMARY KEY,
14132         subscription          INT     NOT NULL
14133                                       REFERENCES serial.subscription (id)
14134                                                                   ON DELETE CASCADE
14135                                                                   DEFERRABLE INITIALLY DEFERRED,
14136         holding_lib           INT     NOT NULL
14137                                       REFERENCES actor.org_unit (id)
14138                                                                   DEFERRABLE INITIALLY DEFERRED,
14139         label                 TEXT    NOT NULL,
14140         receive_call_number   BIGINT  REFERENCES asset.call_number (id)
14141                                       DEFERRABLE INITIALLY DEFERRED,
14142         receive_unit_template INT     REFERENCES asset.copy_template (id)
14143                                       DEFERRABLE INITIALLY DEFERRED,
14144         bind_call_number      BIGINT  REFERENCES asset.call_number (id)
14145                                       DEFERRABLE INITIALLY DEFERRED,
14146         bind_unit_template    INT     REFERENCES asset.copy_template (id)
14147                                       DEFERRABLE INITIALLY DEFERRED,
14148         unit_label_prefix     TEXT,
14149         unit_label_suffix     TEXT,
14150         record_entry          INT     REFERENCES serial.record_entry (id)
14151                                       ON DELETE SET NULL
14152                                       DEFERRABLE INITIALLY DEFERRED
14153 );
14154
14155 CREATE UNIQUE INDEX one_dist_per_sre_idx ON serial.distribution (record_entry);
14156
14157 CREATE TABLE serial.stream (
14158         id              SERIAL  PRIMARY KEY,
14159         distribution    INT     NOT NULL
14160                                 REFERENCES serial.distribution (id)
14161                                 ON DELETE CASCADE
14162                                 DEFERRABLE INITIALLY DEFERRED,
14163         routing_label   TEXT
14164 );
14165
14166 CREATE UNIQUE INDEX label_once_per_dist
14167         ON serial.stream (distribution, routing_label)
14168         WHERE routing_label IS NOT NULL;
14169
14170 CREATE TABLE serial.routing_list_user (
14171         id             SERIAL       PRIMARY KEY,
14172         stream         INT          NOT NULL
14173                                     REFERENCES serial.stream
14174                                     ON DELETE CASCADE
14175                                     DEFERRABLE INITIALLY DEFERRED,
14176         pos            INT          NOT NULL DEFAULT 1,
14177         reader         INT          REFERENCES actor.usr
14178                                     ON DELETE CASCADE
14179                                     DEFERRABLE INITIALLY DEFERRED,
14180         department     TEXT,
14181         note           TEXT,
14182         CONSTRAINT one_pos_per_routing_list UNIQUE ( stream, pos ),
14183         CONSTRAINT reader_or_dept CHECK
14184         (
14185             -- Recipient is a person or a department, but not both
14186                 (reader IS NOT NULL AND department IS NULL) OR
14187                 (reader IS NULL AND department IS NOT NULL)
14188         )
14189 );
14190
14191 CREATE TABLE serial.caption_and_pattern (
14192         id           SERIAL       PRIMARY KEY,
14193         type         TEXT         NOT NULL
14194                                   CONSTRAINT cap_type CHECK ( type in
14195                                   ( 'basic', 'supplement', 'index' )),
14196         create_date  TIMESTAMPTZ  NOT NULL DEFAULT now(),
14197         active       BOOL         NOT NULL DEFAULT FALSE,
14198         pattern_code TEXT         NOT NULL,       -- must contain JSON
14199         enum_1       TEXT,
14200         enum_2       TEXT,
14201         enum_3       TEXT,
14202         enum_4       TEXT,
14203         enum_5       TEXT,
14204         enum_6       TEXT,
14205         chron_1      TEXT,
14206         chron_2      TEXT,
14207         chron_3      TEXT,
14208         chron_4      TEXT,
14209         chron_5      TEXT,
14210         subscription INT          NOT NULL REFERENCES serial.subscription (id)
14211                                   ON DELETE CASCADE
14212                                   DEFERRABLE INITIALLY DEFERRED
14213 );
14214
14215 CREATE TABLE serial.issuance (
14216         id              SERIAL    PRIMARY KEY,
14217         creator         INT       NOT NULL
14218                                   REFERENCES actor.usr (id)
14219                                                           DEFERRABLE INITIALLY DEFERRED,
14220         editor          INT       NOT NULL
14221                                   REFERENCES actor.usr (id)
14222                                   DEFERRABLE INITIALLY DEFERRED,
14223         create_date     TIMESTAMP WITH TIME ZONE        NOT NULL DEFAULT now(),
14224         edit_date       TIMESTAMP WITH TIME ZONE        NOT NULL DEFAULT now(),
14225         subscription    INT       NOT NULL
14226                                   REFERENCES serial.subscription (id)
14227                                   ON DELETE CASCADE
14228                                   DEFERRABLE INITIALLY DEFERRED,
14229         label           TEXT,
14230         date_published  TIMESTAMP WITH TIME ZONE,
14231         holding_code    TEXT,
14232         holding_type    TEXT      CONSTRAINT valid_holding_type CHECK
14233                                   (
14234                                       holding_type IS NULL
14235                                       OR holding_type IN ('basic','supplement','index')
14236                                   ),
14237         holding_link_id INT,
14238         caption_and_pattern  INT  REFERENCES serial.caption_and_pattern (id)
14239                               DEFERRABLE INITIALLY DEFERRED
14240         -- TODO: add columns for separate enumeration/chronology values
14241 );
14242
14243 CREATE TABLE serial.unit (
14244         label           TEXT,
14245         label_sort_key  TEXT,
14246         contents        TEXT    NOT NULL
14247 ) INHERITS (asset.copy);
14248
14249 ALTER TABLE serial.unit ADD PRIMARY KEY (id);
14250
14251 ALTER TABLE serial.unit ADD CONSTRAINT serial_unit_call_number_fkey FOREIGN KEY (call_number) REFERENCES asset.call_number (id) DEFERRABLE INITIALLY DEFERRED;
14252
14253 ALTER TABLE serial.unit ADD CONSTRAINT serial_unit_creator_fkey FOREIGN KEY (creator) REFERENCES actor.usr (id) ON DELETE SET NULL DEFERRABLE INITIALLY DEFERRED;
14254
14255 ALTER TABLE serial.unit ADD CONSTRAINT serial_unit_editor_fkey FOREIGN KEY (editor) REFERENCES actor.usr (id) ON DELETE SET NULL DEFERRABLE INITIALLY DEFERRED;
14256
14257 CREATE TABLE serial.item (
14258         id              SERIAL  PRIMARY KEY,
14259         creator         INT     NOT NULL
14260                                 REFERENCES actor.usr (id)
14261                                 DEFERRABLE INITIALLY DEFERRED,
14262         editor          INT     NOT NULL
14263                                 REFERENCES actor.usr (id)
14264                                 DEFERRABLE INITIALLY DEFERRED,
14265         create_date     TIMESTAMP WITH TIME ZONE        NOT NULL DEFAULT now(),
14266         edit_date       TIMESTAMP WITH TIME ZONE        NOT NULL DEFAULT now(),
14267         issuance        INT     NOT NULL
14268                                 REFERENCES serial.issuance (id)
14269                                 ON DELETE CASCADE
14270                                 DEFERRABLE INITIALLY DEFERRED,
14271         stream          INT     NOT NULL
14272                                 REFERENCES serial.stream (id)
14273                                 ON DELETE CASCADE
14274                                 DEFERRABLE INITIALLY DEFERRED,
14275         unit            INT     REFERENCES serial.unit (id)
14276                                 ON DELETE SET NULL
14277                                 DEFERRABLE INITIALLY DEFERRED,
14278         uri             INT     REFERENCES asset.uri (id)
14279                                 ON DELETE SET NULL
14280                                 DEFERRABLE INITIALLY DEFERRED,
14281         date_expected   TIMESTAMP WITH TIME ZONE,
14282         date_received   TIMESTAMP WITH TIME ZONE,
14283         status          TEXT    CONSTRAINT value_status_check CHECK (
14284                                status IN ( 'Bindery', 'Bound', 'Claimed', 'Discarded',
14285                                'Expected', 'Not Held', 'Not Published', 'Received'))
14286                             DEFAULT 'Expected',
14287         shadowed        BOOL    NOT NULL DEFAULT FALSE
14288 );
14289
14290 CREATE TABLE serial.item_note (
14291         id          SERIAL  PRIMARY KEY,
14292         item        INT     NOT NULL
14293                             REFERENCES serial.item (id)
14294                             ON DELETE CASCADE
14295                             DEFERRABLE INITIALLY DEFERRED,
14296         creator     INT     NOT NULL
14297                             REFERENCES actor.usr (id)
14298                             DEFERRABLE INITIALLY DEFERRED,
14299         create_date TIMESTAMP WITH TIME ZONE    DEFAULT NOW(),
14300         pub         BOOL    NOT NULL    DEFAULT FALSE,
14301         title       TEXT    NOT NULL,
14302         value       TEXT    NOT NULL
14303 );
14304
14305 CREATE TABLE serial.basic_summary (
14306         id                  SERIAL  PRIMARY KEY,
14307         distribution        INT     NOT NULL
14308                                     REFERENCES serial.distribution (id)
14309                                     ON DELETE CASCADE
14310                                     DEFERRABLE INITIALLY DEFERRED,
14311         generated_coverage  TEXT    NOT NULL,
14312         textual_holdings    TEXT
14313 );
14314
14315 CREATE TABLE serial.supplement_summary (
14316         id                  SERIAL  PRIMARY KEY,
14317         distribution        INT     NOT NULL
14318                                     REFERENCES serial.distribution (id)
14319                                     ON DELETE CASCADE
14320                                     DEFERRABLE INITIALLY DEFERRED,
14321         generated_coverage  TEXT    NOT NULL,
14322         textual_holdings    TEXT
14323 );
14324
14325 CREATE TABLE serial.index_summary (
14326         id                  SERIAL  PRIMARY KEY,
14327         distribution        INT     NOT NULL
14328                                     REFERENCES serial.distribution (id)
14329                                     ON DELETE CASCADE
14330                                     DEFERRABLE INITIALLY DEFERRED,
14331         generated_coverage  TEXT    NOT NULL,
14332         textual_holdings    TEXT
14333 );
14334
14335 -- 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.
14336
14337 DROP INDEX IF EXISTS authority.authority_record_unique_tcn;
14338 CREATE UNIQUE INDEX authority_record_unique_tcn ON authority.record_entry (arn_source,arn_value) WHERE deleted = FALSE OR deleted IS FALSE;
14339
14340 DROP INDEX IF EXISTS asset.asset_call_number_label_once_per_lib;
14341 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;
14342
14343 DROP INDEX IF EXISTS biblio.biblio_record_unique_tcn;
14344 CREATE UNIQUE INDEX biblio_record_unique_tcn ON biblio.record_entry (tcn_value) WHERE deleted = FALSE OR deleted IS FALSE;
14345
14346 CREATE OR REPLACE FUNCTION config.interval_to_seconds( interval_val INTERVAL )
14347 RETURNS INTEGER AS $$
14348 BEGIN
14349         RETURN EXTRACT( EPOCH FROM interval_val );
14350 END;
14351 $$ LANGUAGE plpgsql;
14352
14353 CREATE OR REPLACE FUNCTION config.interval_to_seconds( interval_string TEXT )
14354 RETURNS INTEGER AS $$
14355 BEGIN
14356         RETURN config.interval_to_seconds( interval_string::INTERVAL );
14357 END;
14358 $$ LANGUAGE plpgsql;
14359
14360 INSERT INTO container.biblio_record_entry_bucket_type( code, label ) VALUES (
14361     'temp',
14362     oils_i18n_gettext(
14363         'temp',
14364         'Temporary bucket which gets deleted after use.',
14365         'cbrebt',
14366         'label'
14367     )
14368 );
14369
14370 -- 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.
14371
14372 CREATE OR REPLACE FUNCTION biblio.check_marcxml_well_formed () RETURNS TRIGGER AS $func$
14373 BEGIN
14374
14375     IF xml_is_well_formed(NEW.marc) THEN
14376         RETURN NEW;
14377     ELSE
14378         RAISE EXCEPTION 'Attempted to % MARCXML that is not well formed', TG_OP;
14379     END IF;
14380     
14381 END;
14382 $func$ LANGUAGE PLPGSQL;
14383
14384 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();
14385
14386 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();
14387
14388 ALTER TABLE serial.record_entry
14389         ALTER COLUMN marc DROP NOT NULL;
14390
14391 insert INTO CONFIG.xml_transform(name, namespace_uri, prefix, xslt)
14392 VALUES ('marc21expand880', 'http://www.loc.gov/MARC21/slim', 'marc', $$<?xml version="1.0" encoding="UTF-8"?>
14393 <xsl:stylesheet
14394     xmlns:xsl="http://www.w3.org/1999/XSL/Transform" 
14395     xmlns:marc="http://www.loc.gov/MARC21/slim"
14396     version="1.0">
14397 <!--
14398 Copyright (C) 2010  Equinox Software, Inc.
14399 Galen Charlton <gmc@esilibrary.cOM.
14400
14401 This program is free software; you can redistribute it and/or
14402 modify it under the terms of the GNU General Public License
14403 as published by the Free Software Foundation; either version 2
14404 of the License, or (at your option) any later version.
14405
14406 This program is distributed in the hope that it will be useful,
14407 but WITHOUT ANY WARRANTY; without even the implied warranty of
14408 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14409 GNU General Public License for more details.
14410
14411 marc21_expand_880.xsl - stylesheet used during indexing to
14412                         map alternative graphical representations
14413                         of MARC fields stored in 880 fields
14414                         to the corresponding tag name and value.
14415
14416 For example, if a MARC record for a Chinese book has
14417
14418 245.00 $6 880-01 $a Ba shi san nian duan pian xiao shuo xuan
14419 880.00 $6 245-01/$1 $a八十三年短篇小說選
14420
14421 this stylesheet will transform it to the equivalent of
14422
14423 245.00 $6 880-01 $a Ba shi san nian duan pian xiao shuo xuan
14424 245.00 $6 245-01/$1 $a八十三年短篇小說選
14425
14426 -->
14427     <xsl:output encoding="UTF-8" indent="yes" method="xml"/>
14428
14429     <xsl:template match="@*|node()">
14430         <xsl:copy>
14431             <xsl:apply-templates select="@*|node()"/>
14432         </xsl:copy>
14433     </xsl:template>
14434
14435     <xsl:template match="//marc:datafield[@tag='880']">
14436         <xsl:if test="./marc:subfield[@code='6'] and string-length(./marc:subfield[@code='6']) &gt;= 6">
14437             <marc:datafield>
14438                 <xsl:attribute name="tag">
14439                     <xsl:value-of select="substring(./marc:subfield[@code='6'], 1, 3)" />
14440                 </xsl:attribute>
14441                 <xsl:attribute name="ind1">
14442                     <xsl:value-of select="@ind1" />
14443                 </xsl:attribute>
14444                 <xsl:attribute name="ind2">
14445                     <xsl:value-of select="@ind2" />
14446                 </xsl:attribute>
14447                 <xsl:apply-templates />
14448             </marc:datafield>
14449         </xsl:if>
14450     </xsl:template>
14451     
14452 </xsl:stylesheet>$$);
14453
14454 -- Splitting the ingest trigger up into little bits
14455
14456 CREATE TEMPORARY TABLE eg_0301_check_if_has_contents (
14457     flag INTEGER PRIMARY KEY
14458 ) ON COMMIT DROP;
14459 INSERT INTO eg_0301_check_if_has_contents VALUES (1);
14460
14461 -- cause failure if either of the tables we want to drop have rows
14462 INSERT INTO eg_0301_check_if_has_contents SELECT 1 FROM asset.copy_transparency LIMIT 1;
14463 INSERT INTO eg_0301_check_if_has_contents SELECT 1 FROM asset.copy_transparency_map LIMIT 1;
14464
14465 DROP TABLE IF EXISTS asset.copy_transparency_map;
14466 DROP TABLE IF EXISTS asset.copy_transparency;
14467
14468 UPDATE config.metabib_field SET facet_xpath = '//' || facet_xpath WHERE facet_xpath IS NOT NULL;
14469
14470 -- We won't necessarily use all of these, but they are here for completeness.
14471 -- Source is the EDI spec 1229 codelist, eg: http://www.stylusstudio.com/edifact/D04B/1229.htm
14472 -- Values are the EDI code value + 1000
14473
14474 INSERT INTO acq.cancel_reason (keep_debits, id, org_unit, label, description) VALUES 
14475 ('t',(  1+1000), 1, 'Added',     'The information is to be or has been added.'),
14476 ('f',(  2+1000), 1, 'Deleted',   'The information is to be or has been deleted.'),
14477 ('t',(  3+1000), 1, 'Changed',   'The information is to be or has been changed.'),
14478 ('t',(  4+1000), 1, 'No action',                  'This line item is not affected by the actual message.'),
14479 ('t',(  5+1000), 1, 'Accepted without amendment', 'This line item is entirely accepted by the seller.'),
14480 ('t',(  6+1000), 1, 'Accepted with amendment',    'This line item is accepted but amended by the seller.'),
14481 ('f',(  7+1000), 1, 'Not accepted',               'This line item is not accepted by the seller.'),
14482 ('t',(  8+1000), 1, 'Schedule only', 'Code specifying that the message is a schedule only.'),
14483 ('t',(  9+1000), 1, 'Amendments',    'Code specifying that amendments are requested/notified.'),
14484 ('f',( 10+1000), 1, 'Not found',   'This line item is not found in the referenced message.'),
14485 ('t',( 11+1000), 1, 'Not amended', 'This line is not amended by the buyer.'),
14486 ('t',( 12+1000), 1, 'Line item numbers changed', 'Code specifying that the line item numbers have changed.'),
14487 ('t',( 13+1000), 1, 'Buyer has deducted amount', 'Buyer has deducted amount from payment.'),
14488 ('t',( 14+1000), 1, 'Buyer claims against invoice', 'Buyer has a claim against an outstanding invoice.'),
14489 ('t',( 15+1000), 1, 'Charge back by seller', 'Factor has been requested to charge back the outstanding item.'),
14490 ('t',( 16+1000), 1, 'Seller will issue credit note', 'Seller agrees to issue a credit note.'),
14491 ('t',( 17+1000), 1, 'Terms changed for new terms', 'New settlement terms have been agreed.'),
14492 ('t',( 18+1000), 1, 'Abide outcome of negotiations', 'Factor agrees to abide by the outcome of negotiations between seller and buyer.'),
14493 ('t',( 19+1000), 1, 'Seller rejects dispute', 'Seller does not accept validity of dispute.'),
14494 ('t',( 20+1000), 1, 'Settlement', 'The reported situation is settled.'),
14495 ('t',( 21+1000), 1, 'No delivery', 'Code indicating that no delivery will be required.'),
14496 ('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).'),
14497 ('t',( 23+1000), 1, 'Proposed amendment', 'A code used to indicate an amendment suggested by the sender.'),
14498 ('t',( 24+1000), 1, 'Accepted with amendment, no confirmation required', 'Accepted with changes which require no confirmation.'),
14499 ('t',( 25+1000), 1, 'Equipment provisionally repaired', 'The equipment or component has been provisionally repaired.'),
14500 ('t',( 26+1000), 1, 'Included', 'Code indicating that the entity is included.'),
14501 ('t',( 27+1000), 1, 'Verified documents for coverage', 'Upon receipt and verification of documents we shall cover you when due as per your instructions.'),
14502 ('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.'),
14503 ('t',( 29+1000), 1, 'Authenticated advice for coverage',      'On receipt of your authenticated advice we shall cover you when due as per your instructions.'),
14504 ('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.'),
14505 ('t',( 31+1000), 1, 'Authenticated advice for credit',        'On receipt of your authenticated advice we shall credit your account with us when due.'),
14506 ('t',( 32+1000), 1, 'Credit advice requested for direct debit',           'A credit advice is requested for the direct debit.'),
14507 ('t',( 33+1000), 1, 'Credit advice and acknowledgement for direct debit', 'A credit advice and acknowledgement are requested for the direct debit.'),
14508 ('t',( 34+1000), 1, 'Inquiry',     'Request for information.'),
14509 ('t',( 35+1000), 1, 'Checked',     'Checked.'),
14510 ('t',( 36+1000), 1, 'Not checked', 'Not checked.'),
14511 ('f',( 37+1000), 1, 'Cancelled',   'Discontinued.'),
14512 ('t',( 38+1000), 1, 'Replaced',    'Provide a replacement.'),
14513 ('t',( 39+1000), 1, 'New',         'Not existing before.'),
14514 ('t',( 40+1000), 1, 'Agreed',      'Consent.'),
14515 ('t',( 41+1000), 1, 'Proposed',    'Put forward for consideration.'),
14516 ('t',( 42+1000), 1, 'Already delivered', 'Delivery has taken place.'),
14517 ('t',( 43+1000), 1, 'Additional subordinate structures will follow', 'Additional subordinate structures will follow the current hierarchy level.'),
14518 ('t',( 44+1000), 1, 'Additional subordinate structures will not follow', 'No additional subordinate structures will follow the current hierarchy level.'),
14519 ('t',( 45+1000), 1, 'Result opposed',         'A notification that the result is opposed.'),
14520 ('t',( 46+1000), 1, 'Auction held',           'A notification that an auction was held.'),
14521 ('t',( 47+1000), 1, 'Legal action pursued',   'A notification that legal action has been pursued.'),
14522 ('t',( 48+1000), 1, 'Meeting held',           'A notification that a meeting was held.'),
14523 ('t',( 49+1000), 1, 'Result set aside',       'A notification that the result has been set aside.'),
14524 ('t',( 50+1000), 1, 'Result disputed',        'A notification that the result has been disputed.'),
14525 ('t',( 51+1000), 1, 'Countersued',            'A notification that a countersuit has been filed.'),
14526 ('t',( 52+1000), 1, 'Pending',                'A notification that an action is awaiting settlement.'),
14527 ('f',( 53+1000), 1, 'Court action dismissed', 'A notification that a court action will no longer be heard.'),
14528 ('t',( 54+1000), 1, 'Referred item, accepted', 'The item being referred to has been accepted.'),
14529 ('f',( 55+1000), 1, 'Referred item, rejected', 'The item being referred to has been rejected.'),
14530 ('t',( 56+1000), 1, 'Debit advice statement line',  'Notification that the statement line is a debit advice.'),
14531 ('t',( 57+1000), 1, 'Credit advice statement line', 'Notification that the statement line is a credit advice.'),
14532 ('t',( 58+1000), 1, 'Grouped credit advices',       'Notification that the credit advices are grouped.'),
14533 ('t',( 59+1000), 1, 'Grouped debit advices',        'Notification that the debit advices are grouped.'),
14534 ('t',( 60+1000), 1, 'Registered', 'The name is registered.'),
14535 ('f',( 61+1000), 1, 'Payment denied', 'The payment has been denied.'),
14536 ('t',( 62+1000), 1, 'Approved as amended', 'Approved with modifications.'),
14537 ('t',( 63+1000), 1, 'Approved as submitted', 'The request has been approved as submitted.'),
14538 ('f',( 64+1000), 1, 'Cancelled, no activity', 'Cancelled due to the lack of activity.'),
14539 ('t',( 65+1000), 1, 'Under investigation', 'Investigation is being done.'),
14540 ('t',( 66+1000), 1, 'Initial claim received', 'Notification that the initial claim was received.'),
14541 ('f',( 67+1000), 1, 'Not in process', 'Not in process.'),
14542 ('f',( 68+1000), 1, 'Rejected, duplicate', 'Rejected because it is a duplicate.'),
14543 ('f',( 69+1000), 1, 'Rejected, resubmit with corrections', 'Rejected but may be resubmitted when corrected.'),
14544 ('t',( 70+1000), 1, 'Pending, incomplete', 'Pending because of incomplete information.'),
14545 ('t',( 71+1000), 1, 'Under field office investigation', 'Investigation by the field is being done.'),
14546 ('t',( 72+1000), 1, 'Pending, awaiting additional material', 'Pending awaiting receipt of additional material.'),
14547 ('t',( 73+1000), 1, 'Pending, awaiting review', 'Pending while awaiting review.'),
14548 ('t',( 74+1000), 1, 'Reopened', 'Opened again.'),
14549 ('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).'),
14550 ('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).'),
14551 ('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).'),
14552 ('t',( 78+1000), 1, 'Previous payment decision reversed', 'A previous payment decision has been reversed.'),
14553 ('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).'),
14554 ('t',( 80+1000), 1, 'Transferred to correct insurance carrier', 'The request has been transferred to the correct insurance carrier for processing.'),
14555 ('t',( 81+1000), 1, 'Not paid, predetermination pricing only', 'Payment has not been made and the enclosed response is predetermination pricing only.'),
14556 ('t',( 82+1000), 1, 'Documentation claim', 'The claim is for documentation purposes only, no payment required.'),
14557 ('t',( 83+1000), 1, 'Reviewed', 'Assessed.'),
14558 ('f',( 84+1000), 1, 'Repriced', 'This price was changed.'),
14559 ('t',( 85+1000), 1, 'Audited', 'An official examination has occurred.'),
14560 ('t',( 86+1000), 1, 'Conditionally paid', 'Payment has been conditionally made.'),
14561 ('t',( 87+1000), 1, 'On appeal', 'Reconsideration of the decision has been applied for.'),
14562 ('t',( 88+1000), 1, 'Closed', 'Shut.'),
14563 ('t',( 89+1000), 1, 'Reaudited', 'A subsequent official examination has occurred.'),
14564 ('t',( 90+1000), 1, 'Reissued', 'Issued again.'),
14565 ('t',( 91+1000), 1, 'Closed after reopening', 'Reopened and then closed.'),
14566 ('t',( 92+1000), 1, 'Redetermined', 'Determined again or differently.'),
14567 ('t',( 93+1000), 1, 'Processed as primary',   'Processed as the first.'),
14568 ('t',( 94+1000), 1, 'Processed as secondary', 'Processed as the second.'),
14569 ('t',( 95+1000), 1, 'Processed as tertiary',  'Processed as the third.'),
14570 ('t',( 96+1000), 1, 'Correction of error', 'A correction to information previously communicated which contained an error.'),
14571 ('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.'),
14572 ('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.'),
14573 ('t',( 99+1000), 1, 'Interim response', 'The response is an interim one.'),
14574 ('t',(100+1000), 1, 'Final response',   'The response is an final one.'),
14575 ('t',(101+1000), 1, 'Debit advice requested', 'A debit advice is requested for the transaction.'),
14576 ('t',(102+1000), 1, 'Transaction not impacted', 'Advice that the transaction is not impacted.'),
14577 ('t',(103+1000), 1, 'Patient to be notified',                    'The action to take is to notify the patient.'),
14578 ('t',(104+1000), 1, 'Healthcare provider to be notified',        'The action to take is to notify the healthcare provider.'),
14579 ('t',(105+1000), 1, 'Usual general practitioner to be notified', 'The action to take is to notify the usual general practitioner.'),
14580 ('t',(106+1000), 1, 'Advice without details', 'An advice without details is requested or notified.'),
14581 ('t',(107+1000), 1, 'Advice with details', 'An advice with details is requested or notified.'),
14582 ('t',(108+1000), 1, 'Amendment requested', 'An amendment is requested.'),
14583 ('t',(109+1000), 1, 'For information', 'Included for information only.'),
14584 ('f',(110+1000), 1, 'Withdraw', 'A code indicating discontinuance or retraction.'),
14585 ('t',(111+1000), 1, 'Delivery date change', 'The action / notiification is a change of the delivery date.'),
14586 ('f',(112+1000), 1, 'Quantity change',      'The action / notification is a change of quantity.'),
14587 ('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.'),
14588 ('t',(114+1000), 1, 'Resale',           'The identified items have been sold by the distributor to the end customer.'),
14589 ('t',(115+1000), 1, 'Prior addition', 'This existing line item becomes available at an earlier date.');
14590
14591 INSERT INTO config.metabib_field ( id, field_class, name, label, format, xpath, facet_field, search_field ) VALUES
14592     (26, 'identifier', 'arcn', oils_i18n_gettext(26, 'Authority record control number', 'cmf', 'label'), 'marcxml', $$//marc:subfield[@code='0']$$, TRUE, FALSE );
14593  
14594 SELECT SETVAL('config.metabib_field_id_seq'::TEXT, (SELECT MAX(id) FROM config.metabib_field), TRUE);
14595  
14596 INSERT INTO config.index_normalizer (name, description, func, param_count) VALUES (
14597         'Remove Parenthesized Substring',
14598         'Remove any parenthesized substrings from the extracted text, such as the agency code preceding authority record control numbers in subfield 0.',
14599         'remove_paren_substring',
14600         0
14601 );
14602
14603 INSERT INTO config.index_normalizer (name, description, func, param_count) VALUES (
14604         'Trim Surrounding Space',
14605         'Trim leading and trailing spaces from extracted text.',
14606         'btrim',
14607         0
14608 );
14609
14610 INSERT INTO config.metabib_field_index_norm_map (field,norm,pos)
14611     SELECT  m.id,
14612             i.id,
14613             -2
14614       FROM  config.metabib_field m,
14615             config.index_normalizer i
14616       WHERE i.func IN ('remove_paren_substring')
14617             AND m.id IN (26);
14618
14619 INSERT INTO config.metabib_field_index_norm_map (field,norm,pos)
14620     SELECT  m.id,
14621             i.id,
14622             -1
14623       FROM  config.metabib_field m,
14624             config.index_normalizer i
14625       WHERE i.func IN ('btrim')
14626             AND m.id IN (26);
14627
14628 -- Function that takes, and returns, marcxml and compiles an embedded ruleset for you, and they applys it
14629 CREATE OR REPLACE FUNCTION vandelay.merge_record_xml ( target_marc TEXT, template_marc TEXT ) RETURNS TEXT AS $$
14630 DECLARE
14631     dyn_profile     vandelay.compile_profile%ROWTYPE;
14632     replace_rule    TEXT;
14633     tmp_marc        TEXT;
14634     trgt_marc        TEXT;
14635     tmpl_marc        TEXT;
14636     match_count     INT;
14637 BEGIN
14638
14639     IF target_marc IS NULL OR template_marc IS NULL THEN
14640         -- RAISE NOTICE 'no marc for target or template record';
14641         RETURN NULL;
14642     END IF;
14643
14644     dyn_profile := vandelay.compile_profile( template_marc );
14645
14646     IF dyn_profile.replace_rule <> '' AND dyn_profile.preserve_rule <> '' THEN
14647         -- RAISE NOTICE 'both replace [%] and preserve [%] specified', dyn_profile.replace_rule, dyn_profile.preserve_rule;
14648         RETURN NULL;
14649     END IF;
14650
14651     IF dyn_profile.replace_rule <> '' THEN
14652         trgt_marc = target_marc;
14653         tmpl_marc = template_marc;
14654         replace_rule = dyn_profile.replace_rule;
14655     ELSE
14656         tmp_marc = target_marc;
14657         trgt_marc = template_marc;
14658         tmpl_marc = tmp_marc;
14659         replace_rule = dyn_profile.preserve_rule;
14660     END IF;
14661
14662     RETURN vandelay.merge_record_xml( trgt_marc, tmpl_marc, dyn_profile.add_rule, replace_rule, dyn_profile.strip_rule );
14663
14664 END;
14665 $$ LANGUAGE PLPGSQL;
14666
14667 -- Function to generate an ephemeral overlay template from an authority record
14668 CREATE OR REPLACE FUNCTION authority.generate_overlay_template ( TEXT, BIGINT ) RETURNS TEXT AS $func$
14669
14670     use MARC::Record;
14671     use MARC::File::XML (BinaryEncoding => 'UTF-8');
14672
14673     my $xml = shift;
14674     my $r = MARC::Record->new_from_xml( $xml );
14675
14676     return undef unless ($r);
14677
14678     my $id = shift() || $r->subfield( '901' => 'c' );
14679     $id =~ s/^\s*(?:\([^)]+\))?\s*(.+)\s*?$/$1/;
14680     return undef unless ($id); # We need an ID!
14681
14682     my $tmpl = MARC::Record->new();
14683
14684     my @rule_fields;
14685     for my $field ( $r->field( '1..' ) ) { # Get main entry fields from the authority record
14686
14687         my $tag = $field->tag;
14688         my $i1 = $field->indicator(1);
14689         my $i2 = $field->indicator(2);
14690         my $sf = join '', map { $_->[0] } $field->subfields;
14691         my @data = map { @$_ } $field->subfields;
14692
14693         my @replace_them;
14694
14695         # Map the authority field to bib fields it can control.
14696         if ($tag >= 100 and $tag <= 111) {       # names
14697             @replace_them = map { $tag + $_ } (0, 300, 500, 600, 700);
14698         } elsif ($tag eq '130') {                # uniform title
14699             @replace_them = qw/130 240 440 730 830/;
14700         } elsif ($tag >= 150 and $tag <= 155) {  # subjects
14701             @replace_them = ($tag + 500);
14702         } elsif ($tag >= 180 and $tag <= 185) {  # floating subdivisions
14703             @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/;
14704         } else {
14705             next;
14706         }
14707
14708         # Dummy up the bib-side data
14709         $tmpl->append_fields(
14710             map {
14711                 MARC::Field->new( $_, $i1, $i2, @data )
14712             } @replace_them
14713         );
14714
14715         # Construct some 'replace' rules
14716         push @rule_fields, map { $_ . $sf . '[0~\)' .$id . '$]' } @replace_them;
14717     }
14718
14719     # Insert the replace rules into the template
14720     $tmpl->append_fields(
14721         MARC::Field->new( '905' => ' ' => ' ' => 'r' => join(',', @rule_fields ) )
14722     );
14723
14724     $xml = $tmpl->as_xml_record;
14725     $xml =~ s/^<\?.+?\?>$//mo;
14726     $xml =~ s/\n//sgo;
14727     $xml =~ s/>\s+</></sgo;
14728
14729     return $xml;
14730
14731 $func$ LANGUAGE PLPERLU;
14732
14733 CREATE OR REPLACE FUNCTION authority.generate_overlay_template ( BIGINT ) RETURNS TEXT AS $func$
14734     SELECT authority.generate_overlay_template( marc, id ) FROM authority.record_entry WHERE id = $1;
14735 $func$ LANGUAGE SQL;
14736
14737 CREATE OR REPLACE FUNCTION authority.generate_overlay_template ( TEXT ) RETURNS TEXT AS $func$
14738     SELECT authority.generate_overlay_template( $1, NULL );
14739 $func$ LANGUAGE SQL;
14740
14741 DELETE FROM config.metabib_field_index_norm_map WHERE field = 26;
14742 DELETE FROM config.metabib_field WHERE id = 26;
14743
14744 -- Making this a global_flag (UI accessible) instead of an internal_flag
14745 INSERT INTO config.global_flag (name, label) -- defaults to enabled=FALSE
14746     VALUES (
14747         'ingest.disable_authority_linking',
14748         oils_i18n_gettext(
14749             'ingest.disable_authority_linking',
14750             'Authority Automation: Disable bib-authority link tracking',
14751             'cgf', 
14752             'label'
14753         )
14754     );
14755 UPDATE config.global_flag SET enabled = (SELECT enabled FROM ONLY config.internal_flag WHERE name = 'ingest.disable_authority_linking');
14756 DELETE FROM config.internal_flag WHERE name = 'ingest.disable_authority_linking';
14757
14758 INSERT INTO config.global_flag (name, label) -- defaults to enabled=FALSE
14759     VALUES (
14760         'ingest.disable_authority_auto_update',
14761         oils_i18n_gettext(
14762             'ingest.disable_authority_auto_update',
14763             'Authority Automation: Disable automatic authority updating (requires link tracking)',
14764             'cgf', 
14765             'label'
14766         )
14767     );
14768
14769 -- Enable automated ingest of authority records; just insert the row into
14770 -- authority.record_entry and authority.full_rec will automatically be populated
14771
14772 CREATE OR REPLACE FUNCTION authority.propagate_changes (aid BIGINT, bid BIGINT) RETURNS BIGINT AS $func$
14773     UPDATE  biblio.record_entry
14774       SET   marc = vandelay.merge_record_xml( marc, authority.generate_overlay_template( $1 ) )
14775       WHERE id = $2;
14776     SELECT $1;
14777 $func$ LANGUAGE SQL;
14778
14779 CREATE OR REPLACE FUNCTION authority.propagate_changes (aid BIGINT) RETURNS SETOF BIGINT AS $func$
14780     SELECT authority.propagate_changes( authority, bib ) FROM authority.bib_linking WHERE authority = $1;
14781 $func$ LANGUAGE SQL;
14782
14783 -- authority.rec_descriptor appears to be unused currently
14784 CREATE OR REPLACE FUNCTION authority.reingest_authority_rec_descriptor( auth_id BIGINT ) RETURNS VOID AS $func$
14785 BEGIN
14786     DELETE FROM authority.rec_descriptor WHERE record = auth_id;
14787 --    INSERT INTO authority.rec_descriptor (record, record_status, char_encoding)
14788 --        SELECT  auth_id, ;
14789
14790     RETURN;
14791 END;
14792 $func$ LANGUAGE PLPGSQL;
14793
14794 CREATE OR REPLACE FUNCTION authority.reingest_authority_full_rec( auth_id BIGINT ) RETURNS VOID AS $func$
14795 BEGIN
14796     DELETE FROM authority.full_rec WHERE record = auth_id;
14797     INSERT INTO authority.full_rec (record, tag, ind1, ind2, subfield, value)
14798         SELECT record, tag, ind1, ind2, subfield, value FROM authority.flatten_marc( auth_id );
14799
14800     RETURN;
14801 END;
14802 $func$ LANGUAGE PLPGSQL;
14803
14804 -- AFTER UPDATE OR INSERT trigger for authority.record_entry
14805 CREATE OR REPLACE FUNCTION authority.indexing_ingest_or_delete () RETURNS TRIGGER AS $func$
14806 BEGIN
14807
14808     IF NEW.deleted IS TRUE THEN -- If this authority is deleted
14809         DELETE FROM authority.bib_linking WHERE authority = NEW.id; -- Avoid updating fields in bibs that are no longer visible
14810           -- Should remove matching $0 from controlled fields at the same time?
14811         RETURN NEW; -- and we're done
14812     END IF;
14813
14814     IF TG_OP = 'UPDATE' THEN -- re-ingest?
14815         PERFORM * FROM config.internal_flag WHERE name = 'ingest.reingest.force_on_same_marc' AND enabled;
14816
14817         IF NOT FOUND AND OLD.marc = NEW.marc THEN -- don't do anything if the MARC didn't change
14818             RETURN NEW;
14819         END IF;
14820     END IF;
14821
14822     -- Flatten and insert the afr data
14823     PERFORM * FROM config.internal_flag WHERE name = 'ingest.disable_authority_full_rec' AND enabled;
14824     IF NOT FOUND THEN
14825         PERFORM authority.reingest_authority_full_rec(NEW.id);
14826 -- authority.rec_descriptor is not currently used
14827 --        PERFORM * FROM config.internal_flag WHERE name = 'ingest.disable_authority_rec_descriptor' AND enabled;
14828 --        IF NOT FOUND THEN
14829 --            PERFORM authority.reingest_authority_rec_descriptor(NEW.id);
14830 --        END IF;
14831     END IF;
14832
14833     RETURN NEW;
14834 END;
14835 $func$ LANGUAGE PLPGSQL;
14836
14837 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 ();
14838
14839 -- Some records manage to get XML namespace declarations into each element,
14840 -- like <datafield xmlns:marc="http://www.loc.gov/MARC21/slim"
14841 -- This broke the old maintain_901(), so we'll make the regex more robust
14842
14843 CREATE OR REPLACE FUNCTION maintain_901 () RETURNS TRIGGER AS $func$
14844 BEGIN
14845     -- Remove any existing 901 fields before we insert the authoritative one
14846     NEW.marc := REGEXP_REPLACE(NEW.marc, E'<datafield\s*[^<>]*?\s*tag="901".+?</datafield>', '', 'g');
14847     IF TG_TABLE_SCHEMA = 'biblio' THEN
14848         NEW.marc := REGEXP_REPLACE(
14849             NEW.marc,
14850             E'(</(?:[^:]*?:)?record>)',
14851             E'<datafield tag="901" ind1=" " ind2=" ">' ||
14852                 '<subfield code="a">' || NEW.tcn_value || E'</subfield>' ||
14853                 '<subfield code="b">' || NEW.tcn_source || E'</subfield>' ||
14854                 '<subfield code="c">' || NEW.id || E'</subfield>' ||
14855                 '<subfield code="t">' || TG_TABLE_SCHEMA || E'</subfield>' ||
14856                 CASE WHEN NEW.owner IS NOT NULL THEN '<subfield code="o">' || NEW.owner || E'</subfield>' ELSE '' END ||
14857                 CASE WHEN NEW.share_depth IS NOT NULL THEN '<subfield code="d">' || NEW.share_depth || E'</subfield>' ELSE '' END ||
14858              E'</datafield>\\1'
14859         );
14860     ELSIF TG_TABLE_SCHEMA = 'authority' THEN
14861         NEW.marc := REGEXP_REPLACE(
14862             NEW.marc,
14863             E'(</(?:[^:]*?:)?record>)',
14864             E'<datafield tag="901" ind1=" " ind2=" ">' ||
14865                 '<subfield code="a">' || NEW.arn_value || E'</subfield>' ||
14866                 '<subfield code="b">' || NEW.arn_source || E'</subfield>' ||
14867                 '<subfield code="c">' || NEW.id || E'</subfield>' ||
14868                 '<subfield code="t">' || TG_TABLE_SCHEMA || E'</subfield>' ||
14869              E'</datafield>\\1'
14870         );
14871     ELSIF TG_TABLE_SCHEMA = 'serial' THEN
14872         NEW.marc := REGEXP_REPLACE(
14873             NEW.marc,
14874             E'(</(?:[^:]*?:)?record>)',
14875             E'<datafield tag="901" ind1=" " ind2=" ">' ||
14876                 '<subfield code="c">' || NEW.id || E'</subfield>' ||
14877                 '<subfield code="t">' || TG_TABLE_SCHEMA || E'</subfield>' ||
14878                 '<subfield code="o">' || NEW.owning_lib || E'</subfield>' ||
14879                 CASE WHEN NEW.record IS NOT NULL THEN '<subfield code="r">' || NEW.record || E'</subfield>' ELSE '' END ||
14880              E'</datafield>\\1'
14881         );
14882     ELSE
14883         NEW.marc := REGEXP_REPLACE(
14884             NEW.marc,
14885             E'(</(?:[^:]*?:)?record>)',
14886             E'<datafield tag="901" ind1=" " ind2=" ">' ||
14887                 '<subfield code="c">' || NEW.id || E'</subfield>' ||
14888                 '<subfield code="t">' || TG_TABLE_SCHEMA || E'</subfield>' ||
14889              E'</datafield>\\1'
14890         );
14891     END IF;
14892
14893     RETURN NEW;
14894 END;
14895 $func$ LANGUAGE PLPGSQL;
14896
14897 CREATE TRIGGER b_maintain_901 BEFORE INSERT OR UPDATE ON biblio.record_entry FOR EACH ROW EXECUTE PROCEDURE maintain_901();
14898 CREATE TRIGGER b_maintain_901 BEFORE INSERT OR UPDATE ON authority.record_entry FOR EACH ROW EXECUTE PROCEDURE maintain_901();
14899 CREATE TRIGGER b_maintain_901 BEFORE INSERT OR UPDATE ON serial.record_entry FOR EACH ROW EXECUTE PROCEDURE maintain_901();
14900  
14901 -- In booking, elbow room defines:
14902 --  a) how far in the future you must make a reservation on a given item if
14903 --      that item will have to transit somewhere to fulfill the reservation.
14904 --  b) how soon a reservation must be starting for the reserved item to
14905 --      be op-captured by the checkin interface.
14906
14907 -- We don't want to clobber any default_elbow room at any level:
14908
14909 CREATE OR REPLACE FUNCTION pg_temp.default_elbow() RETURNS INTEGER AS $$
14910 DECLARE
14911     existing    actor.org_unit_setting%ROWTYPE;
14912 BEGIN
14913     SELECT INTO existing id FROM actor.org_unit_setting WHERE name = 'circ.booking_reservation.default_elbow_room';
14914     IF NOT FOUND THEN
14915         INSERT INTO actor.org_unit_setting (org_unit, name, value) VALUES (
14916             (SELECT id FROM actor.org_unit WHERE parent_ou IS NULL),
14917             'circ.booking_reservation.default_elbow_room',
14918             '"1 day"'
14919         );
14920         RETURN 1;
14921     END IF;
14922     RETURN 0;
14923 END;
14924 $$ LANGUAGE plpgsql;
14925
14926 SELECT pg_temp.default_elbow();
14927
14928 DROP FUNCTION IF EXISTS action.usr_visible_circ_copies( INTEGER );
14929
14930 -- returns the distinct set of target copy IDs from a user's visible circulation history
14931 CREATE OR REPLACE FUNCTION action.usr_visible_circ_copies( INTEGER ) RETURNS SETOF BIGINT AS $$
14932     SELECT DISTINCT(target_copy) FROM action.usr_visible_circs($1)
14933 $$ LANGUAGE SQL;
14934
14935 ALTER TABLE action.in_house_use DROP CONSTRAINT in_house_use_item_fkey;
14936 ALTER TABLE action.transit_copy DROP CONSTRAINT transit_copy_target_copy_fkey;
14937 ALTER TABLE action.hold_transit_copy DROP CONSTRAINT ahtc_tc_fkey;
14938
14939 ALTER TABLE asset.stat_cat_entry_copy_map DROP CONSTRAINT a_sc_oc_fkey;
14940
14941 ALTER TABLE authority.record_entry ADD COLUMN owner INT;
14942 ALTER TABLE serial.record_entry ADD COLUMN owner INT;
14943
14944 INSERT INTO config.global_flag (name, label) -- defaults to enabled=FALSE
14945     VALUES (
14946         'cat.maintain_control_numbers',
14947         oils_i18n_gettext(
14948             'cat.maintain_control_numbers',
14949             'Cat: Maintain 001/003/035 according to the MARC21 specification',
14950             'cgf', 
14951             'label'
14952         )
14953     );
14954
14955 CREATE OR REPLACE FUNCTION maintain_control_numbers() RETURNS TRIGGER AS $func$
14956 use strict;
14957 use MARC::Record;
14958 use MARC::File::XML (BinaryEncoding => 'UTF-8');
14959 use Encode;
14960 use Unicode::Normalize;
14961
14962 my $record = MARC::Record->new_from_xml($_TD->{new}{marc});
14963 my $schema = $_TD->{table_schema};
14964 my $rec_id = $_TD->{new}{id};
14965
14966 # Short-circuit if maintaining control numbers per MARC21 spec is not enabled
14967 my $enable = spi_exec_query("SELECT enabled FROM config.global_flag WHERE name = 'cat.maintain_control_numbers'");
14968 if (!($enable->{processed}) or $enable->{rows}[0]->{enabled} eq 'f') {
14969     return;
14970 }
14971
14972 # Get the control number identifier from an OU setting based on $_TD->{new}{owner}
14973 my $ou_cni = 'EVRGRN';
14974
14975 my $owner;
14976 if ($schema eq 'serial') {
14977     $owner = $_TD->{new}{owning_lib};
14978 } else {
14979     # are.owner and bre.owner can be null, so fall back to the consortial setting
14980     $owner = $_TD->{new}{owner} || 1;
14981 }
14982
14983 my $ous_rv = spi_exec_query("SELECT value FROM actor.org_unit_ancestor_setting('cat.marc_control_number_identifier', $owner)");
14984 if ($ous_rv->{processed}) {
14985     $ou_cni = $ous_rv->{rows}[0]->{value};
14986     $ou_cni =~ s/"//g; # Stupid VIM syntax highlighting"
14987 } else {
14988     # Fall back to the shortname of the OU if there was no OU setting
14989     $ous_rv = spi_exec_query("SELECT shortname FROM actor.org_unit WHERE id = $owner");
14990     if ($ous_rv->{processed}) {
14991         $ou_cni = $ous_rv->{rows}[0]->{shortname};
14992     }
14993 }
14994
14995 my ($create, $munge) = (0, 0);
14996 my ($orig_001, $orig_003) = ('', '');
14997
14998 # Incoming MARC records may have multiple 001s or 003s, despite the spec
14999 my @control_ids = $record->field('003');
15000 my @scns = $record->field('035');
15001
15002 foreach my $id_field ('001', '003') {
15003     my $spec_value;
15004     my @controls = $record->field($id_field);
15005
15006     if ($id_field eq '001') {
15007         $spec_value = $rec_id;
15008     } else {
15009         $spec_value = $ou_cni;
15010     }
15011
15012     # Create the 001/003 if none exist
15013     if (scalar(@controls) == 0) {
15014         $record->insert_fields_ordered(MARC::Field->new($id_field, $spec_value));
15015         $create = 1;
15016     } elsif (scalar(@controls) > 1) {
15017         # Do we already have the right 001/003 value in the existing set?
15018         unless (grep $_->data() eq $spec_value, @controls) {
15019             $munge = 1;
15020         }
15021
15022         # Delete the other fields, as with more than 1 001/003 we do not know which 003/001 to match
15023         foreach my $control (@controls) {
15024             unless ($control->data() eq $spec_value) {
15025                 $record->delete_field($control);
15026             }
15027         }
15028     } else {
15029         # Only one field; check to see if we need to munge it
15030         unless (grep $_->data() eq $spec_value, @controls) {
15031             $munge = 1;
15032         }
15033     }
15034 }
15035
15036 # Now, if we need to munge the 001, we will first push the existing 001/003 into the 035
15037 if ($munge) {
15038     my $scn = "(" . $record->field('003')->data() . ")" . $record->field('001')->data();
15039
15040     # Do not create duplicate 035 fields
15041     unless (grep $_->subfield('a') eq $scn, @scns) {
15042         $record->insert_fields_ordered(MARC::Field->new('035', '', '', 'a' => $scn));
15043     }
15044 }
15045
15046 # Set the 001/003 and update the MARC
15047 if ($create or $munge) {
15048     $record->field('001')->data($rec_id);
15049     $record->field('003')->data($ou_cni);
15050
15051     my $xml = $record->as_xml_record();
15052     $xml =~ s/\n//sgo;
15053     $xml =~ s/^<\?xml.+\?\s*>//go;
15054     $xml =~ s/>\s+</></go;
15055     $xml =~ s/\p{Cc}//go;
15056
15057     # Embed a version of OpenILS::Application::AppUtils->entityize()
15058     # to avoid having to set PERL5LIB for PostgreSQL as well
15059
15060     # If we are going to convert non-ASCII characters to XML entities,
15061     # we had better be dealing with a UTF8 string to begin with
15062     $xml = decode_utf8($xml);
15063
15064     $xml = NFC($xml);
15065
15066     # Convert raw ampersands to entities
15067     $xml =~ s/&(?!\S+;)/&amp;/gso;
15068
15069     # Convert Unicode characters to entities
15070     $xml =~ s/([\x{0080}-\x{fffd}])/sprintf('&#x%X;',ord($1))/sgoe;
15071
15072     $xml =~ s/[\x00-\x1f]//go;
15073     $_TD->{new}{marc} = $xml;
15074
15075     return "MODIFY";
15076 }
15077
15078 return;
15079 $func$ LANGUAGE PLPERLU;
15080
15081 CREATE TRIGGER c_maintain_control_numbers BEFORE INSERT OR UPDATE ON authority.record_entry FOR EACH ROW EXECUTE PROCEDURE maintain_control_numbers();
15082 CREATE TRIGGER c_maintain_control_numbers BEFORE INSERT OR UPDATE ON biblio.record_entry FOR EACH ROW EXECUTE PROCEDURE maintain_control_numbers();
15083 CREATE TRIGGER c_maintain_control_numbers BEFORE INSERT OR UPDATE ON serial.record_entry FOR EACH ROW EXECUTE PROCEDURE maintain_control_numbers();
15084
15085 INSERT INTO metabib.facet_entry (source, field, value)
15086     SELECT source, field, value FROM (
15087         SELECT * FROM metabib.author_field_entry
15088             UNION ALL
15089         SELECT * FROM metabib.keyword_field_entry
15090             UNION ALL
15091         SELECT * FROM metabib.identifier_field_entry
15092             UNION ALL
15093         SELECT * FROM metabib.title_field_entry
15094             UNION ALL
15095         SELECT * FROM metabib.subject_field_entry
15096             UNION ALL
15097         SELECT * FROM metabib.series_field_entry
15098         )x
15099     WHERE x.index_vector = '';
15100         
15101 DELETE FROM metabib.author_field_entry WHERE index_vector = '';
15102 DELETE FROM metabib.keyword_field_entry WHERE index_vector = '';
15103 DELETE FROM metabib.identifier_field_entry WHERE index_vector = '';
15104 DELETE FROM metabib.title_field_entry WHERE index_vector = '';
15105 DELETE FROM metabib.subject_field_entry WHERE index_vector = '';
15106 DELETE FROM metabib.series_field_entry WHERE index_vector = '';
15107
15108 CREATE INDEX metabib_facet_entry_field_idx ON metabib.facet_entry (field);
15109 CREATE INDEX metabib_facet_entry_value_idx ON metabib.facet_entry (SUBSTRING(value,1,1024));
15110 CREATE INDEX metabib_facet_entry_source_idx ON metabib.facet_entry (source);
15111
15112 -- copy OPAC visibility materialized view
15113 CREATE OR REPLACE FUNCTION asset.refresh_opac_visible_copies_mat_view () RETURNS VOID AS $$
15114
15115     TRUNCATE TABLE asset.opac_visible_copies;
15116
15117     INSERT INTO asset.opac_visible_copies (id, circ_lib, record)
15118     SELECT  cp.id, cp.circ_lib, cn.record
15119     FROM  asset.copy cp
15120         JOIN asset.call_number cn ON (cn.id = cp.call_number)
15121         JOIN actor.org_unit a ON (cp.circ_lib = a.id)
15122         JOIN asset.copy_location cl ON (cp.location = cl.id)
15123         JOIN config.copy_status cs ON (cp.status = cs.id)
15124         JOIN biblio.record_entry b ON (cn.record = b.id)
15125     WHERE NOT cp.deleted
15126         AND NOT cn.deleted
15127         AND NOT b.deleted
15128         AND cs.opac_visible
15129         AND cl.opac_visible
15130         AND cp.opac_visible
15131         AND a.opac_visible;
15132
15133 $$ LANGUAGE SQL;
15134 COMMENT ON FUNCTION asset.refresh_opac_visible_copies_mat_view() IS $$
15135 Rebuild the copy OPAC visibility cache.  Useful during migrations.
15136 $$;
15137
15138 -- and actually populate the table
15139 SELECT asset.refresh_opac_visible_copies_mat_view();
15140
15141 CREATE OR REPLACE FUNCTION asset.cache_copy_visibility () RETURNS TRIGGER as $func$
15142 DECLARE
15143     add_query       TEXT;
15144     remove_query    TEXT;
15145     do_add          BOOLEAN := false;
15146     do_remove       BOOLEAN := false;
15147 BEGIN
15148     add_query := $$
15149             INSERT INTO asset.opac_visible_copies (id, circ_lib, record)
15150                 SELECT  cp.id, cp.circ_lib, cn.record
15151                   FROM  asset.copy cp
15152                         JOIN asset.call_number cn ON (cn.id = cp.call_number)
15153                         JOIN actor.org_unit a ON (cp.circ_lib = a.id)
15154                         JOIN asset.copy_location cl ON (cp.location = cl.id)
15155                         JOIN config.copy_status cs ON (cp.status = cs.id)
15156                         JOIN biblio.record_entry b ON (cn.record = b.id)
15157                   WHERE NOT cp.deleted
15158                         AND NOT cn.deleted
15159                         AND NOT b.deleted
15160                         AND cs.opac_visible
15161                         AND cl.opac_visible
15162                         AND cp.opac_visible
15163                         AND a.opac_visible
15164     $$;
15165  
15166     remove_query := $$ DELETE FROM asset.opac_visible_copies WHERE id IN ( SELECT id FROM asset.copy WHERE $$;
15167
15168     IF TG_OP = 'INSERT' THEN
15169
15170         IF TG_TABLE_NAME IN ('copy', 'unit') THEN
15171             add_query := add_query || 'AND cp.id = ' || NEW.id || ';';
15172             EXECUTE add_query;
15173         END IF;
15174
15175         RETURN NEW;
15176
15177     END IF;
15178
15179     -- handle items first, since with circulation activity
15180     -- their statuses change frequently
15181     IF TG_TABLE_NAME IN ('copy', 'unit') THEN
15182
15183         IF OLD.location    <> NEW.location OR
15184            OLD.call_number <> NEW.call_number OR
15185            OLD.status      <> NEW.status OR
15186            OLD.circ_lib    <> NEW.circ_lib THEN
15187             -- any of these could change visibility, but
15188             -- we'll save some queries and not try to calculate
15189             -- the change directly
15190             do_remove := true;
15191             do_add := true;
15192         ELSE
15193
15194             IF OLD.deleted <> NEW.deleted THEN
15195                 IF NEW.deleted THEN
15196                     do_remove := true;
15197                 ELSE
15198                     do_add := true;
15199                 END IF;
15200             END IF;
15201
15202             IF OLD.opac_visible <> NEW.opac_visible THEN
15203                 IF OLD.opac_visible THEN
15204                     do_remove := true;
15205                 ELSIF NOT do_remove THEN -- handle edge case where deleted item
15206                                         -- is also marked opac_visible
15207                     do_add := true;
15208                 END IF;
15209             END IF;
15210
15211         END IF;
15212
15213         IF do_remove THEN
15214             DELETE FROM asset.opac_visible_copies WHERE id = NEW.id;
15215         END IF;
15216         IF do_add THEN
15217             add_query := add_query || 'AND cp.id = ' || NEW.id || ';';
15218             EXECUTE add_query;
15219         END IF;
15220
15221         RETURN NEW;
15222
15223     END IF;
15224
15225     IF TG_TABLE_NAME IN ('call_number', 'record_entry') THEN -- these have a 'deleted' column
15226  
15227         IF OLD.deleted AND NEW.deleted THEN -- do nothing
15228
15229             RETURN NEW;
15230  
15231         ELSIF NEW.deleted THEN -- remove rows
15232  
15233             IF TG_TABLE_NAME = 'call_number' THEN
15234                 DELETE FROM asset.opac_visible_copies WHERE id IN (SELECT id FROM asset.copy WHERE call_number = NEW.id);
15235             ELSIF TG_TABLE_NAME = 'record_entry' THEN
15236                 DELETE FROM asset.opac_visible_copies WHERE record = NEW.id;
15237             END IF;
15238  
15239             RETURN NEW;
15240  
15241         ELSIF OLD.deleted THEN -- add rows
15242  
15243             IF TG_TABLE_NAME IN ('copy','unit') THEN
15244                 add_query := add_query || 'AND cp.id = ' || NEW.id || ';';
15245             ELSIF TG_TABLE_NAME = 'call_number' THEN
15246                 add_query := add_query || 'AND cp.call_number = ' || NEW.id || ';';
15247             ELSIF TG_TABLE_NAME = 'record_entry' THEN
15248                 add_query := add_query || 'AND cn.record = ' || NEW.id || ';';
15249             END IF;
15250  
15251             EXECUTE add_query;
15252             RETURN NEW;
15253  
15254         END IF;
15255  
15256     END IF;
15257
15258     IF TG_TABLE_NAME = 'call_number' THEN
15259
15260         IF OLD.record <> NEW.record THEN
15261             -- call number is linked to different bib
15262             remove_query := remove_query || 'call_number = ' || NEW.id || ');';
15263             EXECUTE remove_query;
15264             add_query := add_query || 'AND cp.call_number = ' || NEW.id || ';';
15265             EXECUTE add_query;
15266         END IF;
15267
15268         RETURN NEW;
15269
15270     END IF;
15271
15272     IF TG_TABLE_NAME IN ('record_entry') THEN
15273         RETURN NEW; -- don't have 'opac_visible'
15274     END IF;
15275
15276     -- actor.org_unit, asset.copy_location, asset.copy_status
15277     IF NEW.opac_visible = OLD.opac_visible THEN -- do nothing
15278
15279         RETURN NEW;
15280
15281     ELSIF NEW.opac_visible THEN -- add rows
15282
15283         IF TG_TABLE_NAME = 'org_unit' THEN
15284             add_query := add_query || 'AND cp.circ_lib = ' || NEW.id || ';';
15285         ELSIF TG_TABLE_NAME = 'copy_location' THEN
15286             add_query := add_query || 'AND cp.location = ' || NEW.id || ';';
15287         ELSIF TG_TABLE_NAME = 'copy_status' THEN
15288             add_query := add_query || 'AND cp.status = ' || NEW.id || ';';
15289         END IF;
15290  
15291         EXECUTE add_query;
15292  
15293     ELSE -- delete rows
15294
15295         IF TG_TABLE_NAME = 'org_unit' THEN
15296             remove_query := 'DELETE FROM asset.opac_visible_copies WHERE circ_lib = ' || NEW.id || ';';
15297         ELSIF TG_TABLE_NAME = 'copy_location' THEN
15298             remove_query := remove_query || 'location = ' || NEW.id || ');';
15299         ELSIF TG_TABLE_NAME = 'copy_status' THEN
15300             remove_query := remove_query || 'status = ' || NEW.id || ');';
15301         END IF;
15302  
15303         EXECUTE remove_query;
15304  
15305     END IF;
15306  
15307     RETURN NEW;
15308 END;
15309 $func$ LANGUAGE PLPGSQL;
15310 COMMENT ON FUNCTION asset.cache_copy_visibility() IS $$
15311 Trigger function to update the copy OPAC visiblity cache.
15312 $$;
15313 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();
15314 CREATE TRIGGER a_opac_vis_mat_view_tgr AFTER INSERT OR UPDATE ON asset.copy FOR EACH ROW EXECUTE PROCEDURE asset.cache_copy_visibility();
15315 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();
15316 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();
15317 CREATE TRIGGER a_opac_vis_mat_view_tgr AFTER INSERT OR UPDATE ON serial.unit FOR EACH ROW EXECUTE PROCEDURE asset.cache_copy_visibility();
15318 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();
15319 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();
15320
15321 -- must create this rule explicitly; it is not inherited from asset.copy
15322 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;
15323
15324 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);
15325
15326 CREATE OR REPLACE FUNCTION authority.merge_records ( target_record BIGINT, source_record BIGINT ) RETURNS INT AS $func$
15327 DECLARE
15328     moved_objects INT := 0;
15329     bib_id        INT := 0;
15330     bib_rec       biblio.record_entry%ROWTYPE;
15331     auth_link     authority.bib_linking%ROWTYPE;
15332 BEGIN
15333
15334     -- 1. Make source_record MARC a copy of the target_record to get auto-sync in linked bib records
15335     UPDATE authority.record_entry
15336       SET marc = (
15337         SELECT marc
15338           FROM authority.record_entry
15339           WHERE id = target_record
15340       )
15341       WHERE id = source_record;
15342
15343     -- 2. Update all bib records with the ID from target_record in their $0
15344     FOR bib_rec IN SELECT bre.* FROM biblio.record_entry bre 
15345       INNER JOIN authority.bib_linking abl ON abl.bib = bre.id
15346       WHERE abl.authority = target_record LOOP
15347
15348         UPDATE biblio.record_entry
15349           SET marc = REGEXP_REPLACE(marc, 
15350             E'(<subfield\\s+code="0"\\s*>[^<]*?\\))' || source_record || '<',
15351             E'\\1' || target_record || '<', 'g')
15352           WHERE id = bib_rec.id;
15353
15354           moved_objects := moved_objects + 1;
15355     END LOOP;
15356
15357     -- 3. "Delete" source_record
15358     DELETE FROM authority.record_entry
15359       WHERE id = source_record;
15360
15361     RETURN moved_objects;
15362 END;
15363 $func$ LANGUAGE plpgsql;
15364
15365 -- serial.record_entry already had an owner column spelled "owning_lib"
15366 -- Adjust the table and affected functions accordingly
15367
15368 ALTER TABLE serial.record_entry DROP COLUMN owner;
15369
15370 CREATE TABLE actor.usr_saved_search (
15371     id              SERIAL          PRIMARY KEY,
15372         owner           INT             NOT NULL REFERENCES actor.usr (id)
15373                                         ON DELETE CASCADE
15374                                         DEFERRABLE INITIALLY DEFERRED,
15375         name            TEXT            NOT NULL,
15376         create_date     TIMESTAMPTZ     NOT NULL DEFAULT now(),
15377         query_text      TEXT            NOT NULL,
15378         query_type      TEXT            NOT NULL
15379                                         CONSTRAINT valid_query_text CHECK (
15380                                         query_type IN ( 'URL' )) DEFAULT 'URL',
15381                                         -- we may add other types someday
15382         target          TEXT            NOT NULL
15383                                         CONSTRAINT valid_target CHECK (
15384                                         target IN ( 'record', 'metarecord', 'callnumber' )),
15385         CONSTRAINT name_once_per_user UNIQUE (owner, name)
15386 );
15387
15388 -- Apply Dan Wells' changes to the serial schema, from the
15389 -- seials-integration branch
15390
15391 CREATE TABLE serial.subscription_note (
15392         id           SERIAL PRIMARY KEY,
15393         subscription INT    NOT NULL
15394                             REFERENCES serial.subscription (id)
15395                             ON DELETE CASCADE
15396                             DEFERRABLE INITIALLY DEFERRED,
15397         creator      INT    NOT NULL
15398                             REFERENCES actor.usr (id)
15399                             DEFERRABLE INITIALLY DEFERRED,
15400         create_date  TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
15401         pub          BOOL   NOT NULL DEFAULT FALSE,
15402         title        TEXT   NOT NULL,
15403         value        TEXT   NOT NULL
15404 );
15405
15406 CREATE TABLE serial.distribution_note (
15407         id           SERIAL PRIMARY KEY,
15408         distribution INT    NOT NULL
15409                             REFERENCES serial.distribution (id)
15410                             ON DELETE CASCADE
15411                             DEFERRABLE INITIALLY DEFERRED,
15412         creator      INT    NOT NULL
15413                             REFERENCES actor.usr (id)
15414                             DEFERRABLE INITIALLY DEFERRED,
15415         create_date  TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
15416         pub          BOOL   NOT NULL DEFAULT FALSE,
15417         title        TEXT   NOT NULL,
15418         value        TEXT   NOT NULL
15419 );
15420
15421 ------- Begin surgery on serial.unit
15422
15423 ALTER TABLE serial.unit
15424         DROP COLUMN label;
15425
15426 ALTER TABLE serial.unit
15427         RENAME COLUMN label_sort_key TO sort_key;
15428
15429 ALTER TABLE serial.unit
15430         RENAME COLUMN contents TO detailed_contents;
15431
15432 ALTER TABLE serial.unit
15433         ADD COLUMN summary_contents TEXT;
15434
15435 UPDATE serial.unit
15436 SET summary_contents = detailed_contents;
15437
15438 ALTER TABLE serial.unit
15439         ALTER column summary_contents SET NOT NULL;
15440
15441 ------- End surgery on serial.unit
15442
15443 -- 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' );
15444
15445 -- Now rebuild the constraints dropped via cascade.
15446 -- ALTER TABLE acq.provider    ADD CONSTRAINT provider_edi_default_fkey FOREIGN KEY (edi_default) REFERENCES acq.edi_account (id) DEFERRABLE INITIALLY DEFERRED;
15447 DROP INDEX IF EXISTS money.money_mat_summary_id_idx;
15448 ALTER TABLE money.materialized_billable_xact_summary ADD PRIMARY KEY (id);
15449
15450 -- ALTER TABLE staging.billing_address_stage ADD PRIMARY KEY (row_id);
15451
15452 DELETE FROM config.metabib_field_index_norm_map
15453     WHERE norm IN (
15454         SELECT id 
15455             FROM config.index_normalizer
15456             WHERE func IN ('first_word', 'naco_normalize', 'split_date_range')
15457     )
15458     AND field = 18
15459 ;
15460
15461 -- We won't necessarily use all of these, but they are here for completeness.
15462 -- Source is the EDI spec 6063 codelist, eg: http://www.stylusstudio.com/edifact/D04B/6063.htm
15463 -- Values are the EDI code value + 1200
15464
15465 INSERT INTO acq.cancel_reason (org_unit, keep_debits, id, label, description) VALUES 
15466 (1, 't', 1201, 'Discrete quantity', 'Individually separated and distinct quantity.'),
15467 (1, 't', 1202, 'Charge', 'Quantity relevant for charge.'),
15468 (1, 't', 1203, 'Cumulative quantity', 'Quantity accumulated.'),
15469 (1, 't', 1204, 'Interest for overdrawn account', 'Interest for overdrawing the account.'),
15470 (1, 't', 1205, 'Active ingredient dose per unit', 'The dosage of active ingredient per unit.'),
15471 (1, 't', 1206, 'Auditor', 'The number of entities that audit accounts.'),
15472 (1, 't', 1207, 'Branch locations, leased', 'The number of branch locations being leased by an entity.'),
15473 (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.'),
15474 (1, 't', 1209, 'Branch locations, owned', 'The number of branch locations owned by an entity.'),
15475 (1, 't', 1210, 'Judgements registered', 'The number of judgements registered against an entity.'),
15476 (1, 't', 1211, 'Split quantity', 'Part of the whole quantity.'),
15477 (1, 't', 1212, 'Despatch quantity', 'Quantity despatched by the seller.'),
15478 (1, 't', 1213, 'Liens registered', 'The number of liens registered against an entity.'),
15479 (1, 't', 1214, 'Livestock', 'The number of animals kept for use or profit.'),
15480 (1, 't', 1215, 'Insufficient funds returned cheques', 'The number of cheques returned due to insufficient funds.'),
15481 (1, 't', 1216, 'Stolen cheques', 'The number of stolen cheques.'),
15482 (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.'),
15483 (1, 't', 1218, 'Previous quantity', 'Quantity previously referenced.'),
15484 (1, 't', 1219, 'Paid-in security shares', 'The number of security shares issued and for which full payment has been made.'),
15485 (1, 't', 1220, 'Unusable quantity', 'Quantity not usable.'),
15486 (1, 't', 1221, 'Ordered quantity', '[6024] The quantity which has been ordered.'),
15487 (1, 't', 1222, 'Quantity at 100%', 'Equivalent quantity at 100% purity.'),
15488 (1, 't', 1223, 'Active ingredient', 'Quantity at 100% active agent content.'),
15489 (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.'),
15490 (1, 't', 1225, 'Retail sales', 'Quantity of retail point of sale activity.'),
15491 (1, 't', 1226, 'Promotion quantity', 'A quantity associated with a promotional event.'),
15492 (1, 't', 1227, 'On hold for shipment', 'Article received which cannot be shipped in its present form.'),
15493 (1, 't', 1228, 'Military sales quantity', 'Quantity of goods or services sold to a military organization.'),
15494 (1, 't', 1229, 'On premises sales',  'Sale of product in restaurants or bars.'),
15495 (1, 't', 1230, 'Off premises sales', 'Sale of product directly to a store.'),
15496 (1, 't', 1231, 'Estimated annual volume', 'Volume estimated for a year.'),
15497 (1, 't', 1232, 'Minimum delivery batch', 'Minimum quantity of goods delivered at one time.'),
15498 (1, 't', 1233, 'Maximum delivery batch', 'Maximum quantity of goods delivered at one time.'),
15499 (1, 't', 1234, 'Pipes', 'The number of tubes used to convey a substance.'),
15500 (1, 't', 1235, 'Price break from', 'The minimum quantity of a quantity range for a specified (unit) price.'),
15501 (1, 't', 1236, 'Price break to', 'Maximum quantity to which the price break applies.'),
15502 (1, 't', 1237, 'Poultry', 'The number of domestic fowl.'),
15503 (1, 't', 1238, 'Secured charges registered', 'The number of secured charges registered against an entity.'),
15504 (1, 't', 1239, 'Total properties owned', 'The total number of properties owned by an entity.'),
15505 (1, 't', 1240, 'Normal delivery', 'Quantity normally delivered by the seller.'),
15506 (1, 't', 1241, 'Sales quantity not included in the replenishment', 'calculation Sales which will not be included in the calculation of replenishment requirements.'),
15507 (1, 't', 1242, 'Maximum supply quantity, supplier endorsed', 'Maximum supply quantity endorsed by a supplier.'),
15508 (1, 't', 1243, 'Buyer', 'The number of buyers.'),
15509 (1, 't', 1244, 'Debenture bond', 'The number of fixed-interest bonds of an entity backed by general credit rather than specified assets.'),
15510 (1, 't', 1245, 'Debentures filed against directors', 'The number of notices of indebtedness filed against an entity''s directors.'),
15511 (1, 't', 1246, 'Pieces delivered', 'Number of pieces actually received at the final destination.'),
15512 (1, 't', 1247, 'Invoiced quantity', 'The quantity as per invoice.'),
15513 (1, 't', 1248, 'Received quantity', 'The quantity which has been received.'),
15514 (1, 't', 1249, 'Chargeable distance', '[6110] The distance between two points for which a specific tariff applies.'),
15515 (1, 't', 1250, 'Disposition undetermined quantity', 'Product quantity that has not yet had its disposition determined.'),
15516 (1, 't', 1251, 'Inventory category transfer', 'Inventory that has been moved from one inventory category to another.'),
15517 (1, 't', 1252, 'Quantity per pack', 'Quantity for each pack.'),
15518 (1, 't', 1253, 'Minimum order quantity', 'Minimum quantity of goods for an order.'),
15519 (1, 't', 1254, 'Maximum order quantity', 'Maximum quantity of goods for an order.'),
15520 (1, 't', 1255, 'Total sales', 'The summation of total quantity sales.'),
15521 (1, 't', 1256, 'Wholesaler to wholesaler sales', 'Sale of product to other wholesalers by a wholesaler.'),
15522 (1, 't', 1257, 'In transit quantity', 'A quantity that is en route.'),
15523 (1, 't', 1258, 'Quantity withdrawn', 'Quantity withdrawn from a location.'),
15524 (1, 't', 1259, 'Numbers of consumer units in the traded unit', 'Number of units for consumer sales in a unit for trading.'),
15525 (1, 't', 1260, 'Current inventory quantity available for shipment', 'Current inventory quantity available for shipment.'),
15526 (1, 't', 1261, 'Return quantity', 'Quantity of goods returned.'),
15527 (1, 't', 1262, 'Sorted quantity', 'The quantity that is sorted.'),
15528 (1, 'f', 1263, 'Sorted quantity rejected', 'The sorted quantity that is rejected.'),
15529 (1, 't', 1264, 'Scrap quantity', 'Remainder of the total quantity after split deliveries.'),
15530 (1, 'f', 1265, 'Destroyed quantity', 'Quantity of goods destroyed.'),
15531 (1, 't', 1266, 'Committed quantity', 'Quantity a party is committed to.'),
15532 (1, 't', 1267, 'Estimated reading quantity', 'The value that is estimated to be the reading of a measuring device (e.g. meter).'),
15533 (1, 't', 1268, 'End quantity', 'The quantity recorded at the end of an agreement or period.'),
15534 (1, 't', 1269, 'Start quantity', 'The quantity recorded at the start of an agreement or period.'),
15535 (1, 't', 1270, 'Cumulative quantity received', 'Cumulative quantity of all deliveries of this article received by the buyer.'),
15536 (1, 't', 1271, 'Cumulative quantity ordered', 'Cumulative quantity of all deliveries, outstanding and scheduled orders.'),
15537 (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.'),
15538 (1, 't', 1273, 'Outstanding quantity', 'Difference between quantity ordered and quantity received.'),
15539 (1, 't', 1274, 'Latest cumulative quantity', 'Cumulative quantity after complete delivery of all scheduled quantities of the product.'),
15540 (1, 't', 1275, 'Previous highest cumulative quantity', 'Cumulative quantity after complete delivery of all scheduled quantities of the product from a prior schedule period.'),
15541 (1, 't', 1276, 'Adjusted corrector reading', 'A corrector reading after it has been adjusted.'),
15542 (1, 't', 1277, 'Work days', 'Number of work days, e.g. per respective period.'),
15543 (1, 't', 1278, 'Cumulative quantity scheduled', 'Adding the quantity actually scheduled to previous cumulative quantity.'),
15544 (1, 't', 1279, 'Previous cumulative quantity', 'Cumulative quantity prior the actual order.'),
15545 (1, 't', 1280, 'Unadjusted corrector reading', 'A corrector reading before it has been adjusted.'),
15546 (1, 't', 1281, 'Extra unplanned delivery', 'Non scheduled additional quantity.'),
15547 (1, 't', 1282, 'Quantity requirement for sample inspection', 'Required quantity for sample inspection.'),
15548 (1, 't', 1283, 'Backorder quantity', 'The quantity of goods that is on back-order.'),
15549 (1, 't', 1284, 'Urgent delivery quantity', 'Quantity for urgent delivery.'),
15550 (1, 'f', 1285, 'Previous order quantity to be cancelled', 'Quantity ordered previously to be cancelled.'),
15551 (1, 't', 1286, 'Normal reading quantity', 'The value recorded or read from a measuring device (e.g. meter) in the normal conditions.'),
15552 (1, 't', 1287, 'Customer reading quantity', 'The value recorded or read from a measuring device (e.g. meter) by the customer.'),
15553 (1, 't', 1288, 'Information reading quantity', 'The value recorded or read from a measuring device (e.g. meter) for information purposes.'),
15554 (1, 't', 1289, 'Quality control held', 'Quantity of goods held pending completion of a quality control assessment.'),
15555 (1, 't', 1290, 'As is quantity', 'Quantity as it is in the existing circumstances.'),
15556 (1, 't', 1291, 'Open quantity', 'Quantity remaining after partial delivery.'),
15557 (1, 't', 1292, 'Final delivery quantity', 'Quantity of final delivery to a respective order.'),
15558 (1, 't', 1293, 'Subsequent delivery quantity', 'Quantity delivered to a respective order after it''s final delivery.'),
15559 (1, 't', 1294, 'Substitutional quantity', 'Quantity delivered replacing previous deliveries.'),
15560 (1, 't', 1295, 'Redelivery after post processing', 'Quantity redelivered after post processing.'),
15561 (1, 'f', 1296, 'Quality control failed', 'Quantity of goods which have failed quality control.'),
15562 (1, 't', 1297, 'Minimum inventory', 'Minimum stock quantity on which replenishment is based.'),
15563 (1, 't', 1298, 'Maximum inventory', 'Maximum stock quantity on which replenishment is based.'),
15564 (1, 't', 1299, 'Estimated quantity', 'Quantity estimated.'),
15565 (1, 't', 1300, 'Chargeable weight', 'The weight on which charges are based.'),
15566 (1, 't', 1301, 'Chargeable gross weight', 'The gross weight on which charges are based.'),
15567 (1, 't', 1302, 'Chargeable tare weight', 'The tare weight on which charges are based.'),
15568 (1, 't', 1303, 'Chargeable number of axles', 'The number of axles on which charges are based.'),
15569 (1, 't', 1304, 'Chargeable number of containers', 'The number of containers on which charges are based.'),
15570 (1, 't', 1305, 'Chargeable number of rail wagons', 'The number of rail wagons on which charges are based.'),
15571 (1, 't', 1306, 'Chargeable number of packages', 'The number of packages on which charges are based.'),
15572 (1, 't', 1307, 'Chargeable number of units', 'The number of units on which charges are based.'),
15573 (1, 't', 1308, 'Chargeable period', 'The period of time on which charges are based.'),
15574 (1, 't', 1309, 'Chargeable volume', 'The volume on which charges are based.'),
15575 (1, 't', 1310, 'Chargeable cubic measurements', 'The cubic measurements on which charges are based.'),
15576 (1, 't', 1311, 'Chargeable surface', 'The surface area on which charges are based.'),
15577 (1, 't', 1312, 'Chargeable length', 'The length on which charges are based.'),
15578 (1, 't', 1313, 'Quantity to be delivered', 'The quantity to be delivered.'),
15579 (1, 't', 1314, 'Number of passengers', 'Total number of passengers on the conveyance.'),
15580 (1, 't', 1315, 'Number of crew', 'Total number of crew members on the conveyance.'),
15581 (1, 't', 1316, 'Number of transport documents', 'Total number of air waybills, bills of lading, etc. being reported for a specific conveyance.'),
15582 (1, 't', 1317, 'Quantity landed', 'Quantity of goods actually arrived.'),
15583 (1, 't', 1318, 'Quantity manifested', 'Quantity of goods contracted for delivery by the carrier.'),
15584 (1, 't', 1319, 'Short shipped', 'Indication that part of the consignment was not shipped.'),
15585 (1, 't', 1320, 'Split shipment', 'Indication that the consignment has been split into two or more shipments.'),
15586 (1, 't', 1321, 'Over shipped', 'The quantity of goods shipped that exceeds the quantity contracted.'),
15587 (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.'),
15588 (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.'),
15589 (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.'),
15590 (1, 'f', 1325, 'Pilferage goods', 'Quantity of goods stolen during transport.'),
15591 (1, 'f', 1326, 'Lost goods', 'Quantity of goods that disappeared in transport.'),
15592 (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.'),
15593 (1, 't', 1328, 'Quantity loaded', 'Quantity of goods loaded onto a means of transport.'),
15594 (1, 't', 1329, 'Units per unit price', 'Number of units per unit price.'),
15595 (1, 't', 1330, 'Allowance', 'Quantity relevant for allowance.'),
15596 (1, 't', 1331, 'Delivery quantity', 'Quantity required by buyer to be delivered.'),
15597 (1, 't', 1332, 'Cumulative quantity, preceding period, planned', 'Cumulative quantity originally planned for the preceding period.'),
15598 (1, 't', 1333, 'Cumulative quantity, preceding period, reached', 'Cumulative quantity reached in the preceding period.'),
15599 (1, 't', 1334, 'Cumulative quantity, actual planned',            'Cumulative quantity planned for now.'),
15600 (1, 't', 1335, 'Period quantity, planned', 'Quantity planned for this period.'),
15601 (1, 't', 1336, 'Period quantity, reached', 'Quantity reached during this period.'),
15602 (1, 't', 1337, 'Cumulative quantity, preceding period, estimated', 'Estimated cumulative quantity reached in the preceding period.'),
15603 (1, 't', 1338, 'Cumulative quantity, actual estimated',            'Estimated cumulative quantity reached now.'),
15604 (1, 't', 1339, 'Cumulative quantity, preceding period, measured', 'Surveyed cumulative quantity reached in the preceding period.'),
15605 (1, 't', 1340, 'Cumulative quantity, actual measured', 'Surveyed cumulative quantity reached now.'),
15606 (1, 't', 1341, 'Period quantity, measured',            'Surveyed quantity reached during this period.'),
15607 (1, 't', 1342, 'Total quantity, planned', 'Total quantity planned.'),
15608 (1, 't', 1343, 'Quantity, remaining', 'Quantity remaining.'),
15609 (1, 't', 1344, 'Tolerance', 'Plus or minus tolerance expressed as a monetary amount.'),
15610 (1, 't', 1345, 'Actual stock',          'The stock on hand, undamaged, and available for despatch, sale or use.'),
15611 (1, 't', 1346, 'Model or target stock', 'The stock quantity required or planned to have on hand, undamaged and available for use.'),
15612 (1, 't', 1347, 'Direct shipment quantity', 'Quantity to be shipped directly to a customer from a manufacturing site.'),
15613 (1, 't', 1348, 'Amortization total quantity',     'Indication of final quantity for amortization.'),
15614 (1, 't', 1349, 'Amortization order quantity',     'Indication of actual share of the order quantity for amortization.'),
15615 (1, 't', 1350, 'Amortization cumulated quantity', 'Indication of actual cumulated quantity of previous and actual amortization order quantity.'),
15616 (1, 't', 1351, 'Quantity advised',  'Quantity advised by supplier or shipper, in contrast to quantity actually received.'),
15617 (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.'),
15618 (1, 't', 1353, 'Statistical sales quantity', 'Quantity of goods sold in a specified period.'),
15619 (1, 't', 1354, 'Sales quantity planned',     'Quantity of goods required to meet future demands. - Market intelligence quantity.'),
15620 (1, 't', 1355, 'Replenishment quantity',     'Quantity required to maintain the requisite on-hand stock of goods.'),
15621 (1, 't', 1356, 'Inventory movement quantity', 'To specify the quantity of an inventory movement.'),
15622 (1, 't', 1357, 'Opening stock balance quantity', 'To specify the quantity of an opening stock balance.'),
15623 (1, 't', 1358, 'Closing stock balance quantity', 'To specify the quantity of a closing stock balance.'),
15624 (1, 't', 1359, 'Number of stops', 'Number of times a means of transport stops before arriving at destination.'),
15625 (1, 't', 1360, 'Minimum production batch', 'The quantity specified is the minimum output from a single production run.'),
15626 (1, 't', 1361, 'Dimensional sample quantity', 'The quantity defined is a sample for the purpose of validating dimensions.'),
15627 (1, 't', 1362, 'Functional sample quantity', 'The quantity defined is a sample for the purpose of validating function and performance.'),
15628 (1, 't', 1363, 'Pre-production quantity', 'Quantity of the referenced item required prior to full production.'),
15629 (1, 't', 1364, 'Delivery batch', 'Quantity of the referenced item which constitutes a standard batch for deliver purposes.'),
15630 (1, 't', 1365, 'Delivery batch multiple', 'The multiples in which delivery batches can be supplied.'),
15631 (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.'),
15632 (1, 't', 1367, 'Total delivery quantity',  'The total quantity required by the buyer to be delivered.'),
15633 (1, 't', 1368, 'Single delivery quantity', 'The quantity required by the buyer to be delivered in a single shipment.'),
15634 (1, 't', 1369, 'Supplied quantity',  'Quantity of the referenced item actually shipped.'),
15635 (1, 't', 1370, 'Allocated quantity', 'Quantity of the referenced item allocated from available stock for delivery.'),
15636 (1, 't', 1371, 'Maximum stackability', 'The number of pallets/handling units which can be safely stacked one on top of another.'),
15637 (1, 't', 1372, 'Amortisation quantity', 'The quantity of the referenced item which has a cost for tooling amortisation included in the item price.'),
15638 (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.'),
15639 (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.'),
15640 (1, 't', 1375, 'Number of moulds', 'The number of pressing moulds contained within a single piece of the referenced tooling.'),
15641 (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.'),
15642 (1, 't', 1377, 'Periodic capacity of tooling', 'Maximum production output of the referenced tool over a period of time.'),
15643 (1, 't', 1378, 'Lifetime capacity of tooling', 'Maximum production output of the referenced tool over its productive lifetime.'),
15644 (1, 't', 1379, 'Number of deliveries per despatch period', 'The number of deliveries normally expected to be despatched within each despatch period.'),
15645 (1, 't', 1380, 'Provided quantity', 'The quantity of a referenced component supplied by the buyer for manufacturing of an ordered item.'),
15646 (1, 't', 1381, 'Maximum production batch', 'The quantity specified is the maximum output from a single production run.'),
15647 (1, 'f', 1382, 'Cancelled quantity', 'Quantity of the referenced item which has previously been ordered and is now cancelled.'),
15648 (1, 't', 1383, 'No delivery requirement in this instruction', 'This delivery instruction does not contain any delivery requirements.'),
15649 (1, 't', 1384, 'Quantity of material in ordered time', 'Quantity of the referenced material within the ordered time.'),
15650 (1, 'f', 1385, 'Rejected quantity', 'The quantity of received goods rejected for quantity reasons.'),
15651 (1, 't', 1386, 'Cumulative quantity scheduled up to accumulation start date', 'The cumulative quantity scheduled up to the accumulation start date.'),
15652 (1, 't', 1387, 'Quantity scheduled', 'The quantity scheduled for delivery.'),
15653 (1, 't', 1388, 'Number of identical handling units', 'Number of identical handling units in terms of type and contents.'),
15654 (1, 't', 1389, 'Number of packages in handling unit', 'The number of packages contained in one handling unit.'),
15655 (1, 't', 1390, 'Despatch note quantity', 'The item quantity specified on the despatch note.'),
15656 (1, 't', 1391, 'Adjustment to inventory quantity', 'An adjustment to inventory quantity.'),
15657 (1, 't', 1392, 'Free goods quantity',    'Quantity of goods which are free of charge.'),
15658 (1, 't', 1393, 'Free quantity included', 'Quantity included to which no charge is applicable.'),
15659 (1, 't', 1394, 'Received and accepted',  'Quantity which has been received and accepted at a given location.'),
15660 (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.'),
15661 (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.'),
15662 (1, 't', 1397, 'Reordering level', 'Quantity at which an order may be triggered to replenish.'),
15663 (1, 't', 1399, 'Inventory withdrawal quantity', 'Quantity which has been withdrawn from inventory since the last inventory report.'),
15664 (1, 't', 1400, 'Free quantity not included', 'Free quantity not included in ordered quantity.'),
15665 (1, 't', 1401, 'Recommended overhaul and repair quantity', 'To indicate the recommended quantity of an article required to support overhaul and repair activities.'),
15666 (1, 't', 1402, 'Quantity per next higher assembly', 'To indicate the quantity required for the next higher assembly.'),
15667 (1, 't', 1403, 'Quantity per unit of issue', 'Provides the standard quantity of an article in which one unit can be issued.'),
15668 (1, 't', 1404, 'Cumulative scrap quantity',  'Provides the cumulative quantity of an item which has been identified as scrapped.'),
15669 (1, 't', 1405, 'Publication turn size', 'The quantity of magazines or newspapers grouped together with the spine facing alternate directions in a bundle.'),
15670 (1, 't', 1406, 'Recommended maintenance quantity', 'Recommended quantity of an article which is required to meet an agreed level of maintenance.'),
15671 (1, 't', 1407, 'Labour hours', 'Number of labour hours.'),
15672 (1, 't', 1408, 'Quantity requirement for maintenance and repair of', 'equipment Quantity of the material needed to maintain and repair equipment.'),
15673 (1, 't', 1409, 'Additional replenishment demand quantity', 'Incremental needs over and above normal replenishment calculations, but not intended to permanently change the model parameters.'),
15674 (1, 't', 1410, 'Returned by consumer quantity', 'Quantity returned by a consumer.'),
15675 (1, 't', 1411, 'Replenishment override quantity', 'Quantity to override the normal replenishment model calculations, but not intended to permanently change the model parameters.'),
15676 (1, 't', 1412, 'Quantity sold, net', 'Net quantity sold which includes returns of saleable inventory and other adjustments.'),
15677 (1, 't', 1413, 'Transferred out quantity',   'Quantity which was transferred out of this location.'),
15678 (1, 't', 1414, 'Transferred in quantity',    'Quantity which was transferred into this location.'),
15679 (1, 't', 1415, 'Unsaleable quantity',        'Quantity of inventory received which cannot be sold in its present condition.'),
15680 (1, 't', 1416, 'Consumer reserved quantity', 'Quantity reserved for consumer delivery or pickup and not yet withdrawn from inventory.'),
15681 (1, 't', 1417, 'Out of inventory quantity',  'Quantity of inventory which was requested but was not available.'),
15682 (1, 't', 1418, 'Quantity returned, defective or damaged', 'Quantity returned in a damaged or defective condition.'),
15683 (1, 't', 1419, 'Taxable quantity',           'Quantity subject to taxation.'),
15684 (1, 't', 1420, 'Meter reading', 'The numeric value of measure units counted by a meter.'),
15685 (1, 't', 1421, 'Maximum requestable quantity', 'The maximum quantity which may be requested.'),
15686 (1, 't', 1422, 'Minimum requestable quantity', 'The minimum quantity which may be requested.'),
15687 (1, 't', 1423, 'Daily average quantity', 'The quantity for a defined period divided by the number of days of the period.'),
15688 (1, 't', 1424, 'Budgeted hours',     'The number of budgeted hours.'),
15689 (1, 't', 1425, 'Actual hours',       'The number of actual hours.'),
15690 (1, 't', 1426, 'Earned value hours', 'The number of earned value hours.'),
15691 (1, 't', 1427, 'Estimated hours',    'The number of estimated hours.'),
15692 (1, 't', 1428, 'Level resource task quantity', 'Quantity of a resource that is level for the duration of the task.'),
15693 (1, 't', 1429, 'Available resource task quantity', 'Quantity of a resource available to complete a task.'),
15694 (1, 't', 1430, 'Work time units',   'Quantity of work units of time.'),
15695 (1, 't', 1431, 'Daily work shifts', 'Quantity of work shifts per day.'),
15696 (1, 't', 1432, 'Work time units per shift', 'Work units of time per work shift.'),
15697 (1, 't', 1433, 'Work calendar units',       'Work calendar units of time.'),
15698 (1, 't', 1434, 'Elapsed duration',   'Quantity representing the elapsed duration.'),
15699 (1, 't', 1435, 'Remaining duration', 'Quantity representing the remaining duration.'),
15700 (1, 't', 1436, 'Original duration',  'Quantity representing the original duration.'),
15701 (1, 't', 1437, 'Current duration',   'Quantity representing the current duration.'),
15702 (1, 't', 1438, 'Total float time',   'Quantity representing the total float time.'),
15703 (1, 't', 1439, 'Free float time',    'Quantity representing the free float time.'),
15704 (1, 't', 1440, 'Lag time',           'Quantity representing lag time.'),
15705 (1, 't', 1441, 'Lead time',          'Quantity representing lead time.'),
15706 (1, 't', 1442, 'Number of months', 'The number of months.'),
15707 (1, 't', 1443, 'Reserved quantity customer direct delivery sales', 'Quantity of products reserved for sales delivered direct to the customer.'),
15708 (1, 't', 1444, 'Reserved quantity retail sales', 'Quantity of products reserved for retail sales.'),
15709 (1, 't', 1445, 'Consolidated discount inventory', 'A quantity of inventory supplied at consolidated discount terms.'),
15710 (1, 't', 1446, 'Returns replacement quantity',    'A quantity of goods issued as a replacement for a returned quantity.'),
15711 (1, 't', 1447, 'Additional promotion sales forecast quantity', 'A forecast of additional quantity which will be sold during a period of promotional activity.'),
15712 (1, 't', 1448, 'Reserved quantity', 'Quantity reserved for specific purposes.'),
15713 (1, 't', 1449, 'Quantity displayed not available for sale', 'Quantity displayed within a retail outlet but not available for sale.'),
15714 (1, 't', 1450, 'Inventory discrepancy', 'The difference recorded between theoretical and physical inventory.'),
15715 (1, 't', 1451, 'Incremental order quantity', 'The incremental quantity by which ordering is carried out.'),
15716 (1, 't', 1452, 'Quantity requiring manipulation before despatch', 'A quantity of goods which needs manipulation before despatch.'),
15717 (1, 't', 1453, 'Quantity in quarantine',              'A quantity of goods which are held in a restricted area for quarantine purposes.'),
15718 (1, 't', 1454, 'Quantity withheld by owner of goods', 'A quantity of goods which has been withheld by the owner of the goods.'),
15719 (1, 't', 1455, 'Quantity not available for despatch', 'A quantity of goods not available for despatch.'),
15720 (1, 't', 1456, 'Quantity awaiting delivery', 'Quantity of goods which are awaiting delivery.'),
15721 (1, 't', 1457, 'Quantity in physical inventory',      'A quantity of goods held in physical inventory.'),
15722 (1, 't', 1458, 'Quantity held by logistic service provider', 'Quantity of goods under the control of a logistic service provider.'),
15723 (1, 't', 1459, 'Optimal quantity', 'The optimal quantity for a given purpose.'),
15724 (1, 't', 1460, 'Delivery quantity balance', 'The difference between the scheduled quantity and the quantity delivered to the consignee at a given date.'),
15725 (1, 't', 1461, 'Cumulative quantity shipped', 'Cumulative quantity of all shipments.'),
15726 (1, 't', 1462, 'Quantity suspended', 'The quantity of something which is suspended.'),
15727 (1, 't', 1463, 'Control quantity', 'The quantity designated for control purposes.'),
15728 (1, 't', 1464, 'Equipment quantity', 'A count of a quantity of equipment.'),
15729 (1, 't', 1465, 'Factor', 'Number by which the measured unit has to be multiplied to calculate the units used.'),
15730 (1, 't', 1466, 'Unsold quantity held by wholesaler', 'Unsold quantity held by the wholesaler.'),
15731 (1, 't', 1467, 'Quantity held by delivery vehicle', 'Quantity of goods held by the delivery vehicle.'),
15732 (1, 't', 1468, 'Quantity held by retail outlet', 'Quantity held by the retail outlet.'),
15733 (1, 'f', 1469, 'Rejected return quantity', 'A quantity for return which has been rejected.'),
15734 (1, 't', 1470, 'Accounts', 'The number of accounts.'),
15735 (1, 't', 1471, 'Accounts placed for collection', 'The number of accounts placed for collection.'),
15736 (1, 't', 1472, 'Activity codes', 'The number of activity codes.'),
15737 (1, 't', 1473, 'Agents', 'The number of agents.'),
15738 (1, 't', 1474, 'Airline attendants', 'The number of airline attendants.'),
15739 (1, 't', 1475, 'Authorised shares',  'The number of shares authorised for issue.'),
15740 (1, 't', 1476, 'Employee average',   'The average number of employees.'),
15741 (1, 't', 1477, 'Branch locations',   'The number of branch locations.'),
15742 (1, 't', 1478, 'Capital changes',    'The number of capital changes made.'),
15743 (1, 't', 1479, 'Clerks', 'The number of clerks.'),
15744 (1, 't', 1480, 'Companies in same activity', 'The number of companies doing business in the same activity category.'),
15745 (1, 't', 1481, 'Companies included in consolidated financial statement', 'The number of companies included in a consolidated financial statement.'),
15746 (1, 't', 1482, 'Cooperative shares', 'The number of cooperative shares.'),
15747 (1, 't', 1483, 'Creditors',   'The number of creditors.'),
15748 (1, 't', 1484, 'Departments', 'The number of departments.'),
15749 (1, 't', 1485, 'Design employees', 'The number of employees involved in the design process.'),
15750 (1, 't', 1486, 'Physicians', 'The number of medical doctors.'),
15751 (1, 't', 1487, 'Domestic affiliated companies', 'The number of affiliated companies located within the country.'),
15752 (1, 't', 1488, 'Drivers', 'The number of drivers.'),
15753 (1, 't', 1489, 'Employed at location',     'The number of employees at the specified location.'),
15754 (1, 't', 1490, 'Employed by this company', 'The number of employees at the specified company.'),
15755 (1, 't', 1491, 'Total employees',    'The total number of employees.'),
15756 (1, 't', 1492, 'Employees shared',   'The number of employees shared among entities.'),
15757 (1, 't', 1493, 'Engineers',          'The number of engineers.'),
15758 (1, 't', 1494, 'Estimated accounts', 'The estimated number of accounts.'),
15759 (1, 't', 1495, 'Estimated employees at location', 'The estimated number of employees at the specified location.'),
15760 (1, 't', 1496, 'Estimated total employees',       'The total estimated number of employees.'),
15761 (1, 't', 1497, 'Executives', 'The number of executives.'),
15762 (1, 't', 1498, 'Agricultural workers',   'The number of agricultural workers.'),
15763 (1, 't', 1499, 'Financial institutions', 'The number of financial institutions.'),
15764 (1, 't', 1500, 'Floors occupied', 'The number of floors occupied.'),
15765 (1, 't', 1501, 'Foreign related entities', 'The number of related entities located outside the country.'),
15766 (1, 't', 1502, 'Group employees',    'The number of employees within the group.'),
15767 (1, 't', 1503, 'Indirect employees', 'The number of employees not associated with direct production.'),
15768 (1, 't', 1504, 'Installers',    'The number of employees involved with the installation process.'),
15769 (1, 't', 1505, 'Invoices',      'The number of invoices.'),
15770 (1, 't', 1506, 'Issued shares', 'The number of shares actually issued.'),
15771 (1, 't', 1507, 'Labourers',     'The number of labourers.'),
15772 (1, 't', 1508, 'Manufactured units', 'The number of units manufactured.'),
15773 (1, 't', 1509, 'Maximum number of employees', 'The maximum number of people employed.'),
15774 (1, 't', 1510, 'Maximum number of employees at location', 'The maximum number of people employed at a location.'),
15775 (1, 't', 1511, 'Members in group', 'The number of members within a group.'),
15776 (1, 't', 1512, 'Minimum number of employees at location', 'The minimum number of people employed at a location.'),
15777 (1, 't', 1513, 'Minimum number of employees', 'The minimum number of people employed.'),
15778 (1, 't', 1514, 'Non-union employees', 'The number of employees not belonging to a labour union.'),
15779 (1, 't', 1515, 'Floors', 'The number of floors in a building.'),
15780 (1, 't', 1516, 'Nurses', 'The number of nurses.'),
15781 (1, 't', 1517, 'Office workers', 'The number of workers in an office.'),
15782 (1, 't', 1518, 'Other employees', 'The number of employees otherwise categorised.'),
15783 (1, 't', 1519, 'Part time employees', 'The number of employees working on a part time basis.'),
15784 (1, 't', 1520, 'Accounts payable average overdue days', 'The average number of days accounts payable are overdue.'),
15785 (1, 't', 1521, 'Pilots', 'The number of pilots.'),
15786 (1, 't', 1522, 'Plant workers', 'The number of workers within a plant.'),
15787 (1, 't', 1523, 'Previous number of accounts', 'The number of accounts which preceded the current count.'),
15788 (1, 't', 1524, 'Previous number of branch locations', 'The number of branch locations which preceded the current count.'),
15789 (1, 't', 1525, 'Principals included as employees', 'The number of principals which are included in the count of employees.'),
15790 (1, 't', 1526, 'Protested bills', 'The number of bills which are protested.'),
15791 (1, 't', 1527, 'Registered brands distributed', 'The number of registered brands which are being distributed.'),
15792 (1, 't', 1528, 'Registered brands manufactured', 'The number of registered brands which are being manufactured.'),
15793 (1, 't', 1529, 'Related business entities', 'The number of related business entities.'),
15794 (1, 't', 1530, 'Relatives employed', 'The number of relatives which are counted as employees.'),
15795 (1, 't', 1531, 'Rooms',        'The number of rooms.'),
15796 (1, 't', 1532, 'Salespersons', 'The number of salespersons.'),
15797 (1, 't', 1533, 'Seats',        'The number of seats.'),
15798 (1, 't', 1534, 'Shareholders', 'The number of shareholders.'),
15799 (1, 't', 1535, 'Shares of common stock', 'The number of shares of common stock.'),
15800 (1, 't', 1536, 'Shares of preferred stock', 'The number of shares of preferred stock.'),
15801 (1, 't', 1537, 'Silent partners', 'The number of silent partners.'),
15802 (1, 't', 1538, 'Subcontractors',  'The number of subcontractors.'),
15803 (1, 't', 1539, 'Subsidiaries',    'The number of subsidiaries.'),
15804 (1, 't', 1540, 'Law suits',       'The number of law suits.'),
15805 (1, 't', 1541, 'Suppliers',       'The number of suppliers.'),
15806 (1, 't', 1542, 'Teachers',        'The number of teachers.'),
15807 (1, 't', 1543, 'Technicians',     'The number of technicians.'),
15808 (1, 't', 1544, 'Trainees',        'The number of trainees.'),
15809 (1, 't', 1545, 'Union employees', 'The number of employees who are members of a labour union.'),
15810 (1, 't', 1546, 'Number of units', 'The quantity of units.'),
15811 (1, 't', 1547, 'Warehouse employees', 'The number of employees who work in a warehouse setting.'),
15812 (1, 't', 1548, 'Shareholders holding remainder of shares', 'Number of shareholders owning the remainder of shares.'),
15813 (1, 't', 1549, 'Payment orders filed', 'Number of payment orders filed.'),
15814 (1, 't', 1550, 'Uncovered cheques', 'Number of uncovered cheques.'),
15815 (1, 't', 1551, 'Auctions', 'Number of auctions.'),
15816 (1, 't', 1552, 'Units produced', 'The number of units produced.'),
15817 (1, 't', 1553, 'Added employees', 'Number of employees that were added to the workforce.'),
15818 (1, 't', 1554, 'Number of added locations', 'Number of locations that were added.'),
15819 (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.'),
15820 (1, 't', 1556, 'Number of closed locations', 'Number of locations that were closed.'),
15821 (1, 't', 1557, 'Counter clerks', 'The number of clerks that work behind a flat-topped fitment.'),
15822 (1, 't', 1558, 'Payment experiences in the last 3 months', 'The number of payment experiences received for an entity over the last 3 months.'),
15823 (1, 't', 1559, 'Payment experiences in the last 12 months', 'The number of payment experiences received for an entity over the last 12 months.'),
15824 (1, 't', 1560, 'Total number of subsidiaries not included in the financial', 'statement The total number of subsidiaries not included in the financial statement.'),
15825 (1, 't', 1561, 'Paid-in common shares', 'The number of paid-in common shares.'),
15826 (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.'),
15827 (1, 't', 1563, 'Total number of foreign subsidiaries included in financial statement', 'The total number of foreign subsidiaries included in the financial statement.'),
15828 (1, 't', 1564, 'Total number of domestic subsidiaries included in financial statement', 'The total number of domestic subsidiaries included in the financial statement.'),
15829 (1, 't', 1565, 'Total transactions', 'The total number of transactions.'),
15830 (1, 't', 1566, 'Paid-in preferred shares', 'The number of paid-in preferred shares.'),
15831 (1, 't', 1567, 'Employees', 'Code specifying the quantity of persons working for a company, whose services are used for pay.'),
15832 (1, 't', 1568, 'Active ingredient dose per unit, dispensed', 'The dosage of active ingredient per dispensed unit.'),
15833 (1, 't', 1569, 'Budget', 'Budget quantity.'),
15834 (1, 't', 1570, 'Budget, cumulative to date', 'Budget quantity, cumulative to date.'),
15835 (1, 't', 1571, 'Actual units', 'The number of actual units.'),
15836 (1, 't', 1572, 'Actual units, cumulative to date', 'The number of cumulative to date actual units.'),
15837 (1, 't', 1573, 'Earned value', 'Earned value quantity.'),
15838 (1, 't', 1574, 'Earned value, cumulative to date', 'Earned value quantity accumulated to date.'),
15839 (1, 't', 1575, 'At completion quantity, estimated', 'The estimated quantity when a project is complete.'),
15840 (1, 't', 1576, 'To complete quantity, estimated', 'The estimated quantity required to complete a project.'),
15841 (1, 't', 1577, 'Adjusted units', 'The number of adjusted units.'),
15842 (1, 't', 1578, 'Number of limited partnership shares', 'Number of shares held in a limited partnership.'),
15843 (1, 't', 1579, 'National business failure incidences', 'Number of firms in a country that discontinued with a loss to creditors.'),
15844 (1, 't', 1580, 'Industry business failure incidences', 'Number of firms in a specific industry that discontinued with a loss to creditors.'),
15845 (1, 't', 1581, 'Business class failure incidences', 'Number of firms in a specific class that discontinued with a loss to creditors.'),
15846 (1, 't', 1582, 'Mechanics', 'Number of mechanics.'),
15847 (1, 't', 1583, 'Messengers', 'Number of messengers.'),
15848 (1, 't', 1584, 'Primary managers', 'Number of primary managers.'),
15849 (1, 't', 1585, 'Secretaries', 'Number of secretaries.'),
15850 (1, 't', 1586, 'Detrimental legal filings', 'Number of detrimental legal filings.'),
15851 (1, 't', 1587, 'Branch office locations, estimated', 'Estimated number of branch office locations.'),
15852 (1, 't', 1588, 'Previous number of employees', 'The number of employees for a previous period.'),
15853 (1, 't', 1589, 'Asset seizers', 'Number of entities that seize assets of another entity.'),
15854 (1, 't', 1590, 'Out-turned quantity', 'The quantity discharged.'),
15855 (1, 't', 1591, 'Material on-board quantity, prior to loading', 'The material in vessel tanks, void spaces, and pipelines prior to loading.'),
15856 (1, 't', 1592, 'Supplier estimated previous meter reading', 'Previous meter reading estimated by the supplier.'),
15857 (1, 't', 1593, 'Supplier estimated latest meter reading',   'Latest meter reading estimated by the supplier.'),
15858 (1, 't', 1594, 'Customer estimated previous meter reading', 'Previous meter reading estimated by the customer.'),
15859 (1, 't', 1595, 'Customer estimated latest meter reading',   'Latest meter reading estimated by the customer.'),
15860 (1, 't', 1596, 'Supplier previous meter reading',           'Previous meter reading done by the supplier.'),
15861 (1, 't', 1597, 'Supplier latest meter reading',             'Latest meter reading recorded by the supplier.'),
15862 (1, 't', 1598, 'Maximum number of purchase orders allowed', 'Maximum number of purchase orders that are allowed.'),
15863 (1, 't', 1599, 'File size before compression', 'The size of a file before compression.'),
15864 (1, 't', 1600, 'File size after compression', 'The size of a file after compression.'),
15865 (1, 't', 1601, 'Securities shares', 'Number of shares of securities.'),
15866 (1, 't', 1602, 'Patients',         'Number of patients.'),
15867 (1, 't', 1603, 'Completed projects', 'Number of completed projects.'),
15868 (1, 't', 1604, 'Promoters',        'Number of entities who finance or organize an event or a production.'),
15869 (1, 't', 1605, 'Administrators',   'Number of administrators.'),
15870 (1, 't', 1606, 'Supervisors',      'Number of supervisors.'),
15871 (1, 't', 1607, 'Professionals',    'Number of professionals.'),
15872 (1, 't', 1608, 'Debt collectors',  'Number of debt collectors.'),
15873 (1, 't', 1609, 'Inspectors',       'Number of individuals who perform inspections.'),
15874 (1, 't', 1610, 'Operators',        'Number of operators.'),
15875 (1, 't', 1611, 'Trainers',         'Number of trainers.'),
15876 (1, 't', 1612, 'Active accounts',  'Number of accounts in a current or active status.'),
15877 (1, 't', 1613, 'Trademarks used',  'Number of trademarks used.'),
15878 (1, 't', 1614, 'Machines',         'Number of machines.'),
15879 (1, 't', 1615, 'Fuel pumps',       'Number of fuel pumps.'),
15880 (1, 't', 1616, 'Tables available', 'Number of tables available for use.'),
15881 (1, 't', 1617, 'Directors',        'Number of directors.'),
15882 (1, 't', 1618, 'Freelance debt collectors', 'Number of debt collectors who work on a freelance basis.'),
15883 (1, 't', 1619, 'Freelance salespersons',    'Number of salespersons who work on a freelance basis.'),
15884 (1, 't', 1620, 'Travelling employees',      'Number of travelling employees.'),
15885 (1, 't', 1621, 'Foremen', 'Number of workers with limited supervisory responsibilities.'),
15886 (1, 't', 1622, 'Production workers', 'Number of employees engaged in production.'),
15887 (1, 't', 1623, 'Employees not including owners', 'Number of employees excluding business owners.'),
15888 (1, 't', 1624, 'Beds', 'Number of beds.'),
15889 (1, 't', 1625, 'Resting quantity', 'A quantity of product that is at rest before it can be used.'),
15890 (1, 't', 1626, 'Production requirements', 'Quantity needed to meet production requirements.'),
15891 (1, 't', 1627, 'Corrected quantity', 'The quantity has been corrected.'),
15892 (1, 't', 1628, 'Operating divisions', 'Number of divisions operating.'),
15893 (1, 't', 1629, 'Quantitative incentive scheme base', 'Quantity constituting the base for the quantitative incentive scheme.'),
15894 (1, 't', 1630, 'Petitions filed', 'Number of petitions that have been filed.'),
15895 (1, 't', 1631, 'Bankruptcy petitions filed', 'Number of bankruptcy petitions that have been filed.'),
15896 (1, 't', 1632, 'Projects in process', 'Number of projects in process.'),
15897 (1, 't', 1633, 'Changes in capital structure', 'Number of modifications made to the capital structure of an entity.'),
15898 (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.'),
15899 (1, 't', 1635, 'Number of failed businesses of directors', 'The number of failed businesses with which the directors have been associated.'),
15900 (1, 't', 1636, 'Professor', 'The number of professors.'),
15901 (1, 't', 1637, 'Seller',    'The number of sellers.'),
15902 (1, 't', 1638, 'Skilled worker', 'The number of skilled workers.'),
15903 (1, 't', 1639, 'Trademark represented', 'The number of trademarks represented.'),
15904 (1, 't', 1640, 'Number of quantitative incentive scheme units', 'Number of units allocated to a quantitative incentive scheme.'),
15905 (1, 't', 1641, 'Quantity in manufacturing process', 'Quantity currently in the manufacturing process.'),
15906 (1, 't', 1642, 'Number of units in the width of a layer', 'Number of units which make up the width of a layer.'),
15907 (1, 't', 1643, 'Number of units in the depth of a layer', 'Number of units which make up the depth of a layer.'),
15908 (1, 't', 1644, 'Return to warehouse', 'A quantity of products sent back to the warehouse.'),
15909 (1, 't', 1645, 'Return to the manufacturer', 'A quantity of products sent back from the manufacturer.'),
15910 (1, 't', 1646, 'Delta quantity', 'An increment or decrement to a quantity.'),
15911 (1, 't', 1647, 'Quantity moved between outlets', 'A quantity of products moved between outlets.'),
15912 (1, 't', 1648, 'Pre-paid invoice annual consumption, estimated', 'The estimated annual consumption used for a prepayment invoice.'),
15913 (1, 't', 1649, 'Total quoted quantity', 'The sum of quoted quantities.'),
15914 (1, 't', 1650, 'Requests pertaining to entity in last 12 months', 'Number of requests received in last 12 months pertaining to the entity.'),
15915 (1, 't', 1651, 'Total inquiry matches', 'Number of instances which correspond with the inquiry.'),
15916 (1, 't', 1652, 'En route to warehouse quantity',   'A quantity of products that is en route to a warehouse.'),
15917 (1, 't', 1653, 'En route from warehouse quantity', 'A quantity of products that is en route from a warehouse.'),
15918 (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.'),
15919 (1, 't', 1655, 'Not yet ordered quantity', 'The quantity which has not yet been ordered.'),
15920 (1, 't', 1656, 'Net reserve power', 'The reserve power available for the net.'),
15921 (1, 't', 1657, 'Maximum number of units per shelf', 'Maximum number of units of a product that can be placed on a shelf.'),
15922 (1, 't', 1658, 'Stowaway', 'Number of stowaway(s) on a conveyance.'),
15923 (1, 't', 1659, 'Tug', 'The number of tugboat(s).'),
15924 (1, 't', 1660, 'Maximum quantity capability of the package', 'Maximum quantity of a product that can be contained in a package.'),
15925 (1, 't', 1661, 'Calculated', 'The calculated quantity.'),
15926 (1, 't', 1662, 'Monthly volume, estimated', 'Volume estimated for a month.'),
15927 (1, 't', 1663, 'Total number of persons', 'Quantity representing the total number of persons.'),
15928 (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.'),
15929 (1, 't', 1665, 'Deducted tariff quantity',   'Quantity deducted from tariff quantity to reckon duty/tax/fee assessment bases.'),
15930 (1, 't', 1666, 'Advised but not arrived',    'Goods are advised by the consignor or supplier, but have not yet arrived at the destination.'),
15931 (1, 't', 1667, 'Received but not available', 'Goods have been received in the arrival area but are not yet available.'),
15932 (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.'),
15933 (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.'),
15934 (1, 't', 1670, 'Chargeable number of trailers', 'The number of trailers on which charges are based.'),
15935 (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.'),
15936 (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.'),
15937 (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.'),
15938 (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.'),
15939 (1, 't', 1675, 'Agreed maximum buying quantity', 'The agreed maximum quantity of the trade item that may be purchased.'),
15940 (1, 't', 1676, 'Agreed minimum buying quantity', 'The agreed minimum quantity of the trade item that may be purchased.'),
15941 (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.'),
15942 (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.'),
15943 (1, 't', 1679, 'Marine Diesel Oil bunkers, loaded',                  'Number of Marine Diesel Oil (MDO) bunkers taken on in the port.'),
15944 (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.'),
15945 (1, 't', 1681, 'Intermediate Fuel Oil bunkers, loaded',              'Number of Intermediate Fuel Oil (IFO) bunkers taken on in the port.'),
15946 (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.'),
15947 (1, 't', 1683, 'Bunker C bunkers, loaded', 'Number of Bunker C, or Number 6 fuel oil bunkers, taken on in the port.'),
15948 (1, 't', 1684, 'Number of individual units within the smallest packaging', 'unit Total number of individual units contained within the smallest unit of packaging.'),
15949 (1, 't', 1685, 'Percentage of constituent element', 'The part of a product or material that is composed of the constituent element, as a percentage.'),
15950 (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).'),
15951 (1, 't', 1687, 'Regulated commodity count', 'The number of regulated items.'),
15952 (1, 't', 1688, 'Number of passengers, embarking', 'The number of passengers going aboard a conveyance.'),
15953 (1, 't', 1689, 'Number of passengers, disembarking', 'The number of passengers disembarking the conveyance.'),
15954 (1, 't', 1690, 'Constituent element or component quantity', 'The specific quantity of the identified constituent element.')
15955 ;
15956 -- ZZZ, 'Mutually defined', 'As agreed by the trading partners.'),
15957
15958 CREATE TABLE acq.serial_claim (
15959     id     SERIAL           PRIMARY KEY,
15960     type   INT              NOT NULL REFERENCES acq.claim_type
15961                                      DEFERRABLE INITIALLY DEFERRED,
15962     item    BIGINT          NOT NULL REFERENCES serial.item
15963                                      DEFERRABLE INITIALLY DEFERRED
15964 );
15965
15966 CREATE INDEX serial_claim_lid_idx ON acq.serial_claim( item );
15967
15968 CREATE TABLE acq.serial_claim_event (
15969     id             BIGSERIAL        PRIMARY KEY,
15970     type           INT              NOT NULL REFERENCES acq.claim_event_type
15971                                              DEFERRABLE INITIALLY DEFERRED,
15972     claim          SERIAL           NOT NULL REFERENCES acq.serial_claim
15973                                              DEFERRABLE INITIALLY DEFERRED,
15974     event_date     TIMESTAMPTZ      NOT NULL DEFAULT now(),
15975     creator        INT              NOT NULL REFERENCES actor.usr
15976                                              DEFERRABLE INITIALLY DEFERRED,
15977     note           TEXT
15978 );
15979
15980 CREATE INDEX serial_claim_event_claim_date_idx ON acq.serial_claim_event( claim, event_date );
15981
15982 ALTER TABLE asset.stat_cat ADD COLUMN required BOOL NOT NULL DEFAULT FALSE;
15983
15984 -- now what about the auditor.*_lifecycle views??
15985
15986 INSERT INTO config.metabib_field ( id, field_class, name, label, format, xpath ) VALUES
15987     (26, 'identifier', 'tcn', oils_i18n_gettext(26, 'Title Control Number', 'cmf', 'label'), 'marcxml', $$//marc:datafield[@tag='901']/marc:subfield[@code='a']$$ );
15988 INSERT INTO config.metabib_field ( id, field_class, name, label, format, xpath ) VALUES
15989     (27, 'identifier', 'bibid', oils_i18n_gettext(27, 'Internal ID', 'cmf', 'label'), 'marcxml', $$//marc:datafield[@tag='901']/marc:subfield[@code='c']$$ );
15990 INSERT INTO config.metabib_search_alias (alias,field_class,field) VALUES ('eg.tcn','identifier', 26);
15991 INSERT INTO config.metabib_search_alias (alias,field_class,field) VALUES ('eg.bibid','identifier', 27);
15992
15993 CREATE TABLE asset.call_number_class (
15994     id             bigserial     PRIMARY KEY,
15995     name           TEXT          NOT NULL,
15996     normalizer     TEXT          NOT NULL DEFAULT 'asset.normalize_generic',
15997     field          TEXT          NOT NULL DEFAULT '050ab,055ab,060ab,070ab,080ab,082ab,086ab,088ab,090,092,096,098,099'
15998 );
15999
16000 COMMENT ON TABLE asset.call_number_class IS $$
16001 Defines the call number normalization database functions in the "normalizer"
16002 column and the tag/subfield combinations to use to lookup the call number in
16003 the "field" column for a given classification scheme. Tag/subfield combinations
16004 are delimited by commas.
16005 $$;
16006
16007 INSERT INTO asset.call_number_class (name, normalizer) VALUES 
16008     ('Generic', 'asset.label_normalizer_generic'),
16009     ('Dewey (DDC)', 'asset.label_normalizer_dewey'),
16010     ('Library of Congress (LC)', 'asset.label_normalizer_lc')
16011 ;
16012
16013 -- Generic fields
16014 UPDATE asset.call_number_class
16015     SET field = '050ab,055ab,060ab,070ab,080ab,082ab,086ab,088ab,090,092,096,098,099'
16016     WHERE id = 1
16017 ;
16018
16019 -- Dewey fields
16020 UPDATE asset.call_number_class
16021     SET field = '080ab,082ab'
16022     WHERE id = 2
16023 ;
16024
16025 -- LC fields
16026 UPDATE asset.call_number_class
16027     SET field = '050ab,055ab'
16028     WHERE id = 3
16029 ;
16030  
16031 ALTER TABLE auditor.asset_call_number_history ADD COLUMN label_class BIGINT;
16032 ALTER TABLE auditor.asset_call_number_history ADD COLUMN label_sortkey TEXT;
16033 ALTER TABLE asset.call_number ADD COLUMN label_class BIGINT DEFAULT 1 NOT NULL REFERENCES asset.call_number_class(id) DEFERRABLE INITIALLY DEFERRED;
16034 ALTER TABLE asset.call_number ADD COLUMN label_sortkey TEXT;
16035 CREATE INDEX asset_call_number_label_sortkey ON asset.call_number(label_sortkey);
16036
16037 CREATE OR REPLACE FUNCTION asset.label_normalizer() RETURNS TRIGGER AS $func$
16038 DECLARE
16039     sortkey        TEXT := '';
16040 BEGIN
16041     sortkey := NEW.label_sortkey;
16042
16043     EXECUTE 'SELECT ' || acnc.normalizer || '(' || 
16044        quote_literal( NEW.label ) || ')'
16045        FROM asset.call_number_class acnc
16046        WHERE acnc.id = NEW.label_class
16047        INTO sortkey;
16048
16049     NEW.label_sortkey = sortkey;
16050
16051     RETURN NEW;
16052 END;
16053 $func$ LANGUAGE PLPGSQL;
16054
16055 CREATE OR REPLACE FUNCTION asset.label_normalizer_generic(TEXT) RETURNS TEXT AS $func$
16056     # Created after looking at the Koha C4::ClassSortRoutine::Generic module,
16057     # thus could probably be considered a derived work, although nothing was
16058     # directly copied - but to err on the safe side of providing attribution:
16059     # Copyright (C) 2007 LibLime
16060     # Licensed under the GPL v2 or later
16061
16062     use strict;
16063     use warnings;
16064
16065     # Converts the callnumber to uppercase
16066     # Strips spaces from start and end of the call number
16067     # Converts anything other than letters, digits, and periods into underscores
16068     # Collapses multiple underscores into a single underscore
16069     my $callnum = uc(shift);
16070     $callnum =~ s/^\s//g;
16071     $callnum =~ s/\s$//g;
16072     $callnum =~ s/[^A-Z0-9_.]/_/g;
16073     $callnum =~ s/_{2,}/_/g;
16074
16075     return $callnum;
16076 $func$ LANGUAGE PLPERLU;
16077
16078 CREATE OR REPLACE FUNCTION asset.label_normalizer_dewey(TEXT) RETURNS TEXT AS $func$
16079     # Derived from the Koha C4::ClassSortRoutine::Dewey module
16080     # Copyright (C) 2007 LibLime
16081     # Licensed under the GPL v2 or later
16082
16083     use strict;
16084     use warnings;
16085
16086     my $init = uc(shift);
16087     $init =~ s/^\s+//;
16088     $init =~ s/\s+$//;
16089     $init =~ s!/!!g;
16090     $init =~ s/^([\p{IsAlpha}]+)/$1 /;
16091     my @tokens = split /\.|\s+/, $init;
16092     my $digit_group_count = 0;
16093     for (my $i = 0; $i <= $#tokens; $i++) {
16094         if ($tokens[$i] =~ /^\d+$/) {
16095             $digit_group_count++;
16096             if (2 == $digit_group_count) {
16097                 $tokens[$i] = sprintf("%-15.15s", $tokens[$i]);
16098                 $tokens[$i] =~ tr/ /0/;
16099             }
16100         }
16101     }
16102     my $key = join("_", @tokens);
16103     $key =~ s/[^\p{IsAlnum}_]//g;
16104
16105     return $key;
16106
16107 $func$ LANGUAGE PLPERLU;
16108
16109 CREATE OR REPLACE FUNCTION asset.label_normalizer_lc(TEXT) RETURNS TEXT AS $func$
16110     use strict;
16111     use warnings;
16112
16113     # Library::CallNumber::LC is currently hosted at http://code.google.com/p/library-callnumber-lc/
16114     # The author hopes to upload it to CPAN some day, which would make our lives easier
16115     use Library::CallNumber::LC;
16116
16117     my $callnum = Library::CallNumber::LC->new(shift);
16118     return $callnum->normalize();
16119
16120 $func$ LANGUAGE PLPERLU;
16121
16122 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$
16123 DECLARE
16124     ans RECORD;
16125     trans INT;
16126 BEGIN
16127     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;
16128
16129     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
16130         RETURN QUERY
16131         SELECT  ans.depth,
16132                 ans.id,
16133                 COUNT( av.id ),
16134                 SUM( CASE WHEN cp.status IN (0,7,12) THEN 1 ELSE 0 END ),
16135                 COUNT( av.id ),
16136                 trans
16137           FROM
16138                 actor.org_unit_descendants(ans.id) d
16139                 JOIN asset.opac_visible_copies av ON (av.record = record AND av.circ_lib = d.id)
16140                 JOIN asset.copy cp ON (cp.id = av.id)
16141           GROUP BY 1,2,6;
16142
16143         IF NOT FOUND THEN
16144             RETURN QUERY SELECT ans.depth, ans.id, 0::BIGINT, 0::BIGINT, 0::BIGINT, trans;
16145         END IF;
16146
16147     END LOOP;
16148
16149     RETURN;
16150 END;
16151 $f$ LANGUAGE PLPGSQL;
16152
16153 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$
16154 DECLARE
16155     ans RECORD;
16156     trans INT;
16157 BEGIN
16158     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;
16159
16160     FOR ans IN SELECT u.org_unit AS id FROM actor.org_lasso_map AS u WHERE lasso = i_lasso LOOP
16161         RETURN QUERY
16162         SELECT  -1,
16163                 ans.id,
16164                 COUNT( av.id ),
16165                 SUM( CASE WHEN cp.status IN (0,7,12) THEN 1 ELSE 0 END ),
16166                 COUNT( av.id ),
16167                 trans
16168           FROM
16169                 actor.org_unit_descendants(ans.id) d
16170                 JOIN asset.opac_visible_copies av ON (av.record = record AND av.circ_lib = d.id)
16171                 JOIN asset.copy cp ON (cp.id = av.id)
16172           GROUP BY 1,2,6;
16173
16174         IF NOT FOUND THEN
16175             RETURN QUERY SELECT ans.depth, ans.id, 0::BIGINT, 0::BIGINT, 0::BIGINT, trans;
16176         END IF;
16177
16178     END LOOP;
16179
16180     RETURN;
16181 END;
16182 $f$ LANGUAGE PLPGSQL;
16183
16184 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$
16185 DECLARE
16186     ans RECORD;
16187     trans INT;
16188 BEGIN
16189     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;
16190
16191     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
16192         RETURN QUERY
16193         SELECT  ans.depth,
16194                 ans.id,
16195                 COUNT( cp.id ),
16196                 SUM( CASE WHEN cp.status IN (0,7,12) THEN 1 ELSE 0 END ),
16197                 COUNT( cp.id ),
16198                 trans
16199           FROM
16200                 actor.org_unit_descendants(ans.id) d
16201                 JOIN asset.copy cp ON (cp.circ_lib = d.id)
16202                 JOIN asset.call_number cn ON (cn.record = record AND cn.id = cp.call_number)
16203           GROUP BY 1,2,6;
16204
16205         IF NOT FOUND THEN
16206             RETURN QUERY SELECT ans.depth, ans.id, 0::BIGINT, 0::BIGINT, 0::BIGINT, trans;
16207         END IF;
16208
16209     END LOOP;
16210
16211     RETURN;
16212 END;
16213 $f$ LANGUAGE PLPGSQL;
16214
16215 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$
16216 DECLARE
16217     ans RECORD;
16218     trans INT;
16219 BEGIN
16220     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;
16221
16222     FOR ans IN SELECT u.org_unit AS id FROM actor.org_lasso_map AS u WHERE lasso = i_lasso LOOP
16223         RETURN QUERY
16224         SELECT  -1,
16225                 ans.id,
16226                 COUNT( cp.id ),
16227                 SUM( CASE WHEN cp.status IN (0,7,12) THEN 1 ELSE 0 END ),
16228                 COUNT( cp.id ),
16229                 trans
16230           FROM
16231                 actor.org_unit_descendants(ans.id) d
16232                 JOIN asset.copy cp ON (cp.circ_lib = d.id)
16233                 JOIN asset.call_number cn ON (cn.record = record AND cn.id = cp.call_number)
16234           GROUP BY 1,2,6;
16235
16236         IF NOT FOUND THEN
16237             RETURN QUERY SELECT ans.depth, ans.id, 0::BIGINT, 0::BIGINT, 0::BIGINT, trans;
16238         END IF;
16239
16240     END LOOP;
16241
16242     RETURN;
16243 END;
16244 $f$ LANGUAGE PLPGSQL;
16245
16246 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$
16247 BEGIN
16248     IF staff IS TRUE THEN
16249         IF place > 0 THEN
16250             RETURN QUERY SELECT * FROM asset.staff_ou_record_copy_count( place, record );
16251         ELSE
16252             RETURN QUERY SELECT * FROM asset.staff_lasso_record_copy_count( -place, record );
16253         END IF;
16254     ELSE
16255         IF place > 0 THEN
16256             RETURN QUERY SELECT * FROM asset.opac_ou_record_copy_count( place, record );
16257         ELSE
16258             RETURN QUERY SELECT * FROM asset.opac_lasso_record_copy_count( -place, record );
16259         END IF;
16260     END IF;
16261
16262     RETURN;
16263 END;
16264 $f$ LANGUAGE PLPGSQL;
16265
16266 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$
16267 DECLARE
16268     ans RECORD;
16269     trans INT;
16270 BEGIN
16271     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;
16272
16273     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
16274         RETURN QUERY
16275         SELECT  ans.depth,
16276                 ans.id,
16277                 COUNT( av.id ),
16278                 SUM( CASE WHEN cp.status IN (0,7,12) THEN 1 ELSE 0 END ),
16279                 COUNT( av.id ),
16280                 trans
16281           FROM
16282                 actor.org_unit_descendants(ans.id) d
16283                 JOIN asset.opac_visible_copies av ON (av.record = record AND av.circ_lib = d.id)
16284                 JOIN asset.copy cp ON (cp.id = av.id)
16285                 JOIN metabib.metarecord_source_map m ON (m.source = av.record)
16286           GROUP BY 1,2,6;
16287
16288         IF NOT FOUND THEN
16289             RETURN QUERY SELECT ans.depth, ans.id, 0::BIGINT, 0::BIGINT, 0::BIGINT, trans;
16290         END IF;
16291
16292     END LOOP;
16293
16294     RETURN;
16295 END;
16296 $f$ LANGUAGE PLPGSQL;
16297
16298 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$
16299 DECLARE
16300     ans RECORD;
16301     trans INT;
16302 BEGIN
16303     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;
16304
16305     FOR ans IN SELECT u.org_unit AS id FROM actor.org_lasso_map AS u WHERE lasso = i_lasso LOOP
16306         RETURN QUERY
16307         SELECT  -1,
16308                 ans.id,
16309                 COUNT( av.id ),
16310                 SUM( CASE WHEN cp.status IN (0,7,12) THEN 1 ELSE 0 END ),
16311                 COUNT( av.id ),
16312                 trans
16313           FROM
16314                 actor.org_unit_descendants(ans.id) d
16315                 JOIN asset.opac_visible_copies av ON (av.record = record AND av.circ_lib = d.id)
16316                 JOIN asset.copy cp ON (cp.id = av.id)
16317                 JOIN metabib.metarecord_source_map m ON (m.source = av.record)
16318           GROUP BY 1,2,6;
16319
16320         IF NOT FOUND THEN
16321             RETURN QUERY SELECT ans.depth, ans.id, 0::BIGINT, 0::BIGINT, 0::BIGINT, trans;
16322         END IF;
16323
16324     END LOOP;
16325
16326     RETURN;
16327 END;
16328 $f$ LANGUAGE PLPGSQL;
16329
16330 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$
16331 DECLARE
16332     ans RECORD;
16333     trans INT;
16334 BEGIN
16335     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;
16336
16337     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
16338         RETURN QUERY
16339         SELECT  ans.depth,
16340                 ans.id,
16341                 COUNT( cp.id ),
16342                 SUM( CASE WHEN cp.status IN (0,7,12) THEN 1 ELSE 0 END ),
16343                 COUNT( cp.id ),
16344                 trans
16345           FROM
16346                 actor.org_unit_descendants(ans.id) d
16347                 JOIN asset.copy cp ON (cp.circ_lib = d.id)
16348                 JOIN asset.call_number cn ON (cn.record = record AND cn.id = cp.call_number)
16349                 JOIN metabib.metarecord_source_map m ON (m.source = cn.record)
16350           GROUP BY 1,2,6;
16351
16352         IF NOT FOUND THEN
16353             RETURN QUERY SELECT ans.depth, ans.id, 0::BIGINT, 0::BIGINT, 0::BIGINT, trans;
16354         END IF;
16355
16356     END LOOP;
16357
16358     RETURN;
16359 END;
16360 $f$ LANGUAGE PLPGSQL;
16361
16362 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$
16363 DECLARE
16364     ans RECORD;
16365     trans INT;
16366 BEGIN
16367     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;
16368
16369     FOR ans IN SELECT u.org_unit AS id FROM actor.org_lasso_map AS u WHERE lasso = i_lasso LOOP
16370         RETURN QUERY
16371         SELECT  -1,
16372                 ans.id,
16373                 COUNT( cp.id ),
16374                 SUM( CASE WHEN cp.status IN (0,7,12) THEN 1 ELSE 0 END ),
16375                 COUNT( cp.id ),
16376                 trans
16377           FROM
16378                 actor.org_unit_descendants(ans.id) d
16379                 JOIN asset.copy cp ON (cp.circ_lib = d.id)
16380                 JOIN asset.call_number cn ON (cn.record = record AND cn.id = cp.call_number)
16381                 JOIN metabib.metarecord_source_map m ON (m.source = cn.record)
16382           GROUP BY 1,2,6;
16383
16384         IF NOT FOUND THEN
16385             RETURN QUERY SELECT ans.depth, ans.id, 0::BIGINT, 0::BIGINT, 0::BIGINT, trans;
16386         END IF;
16387
16388     END LOOP;
16389
16390     RETURN;
16391 END;
16392 $f$ LANGUAGE PLPGSQL;
16393
16394 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$
16395 BEGIN
16396     IF staff IS TRUE THEN
16397         IF place > 0 THEN
16398             RETURN QUERY SELECT * FROM asset.staff_ou_metarecord_copy_count( place, record );
16399         ELSE
16400             RETURN QUERY SELECT * FROM asset.staff_lasso_metarecord_copy_count( -place, record );
16401         END IF;
16402     ELSE
16403         IF place > 0 THEN
16404             RETURN QUERY SELECT * FROM asset.opac_ou_metarecord_copy_count( place, record );
16405         ELSE
16406             RETURN QUERY SELECT * FROM asset.opac_lasso_metarecord_copy_count( -place, record );
16407         END IF;
16408     END IF;
16409
16410     RETURN;
16411 END;
16412 $f$ LANGUAGE PLPGSQL;
16413
16414 -- No transaction is required
16415
16416 -- Triggers on the vandelay.queued_*_record tables delete entries from
16417 -- the associated vandelay.queued_*_record_attr tables based on the record's
16418 -- ID; create an index on that column to avoid sequential scans for each
16419 -- queued record that is deleted
16420 CREATE INDEX queued_bib_record_attr_record_idx ON vandelay.queued_bib_record_attr (record);
16421 CREATE INDEX queued_authority_record_attr_record_idx ON vandelay.queued_authority_record_attr (record);
16422
16423 -- Avoid sequential scans for queue retrieval operations by providing an
16424 -- index on the queue column
16425 CREATE INDEX queued_bib_record_queue_idx ON vandelay.queued_bib_record (queue);
16426 CREATE INDEX queued_authority_record_queue_idx ON vandelay.queued_authority_record (queue);
16427
16428 CREATE INDEX actor_card_barcode_lower_idx ON actor.card (lower(barcode));
16429
16430 -- Start picking up call number label prefixes and suffixes
16431 -- from asset.copy_location
16432 ALTER TABLE asset.copy_location ADD COLUMN label_prefix TEXT;
16433 ALTER TABLE asset.copy_location ADD COLUMN label_suffix TEXT;
16434
16435 ALTER TABLE reporter.report RENAME COLUMN recurance TO recurrence;
16436
16437 -- Let's not break existing reports
16438 UPDATE reporter.template SET data = REGEXP_REPLACE(data, E'^(.*)recuring(.*)$', E'\\1recurring\\2') WHERE data LIKE '%recuring%';
16439 UPDATE reporter.template SET data = REGEXP_REPLACE(data, E'^(.*)recurance(.*)$', E'\\1recurrence\\2') WHERE data LIKE '%recurance%';
16440
16441 -- Need to recreate this view with DISTINCT calls to ARRAY_ACCUM, thus avoiding duplicated ISBN and ISSN values
16442 CREATE OR REPLACE VIEW reporter.old_super_simple_record AS
16443 SELECT  r.id,
16444     r.fingerprint,
16445     r.quality,
16446     r.tcn_source,
16447     r.tcn_value,
16448     FIRST(title.value) AS title,
16449     FIRST(author.value) AS author,
16450     ARRAY_TO_STRING(ARRAY_ACCUM( DISTINCT publisher.value), ', ') AS publisher,
16451     ARRAY_TO_STRING(ARRAY_ACCUM( DISTINCT SUBSTRING(pubdate.value FROM $$\d+$$) ), ', ') AS pubdate,
16452     ARRAY_ACCUM( DISTINCT SUBSTRING(isbn.value FROM $$^\S+$$) ) AS isbn,
16453     ARRAY_ACCUM( DISTINCT SUBSTRING(issn.value FROM $$^\S+$$) ) AS issn
16454   FROM  biblio.record_entry r
16455     LEFT JOIN metabib.full_rec title ON (r.id = title.record AND title.tag = '245' AND title.subfield = 'a')
16456     LEFT JOIN metabib.full_rec author ON (r.id = author.record AND author.tag IN ('100','110','111') AND author.subfield = 'a')
16457     LEFT JOIN metabib.full_rec publisher ON (r.id = publisher.record AND publisher.tag = '260' AND publisher.subfield = 'b')
16458     LEFT JOIN metabib.full_rec pubdate ON (r.id = pubdate.record AND pubdate.tag = '260' AND pubdate.subfield = 'c')
16459     LEFT JOIN metabib.full_rec isbn ON (r.id = isbn.record AND isbn.tag IN ('024', '020') AND isbn.subfield IN ('a','z'))
16460     LEFT JOIN metabib.full_rec issn ON (r.id = issn.record AND issn.tag = '022' AND issn.subfield = 'a')
16461   GROUP BY 1,2,3,4,5;
16462
16463 -- Correct the ISSN array definition for reporter.simple_record
16464
16465 CREATE OR REPLACE VIEW reporter.simple_record AS
16466 SELECT  r.id,
16467         s.metarecord,
16468         r.fingerprint,
16469         r.quality,
16470         r.tcn_source,
16471         r.tcn_value,
16472         title.value AS title,
16473         uniform_title.value AS uniform_title,
16474         author.value AS author,
16475         publisher.value AS publisher,
16476         SUBSTRING(pubdate.value FROM $$\d+$$) AS pubdate,
16477         series_title.value AS series_title,
16478         series_statement.value AS series_statement,
16479         summary.value AS summary,
16480         ARRAY_ACCUM( SUBSTRING(isbn.value FROM $$^\S+$$) ) AS isbn,
16481         ARRAY_ACCUM( REGEXP_REPLACE(issn.value, E'^\\S*(\\d{4})[-\\s](\\d{3,4}x?)', E'\\1 \\2') ) AS issn,
16482         ARRAY((SELECT DISTINCT value FROM metabib.full_rec WHERE tag = '650' AND subfield = 'a' AND record = r.id)) AS topic_subject,
16483         ARRAY((SELECT DISTINCT value FROM metabib.full_rec WHERE tag = '651' AND subfield = 'a' AND record = r.id)) AS geographic_subject,
16484         ARRAY((SELECT DISTINCT value FROM metabib.full_rec WHERE tag = '655' AND subfield = 'a' AND record = r.id)) AS genre,
16485         ARRAY((SELECT DISTINCT value FROM metabib.full_rec WHERE tag = '600' AND subfield = 'a' AND record = r.id)) AS name_subject,
16486         ARRAY((SELECT DISTINCT value FROM metabib.full_rec WHERE tag = '610' AND subfield = 'a' AND record = r.id)) AS corporate_subject,
16487         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
16488   FROM  biblio.record_entry r
16489         JOIN metabib.metarecord_source_map s ON (s.source = r.id)
16490         LEFT JOIN metabib.full_rec uniform_title ON (r.id = uniform_title.record AND uniform_title.tag = '240' AND uniform_title.subfield = 'a')
16491         LEFT JOIN metabib.full_rec title ON (r.id = title.record AND title.tag = '245' AND title.subfield = 'a')
16492         LEFT JOIN metabib.full_rec author ON (r.id = author.record AND author.tag = '100' AND author.subfield = 'a')
16493         LEFT JOIN metabib.full_rec publisher ON (r.id = publisher.record AND publisher.tag = '260' AND publisher.subfield = 'b')
16494         LEFT JOIN metabib.full_rec pubdate ON (r.id = pubdate.record AND pubdate.tag = '260' AND pubdate.subfield = 'c')
16495         LEFT JOIN metabib.full_rec isbn ON (r.id = isbn.record AND isbn.tag IN ('024', '020') AND isbn.subfield IN ('a','z'))
16496         LEFT JOIN metabib.full_rec issn ON (r.id = issn.record AND issn.tag = '022' AND issn.subfield = 'a')
16497         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')
16498         LEFT JOIN metabib.full_rec series_statement ON (r.id = series_statement.record AND series_statement.tag = '490' AND series_statement.subfield = 'a')
16499         LEFT JOIN metabib.full_rec summary ON (r.id = summary.record AND summary.tag = '520' AND summary.subfield = 'a')
16500   GROUP BY 1,2,3,4,5,6,7,8,9,10,11,12,13,14;
16501
16502 CREATE OR REPLACE FUNCTION reporter.disable_materialized_simple_record_trigger () RETURNS VOID AS $$
16503     DROP TRIGGER IF EXISTS zzz_update_materialized_simple_record_tgr ON metabib.real_full_rec;
16504 $$ LANGUAGE SQL;
16505
16506 CREATE OR REPLACE FUNCTION reporter.simple_rec_trigger () RETURNS TRIGGER AS $func$
16507 BEGIN
16508     IF TG_OP = 'DELETE' THEN
16509         PERFORM reporter.simple_rec_delete(NEW.id);
16510     ELSE
16511         PERFORM reporter.simple_rec_update(NEW.id);
16512     END IF;
16513
16514     RETURN NEW;
16515 END;
16516 $func$ LANGUAGE PLPGSQL;
16517
16518 CREATE TRIGGER bbb_simple_rec_trigger AFTER INSERT OR UPDATE ON biblio.record_entry FOR EACH ROW EXECUTE PROCEDURE reporter.simple_rec_trigger ();
16519
16520 ALTER TABLE extend_reporter.legacy_circ_count DROP CONSTRAINT legacy_circ_count_id_fkey;
16521
16522 COMMIT;
16523
16524 -- Outside of the commit: some alters that may legitimately fail
16525 -- if the table doesn't exist.
16526
16527 ALTER TABLE auditor.action_hold_request_history ADD COLUMN cut_in_line BOOL;
16528
16529 ALTER TABLE auditor.action_hold_request_history
16530 ADD COLUMN mint_condition boolean NOT NULL DEFAULT TRUE;
16531
16532 ALTER TABLE auditor.action_hold_request_history
16533 ADD COLUMN shelf_expire_time TIMESTAMPTZ;