]> git.evergreen-ils.org Git - Evergreen.git/blob - Open-ILS/src/sql/Pg/1.6.1-2.0-upgrade-db.sql
Move some operations out of the transaction so that they can
[Evergreen.git] / Open-ILS / src / sql / Pg / 1.6.1-2.0-upgrade-db.sql
1 -- Drop a view temporarily in order to alter action.all_circulation, upon
2 -- which it is dependent.  This drop is outside the transaction because the
3 -- extend_reporter schema may not exist in a given database.  We will recreate
4 -- the view later, after the transaction.
5
6 DROP VIEW IF EXISTS extend_reporter.full_circ_count;
7
8 BEGIN;
9
10 -- Highest-numbered individual upgrade script
11 -- incorporated herein:
12
13 INSERT INTO config.upgrade_log (version) VALUES ('0387');
14
15 -- Add ON UPDATE CASCADE to some foreign keys so that, if we renumber the
16 -- permissions, the dependents will follow and stay in sync:
17
18 ALTER TABLE permission.grp_perm_map DROP CONSTRAINT grp_perm_map_perm_fkey;
19 ALTER TABLE permission.grp_perm_map ADD CONSTRAINT grp_perm_map_perm_fkey FOREIGN KEY (perm)
20     REFERENCES permission.perm_list (id) ON UPDATE CASCADE ON DELETE RESTRICT DEFERRABLE INITIALLY DEFERRED;
21
22 ALTER TABLE permission.usr_perm_map DROP CONSTRAINT usr_perm_map_perm_fkey;
23 ALTER TABLE permission.usr_perm_map ADD CONSTRAINT usr_perm_map_perm_fkey FOREIGN KEY (perm)
24     REFERENCES permission.perm_list (id) ON UPDATE CASCADE ON DELETE RESTRICT DEFERRABLE INITIALLY DEFERRED;
25
26 ALTER TABLE permission.usr_object_perm_map DROP CONSTRAINT usr_object_perm_map_perm_fkey;
27 ALTER TABLE permission.usr_object_perm_map ADD CONSTRAINT usr_object_perm_map_perm_fkey FOREIGN KEY (perm)
28     REFERENCES permission.perm_list (id) ON UPDATE CASCADE ON DELETE RESTRICT DEFERRABLE INITIALLY DEFERRED;
29
30 -- Create a reference table as parent to both
31 -- config.org_unit_setting_type and config_usr_setting_type
32
33 CREATE TABLE config.settings_group (
34     name    TEXT PRIMARY KEY,
35     label   TEXT UNIQUE NOT NULL -- I18N
36 );
37
38 -- org_unit setting types
39 CREATE TABLE config.org_unit_setting_type (
40     name            TEXT    PRIMARY KEY,
41     label           TEXT    UNIQUE NOT NULL,
42     grp             TEXT    REFERENCES config.settings_group (name),
43     description     TEXT,
44     datatype        TEXT    NOT NULL DEFAULT 'string',
45     fm_class        TEXT,
46     view_perm       INT,
47     update_perm     INT,
48     --
49     -- define valid datatypes
50     --
51     CONSTRAINT coust_valid_datatype CHECK ( datatype IN
52     ( 'bool', 'integer', 'float', 'currency', 'interval',
53       'date', 'string', 'object', 'array', 'link' ) ),
54     --
55     -- fm_class is meaningful only for 'link' datatype
56     --
57     CONSTRAINT coust_no_empty_link CHECK
58     ( ( datatype =  'link' AND fm_class IS NOT NULL ) OR
59       ( datatype <> 'link' AND fm_class IS NULL ) ),
60         CONSTRAINT view_perm_fkey FOREIGN KEY (view_perm) REFERENCES permission.perm_list (id)
61                 ON UPDATE CASCADE
62                 ON DELETE RESTRICT
63                 DEFERRABLE INITIALLY DEFERRED,
64         CONSTRAINT update_perm_fkey FOREIGN KEY (update_perm) REFERENCES permission.perm_list (id)
65                 ON UPDATE CASCADE
66                 DEFERRABLE INITIALLY DEFERRED
67 );
68
69 CREATE TABLE config.usr_setting_type (
70
71     name TEXT PRIMARY KEY,
72     opac_visible BOOL NOT NULL DEFAULT FALSE,
73     label TEXT UNIQUE NOT NULL,
74     description TEXT,
75     grp             TEXT    REFERENCES config.settings_group (name),
76     datatype TEXT NOT NULL DEFAULT 'string',
77     fm_class TEXT,
78
79     --
80     -- define valid datatypes
81     --
82     CONSTRAINT coust_valid_datatype CHECK ( datatype IN
83     ( 'bool', 'integer', 'float', 'currency', 'interval',
84         'date', 'string', 'object', 'array', 'link' ) ),
85
86     --
87     -- fm_class is meaningful only for 'link' datatype
88     --
89     CONSTRAINT coust_no_empty_link CHECK
90     ( ( datatype = 'link' AND fm_class IS NOT NULL ) OR
91         ( datatype <> 'link' AND fm_class IS NULL ) )
92
93 );
94
95 --------------------------------------
96 -- Seed data for org_unit_setting_type
97 --------------------------------------
98
99 INSERT into config.org_unit_setting_type
100 ( name, label, description, datatype ) VALUES
101
102 ( 'auth.opac_timeout',
103   'OPAC Inactivity Timeout (in seconds)',
104   null,
105   'integer' ),
106
107 ( 'auth.staff_timeout',
108   'Staff Login Inactivity Timeout (in seconds)',
109   null,
110   'integer' ),
111
112 ( 'circ.lost_materials_processing_fee',
113   'Lost Materials Processing Fee',
114   null,
115   'currency' ),
116
117 ( 'cat.default_item_price',
118   'Default Item Price',
119   null,
120   'currency' ),
121
122 ( 'org.bounced_emails',
123   'Sending email address for patron notices',
124   null,
125   'string' ),
126
127 ( 'circ.hold_expire_alert_interval',
128   'Holds: Expire Alert Interval',
129   'Amount of time before a hold expires at which point the patron should be alerted',
130   'interval' ),
131
132 ( 'circ.hold_expire_interval',
133   'Holds: Expire Interval',
134   'Amount of time after a hold is placed before the hold expires.  Example "100 days"',
135   'interval' ),
136
137 ( 'credit.payments.allow',
138   'Allow Credit Card Payments',
139   'If enabled, patrons will be able to pay fines accrued at this location via credit card',
140   'bool' ),
141
142 ( 'global.default_locale',
143   'Global Default Locale',
144   null,
145   'string' ),
146
147 ( 'circ.void_overdue_on_lost',
148   'Void overdue fines when items are marked lost',
149   null,
150   'bool' ),
151
152 ( 'circ.hold_stalling.soft',
153   'Holds: Soft stalling interval',
154   'How long to wait before allowing remote items to be opportunisticaly captured for a hold.  Example "5 days"',
155   'interval' ),
156
157 ( 'circ.hold_stalling_hard',
158   'Holds: Hard stalling interval',
159   '',
160   'interval' ),
161
162 ( 'circ.hold_boundary.hard',
163   'Holds: Hard boundary',
164   null,
165   'integer' ),
166
167 ( 'circ.hold_boundary.soft',
168   'Holds: Soft boundary',
169   null,
170   'integer' ),
171
172 ( 'opac.barcode_regex',
173   'Patron barcode format',
174   'Regular expression defining the patron barcode format',
175   'string' ),
176
177 ( 'global.password_regex',
178   'Password format',
179   'Regular expression defining the password format',
180   'string' ),
181
182 ( 'circ.item_checkout_history.max',
183   'Maximum previous checkouts displayed',
184   'This is maximum number of previous circulations the staff client will display when investigating item details',
185   'integer' ),
186
187 ( 'circ.reshelving_complete.interval',
188   'Change reshelving status interval',
189   'Amount of time to wait before changing an item from "reshelving" status to "available".  Examples "1 day", "6 hours"',
190   'interval' ),
191
192 ( 'circ.holds.default_estimated_wait_interval',
193   'Holds: Default Estimated Wait',
194   '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.',
195   'interval' ),
196
197 ( 'circ.holds.min_estimated_wait_interval',
198   'Holds: Minimum Estimated Wait',
199   '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.',
200   'interval' ),
201
202 ( 'circ.selfcheck.patron_login_timeout',
203   'Selfcheck: Patron Login Timeout (in seconds)',
204   'Number of seconds of inactivity before the patron is logged out of the selfcheck interfacer',
205   'integer' ),
206
207 ( 'circ.selfcheck.alert.popup',
208   'Selfcheck: Pop-up alert for errors',
209   'If true, checkout/renewal errors will cause a pop-up window in addition to the on-screen message',
210   'bool' ),
211
212 ( 'circ.selfcheck.require_patron_password',
213   'Selfcheck: Require patron password',
214   'If true, patrons will be required to enter their password in addition to their username/barcode to log into the selfcheck interface',
215   'bool' ),
216
217 ( 'global.juvenile_age_threshold',
218   'Juvenile Age Threshold',
219   'The age at which a user is no long considered a juvenile.  For example, "18 years".',
220   'interval' ),
221
222 ( 'cat.bib.keep_on_empty',
223   'Retain empty bib records',
224   'Retain a bib record even when all attached copies are deleted',
225   'bool' ),
226
227 ( 'cat.bib.alert_on_empty',
228   'Alert on empty bib records',
229   'Alert staff when the last copy for a record is being deleted',
230   'bool' ),
231
232 ( 'patron.password.use_phone',
233   'Patron: password from phone #',
234   'Use the last 4 digits of the patrons phone number as the default password when creating new users',
235   'bool' ),
236
237 ( 'circ.charge_on_damaged',
238   'Charge item price when marked damaged',
239   'Charge item price when marked damaged',
240   'bool' ),
241
242 ( 'circ.charge_lost_on_zero',
243   'Charge lost on zero',
244   '',
245   'bool' ),
246
247 ( 'circ.damaged_item_processing_fee',
248   'Charge processing fee for damaged items',
249   'Charge processing fee for damaged items',
250   'currency' ),
251
252 ( 'circ.void_lost_on_checkin',
253   'Circ: Void lost item billing when returned',
254   'Void lost item billing when returned',
255   'bool' ),
256
257 ( 'circ.max_accept_return_of_lost',
258   'Circ: Void lost max interval',
259   'Items that have been lost this long will not result in voided billings when returned.  E.g. ''6 months''',
260   'interval' ),
261
262 ( 'circ.void_lost_proc_fee_on_checkin',
263   'Circ: Void processing fee on lost item return',
264   'Void processing fee when lost item returned',
265   'bool' ),
266
267 ( 'circ.restore_overdue_on_lost_return',
268   'Circ: Restore overdues on lost item return',
269   'Restore overdue fines on lost item return',
270   'bool' ),
271
272 ( 'circ.lost_immediately_available',
273   'Circ: Lost items usable on checkin',
274   'Lost items are usable on checkin instead of going ''home'' first',
275   'bool' ),
276
277 ( 'circ.holds_fifo',
278   'Holds: FIFO',
279   'Force holds to a more strict First-In, First-Out capture',
280   'bool' ),
281
282 ( 'opac.allow_pending_address',
283   'OPAC: Allow pending addresses',
284   'If enabled, patrons can create and edit existing addresses.  Addresses are kept in a pending state until staff approves the changes',
285   'bool' ),
286
287 ( 'ui.circ.show_billing_tab_on_bills',
288   'Show billing tab first when bills are present',
289   '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',
290   'bool' ),
291
292 ( 'ui.general.idle_timeout',
293     'GUI: Idle timeout',
294     '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).',
295     'integer' ),
296
297 ( 'ui.circ.in_house_use.entry_cap',
298   'GUI: Record In-House Use: Maximum # of uses allowed per entry.',
299   'The # of uses entry in the Record In-House Use interface may not exceed the value of this setting.',
300   'integer' ),
301
302 ( 'ui.circ.in_house_use.entry_warn',
303   'GUI: Record In-House Use: # of uses threshold for Are You Sure? dialog.',
304   'In the Record In-House Use interface, a submission attempt will warn if the # of uses field exceeds the value of this setting.',
305   'integer' ),
306
307 ( 'acq.default_circ_modifier',
308   'Default circulation modifier',
309   null,
310   'string' ),
311
312 ( 'acq.tmp_barcode_prefix',
313   'Temporary barcode prefix',
314   null,
315   'string' ),
316
317 ( 'acq.tmp_callnumber_prefix',
318   'Temporary call number prefix',
319   null,
320   'string' ),
321
322 ( 'ui.circ.patron_summary.horizontal',
323   'Patron circulation summary is horizontal',
324   null,
325   'bool' ),
326
327 ( 'ui.staff.require_initials',
328   oils_i18n_gettext('ui.staff.require_initials', 'GUI: Require staff initials for entry/edit of item/patron/penalty notes/messages.', 'coust', 'label'),
329   oils_i18n_gettext('ui.staff.require_initials', 'Appends staff initials and edit date into note content.', 'coust', 'description'),
330   'bool' ),
331
332 ( 'ui.general.button_bar',
333   'Button bar',
334   null,
335   'bool' ),
336
337 ( 'circ.hold_shelf_status_delay',
338   'Hold Shelf Status Delay',
339   '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.',
340   'interval' ),
341
342 ( 'circ.patron_invalid_address_apply_penalty',
343   'Invalid patron address penalty',
344   'When set, if a patron address is set to invalid, a penalty is applied.',
345   'bool' ),
346
347 ( 'circ.checkout_fills_related_hold',
348   'Checkout Fills Related Hold',
349   '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',
350   'bool'),
351
352 ( 'circ.selfcheck.auto_override_checkout_events',
353   'Selfcheck override events list',
354   'List of checkout/renewal events that the selfcheck interface should automatically override instead instead of alerting and stopping the transaction',
355   'array' ),
356
357 ( 'circ.staff_client.do_not_auto_attempt_print',
358   'Disable Automatic Print Attempt Type List',
359   '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).',
360   'array' ),
361
362 ( 'ui.patron.default_inet_access_level',
363   'Default level of patrons'' internet access',
364   null,
365   'integer' ),
366
367 ( 'circ.max_patron_claim_return_count',
368     'Max Patron Claims Returned Count',
369     'When this count is exceeded, a staff override is required to mark the item as claims returned',
370     'integer' ),
371
372 ( 'circ.obscure_dob',
373     'Obscure the Date of Birth field',
374     '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.',
375     'bool' ),
376
377 ( 'circ.auto_hide_patron_summary',
378     'GUI: Toggle off the patron summary sidebar after first view.',
379     'When true, the patron summary sidebar will collapse after a new patron sub-interface is selected.',
380     'bool' ),
381
382 ( 'credit.processor.default',
383     'Credit card processing: Name default credit processor',
384     'This might be "AuthorizeNet", "PayPal", etc.',
385     'string' ),
386
387 ( 'credit.processor.authorizenet.enabled',
388     'Credit card processing: AuthorizeNet enabled',
389     '',
390     'bool' ),
391
392 ( 'credit.processor.authorizenet.login',
393     'Credit card processing: AuthorizeNet login',
394     '',
395     'string' ),
396
397 ( 'credit.processor.authorizenet.password',
398     'Credit card processing: AuthorizeNet password',
399     '',
400     'string' ),
401
402 ( 'credit.processor.authorizenet.server',
403     'Credit card processing: AuthorizeNet server',
404     'Required if using a developer/test account with AuthorizeNet',
405     'string' ),
406
407 ( 'credit.processor.authorizenet.testmode',
408     'Credit card processing: AuthorizeNet test mode',
409     '',
410     'bool' ),
411
412 ( 'credit.processor.paypal.enabled',
413     'Credit card processing: PayPal enabled',
414     '',
415     'bool' ),
416 ( 'credit.processor.paypal.login',
417     'Credit card processing: PayPal login',
418     '',
419     'string' ),
420 ( 'credit.processor.paypal.password',
421     'Credit card processing: PayPal password',
422     '',
423     'string' ),
424 ( 'credit.processor.paypal.signature',
425     'Credit card processing: PayPal signature',
426     '',
427     'string' ),
428 ( 'credit.processor.paypal.testmode',
429     'Credit card processing: PayPal test mode',
430     '',
431     'bool' ),
432
433 ( 'ui.admin.work_log.max_entries',
434     oils_i18n_gettext('ui.admin.work_log.max_entries', 'GUI: Work Log: Maximum Actions Logged', 'coust', 'label'),
435     oils_i18n_gettext('ui.admin.work_log.max_entries', 'Maximum entries for "Most Recent Staff Actions" section of the Work Log interface.', 'coust', 'description'),
436   'interval' ),
437
438 ( 'ui.admin.patron_log.max_entries',
439     oils_i18n_gettext('ui.admin.patron_log.max_entries', 'GUI: Work Log: Maximum Patrons Logged', 'coust', 'label'),
440     oils_i18n_gettext('ui.admin.patron_log.max_entries', 'Maximum entries for "Most Recently Affected Patrons..." section of the Work Log interface.', 'coust', 'description'),
441   'interval' ),
442
443 ( 'lib.courier_code',
444     oils_i18n_gettext('lib.courier_code', 'Courier Code', 'coust', 'label'),
445     oils_i18n_gettext('lib.courier_code', 'Courier Code for the library.  Available in transit slip templates as the %courier_code% macro.', 'coust', 'description'),
446     'string'),
447
448 ( 'circ.block_renews_for_holds',
449     oils_i18n_gettext('circ.block_renews_for_holds', 'Holds: Block Renewal of Items Needed for Holds', 'coust', 'label'),
450     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'),
451     'bool' ),
452
453 ( 'circ.password_reset_request_per_user_limit',
454     oils_i18n_gettext('circ.password_reset_request_per_user_limit', 'Circulation: Maximum concurrently active self-serve password reset requests per user', 'coust', 'label'),
455     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'),
456     'string'),
457
458 ( 'circ.password_reset_request_time_to_live',
459     oils_i18n_gettext('circ.password_reset_request_time_to_live', 'Circulation: Self-serve password reset request time-to-live', 'coust', 'label'),
460     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'),
461     'string'),
462
463 ( 'circ.password_reset_request_throttle',
464     oils_i18n_gettext('circ.password_reset_request_throttle', 'Circulation: Maximum concurrently active self-serve password reset requests', 'coust', 'label'),
465     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'),
466     'string')
467 ;
468
469 INSERT INTO config.org_unit_setting_type ( name, label, description, datatype ) VALUES (
470         'ui.circ.suppress_checkin_popups',
471         oils_i18n_gettext(
472             'ui.circ.suppress_checkin_popups', 
473             'Circ: Suppress popup-dialogs during check-in.', 
474             'coust', 
475             'label'),
476         oils_i18n_gettext(
477             'ui.circ.suppress_checkin_popups', 
478             'Circ: Suppress popup-dialogs during check-in.', 
479             'coust', 
480             'description'),
481         'bool'
482 );
483
484 INSERT INTO config.org_unit_setting_type ( name, label, description, datatype ) VALUES (
485         'format.date',
486         oils_i18n_gettext(
487             'format.date',
488             'GUI: Format Dates with this pattern.', 
489             'coust', 
490             'label'),
491         oils_i18n_gettext(
492             'format.date',
493             'GUI: Format Dates with this pattern (examples: "yyyy-MM-dd" for "2010-04-26", "MMM d, yyyy" for "Apr 26, 2010")', 
494             'coust', 
495             'description'),
496         'string'
497 ), (
498         'format.time',
499         oils_i18n_gettext(
500             'format.time',
501             'GUI: Format Times with this pattern.', 
502             'coust', 
503             'label'),
504         oils_i18n_gettext(
505             'format.time',
506             '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")', 
507             'coust', 
508             'description'),
509         'string'
510 );
511
512 INSERT INTO config.org_unit_setting_type ( name, label, description, datatype ) VALUES (
513         'cat.bib.delete_on_no_copy_via_acq_lineitem_cancel',
514         oils_i18n_gettext(
515             'cat.bib.delete_on_no_copy_via_acq_lineitem_cancel',
516             'CAT: Delete bib if all copies are deleted via Acquisitions lineitem cancellation.', 
517             'coust', 
518             'label'),
519         oils_i18n_gettext(
520             'cat.bib.delete_on_no_copy_via_acq_lineitem_cancel',
521             'CAT: Delete bib if all copies are deleted via Acquisitions lineitem cancellation.', 
522             'coust', 
523             'description'),
524         'bool'
525 );
526
527 INSERT INTO config.org_unit_setting_type ( name, label, description, datatype ) VALUES (
528         'url.remote_column_settings',
529         oils_i18n_gettext(
530             'url.remote_column_settings',
531             'GUI: URL for remote directory containing list column settings.', 
532             'coust', 
533             'label'),
534         oils_i18n_gettext(
535             'url.remote_column_settings',
536             '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.', 
537             'coust', 
538             'description'),
539         'string'
540 );
541
542 INSERT INTO config.org_unit_setting_type ( name, label, description, datatype ) VALUES (
543         'gui.disable_local_save_columns',
544         oils_i18n_gettext(
545             'gui.disable_local_save_columns',
546             'GUI: Disable the ability to save list column configurations locally.', 
547             'coust', 
548             'label'),
549         oils_i18n_gettext(
550             'gui.disable_local_save_columns',
551             '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.', 
552             'coust', 
553             'description'),
554         'bool'
555 );
556
557 INSERT INTO config.org_unit_setting_type ( name, label, description, datatype ) VALUES (
558         'circ.password_reset_request_requires_matching_email',
559         oils_i18n_gettext(
560             'circ.password_reset_request_requires_matching_email',
561             'Circulation: Require matching email address for password reset requests', 
562             'coust', 
563             'label'),
564         oils_i18n_gettext(
565             'circ.password_reset_request_requires_matching_email',
566             'Circulation: Require matching email address for password reset requests', 
567             'coust', 
568             'description'),
569         'bool'
570 );
571
572 INSERT INTO config.org_unit_setting_type ( name, label, description, datatype ) VALUES (
573         'circ.holds.expired_patron_block',
574         oils_i18n_gettext(
575             'circ.holds.expired_patron_block',
576             'Circulation: Block hold request if hold recipient privileges have expired', 
577             'coust', 
578             'label'),
579         oils_i18n_gettext(
580             'circ.holds.expired_patron_block',
581             'Circulation: Block hold request if hold recipient privileges have expired', 
582             'coust', 
583             'description'),
584         'bool'
585 );
586
587 INSERT INTO config.org_unit_setting_type
588     (name, label, description, datatype) VALUES (
589         'circ.booking_reservation.default_elbow_room',
590         oils_i18n_gettext(
591             'circ.booking_reservation.default_elbow_room',
592             'Booking: Elbow room',
593             'coust',
594             'label'
595         ),
596         oils_i18n_gettext(
597             'circ.booking_reservation.default_elbow_room',
598             '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.',
599             'coust',
600             'label'
601         ),
602         'interval'
603     );
604
605 -- Org_unit_setting_type(s) that need an fm_class:
606 INSERT into config.org_unit_setting_type
607 ( name, label, description, datatype, fm_class ) VALUES
608 ( 'acq.default_copy_location',
609   'Default copy location',
610   null,
611   'link',
612   'acpl' );
613
614 INSERT INTO config.org_unit_setting_type (name, label, description, datatype) VALUES (
615     'circ.holds.org_unit_target_weight',
616     'Holds: Org Unit Target Weight',
617     '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.',
618     'integer'
619 );
620
621 INSERT INTO config.org_unit_setting_type (name, label, description, datatype) VALUES (
622     'circ.holds.target_holds_by_org_unit_weight',
623     'Holds: Use weight-based hold targeting',
624     'Use library weight based hold targeting',
625     'bool'
626 );
627
628 INSERT INTO config.org_unit_setting_type (name, label, description, datatype) VALUES (
629     'circ.holds.max_org_unit_target_loops',
630     'Holds: Maximum library target attempts',
631     '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',
632     'integer'
633 );
634
635
636 -- Org setting for overriding the circ lib of a precat copy
637 INSERT INTO config.org_unit_setting_type (name, label, description, datatype) VALUES (
638     'circ.pre_cat_copy_circ_lib',
639     'Pre-cat Item Circ Lib',
640     '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',
641     'string'
642 );
643
644 -- Circ auto-renew interval setting
645 INSERT INTO config.org_unit_setting_type (name, label, description, datatype) VALUES (
646     'circ.checkout_auto_renew_age',
647     'Checkout auto renew age',
648     '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',
649     'interval'
650 );
651
652 -- Setting for behind the desk hold pickups
653 INSERT INTO config.org_unit_setting_type (name, label, description, datatype) VALUES (
654     'circ.holds.behind_desk_pickup_supported',
655     'Holds: Behind Desk Pickup Supported',
656     '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',
657     'bool'
658 );
659
660 INSERT INTO config.org_unit_setting_type ( name, label, description, datatype ) VALUES (
661         'acq.holds.allow_holds_from_purchase_request',
662         oils_i18n_gettext(
663             'acq.holds.allow_holds_from_purchase_request', 
664             'Allows patrons to create automatic holds from purchase requests.', 
665             'coust', 
666             'label'),
667         oils_i18n_gettext(
668             'acq.holds.allow_holds_from_purchase_request', 
669             'Allows patrons to create automatic holds from purchase requests.', 
670             'coust', 
671             'description'),
672         'bool'
673 );
674
675 INSERT INTO config.org_unit_setting_type (name, label, description, datatype) VALUES (
676     'circ.holds.target_skip_me',
677     'Skip For Hold Targeting',
678     'When true, don''t target any copies at this org unit for holds',
679     'bool'
680 );
681
682 -- claims returned mark item missing 
683 INSERT INTO
684     config.org_unit_setting_type ( name, label, description, datatype )
685     VALUES (
686         'circ.claim_return.mark_missing',
687         'Claim Return: Mark copy as missing', 
688         'When a circ is marked as claims-returned, also mark the copy as missing',
689         'bool'
690     );
691
692 -- claims never checked out mark item missing 
693 INSERT INTO
694     config.org_unit_setting_type ( name, label, description, datatype )
695     VALUES (
696         'circ.claim_never_checked_out.mark_missing',
697         'Claim Never Checked Out: Mark copy as missing', 
698         'When a circ is marked as claims-never-checked-out, mark the copy as missing',
699         'bool'
700     );
701
702 -- mark damaged void overdue setting
703 INSERT INTO
704     config.org_unit_setting_type ( name, label, description, datatype )
705     VALUES (
706         'circ.damaged.void_ovedue',
707         'Mark item damaged voids overdues',
708         'When an item is marked damaged, overdue fines on the most recent circulation are voided.',
709         'bool'
710     );
711
712 -- hold cancel display limits
713 INSERT INTO config.org_unit_setting_type ( name, label, description, datatype )
714     VALUES (
715         'circ.holds.canceled.display_count',
716         'Holds: Canceled holds display count',
717         'How many canceled holds to show in patron holds interfaces',
718         'integer'
719     );
720
721 INSERT INTO config.org_unit_setting_type ( name, label, description, datatype )
722     VALUES (
723         'circ.holds.canceled.display_age',
724         'Holds: Canceled holds display age',
725         'Show all canceled holds that were canceled within this amount of time',
726         'interval'
727     );
728
729 INSERT INTO config.org_unit_setting_type ( name, label, description, datatype )
730     VALUES (
731         'circ.holds.uncancel.reset_request_time',
732         'Holds: Reset request time on un-cancel',
733         'When a holds is uncanceled, reset the request time to push it to the end of the queue',
734         'bool'
735     );
736
737 INSERT INTO config.org_unit_setting_type (name, label, description, datatype)
738     VALUES (
739         'circ.holds.default_shelf_expire_interval',
740         'Default hold shelf expire interval',
741         '',
742         'interval'
743 );
744
745 INSERT INTO config.org_unit_setting_type (name, label, description, datatype, fm_class)
746     VALUES (
747         'circ.claim_return.copy_status', 
748         'Claim Return Copy Status', 
749         'Claims returned copies are put into this status.  Default is to leave the copy in the Checked Out status',
750         'link', 
751         'ccs' 
752     );
753
754 INSERT INTO config.org_unit_setting_type ( name, label, description, datatype ) 
755     VALUES ( 
756         'circ.max_fine.cap_at_price',
757         oils_i18n_gettext('circ.max_fine.cap_at_price', 'Circ: Cap Max Fine at Item Price', 'coust', 'label'),
758         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'),
759         'bool' 
760     );
761
762 INSERT INTO config.org_unit_setting_type ( name, label, description, datatype, fm_class ) 
763     VALUES ( 
764         'circ.holds.clear_shelf.copy_status',
765         oils_i18n_gettext('circ.holds.clear_shelf.copy_status', 'Holds: Clear shelf copy status', 'coust', 'label'),
766         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'),
767         'link',
768         'ccs'
769     );
770
771 INSERT INTO config.org_unit_setting_type ( name, label, description, datatype )
772     VALUES ( 
773         'circ.selfcheck.workstation_required',
774         oils_i18n_gettext('circ.selfcheck.workstation_required', 'Selfcheck: Workstation Required', 'coust', 'label'),
775         oils_i18n_gettext('circ.selfcheck.workstation_required', 'All selfcheck stations must use a workstation', 'coust', 'description'),
776         'bool'
777     ), (
778         'circ.selfcheck.patron_password_required',
779         oils_i18n_gettext('circ.selfcheck.patron_password_required', 'Selfcheck: Require Patron Password', 'coust', 'label'),
780         oils_i18n_gettext('circ.selfcheck.patron_password_required', 'Patron must log in with barcode and password at selfcheck station', 'coust', 'description'),
781         'bool'
782     );
783
784 INSERT INTO config.org_unit_setting_type ( name, label, description, datatype )
785     VALUES ( 
786         'circ.selfcheck.alert.sound',
787         oils_i18n_gettext('circ.selfcheck.alert.sound', 'Selfcheck: Audio Alerts', 'coust', 'label'),
788         oils_i18n_gettext('circ.selfcheck.alert.sound', 'Use audio alerts for selfcheck events', 'coust', 'description'),
789         'bool'
790     );
791
792 INSERT INTO
793     config.org_unit_setting_type (name, label, description, datatype)
794     VALUES (
795         'notice.telephony.callfile_lines',
796         'Telephony: Arbitrary line(s) to include in each notice callfile',
797         $$
798         This overrides lines from opensrf.xml.
799         Line(s) must be valid for your target server and platform
800         (e.g. Asterisk 1.4).
801         $$,
802         'string'
803     );
804
805 INSERT INTO config.org_unit_setting_type ( name, label, description, datatype )
806     VALUES ( 
807         'circ.offline.username_allowed',
808         oils_i18n_gettext('circ.offline.username_allowed', 'Offline: Patron Usernames Allowed', 'coust', 'label'),
809         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'),
810         'bool'
811     );
812
813 INSERT INTO config.org_unit_setting_type ( name, label, description, datatype )
814 VALUES (
815     'acq.fund.balance_limit.warn',
816     oils_i18n_gettext('acq.fund.balance_limit.warn', 'Fund Spending Limit for Warning', 'coust', 'label'),
817     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'),
818     'integer'
819 );
820
821 INSERT INTO config.org_unit_setting_type ( name, label, description, datatype )
822 VALUES (
823     'acq.fund.balance_limit.block',
824     oils_i18n_gettext('acq.fund.balance_limit.block', 'Fund Spending Limit for Block', 'coust', 'label'),
825     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'),
826     'integer'
827 );
828
829 INSERT INTO config.org_unit_setting_type ( name, label, description, datatype )
830     VALUES (
831         'circ.holds.hold_has_copy_at.alert',
832         oils_i18n_gettext('circ.holds.hold_has_copy_at.alert', 'Holds: Has Local Copy Alert', 'coust', 'label'),
833         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'),
834         'bool'
835     ),(
836         'circ.holds.hold_has_copy_at.block',
837         oils_i18n_gettext('circ.holds.hold_has_copy_at.block', 'Holds: Has Local Copy Block', 'coust', 'label'),
838         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'),
839         'bool'
840     );
841
842 INSERT INTO config.org_unit_setting_type ( name, label, description, datatype )
843 VALUES (
844     'auth.persistent_login_interval',
845     oils_i18n_gettext('auth.persistent_login_interval', 'Persistent Login Duration', 'coust', 'label'),
846     oils_i18n_gettext('auth.persistent_login_interval', 'How long a persistent login lasts.  E.g. ''2 weeks''', 'coust', 'description'),
847     'interval'
848 );
849
850 INSERT INTO config.org_unit_setting_type ( name, label, description, datatype ) VALUES (
851         'cat.marc_control_number_identifier',
852         oils_i18n_gettext(
853             'cat.marc_control_number_identifier', 
854             'Cat: Defines the control number identifier used in 003 and 035 fields.', 
855             'coust', 
856             'label'),
857         oils_i18n_gettext(
858             'cat.marc_control_number_identifier', 
859             'Cat: Defines the control number identifier used in 003 and 035 fields.', 
860             'coust', 
861             'description'),
862         'string'
863 );
864
865 INSERT INTO config.org_unit_setting_type (name, label, description, datatype) 
866     VALUES (
867         'circ.selfcheck.block_checkout_on_copy_status',
868         oils_i18n_gettext(
869             'circ.selfcheck.block_checkout_on_copy_status',
870             'Selfcheck: Block copy checkout status',
871             'coust',
872             'label'
873         ),
874         oils_i18n_gettext(
875             'circ.selfcheck.block_checkout_on_copy_status',
876             'List of copy status IDs that will block checkout even if the generic COPY_NOT_AVAILABLE event is overridden',
877             'coust',
878             'description'
879         ),
880         'array'
881     );
882
883 INSERT INTO config.org_unit_setting_type ( name, label, description, datatype, fm_class )
884 VALUES (
885     'serial.prev_issuance_copy_location',
886     oils_i18n_gettext(
887         'serial.prev_issuance_copy_location',
888         'Serials: Previous Issuance Copy Location',
889         'coust',
890         'label'
891     ),
892     oils_i18n_gettext(
893         'serial.prev_issuance_copy_location',
894         'When a serial issuance is received, copies (units) of the previous issuance will be automatically moved into the configured shelving location',
895         'coust',
896         'descripton'
897         ),
898     'link',
899     'acpl'
900 );
901
902 INSERT INTO config.org_unit_setting_type ( name, label, description, datatype, fm_class )
903 VALUES (
904     'cat.default_classification_scheme',
905     oils_i18n_gettext(
906         'cat.default_classification_scheme',
907         'Cataloging: Default Classification Scheme',
908         'coust',
909         'label'
910     ),
911     oils_i18n_gettext(
912         'cat.default_classification_scheme',
913         'Defines the default classification scheme for new call numbers: 1 = Generic; 2 = Dewey; 3 = LC',
914         'coust',
915         'descripton'
916         ),
917     'link',
918     'acnc'
919 );
920
921 INSERT INTO config.org_unit_setting_type ( name, label, description, datatype ) VALUES (
922         'opac.org_unit_hiding.depth',
923         oils_i18n_gettext(
924             'opac.org_unit_hiding.depth',
925             'OPAC: Org Unit Hiding Depth', 
926             'coust', 
927             'label'),
928         oils_i18n_gettext(
929             'opac.org_unit_hiding.depth',
930             '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.', 
931             'coust', 
932             'description'),
933         'integer'
934 );
935
936 INSERT INTO config.org_unit_setting_type (name, label, description, datatype)
937     VALUES 
938         ('circ.holds.alert_if_local_avail',
939          'Holds: Local available alert',
940          'If local copy is available, alert the person making the hold',
941          'bool'),
942
943         ('circ.holds.deny_if_local_avail',
944          'Holds: Local available block',
945          'If local copy is available, deny the creation of the hold',
946          'bool')
947     ;
948
949 INSERT INTO config.org_unit_setting_type ( name, label, description, datatype )
950     VALUES ( 
951         'circ.holds.clear_shelf.no_capture_holds',
952         oils_i18n_gettext('circ.holds.clear_shelf.no_capture_holds', 'Holds: Bypass hold capture during clear shelf process', 'coust', 'label'),
953         oils_i18n_gettext('circ.holds.clear_shelf.no_capture_holds', 'During the clear shelf process, avoid capturing new holds on cleared items.', 'coust', 'description'),
954         'bool'
955     );
956
957 INSERT INTO config.org_unit_setting_type (name, label, description, datatype) VALUES (
958     'circ.booking_reservation.stop_circ',
959     'Disallow circulation of items when they are on booking reserve and that reserve overlaps with the checkout period',
960     '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.',
961     'bool'
962 );
963
964 ---------------------------------
965 -- Seed data for usr_setting_type
966 ----------------------------------
967
968 INSERT INTO config.usr_setting_type (name,opac_visible,label,description,datatype)
969     VALUES ('opac.default_font', TRUE, 'OPAC Font Size', 'OPAC Font Size', 'string');
970
971 INSERT INTO config.usr_setting_type (name,opac_visible,label,description,datatype)
972     VALUES ('opac.default_search_depth', TRUE, 'OPAC Search Depth', 'OPAC Search Depth', 'integer');
973
974 INSERT INTO config.usr_setting_type (name,opac_visible,label,description,datatype)
975     VALUES ('opac.default_search_location', TRUE, 'OPAC Search Location', 'OPAC Search Location', 'integer');
976
977 INSERT INTO config.usr_setting_type (name,opac_visible,label,description,datatype)
978     VALUES ('opac.hits_per_page', TRUE, 'Hits per Page', 'Hits per Page', 'string');
979
980 INSERT INTO config.usr_setting_type (name,opac_visible,label,description,datatype)
981     VALUES ('opac.hold_notify', TRUE, 'Hold Notification Format', 'Hold Notification Format', 'string');
982
983 INSERT INTO config.usr_setting_type (name,opac_visible,label,description,datatype)
984     VALUES ('staff_client.catalog.record_view.default', TRUE, 'Default Record View', 'Default Record View', 'string');
985
986 INSERT INTO config.usr_setting_type (name,opac_visible,label,description,datatype)
987     VALUES ('staff_client.copy_editor.templates', TRUE, 'Copy Editor Template', 'Copy Editor Template', 'object');
988
989 INSERT INTO config.usr_setting_type (name,opac_visible,label,description,datatype)
990     VALUES ('circ.holds_behind_desk', FALSE, 'Hold is behind Circ Desk', 'Hold is behind Circ Desk', 'bool');
991
992 INSERT INTO config.usr_setting_type (name,opac_visible,label,description,datatype)
993     VALUES (
994         'history.circ.retention_age',
995         TRUE,
996         oils_i18n_gettext('history.circ.retention_age','Historical Circulation Retention Age','cust','label'),
997         oils_i18n_gettext('history.circ.retention_age','Historical Circulation Retention Age','cust','description'),
998         'interval'
999     ),(
1000         'history.circ.retention_start',
1001         FALSE,
1002         oils_i18n_gettext('history.circ.retention_start','Historical Circulation Retention Start Date','cust','label'),
1003         oils_i18n_gettext('history.circ.retention_start','Historical Circulation Retention Start Date','cust','description'),
1004         'date'
1005     );
1006
1007 INSERT INTO config.usr_setting_type (name,opac_visible,label,description,datatype)
1008     VALUES (
1009         'history.hold.retention_age',
1010         TRUE,
1011         oils_i18n_gettext('history.hold.retention_age','Historical Hold Retention Age','cust','label'),
1012         oils_i18n_gettext('history.hold.retention_age','Historical Hold Retention Age','cust','description'),
1013         'interval'
1014     ),(
1015         'history.hold.retention_start',
1016         TRUE,
1017         oils_i18n_gettext('history.hold.retention_start','Historical Hold Retention Start Date','cust','label'),
1018         oils_i18n_gettext('history.hold.retention_start','Historical Hold Retention Start Date','cust','description'),
1019         'interval'
1020     ),(
1021         'history.hold.retention_count',
1022         TRUE,
1023         oils_i18n_gettext('history.hold.retention_count','Historical Hold Retention Count','cust','label'),
1024         oils_i18n_gettext('history.hold.retention_count','Historical Hold Retention Count','cust','description'),
1025         'integer'
1026     );
1027
1028 INSERT INTO config.usr_setting_type (name, opac_visible, label, description, datatype)
1029     VALUES (
1030         'opac.default_sort',
1031         TRUE,
1032         oils_i18n_gettext(
1033             'opac.default_sort',
1034             'OPAC Default Search Sort',
1035             'cust',
1036             'label'
1037         ),
1038         oils_i18n_gettext(
1039             'opac.default_sort',
1040             'OPAC Default Search Sort',
1041             'cust',
1042             'description'
1043         ),
1044         'string'
1045     );
1046
1047 INSERT INTO config.org_unit_setting_type ( name, label, description, datatype, fm_class ) VALUES (
1048         'circ.missing_pieces.copy_status',
1049         oils_i18n_gettext(
1050             'circ.missing_pieces.copy_status',
1051             'Circulation: Item Status for Missing Pieces',
1052             'coust',
1053             'label'),
1054         oils_i18n_gettext(
1055             'circ.missing_pieces.copy_status',
1056             '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.',
1057             'coust',
1058             'description'),
1059         'link',
1060         'ccs'
1061 );
1062
1063 INSERT INTO config.org_unit_setting_type ( name, label, description, datatype ) VALUES (
1064         'circ.do_not_tally_claims_returned',
1065         oils_i18n_gettext(
1066             'circ.do_not_tally_claims_returned',
1067             'Circulation: Do not include outstanding Claims Returned circulations in lump sum tallies in Patron Display.',
1068             'coust',
1069             'label'),
1070         oils_i18n_gettext(
1071             'circ.do_not_tally_claims_returned',
1072             '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.',
1073             'coust',
1074             'description'),
1075         'bool'
1076 );
1077
1078 INSERT INTO config.org_unit_setting_type (name, label, description, datatype)
1079     VALUES
1080         ('cat.label.font.size',
1081             oils_i18n_gettext('cat.label.font.size',
1082                 'Cataloging: Spine and pocket label font size', 'coust', 'label'),
1083             oils_i18n_gettext('cat.label.font.size',
1084                 'Set the default font size for spine and pocket labels', 'coust', 'description'),
1085             'integer'
1086         )
1087         ,('cat.label.font.family',
1088             oils_i18n_gettext('cat.label.font.family',
1089                 'Cataloging: Spine and pocket label font family', 'coust', 'label'),
1090             oils_i18n_gettext('cat.label.font.family',
1091                 '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".',
1092                 'coust', 'description'),
1093             'string'
1094         )
1095         ,('cat.spine.line.width',
1096             oils_i18n_gettext('cat.spine.line.width',
1097                 'Cataloging: Spine label line width', 'coust', 'label'),
1098             oils_i18n_gettext('cat.spine.line.width',
1099                 'Set the default line width for spine labels in number of characters. This specifies the boundary at which lines must be wrapped.',
1100                 'coust', 'description'),
1101             'integer'
1102         )
1103         ,('cat.spine.line.height',
1104             oils_i18n_gettext('cat.spine.line.height',
1105                 'Cataloging: Spine label maximum lines', 'coust', 'label'),
1106             oils_i18n_gettext('cat.spine.line.height',
1107                 'Set the default maximum number of lines for spine labels.',
1108                 'coust', 'description'),
1109             'integer'
1110         )
1111         ,('cat.spine.line.margin',
1112             oils_i18n_gettext('cat.spine.line.margin',
1113                 'Cataloging: Spine label left margin', 'coust', 'label'),
1114             oils_i18n_gettext('cat.spine.line.margin',
1115                 'Set the left margin for spine labels in number of characters.',
1116                 'coust', 'description'),
1117             'integer'
1118         )
1119 ;
1120
1121 INSERT INTO config.org_unit_setting_type (name, label, description, datatype)
1122     VALUES
1123         ('cat.label.font.weight',
1124             oils_i18n_gettext('cat.label.font.weight',
1125                 'Cataloging: Spine and pocket label font weight', 'coust', 'label'),
1126             oils_i18n_gettext('cat.label.font.weight',
1127                 'Set the preferred font weight for spine and pocket labels. You can specify "normal", "bold", "bolder", or "lighter".',
1128                 'coust', 'description'),
1129             'string'
1130         )
1131 ;
1132
1133 INSERT INTO config.org_unit_setting_type ( name, label, description, datatype ) VALUES (
1134         'circ.patron_edit.clone.copy_address',
1135         oils_i18n_gettext(
1136             'circ.patron_edit.clone.copy_address',
1137             'Patron Registration: Cloned patrons get address copy',
1138             'coust',
1139             'label'
1140         ),
1141         oils_i18n_gettext(
1142             'circ.patron_edit.clone.copy_address',
1143             'In the Patron editor, copy addresses from the cloned user instead of linking directly to the address',
1144             'coust',
1145             'description'
1146         ),
1147         'bool'
1148 );
1149
1150 -- Patch the name of an old selfcheck setting
1151 UPDATE actor.org_unit_setting
1152     SET name = 'circ.selfcheck.alert.popup'
1153     WHERE name = 'circ.selfcheck.alert_on_checkout_event';
1154
1155 -- Rename certain existing org_unit settings, if present,
1156 -- and make sure their values are JSON
1157 UPDATE actor.org_unit_setting SET
1158     name = 'circ.holds.default_estimated_wait_interval',
1159     --
1160     -- The value column should be JSON.  The old value should be a number,
1161     -- but it may or may not be quoted.  The following CASE behaves
1162     -- differently depending on whether value is quoted.  It is simplistic,
1163     -- and will be defeated by leading or trailing white space, or various
1164     -- malformations.
1165     --
1166     value = CASE WHEN SUBSTR( value, 1, 1 ) = '"'
1167                 THEN '"' || SUBSTR( value, 2, LENGTH(value) - 2 ) || ' days"'
1168                 ELSE '"' || value || ' days"'
1169             END
1170 WHERE name = 'circ.hold_estimate_wait_interval';
1171
1172 -- Create types for existing org unit settings
1173 -- not otherwise accounted for
1174
1175 INSERT INTO config.org_unit_setting_type(
1176  name,
1177  label,
1178  description
1179 )
1180 SELECT DISTINCT
1181         name,
1182         name,
1183         'FIXME'
1184 FROM
1185         actor.org_unit_setting
1186 WHERE
1187         name NOT IN (
1188                 SELECT name
1189                 FROM config.org_unit_setting_type
1190         );
1191
1192 -- Add foreign key to org_unit_setting
1193
1194 ALTER TABLE actor.org_unit_setting
1195         ADD FOREIGN KEY (name) REFERENCES config.org_unit_setting_type (name)
1196                 DEFERRABLE INITIALLY DEFERRED;
1197
1198 -- Create types for existing user settings
1199 -- not otherwise accounted for
1200
1201 INSERT INTO config.usr_setting_type (
1202         name,
1203         label,
1204         description
1205 )
1206 SELECT DISTINCT
1207         name,
1208         name,
1209         'FIXME'
1210 FROM
1211         actor.usr_setting
1212 WHERE
1213         name NOT IN (
1214                 SELECT name
1215                 FROM config.usr_setting_type
1216         );
1217
1218 -- Add foreign key to user_setting_type
1219
1220 ALTER TABLE actor.usr_setting
1221         ADD FOREIGN KEY (name) REFERENCES config.usr_setting_type (name)
1222                 ON DELETE CASCADE ON UPDATE CASCADE
1223                 DEFERRABLE INITIALLY DEFERRED;
1224
1225 INSERT INTO actor.org_unit_setting (org_unit, name, value) VALUES
1226     (1, 'cat.spine.line.margin', 0)
1227     ,(1, 'cat.spine.line.height', 9)
1228     ,(1, 'cat.spine.line.width', 8)
1229     ,(1, 'cat.label.font.family', '"monospace"')
1230     ,(1, 'cat.label.font.size', 10)
1231     ,(1, 'cat.label.font.weight', '"normal"')
1232 ;
1233
1234 ALTER TABLE action_trigger.event_definition ADD COLUMN granularity TEXT;
1235 ALTER TABLE action_trigger.event ADD COLUMN async_output BIGINT REFERENCES action_trigger.event_output (id);
1236 ALTER TABLE action_trigger.event_definition ADD COLUMN usr_field TEXT;
1237 ALTER TABLE action_trigger.event_definition ADD COLUMN opt_in_setting TEXT REFERENCES config.usr_setting_type (name) DEFERRABLE INITIALLY DEFERRED;
1238
1239 CREATE OR REPLACE FUNCTION is_json( TEXT ) RETURNS BOOL AS $f$
1240     use JSON::XS;
1241     my $json = shift();
1242     eval { JSON::XS->new->allow_nonref->decode( $json ) };
1243     return $@ ? 0 : 1;
1244 $f$ LANGUAGE PLPERLU;
1245
1246 ALTER TABLE action_trigger.event ADD COLUMN user_data TEXT CHECK (user_data IS NULL OR is_json( user_data ));
1247
1248 INSERT INTO action_trigger.hook (key,core_type,description) VALUES (
1249     'hold_request.cancel.expire_no_target',
1250     'ahr',
1251     'A hold is cancelled because no copies were found'
1252 );
1253
1254 INSERT INTO action_trigger.hook (key,core_type,description) VALUES (
1255     'hold_request.cancel.expire_holds_shelf',
1256     'ahr',
1257     'A hold is cancelled becuase it was on the holds shelf too long'
1258 );
1259
1260 INSERT INTO action_trigger.hook (key,core_type,description) VALUES (
1261     'hold_request.cancel.staff',
1262     'ahr',
1263     'A hold is cancelled becuase it was cancelled by staff'
1264 );
1265
1266 INSERT INTO action_trigger.hook (key,core_type,description) VALUES (
1267     'hold_request.cancel.patron',
1268     'ahr',
1269     'A hold is cancelled by the patron'
1270 );
1271
1272 -- Fix typos in descriptions
1273 UPDATE action_trigger.hook SET description = 'A hold is successfully placed' WHERE key = 'hold_request.success';
1274 UPDATE action_trigger.hook SET description = 'A hold is attempted but not successfully placed' WHERE key = 'hold_request.failure';
1275
1276 -- Add a hook for renewals
1277 INSERT INTO action_trigger.hook (key,core_type,description) VALUES ('renewal','circ','Item renewed to user');
1278
1279 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');
1280
1281 -- Sample Pre-due Notice --
1282
1283 INSERT INTO action_trigger.event_definition (id, active, owner, name, hook, validator, reactor, delay, delay_field, group_field, template) 
1284     VALUES (6, 'f', 1, '3 Day Courtesy Notice', 'checkout.due', 'CircIsOpen', 'SendEmail', '-3 days', 'due_date', 'usr', 
1285 $$
1286 [%- USE date -%]
1287 [%- user = target.0.usr -%]
1288 To: [%- params.recipient_email || user.email %]
1289 From: [%- params.sender_email || default_sender %]
1290 Subject: Courtesy Notice
1291
1292 Dear [% user.family_name %], [% user.first_given_name %]
1293 As a reminder, the following items are due in 3 days.
1294
1295 [% FOR circ IN target %]
1296     Title: [% circ.target_copy.call_number.record.simple_record.title %] 
1297     Barcode: [% circ.target_copy.barcode %] 
1298     Due: [% date.format(helpers.format_date(circ.due_date), '%Y-%m-%d') %]
1299     Item Cost: [% helpers.get_copy_price(circ.target_copy) %]
1300     Library: [% circ.circ_lib.name %]
1301     Library Phone: [% circ.circ_lib.phone %]
1302 [% END %]
1303
1304 $$);
1305
1306 INSERT INTO action_trigger.environment (event_def, path) VALUES 
1307     (6, 'target_copy.call_number.record.simple_record'),
1308     (6, 'usr'),
1309     (6, 'circ_lib.billing_address');
1310
1311 INSERT INTO action_trigger.event_params (event_def, param, value) VALUES
1312     (6, 'max_delay_age', '"1 day"');
1313
1314 -- also add the max delay age to the default overdue notice event def
1315 INSERT INTO action_trigger.event_params (event_def, param, value) VALUES
1316     (1, 'max_delay_age', '"1 day"');
1317   
1318 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');
1319
1320 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.');
1321
1322 INSERT INTO action_trigger.hook (
1323         key,
1324         core_type,
1325         description,
1326         passive
1327     ) VALUES (
1328         'hold_request.shelf_expires_soon',
1329         'ahr',
1330         'A hold on the shelf will expire there soon.',
1331         TRUE
1332     );
1333
1334 INSERT INTO action_trigger.event_definition (
1335         id,
1336         active,
1337         owner,
1338         name,
1339         hook,
1340         validator,
1341         reactor,
1342         delay,
1343         delay_field,
1344         group_field,
1345         template
1346     ) VALUES (
1347         7,
1348         FALSE,
1349         1,
1350         'Hold Expires from Shelf Soon',
1351         'hold_request.shelf_expires_soon',
1352         'HoldIsAvailable',
1353         'SendEmail',
1354         '- 1 DAY',
1355         'shelf_expire_time',
1356         'usr',
1357 $$
1358 [%- USE date -%]
1359 [%- user = target.0.usr -%]
1360 To: [%- params.recipient_email || user.email %]
1361 From: [%- params.sender_email || default_sender %]
1362 Subject: Hold Available Notification
1363
1364 Dear [% user.family_name %], [% user.first_given_name %]
1365 You requested holds on the following item(s), which are available for
1366 pickup, but these holds will soon expire.
1367
1368 [% FOR hold IN target %]
1369     [%- data = helpers.get_copy_bib_basics(hold.current_copy.id) -%]
1370     Title: [% data.title %]
1371     Author: [% data.author %]
1372     Library: [% hold.pickup_lib.name %]
1373 [% END %]
1374 $$
1375     );
1376
1377 INSERT INTO action_trigger.environment (
1378         event_def,
1379         path
1380     ) VALUES
1381     ( 7, 'current_copy'),
1382     ( 7, 'pickup_lib.billing_address'),
1383     ( 7, 'usr');
1384
1385 INSERT INTO action_trigger.hook (
1386         key,
1387         core_type,
1388         description,
1389         passive
1390     ) VALUES (
1391         'hold_request.long_wait',
1392         'ahr',
1393         'A patron has been waiting on a hold to be fulfilled for a long time.',
1394         TRUE
1395     );
1396
1397 INSERT INTO action_trigger.event_definition (
1398         id,
1399         active,
1400         owner,
1401         name,
1402         hook,
1403         validator,
1404         reactor,
1405         delay,
1406         delay_field,
1407         group_field,
1408         template
1409     ) VALUES (
1410         9,
1411         FALSE,
1412         1,
1413         'Hold waiting for pickup for long time',
1414         'hold_request.long_wait',
1415         'NOOP_True',
1416         'SendEmail',
1417         '6 MONTHS',
1418         'request_time',
1419         'usr',
1420 $$
1421 [%- USE date -%]
1422 [%- user = target.0.usr -%]
1423 To: [%- params.recipient_email || user.email %]
1424 From: [%- params.sender_email || default_sender %]
1425 Subject: Long Wait Hold Notification
1426
1427 Dear [% user.family_name %], [% user.first_given_name %]
1428
1429 You requested hold(s) on the following item(s), but unfortunately
1430 we have not been able to fulfill your request after a considerable
1431 length of time.  If you would still like to recieve these items,
1432 no action is required.
1433
1434 [% FOR hold IN target %]
1435     Title: [% hold.bib_rec.bib_record.simple_record.title %]
1436     Author: [% hold.bib_rec.bib_record.simple_record.author %]
1437 [% END %]
1438 $$
1439 );
1440
1441 INSERT INTO action_trigger.environment (
1442         event_def,
1443         path
1444     ) VALUES
1445     (9, 'pickup_lib'),
1446     (9, 'usr'),
1447     (9, 'bib_rec.bib_record.simple_record');
1448
1449 INSERT INTO action_trigger.hook (key, core_type, description, passive) 
1450     VALUES (
1451         'format.selfcheck.checkout',
1452         'circ',
1453         'Formats circ objects for self-checkout receipt',
1454         TRUE
1455     );
1456
1457 INSERT INTO action_trigger.event_definition (id, active, owner, name, hook, validator, reactor, group_field, granularity, template )
1458     VALUES (
1459         10,
1460         TRUE,
1461         1,
1462         'Self-Checkout Receipt',
1463         'format.selfcheck.checkout',
1464         'NOOP_True',
1465         'ProcessTemplate',
1466         'usr',
1467         'print-on-demand',
1468 $$
1469 [%- USE date -%]
1470 [%- SET user = target.0.usr -%]
1471 [%- SET lib = target.0.circ_lib -%]
1472 [%- SET lib_addr = target.0.circ_lib.billing_address -%]
1473 [%- SET hours = lib.hours_of_operation -%]
1474 <div>
1475     <style> li { padding: 8px; margin 5px; }</style>
1476     <div>[% date.format %]</div>
1477     <div>[% lib.name %]</div>
1478     <div>[% lib_addr.street1 %] [% lib_addr.street2 %]</div>
1479     <div>[% lib_addr.city %], [% lib_addr.state %] [% lb_addr.post_code %]</div>
1480     <div>[% lib.phone %]</div>
1481     <br/>
1482
1483     [% user.family_name %], [% user.first_given_name %]
1484     <ol>
1485     [% FOR circ IN target %]
1486         [%-
1487             SET idx = loop.count - 1;
1488             SET udata =  user_data.$idx
1489         -%]
1490         <li>
1491             <div>[% helpers.get_copy_bib_basics(circ.target_copy.id).title %]</div>
1492             <div>Barcode: [% circ.target_copy.barcode %]</div>
1493             [% IF udata.renewal_failure %]
1494                 <div style='color:red;'>Renewal Failed</div>
1495             [% ELSE %]
1496                 <div>Due Date: [% date.format(helpers.format_date(circ.due_date), '%Y-%m-%d') %]</div>
1497             [% END %]
1498         </li>
1499     [% END %]
1500     </ol>
1501     
1502     <div>
1503         Library Hours
1504         [%- BLOCK format_time; date.format(time _ ' 1/1/1000', format='%I:%M %p'); END -%]
1505         <div>
1506             Monday 
1507             [% PROCESS format_time time = hours.dow_0_open %] 
1508             [% PROCESS format_time time = hours.dow_0_close %] 
1509         </div>
1510         <div>
1511             Tuesday 
1512             [% PROCESS format_time time = hours.dow_1_open %] 
1513             [% PROCESS format_time time = hours.dow_1_close %] 
1514         </div>
1515         <div>
1516             Wednesday 
1517             [% PROCESS format_time time = hours.dow_2_open %] 
1518             [% PROCESS format_time time = hours.dow_2_close %] 
1519         </div>
1520         <div>
1521             Thursday
1522             [% PROCESS format_time time = hours.dow_3_open %] 
1523             [% PROCESS format_time time = hours.dow_3_close %] 
1524         </div>
1525         <div>
1526             Friday
1527             [% PROCESS format_time time = hours.dow_4_open %] 
1528             [% PROCESS format_time time = hours.dow_4_close %] 
1529         </div>
1530         <div>
1531             Saturday
1532             [% PROCESS format_time time = hours.dow_5_open %] 
1533             [% PROCESS format_time time = hours.dow_5_close %] 
1534         </div>
1535         <div>
1536             Sunday 
1537             [% PROCESS format_time time = hours.dow_6_open %] 
1538             [% PROCESS format_time time = hours.dow_6_close %] 
1539         </div>
1540     </div>
1541 </div>
1542 $$
1543 );
1544
1545 INSERT INTO action_trigger.environment ( event_def, path) VALUES
1546     ( 10, 'target_copy'),
1547     ( 10, 'circ_lib.billing_address'),
1548     ( 10, 'circ_lib.hours_of_operation'),
1549     ( 10, 'usr');
1550
1551 INSERT INTO action_trigger.hook (key, core_type, description, passive) 
1552     VALUES (
1553         'format.selfcheck.items_out',
1554         'circ',
1555         'Formats items out for self-checkout receipt',
1556         TRUE
1557     );
1558
1559 INSERT INTO action_trigger.event_definition (id, active, owner, name, hook, validator, reactor, group_field, granularity, template )
1560     VALUES (
1561         11,
1562         TRUE,
1563         1,
1564         'Self-Checkout Items Out Receipt',
1565         'format.selfcheck.items_out',
1566         'NOOP_True',
1567         'ProcessTemplate',
1568         'usr',
1569         'print-on-demand',
1570 $$
1571 [%- USE date -%]
1572 [%- SET user = target.0.usr -%]
1573 <div>
1574     <style> li { padding: 8px; margin 5px; }</style>
1575     <div>[% date.format %]</div>
1576     <br/>
1577
1578     [% user.family_name %], [% user.first_given_name %]
1579     <ol>
1580     [% FOR circ IN target %]
1581         <li>
1582             <div>[% helpers.get_copy_bib_basics(circ.target_copy.id).title %]</div>
1583             <div>Barcode: [% circ.target_copy.barcode %]</div>
1584             <div>Due Date: [% date.format(helpers.format_date(circ.due_date), '%Y-%m-%d') %]</div>
1585         </li>
1586     [% END %]
1587     </ol>
1588 </div>
1589 $$
1590 );
1591
1592
1593 INSERT INTO action_trigger.environment ( event_def, path) VALUES
1594     ( 11, 'target_copy'),
1595     ( 11, 'circ_lib.billing_address'),
1596     ( 11, 'circ_lib.hours_of_operation'),
1597     ( 11, 'usr');
1598
1599 INSERT INTO action_trigger.hook (key, core_type, description, passive) 
1600     VALUES (
1601         'format.selfcheck.holds',
1602         'ahr',
1603         'Formats holds for self-checkout receipt',
1604         TRUE
1605     );
1606
1607 INSERT INTO action_trigger.event_definition (id, active, owner, name, hook, validator, reactor, group_field, granularity, template )
1608     VALUES (
1609         12,
1610         TRUE,
1611         1,
1612         'Self-Checkout Holds Receipt',
1613         'format.selfcheck.holds',
1614         'NOOP_True',
1615         'ProcessTemplate',
1616         'usr',
1617         'print-on-demand',
1618 $$
1619 [%- USE date -%]
1620 [%- SET user = target.0.usr -%]
1621 <div>
1622     <style> li { padding: 8px; margin 5px; }</style>
1623     <div>[% date.format %]</div>
1624     <br/>
1625
1626     [% user.family_name %], [% user.first_given_name %]
1627     <ol>
1628     [% FOR hold IN target %]
1629         [%-
1630             SET idx = loop.count - 1;
1631             SET udata =  user_data.$idx
1632         -%]
1633         <li>
1634             <div>Title: [% hold.bib_rec.bib_record.simple_record.title %]</div>
1635             <div>Author: [% hold.bib_rec.bib_record.simple_record.author %]</div>
1636             <div>Pickup Location: [% hold.pickup_lib.name %]</div>
1637             <div>Status: 
1638                 [%- IF udata.ready -%]
1639                     Ready for pickup
1640                 [% ELSE %]
1641                     #[% udata.queue_position %] of [% udata.potential_copies %] copies.
1642                 [% END %]
1643             </div>
1644         </li>
1645     [% END %]
1646     </ol>
1647 </div>
1648 $$
1649 );
1650
1651
1652 INSERT INTO action_trigger.environment ( event_def, path) VALUES
1653     ( 12, 'bib_rec.bib_record.simple_record'),
1654     ( 12, 'pickup_lib'),
1655     ( 12, 'usr');
1656
1657 INSERT INTO action_trigger.hook (key, core_type, description, passive) 
1658     VALUES (
1659         'format.selfcheck.fines',
1660         'au',
1661         'Formats fines for self-checkout receipt',
1662         TRUE
1663     );
1664
1665 INSERT INTO action_trigger.event_definition (id, active, owner, name, hook, validator, reactor, granularity, template )
1666     VALUES (
1667         13,
1668         TRUE,
1669         1,
1670         'Self-Checkout Fines Receipt',
1671         'format.selfcheck.fines',
1672         'NOOP_True',
1673         'ProcessTemplate',
1674         'print-on-demand',
1675 $$
1676 [%- USE date -%]
1677 [%- SET user = target -%]
1678 <div>
1679     <style> li { padding: 8px; margin 5px; }</style>
1680     <div>[% date.format %]</div>
1681     <br/>
1682
1683     [% user.family_name %], [% user.first_given_name %]
1684     <ol>
1685     [% FOR xact IN user.open_billable_transactions_summary %]
1686         <li>
1687             <div>Details: 
1688                 [% IF xact.xact_type == 'circulation' %]
1689                     [%- helpers.get_copy_bib_basics(xact.circulation.target_copy).title -%]
1690                 [% ELSE %]
1691                     [%- xact.last_billing_type -%]
1692                 [% END %]
1693             </div>
1694             <div>Total Billed: [% xact.total_owed %]</div>
1695             <div>Total Paid: [% xact.total_paid %]</div>
1696             <div>Balance Owed : [% xact.balance_owed %]</div>
1697         </li>
1698     [% END %]
1699     </ol>
1700 </div>
1701 $$
1702 );
1703
1704 INSERT INTO action_trigger.environment ( event_def, path) VALUES
1705     ( 13, 'open_billable_transactions_summary.circulation' );
1706
1707 INSERT INTO action_trigger.reactor (module,description) VALUES
1708 (   'SendFile',
1709     oils_i18n_gettext(
1710         'SendFile',
1711         '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.',
1712         'atreact',
1713         'description'
1714     )
1715 );
1716
1717 INSERT INTO action_trigger.hook (key, core_type, description, passive) 
1718     VALUES (
1719         'format.acqli.html',
1720         'jub',
1721         'Formats lineitem worksheet for titles received',
1722         TRUE
1723     );
1724
1725 INSERT INTO action_trigger.event_definition (id, active, owner, name, hook, validator, reactor, granularity, template)
1726     VALUES (
1727         14,
1728         TRUE,
1729         1,
1730         'Lineitem Worksheet',
1731         'format.acqli.html',
1732         'NOOP_True',
1733         'ProcessTemplate',
1734         'print-on-demand',
1735 $$
1736 [%- USE date -%]
1737 [%- SET li = target; -%]
1738 <div class="wrapper">
1739     <div class="summary" style='font-size:110%; font-weight:bold;'>
1740
1741         <div>Title: [% helpers.get_li_attr("title", "", li.attributes) %]</div>
1742         <div>Author: [% helpers.get_li_attr("author", "", li.attributes) %]</div>
1743         <div class="count">Item Count: [% li.lineitem_details.size %]</div>
1744         <div class="lineid">Lineitem ID: [% li.id %]</div>
1745
1746         [% IF li.distribution_formulas.size > 0 %]
1747             [% SET forms = [] %]
1748             [% FOREACH form IN li.distribution_formulas; forms.push(form.formula.name); END %]
1749             <div>Distribution Formulas: [% forms.join(',') %]</div>
1750         [% END %]
1751
1752         [% IF li.lineitem_notes.size > 0 %]
1753             Lineitem Notes:
1754             <ul>
1755                 [%- FOR note IN li.lineitem_notes -%]
1756                     <li>
1757                     [% IF note.alert_text %]
1758                         [% note.alert_text.code -%] 
1759                         [% IF note.value -%]
1760                             : [% note.value %]
1761                         [% END %]
1762                     [% ELSE %]
1763                         [% note.value -%] 
1764                     [% END %]
1765                     </li>
1766                 [% END %]
1767             </ul>
1768         [% END %]
1769     </div>
1770     <br/>
1771     <table>
1772         <thead>
1773             <tr>
1774                 <th>Branch</th>
1775                 <th>Barcode</th>
1776                 <th>Call Number</th>
1777                 <th>Fund</th>
1778                 <th>Recd.</th>
1779                 <th>Notes</th>
1780             </tr>
1781         </thead>
1782         <tbody>
1783         [% FOREACH detail IN li.lineitem_details.sort('owning_lib') %]
1784             [% 
1785                 IF copy.eg_copy_id;
1786                     SET copy = copy.eg_copy_id;
1787                     SET cn_label = copy.call_number.label;
1788                 ELSE; 
1789                     SET copy = detail; 
1790                     SET cn_label = detail.cn_label;
1791                 END 
1792             %]
1793             <tr>
1794                 <!-- acq.lineitem_detail.id = [%- detail.id -%] -->
1795                 <td style='padding:5px;'>[% detail.owning_lib.shortname %]</td>
1796                 <td style='padding:5px;'>[% IF copy.barcode   %]<span class="barcode"  >[% detail.barcode   %]</span>[% END %]</td>
1797                 <td style='padding:5px;'>[% IF cn_label %]<span class="cn_label" >[% cn_label  %]</span>[% END %]</td>
1798                 <td style='padding:5px;'>[% IF detail.fund %]<span class="fund">[% detail.fund.code %] ([% detail.fund.year %])</span>[% END %]</td>
1799                 <td style='padding:5px;'>[% IF detail.recv_time %]<span class="recv_time">[% detail.recv_time %]</span>[% END %]</td>
1800                 <td style='padding:5px;'>[% detail.note %]</td>
1801             </tr>
1802         [% END %]
1803         </tbody>
1804     </table>
1805 </div>
1806 $$
1807 );
1808
1809
1810 INSERT INTO action_trigger.environment (event_def, path) VALUES
1811     ( 14, 'attributes' ),
1812     ( 14, 'lineitem_details' ),
1813     ( 14, 'lineitem_details.owning_lib' ),
1814     ( 14, 'lineitem_notes' )
1815 ;
1816
1817 INSERT INTO action_trigger.hook (key,core_type,description,passive) VALUES (
1818         'aur.ordered',
1819         'aur', 
1820         oils_i18n_gettext(
1821             'aur.ordered',
1822             'A patron acquisition request has been marked On-Order.',
1823             'ath',
1824             'description'
1825         ), 
1826         TRUE
1827     ), (
1828         'aur.received', 
1829         'aur', 
1830         oils_i18n_gettext(
1831             'aur.received', 
1832             'A patron acquisition request has been marked Received.',
1833             'ath',
1834             'description'
1835         ),
1836         TRUE
1837     ), (
1838         'aur.cancelled',
1839         'aur',
1840         oils_i18n_gettext(
1841             'aur.cancelled',
1842             'A patron acquisition request has been marked Cancelled.',
1843             'ath',
1844             'description'
1845         ),
1846         TRUE
1847     )
1848 ;
1849
1850 INSERT INTO action_trigger.validator (module,description) VALUES (
1851         'Acq::UserRequestOrdered',
1852         oils_i18n_gettext(
1853             'Acq::UserRequestOrdered',
1854             'Tests to see if the corresponding Line Item has a state of "on-order".',
1855             'atval',
1856             'description'
1857         )
1858     ), (
1859         'Acq::UserRequestReceived',
1860         oils_i18n_gettext(
1861             'Acq::UserRequestReceived',
1862             'Tests to see if the corresponding Line Item has a state of "received".',
1863             'atval',
1864             'description'
1865         )
1866     ), (
1867         'Acq::UserRequestCancelled',
1868         oils_i18n_gettext(
1869             'Acq::UserRequestCancelled',
1870             'Tests to see if the corresponding Line Item has a state of "cancelled".',
1871             'atval',
1872             'description'
1873         )
1874     )
1875 ;
1876
1877 -- What was event_definition #15 in v1.6.1 will be recreated as #20.  This
1878 -- renumbering requires some juggling:
1879 --
1880 -- 1. Update any child rows to point to #20.  These updates will temporarily
1881 -- violate foreign key constraints, but that's okay as long as we create
1882 -- #20 before committing.
1883 --
1884 -- 2. Delete the old #15.
1885 --
1886 -- 3. Insert the new #15.
1887 --
1888 -- 4. Insert #20.
1889 --
1890 -- We could combine steps 2 and 3 into a single update, but that would create
1891 -- additional opportunities for typos, since we already have the insert from
1892 -- an upgrade script.
1893
1894 UPDATE action_trigger.environment
1895 SET event_def = 20
1896 WHERE event_def = 15;
1897
1898 UPDATE action_trigger.event
1899 SET event_def = 20
1900 WHERE event_def = 15;
1901
1902 UPDATE action_trigger.event_params
1903 SET event_def = 20
1904 WHERE event_def = 15;
1905
1906 DELETE FROM action_trigger.event_definition
1907 WHERE id = 15;
1908
1909 INSERT INTO action_trigger.event_definition (
1910         id,
1911         active,
1912         owner,
1913         name,
1914         hook,
1915         validator,
1916         reactor,
1917         template
1918     ) VALUES (
1919         15,
1920         FALSE,
1921         1,
1922         'Email Notice: Patron Acquisition Request marked On-Order.',
1923         'aur.ordered',
1924         'Acq::UserRequestOrdered',
1925         'SendEmail',
1926 $$
1927 [%- USE date -%]
1928 [%- SET li = target.lineitem; -%]
1929 [%- SET user = target.usr -%]
1930 [%- SET title = helpers.get_li_attr("title", "", li.attributes) -%]
1931 [%- SET author = helpers.get_li_attr("author", "", li.attributes) -%]
1932 [%- SET edition = helpers.get_li_attr("edition", "", li.attributes) -%]
1933 [%- SET isbn = helpers.get_li_attr("isbn", "", li.attributes) -%]
1934 [%- SET publisher = helpers.get_li_attr("publisher", "", li.attributes) -%]
1935 [%- SET pubdate = helpers.get_li_attr("pubdate", "", li.attributes) -%]
1936
1937 To: [%- params.recipient_email || user.email %]
1938 From: [%- params.sender_email || default_sender %]
1939 Subject: Acquisition Request Notification
1940
1941 Dear [% user.family_name %], [% user.first_given_name %]
1942 Our records indicate the following acquisition request has been placed on order.
1943
1944 Title: [% title %]
1945 [% IF author %]Author: [% author %][% END %]
1946 [% IF edition %]Edition: [% edition %][% END %]
1947 [% IF isbn %]ISBN: [% isbn %][% END %]
1948 [% IF publisher %]Publisher: [% publisher %][% END %]
1949 [% IF pubdate %]Publication Date: [% pubdate %][% END %]
1950 Lineitem ID: [% li.id %]
1951 $$
1952     ), (
1953         16,
1954         FALSE,
1955         1,
1956         'Email Notice: Patron Acquisition Request marked Received.',
1957         'aur.received',
1958         'Acq::UserRequestReceived',
1959         'SendEmail',
1960 $$
1961 [%- USE date -%]
1962 [%- SET li = target.lineitem; -%]
1963 [%- SET user = target.usr -%]
1964 [%- SET title = helpers.get_li_attr("title", "", li.attributes) %]
1965 [%- SET author = helpers.get_li_attr("author", "", li.attributes) %]
1966 [%- SET edition = helpers.get_li_attr("edition", "", li.attributes) %]
1967 [%- SET isbn = helpers.get_li_attr("isbn", "", li.attributes) %]
1968 [%- SET publisher = helpers.get_li_attr("publisher", "", li.attributes) -%]
1969 [%- SET pubdate = helpers.get_li_attr("pubdate", "", li.attributes) -%]
1970
1971 To: [%- params.recipient_email || user.email %]
1972 From: [%- params.sender_email || default_sender %]
1973 Subject: Acquisition Request Notification
1974
1975 Dear [% user.family_name %], [% user.first_given_name %]
1976 Our records indicate the materials for the following acquisition request have been received.
1977
1978 Title: [% title %]
1979 [% IF author %]Author: [% author %][% END %]
1980 [% IF edition %]Edition: [% edition %][% END %]
1981 [% IF isbn %]ISBN: [% isbn %][% END %]
1982 [% IF publisher %]Publisher: [% publisher %][% END %]
1983 [% IF pubdate %]Publication Date: [% pubdate %][% END %]
1984 Lineitem ID: [% li.id %]
1985 $$
1986     ), (
1987         17,
1988         FALSE,
1989         1,
1990         'Email Notice: Patron Acquisition Request marked Cancelled.',
1991         'aur.cancelled',
1992         'Acq::UserRequestCancelled',
1993         'SendEmail',
1994 $$
1995 [%- USE date -%]
1996 [%- SET li = target.lineitem; -%]
1997 [%- SET user = target.usr -%]
1998 [%- SET title = helpers.get_li_attr("title", "", li.attributes) %]
1999 [%- SET author = helpers.get_li_attr("author", "", li.attributes) %]
2000 [%- SET edition = helpers.get_li_attr("edition", "", li.attributes) %]
2001 [%- SET isbn = helpers.get_li_attr("isbn", "", li.attributes) %]
2002 [%- SET publisher = helpers.get_li_attr("publisher", "", li.attributes) -%]
2003 [%- SET pubdate = helpers.get_li_attr("pubdate", "", li.attributes) -%]
2004
2005 To: [%- params.recipient_email || user.email %]
2006 From: [%- params.sender_email || default_sender %]
2007 Subject: Acquisition Request Notification
2008
2009 Dear [% user.family_name %], [% user.first_given_name %]
2010 Our records indicate the following acquisition request has been cancelled.
2011
2012 Title: [% title %]
2013 [% IF author %]Author: [% author %][% END %]
2014 [% IF edition %]Edition: [% edition %][% END %]
2015 [% IF isbn %]ISBN: [% isbn %][% END %]
2016 [% IF publisher %]Publisher: [% publisher %][% END %]
2017 [% IF pubdate %]Publication Date: [% pubdate %][% END %]
2018 Lineitem ID: [% li.id %]
2019 $$
2020     );
2021
2022 INSERT INTO action_trigger.environment (
2023         event_def,
2024         path
2025     ) VALUES 
2026         ( 15, 'lineitem' ),
2027         ( 15, 'lineitem.attributes' ),
2028         ( 15, 'usr' ),
2029
2030         ( 16, 'lineitem' ),
2031         ( 16, 'lineitem.attributes' ),
2032         ( 16, 'usr' ),
2033
2034         ( 17, 'lineitem' ),
2035         ( 17, 'lineitem.attributes' ),
2036         ( 17, 'usr' )
2037     ;
2038
2039 INSERT INTO action_trigger.event_definition
2040 (id, active, owner, name, hook, validator, reactor, cleanup_success, cleanup_failure, delay, delay_field, group_field, template) VALUES
2041 (23, true, 1, 'PO JEDI', 'acqpo.activated', 'Acq::PurchaseOrderEDIRequired', 'GeneratePurchaseOrderJEDI', NULL, NULL, '00:05:00', NULL, NULL,
2042 $$[%- USE date -%]
2043 [%# start JEDI document -%]
2044 [%- BLOCK big_block -%]
2045 {
2046    "recipient":"[% target.provider.san %]",
2047    "sender":"[% target.ordering_agency.mailing_address.san %]",
2048    "body": [{
2049      "ORDERS":[ "order", {
2050         "po_number":[% target.id %],
2051         "date":"[% date.format(date.now, '%Y%m%d') %]",
2052         "buyer":[{
2053             [%- IF target.provider.edi_default.vendcode -%]
2054                 "id":"[% target.ordering_agency.mailing_address.san _ ' ' _ target.provider.edi_default.vendcode %]", 
2055                 "id-qualifier": 91
2056             [%- ELSE -%]
2057                 "id":"[% target.ordering_agency.mailing_address.san %]"
2058             [%- END  -%]
2059         }],
2060         "vendor":[ 
2061             [%- # target.provider.name (target.provider.id) -%]
2062             "[% target.provider.san %]",
2063             {"id-qualifier": 92, "id":"[% target.provider.id %]"}
2064         ],
2065         "currency":"[% target.provider.currency_type %]",
2066         "items":[
2067         [% FOR li IN target.lineitems %]
2068         {
2069             "identifiers":[   [%-# li.isbns = helpers.get_li_isbns(li.attributes) %]
2070             [% FOR isbn IN helpers.get_li_isbns(li.attributes) -%]
2071                 [% IF isbn.length == 13 -%]
2072                 {"id-qualifier":"EN","id":"[% isbn %]"},
2073                 [% ELSE -%]
2074                 {"id-qualifier":"IB","id":"[% isbn %]"},
2075                 [%- END %]
2076             [% END %]
2077                 {"id-qualifier":"SA","id":"[% li.id %]"}
2078             ],
2079             "price":[% li.estimated_unit_price || '0.00' %],
2080             "desc":[
2081                 {"BTI":"[% helpers.get_li_attr('title',     '', li.attributes) %]"}, 
2082                 {"BPU":"[% helpers.get_li_attr('publisher', '', li.attributes) %]"},
2083                 {"BPD":"[% helpers.get_li_attr('pubdate',   '', li.attributes) %]"},
2084                 {"BPH":"[% helpers.get_li_attr('pagination','', li.attributes) %]"}
2085             ],
2086             "quantity":[% li.lineitem_details.size %]
2087         }[% UNLESS loop.last %],[% END %]
2088         [%-# TODO: lineitem details (later) -%]
2089         [% END %]
2090         ],
2091         "line_items":[% target.lineitems.size %]
2092      }]  [% # close ORDERS array %]
2093    }]    [% # close  body  array %]
2094 }
2095 [% END %]
2096 [% tempo = PROCESS big_block; helpers.escape_json(tempo) %]
2097 $$
2098 );
2099
2100 INSERT INTO action_trigger.environment (event_def, path) VALUES 
2101   (23, 'lineitems.attributes'), 
2102   (23, 'lineitems.lineitem_details'), 
2103   (23, 'lineitems.lineitem_notes'), 
2104   (23, 'ordering_agency.mailing_address'), 
2105   (23, 'provider');
2106
2107 UPDATE action_trigger.event_definition SET template = 
2108 $$
2109 [%- USE date -%]
2110 [%-
2111     # find a lineitem attribute by name and optional type
2112     BLOCK get_li_attr;
2113         FOR attr IN li.attributes;
2114             IF attr.attr_name == attr_name;
2115                 IF !attr_type OR attr_type == attr.attr_type;
2116                     attr.attr_value;
2117                     LAST;
2118                 END;
2119             END;
2120         END;
2121     END
2122 -%]
2123
2124 <h2>Purchase Order [% target.id %]</h2>
2125 <br/>
2126 date <b>[% date.format(date.now, '%Y%m%d') %]</b>
2127 <br/>
2128
2129 <style>
2130     table td { padding:5px; border:1px solid #aaa;}
2131     table { width:95%; border-collapse:collapse; }
2132     #vendor-notes { padding:5px; border:1px solid #aaa; }
2133 </style>
2134 <table id='vendor-table'>
2135   <tr>
2136     <td valign='top'>Vendor</td>
2137     <td>
2138       <div>[% target.provider.name %]</div>
2139       <div>[% target.provider.addresses.0.street1 %]</div>
2140       <div>[% target.provider.addresses.0.street2 %]</div>
2141       <div>[% target.provider.addresses.0.city %]</div>
2142       <div>[% target.provider.addresses.0.state %]</div>
2143       <div>[% target.provider.addresses.0.country %]</div>
2144       <div>[% target.provider.addresses.0.post_code %]</div>
2145     </td>
2146     <td valign='top'>Ship to / Bill to</td>
2147     <td>
2148       <div>[% target.ordering_agency.name %]</div>
2149       <div>[% target.ordering_agency.billing_address.street1 %]</div>
2150       <div>[% target.ordering_agency.billing_address.street2 %]</div>
2151       <div>[% target.ordering_agency.billing_address.city %]</div>
2152       <div>[% target.ordering_agency.billing_address.state %]</div>
2153       <div>[% target.ordering_agency.billing_address.country %]</div>
2154       <div>[% target.ordering_agency.billing_address.post_code %]</div>
2155     </td>
2156   </tr>
2157 </table>
2158
2159 <br/><br/>
2160 <fieldset id='vendor-notes'>
2161     <legend>Notes to the Vendor</legend>
2162     <ul>
2163     [% FOR note IN target.notes %]
2164         [% IF note.vendor_public == 't' %]
2165             <li>[% note.value %]</li>
2166         [% END %]
2167     [% END %]
2168     </ul>
2169 </fieldset>
2170 <br/><br/>
2171
2172 <table>
2173   <thead>
2174     <tr>
2175       <th>PO#</th>
2176       <th>ISBN or Item #</th>
2177       <th>Title</th>
2178       <th>Quantity</th>
2179       <th>Unit Price</th>
2180       <th>Line Total</th>
2181       <th>Notes</th>
2182     </tr>
2183   </thead>
2184   <tbody>
2185
2186   [% subtotal = 0 %]
2187   [% FOR li IN target.lineitems %]
2188
2189   <tr>
2190     [% count = li.lineitem_details.size %]
2191     [% price = li.estimated_unit_price %]
2192     [% litotal = (price * count) %]
2193     [% subtotal = subtotal + litotal %]
2194     [% isbn = PROCESS get_li_attr attr_name = 'isbn' %]
2195     [% ident = PROCESS get_li_attr attr_name = 'identifier' %]
2196
2197     <td>[% target.id %]</td>
2198     <td>[% isbn || ident %]</td>
2199     <td>[% PROCESS get_li_attr attr_name = 'title' %]</td>
2200     <td>[% count %]</td>
2201     <td>[% price %]</td>
2202     <td>[% litotal %]</td>
2203     <td>
2204         <ul>
2205         [% FOR note IN li.lineitem_notes %]
2206             [% IF note.vendor_public == 't' %]
2207                 <li>[% note.value %]</li>
2208             [% END %]
2209         [% END %]
2210         </ul>
2211     </td>
2212   </tr>
2213   [% END %]
2214   <tr>
2215     <td/><td/><td/><td/>
2216     <td>Sub Total</td>
2217     <td>[% subtotal %]</td>
2218   </tr>
2219   </tbody>
2220 </table>
2221
2222 <br/>
2223
2224 Total Line Item Count: [% target.lineitems.size %]
2225 $$
2226 WHERE id = 4;
2227
2228 INSERT INTO action_trigger.environment (event_def, path) VALUES 
2229     (4, 'lineitems.lineitem_notes'),
2230     (4, 'notes');
2231
2232 INSERT INTO action_trigger.environment (event_def, path) VALUES
2233     ( 14, 'lineitem_notes.alert_text' ),
2234     ( 14, 'distribution_formulas.formula' ),
2235     ( 14, 'lineitem_details.fund' ),
2236     ( 14, 'lineitem_details.eg_copy_id' ),
2237     ( 14, 'lineitem_details.eg_copy_id.call_number' )
2238 ;
2239
2240 INSERT INTO action_trigger.hook (key,core_type,description,passive) VALUES (
2241         'aur.created',
2242         'aur',
2243         oils_i18n_gettext(
2244             'aur.created',
2245             'A patron has made an acquisitions request.',
2246             'ath',
2247             'description'
2248         ),
2249         TRUE
2250     ), (
2251         'aur.rejected',
2252         'aur',
2253         oils_i18n_gettext(
2254             'aur.rejected',
2255             'A patron acquisition request has been rejected.',
2256             'ath',
2257             'description'
2258         ),
2259         TRUE
2260     )
2261 ;
2262
2263 INSERT INTO action_trigger.event_definition (
2264         id,
2265         active,
2266         owner,
2267         name,
2268         hook,
2269         validator,
2270         reactor,
2271         template
2272     ) VALUES (
2273         18,
2274         FALSE,
2275         1,
2276         'Email Notice: Acquisition Request created.',
2277         'aur.created',
2278         'NOOP_True',
2279         'SendEmail',
2280 $$
2281 [%- USE date -%]
2282 [%- SET user = target.usr -%]
2283 [%- SET title = target.title -%]
2284 [%- SET author = target.author -%]
2285 [%- SET isxn = target.isxn -%]
2286 [%- SET publisher = target.publisher -%]
2287 [%- SET pubdate = target.pubdate -%]
2288
2289 To: [%- params.recipient_email || user.email %]
2290 From: [%- params.sender_email || default_sender %]
2291 Subject: Acquisition Request Notification
2292
2293 Dear [% user.family_name %], [% user.first_given_name %]
2294 Our records indicate that you have made the following acquisition request:
2295
2296 Title: [% title %]
2297 [% IF author %]Author: [% author %][% END %]
2298 [% IF edition %]Edition: [% edition %][% END %]
2299 [% IF isbn %]ISXN: [% isxn %][% END %]
2300 [% IF publisher %]Publisher: [% publisher %][% END %]
2301 [% IF pubdate %]Publication Date: [% pubdate %][% END %]
2302 $$
2303     ), (
2304         19,
2305         FALSE,
2306         1,
2307         'Email Notice: Acquisition Request Rejected.',
2308         'aur.rejected',
2309         'NOOP_True',
2310         'SendEmail',
2311 $$
2312 [%- USE date -%]
2313 [%- SET user = target.usr -%]
2314 [%- SET title = target.title -%]
2315 [%- SET author = target.author -%]
2316 [%- SET isxn = target.isxn -%]
2317 [%- SET publisher = target.publisher -%]
2318 [%- SET pubdate = target.pubdate -%]
2319 [%- SET cancel_reason = target.cancel_reason.description -%]
2320
2321 To: [%- params.recipient_email || user.email %]
2322 From: [%- params.sender_email || default_sender %]
2323 Subject: Acquisition Request Notification
2324
2325 Dear [% user.family_name %], [% user.first_given_name %]
2326 Our records indicate the following acquisition request has been rejected for this reason: [% cancel_reason %]
2327
2328 Title: [% title %]
2329 [% IF author %]Author: [% author %][% END %]
2330 [% IF edition %]Edition: [% edition %][% END %]
2331 [% IF isbn %]ISBN: [% isbn %][% END %]
2332 [% IF publisher %]Publisher: [% publisher %][% END %]
2333 [% IF pubdate %]Publication Date: [% pubdate %][% END %]
2334 $$
2335     );
2336
2337 INSERT INTO action_trigger.environment (
2338         event_def,
2339         path
2340     ) VALUES 
2341         ( 18, 'usr' ),
2342         ( 19, 'usr' ),
2343         ( 19, 'cancel_reason' )
2344     ;
2345
2346 INSERT INTO action_trigger.event_definition (id, active, owner, name, hook, validator, reactor, delay, template) 
2347     VALUES (20, 'f', 1, 'Password reset request notification', 'password.reset_request', 'NOOP_True', 'SendEmail', '00:00:01',
2348 $$
2349 [%- USE date -%]
2350 [%- user = target.usr -%]
2351 To: [%- params.recipient_email || user.email %]
2352 From: [%- params.sender_email || user.home_ou.email || default_sender %]
2353 Subject: [% user.home_ou.name %]: library account password reset request
2354   
2355 You have received this message because you, or somebody else, requested a reset
2356 of your library system password. If you did not request a reset of your library
2357 system password, just ignore this message and your current password will
2358 continue to work.
2359
2360 If you did request a reset of your library system password, please perform
2361 the following steps to continue the process of resetting your password:
2362
2363 1. Open the following link in a web browser: https://[% params.hostname %]/opac/password/[% params.locale || 'en-US' %]/[% target.uuid %]
2364 The browser displays a password reset form.
2365
2366 2. Enter your new password in the password reset form in the browser. You must
2367 enter the password twice to ensure that you do not make a mistake. If the
2368 passwords match, you will then be able to log in to your library system account
2369 with the new password.
2370
2371 $$);
2372
2373 INSERT INTO action_trigger.hook (key, core_type, description, passive)
2374     VALUES (
2375         'format.acqcle.html',
2376         'acqcle',
2377         'Formats claim events into a voucher',
2378         TRUE
2379     );
2380
2381 INSERT INTO action_trigger.event_definition (
2382         id, active, owner, name, hook, group_field,
2383         validator, reactor, granularity, template
2384     ) VALUES (
2385         21,
2386         TRUE,
2387         1,
2388         'Claim Voucher',
2389         'format.acqcle.html',
2390         'claim',
2391         'NOOP_True',
2392         'ProcessTemplate',
2393         'print-on-demand',
2394 $$
2395 [%- USE date -%]
2396 [%- SET claim = target.0.claim -%]
2397 <!-- This will need refined/prettified. -->
2398 <div class="acq-claim-voucher">
2399     <h2>Claim: [% claim.id %] ([% claim.type.code %])</h2>
2400     <h3>Against: [%- helpers.get_li_attr("title", "", claim.lineitem_detail.lineitem.attributes) -%]</h3>
2401     <ul>
2402         [% FOR event IN target %]
2403         <li>
2404             Event type: [% event.type.code %]
2405             [% IF event.type.library_initiated %](Library initiated)[% END %]
2406             <br />
2407             Event date: [% event.event_date %]<br />
2408             Order date: [% event.claim.lineitem_detail.lineitem.purchase_order.order_date %]<br />
2409             Expected receive date: [% event.claim.lineitem_detail.lineitem.expected_recv_time %]<br />
2410             Initiated by: [% event.creator.family_name %], [% event.creator.first_given_name %] [% event.creator.second_given_name %]<br />
2411             Barcode: [% event.claim.lineitem_detail.barcode %]; Fund:
2412             [% event.claim.lineitem_detail.fund.code %]
2413             ([% event.claim.lineitem_detail.fund.year %])
2414         </li>
2415         [% END %]
2416     </ul>
2417 </div>
2418 $$
2419 );
2420
2421 INSERT INTO action_trigger.environment (event_def, path) VALUES
2422     (21, 'claim'),
2423     (21, 'claim.type'),
2424     (21, 'claim.lineitem_detail'),
2425     (21, 'claim.lineitem_detail.fund'),
2426     (21, 'claim.lineitem_detail.lineitem.attributes'),
2427     (21, 'claim.lineitem_detail.lineitem.purchase_order'),
2428     (21, 'creator'),
2429     (21, 'type')
2430 ;
2431
2432 INSERT INTO action_trigger.hook (key, core_type, description, passive)
2433     VALUES (
2434         'format.acqinv.html',
2435         'acqinv',
2436         'Formats invoices into a voucher',
2437         TRUE
2438     );
2439
2440 INSERT INTO action_trigger.event_definition (
2441         id, active, owner, name, hook,
2442         validator, reactor, granularity, template
2443     ) VALUES (
2444         22,
2445         TRUE,
2446         1,
2447         'Invoice',
2448         'format.acqinv.html',
2449         'NOOP_True',
2450         'ProcessTemplate',
2451         'print-on-demand',
2452 $$
2453 [% FILTER collapse %]
2454 [%- SET invoice = target -%]
2455 <!-- This lacks totals, info about funds (for invoice entries,
2456     funds are per-LID!), and general refinement -->
2457 <div class="acq-invoice-voucher">
2458     <h1>Invoice</h1>
2459     <div>
2460         <strong>No.</strong> [% invoice.inv_ident %]
2461         [% IF invoice.inv_type %]
2462             / <strong>Type:</strong>[% invoice.inv_type %]
2463         [% END %]
2464     </div>
2465     <div>
2466         <dl>
2467             [% BLOCK ent_with_address %]
2468             <dt>[% ent_label %]: [% ent.name %] ([% ent.code %])</dt>
2469             <dd>
2470                 [% IF ent.addresses.0 %]
2471                     [% SET addr = ent.addresses.0 %]
2472                     [% addr.street1 %]<br />
2473                     [% IF addr.street2 %][% addr.street2 %]<br />[% END %]
2474                     [% addr.city %],
2475                     [% IF addr.county %] [% addr.county %], [% END %]
2476                     [% IF addr.state %] [% addr.state %] [% END %]
2477                     [% IF addr.post_code %][% addr.post_code %][% END %]<br />
2478                     [% IF addr.country %] [% addr.country %] [% END %]
2479                 [% END %]
2480                 <p>
2481                     [% IF ent.phone %] Phone: [% ent.phone %]<br />[% END %]
2482                     [% IF ent.fax_phone %] Fax: [% ent.fax_phone %]<br />[% END %]
2483                     [% IF ent.url %] URL: [% ent.url %]<br />[% END %]
2484                     [% IF ent.email %] E-mail: [% ent.email %] [% END %]
2485                 </p>
2486             </dd>
2487             [% END %]
2488             [% INCLUDE ent_with_address
2489                 ent = invoice.provider
2490                 ent_label = "Provider" %]
2491             [% INCLUDE ent_with_address
2492                 ent = invoice.shipper
2493                 ent_label = "Shipper" %]
2494             <dt>Receiver</dt>
2495             <dd>
2496                 [% invoice.receiver.name %] ([% invoice.receiver.shortname %])
2497             </dd>
2498             <dt>Received</dt>
2499             <dd>
2500                 [% helpers.format_date(invoice.recv_date) %] by
2501                 [% invoice.recv_method %]
2502             </dd>
2503             [% IF invoice.note %]
2504                 <dt>Note</dt>
2505                 <dd>
2506                     [% invoice.note %]
2507                 </dd>
2508             [% END %]
2509         </dl>
2510     </div>
2511     <ul>
2512         [% FOR entry IN invoice.entries %]
2513             <li>
2514                 [% IF entry.lineitem %]
2515                     Title: [% helpers.get_li_attr(
2516                         "title", "", entry.lineitem.attributes
2517                     ) %]<br />
2518                     Author: [% helpers.get_li_attr(
2519                         "author", "", entry.lineitem.attributes
2520                     ) %]
2521                 [% END %]
2522                 [% IF entry.purchase_order %]
2523                     (PO: [% entry.purchase_order.name %])
2524                 [% END %]<br />
2525                 Invoice item count: [% entry.inv_item_count %]
2526                 [% IF entry.phys_item_count %]
2527                     / Physical item count: [% entry.phys_item_count %]
2528                 [% END %]
2529                 <br />
2530                 [% IF entry.cost_billed %]
2531                     Cost billed: [% entry.cost_billed %]
2532                     [% IF entry.billed_per_item %](per item)[% END %]
2533                     <br />
2534                 [% END %]
2535                 [% IF entry.actual_cost %]
2536                     Actual cost: [% entry.actual_cost %]<br />
2537                 [% END %]
2538                 [% IF entry.amount_paid %]
2539                     Amount paid: [% entry.amount_paid %]<br />
2540                 [% END %]
2541                 [% IF entry.note %]Note: [% entry.note %][% END %]
2542             </li>
2543         [% END %]
2544         [% FOR item IN invoice.items %]
2545             <li>
2546                 [% IF item.inv_item_type %]
2547                     Item Type: [% item.inv_item_type %]<br />
2548                 [% END %]
2549                 [% IF item.title %]Title/Description:
2550                     [% item.title %]<br />
2551                 [% END %]
2552                 [% IF item.author %]Author: [% item.author %]<br />[% END %]
2553                 [% IF item.purchase_order %]PO: [% item.purchase_order %]<br />[% END %]
2554                 [% IF item.note %]Note: [% item.note %]<br />[% END %]
2555                 [% IF item.cost_billed %]
2556                     Cost billed: [% item.cost_billed %]<br />
2557                 [% END %]
2558                 [% IF item.actual_cost %]
2559                     Actual cost: [% item.actual_cost %]<br />
2560                 [% END %]
2561                 [% IF item.amount_paid %]
2562                     Amount paid: [% item.amount_paid %]<br />
2563                 [% END %]
2564             </li>
2565         [% END %]
2566     </ul>
2567 </div>
2568 [% END %]
2569 $$
2570 );
2571
2572 INSERT INTO action_trigger.environment (event_def, path) VALUES
2573     (22, 'provider'),
2574     (22, 'provider.addresses'),
2575     (22, 'shipper'),
2576     (22, 'shipper.addresses'),
2577     (22, 'receiver'),
2578     (22, 'entries'),
2579     (22, 'entries.purchase_order'),
2580     (22, 'entries.lineitem'),
2581     (22, 'entries.lineitem.attributes'),
2582     (22, 'items')
2583 ;
2584
2585 INSERT INTO action_trigger.environment (event_def, path) VALUES 
2586   (23, 'provider.edi_default');
2587
2588 INSERT INTO action_trigger.validator (module, description) 
2589     VALUES (
2590         'Acq::PurchaseOrderEDIRequired',
2591         oils_i18n_gettext(
2592             'Acq::PurchaseOrderEDIRequired',
2593             'Purchase order is delivered via EDI',
2594             'atval',
2595             'description'
2596         )
2597     );
2598
2599 INSERT INTO action_trigger.reactor (module, description)
2600     VALUES (
2601         'GeneratePurchaseOrderJEDI',
2602         oils_i18n_gettext(
2603             'GeneratePurchaseOrderJEDI',
2604             'Creates purchase order JEDI (JSON EDI) for subsequent EDI processing',
2605             'atreact',
2606             'description'
2607         )
2608     );
2609
2610 UPDATE action_trigger.hook 
2611     SET 
2612         key = 'acqpo.activated', 
2613         passive = FALSE,
2614         description = oils_i18n_gettext(
2615             'acqpo.activated',
2616             'Purchase order was activated',
2617             'ath',
2618             'description'
2619         )
2620     WHERE key = 'format.po.jedi';
2621
2622 -- We just changed a key in action_trigger.hook.  Now redirect any
2623 -- child rows to point to the new key.  (There probably aren't any;
2624 -- this is just a precaution against possible local modifications.)
2625
2626 UPDATE action_trigger.event_definition
2627 SET hook = 'acqpo.activated'
2628 WHERE hook = 'format.po.jedi';
2629
2630 INSERT INTO action_trigger.reactor (module, description) VALUES (
2631     'AstCall', 'Possibly place a phone call with Asterisk'
2632 );
2633
2634 INSERT INTO
2635     action_trigger.event_definition (
2636         id, active, owner, name, hook, validator, reactor,
2637         cleanup_success, cleanup_failure, delay, delay_field, group_field,
2638         max_delay, granularity, usr_field, opt_in_setting, template
2639     ) VALUES (
2640         24,
2641         FALSE,
2642         1,
2643         'Telephone Overdue Notice',
2644         'checkout.due', 'NOOP_True', 'AstCall',
2645         DEFAULT, DEFAULT, '5 seconds', 'due_date', 'usr',
2646         DEFAULT, DEFAULT, DEFAULT, DEFAULT,
2647         $$
2648 [% phone = target.0.usr.day_phone | replace('[\s\-\(\)]', '') -%]
2649 [% IF phone.match('^[2-9]') %][% country = 1 %][% ELSE %][% country = '' %][% END -%]
2650 Channel: [% channel_prefix %]/[% country %][% phone %]
2651 Context: overdue-test
2652 MaxRetries: 1
2653 RetryTime: 60
2654 WaitTime: 30
2655 Extension: 10
2656 Archive: 1
2657 Set: eg_user_id=[% target.0.usr.id %]
2658 Set: items=[% target.size %]
2659 Set: titlestring=[% titles = [] %][% FOR circ IN target %][% titles.push(circ.target_copy.call_number.record.simple_record.title) %][% END %][% titles.join(". ") %]
2660 $$
2661     );
2662
2663 INSERT INTO
2664     action_trigger.environment (id, event_def, path)
2665     VALUES
2666         (DEFAULT, 24, 'target_copy.call_number.record.simple_record'),
2667         (DEFAULT, 24, 'usr')
2668     ;
2669
2670 INSERT INTO action_trigger.hook (key,core_type,description,passive) VALUES (
2671         'circ.format.history.email',
2672         'circ', 
2673         oils_i18n_gettext(
2674             'circ.format.history.email',
2675             'An email has been requested for a circ history.',
2676             'ath',
2677             'description'
2678         ), 
2679         FALSE
2680     )
2681     ,(
2682         'circ.format.history.print',
2683         'circ', 
2684         oils_i18n_gettext(
2685             'circ.format.history.print',
2686             'A circ history needs to be formatted for printing.',
2687             'ath',
2688             'description'
2689         ), 
2690         FALSE
2691     )
2692     ,(
2693         'ahr.format.history.email',
2694         'ahr', 
2695         oils_i18n_gettext(
2696             'ahr.format.history.email',
2697             'An email has been requested for a hold request history.',
2698             'ath',
2699             'description'
2700         ), 
2701         FALSE
2702     )
2703     ,(
2704         'ahr.format.history.print',
2705         'ahr', 
2706         oils_i18n_gettext(
2707             'ahr.format.history.print',
2708             'A hold request history needs to be formatted for printing.',
2709             'ath',
2710             'description'
2711         ), 
2712         FALSE
2713     )
2714
2715 ;
2716
2717 INSERT INTO action_trigger.event_definition (
2718         id,
2719         active,
2720         owner,
2721         name,
2722         hook,
2723         validator,
2724         reactor,
2725         group_field,
2726         granularity,
2727         template
2728     ) VALUES (
2729         25,
2730         TRUE,
2731         1,
2732         'circ.history.email',
2733         'circ.format.history.email',
2734         'NOOP_True',
2735         'SendEmail',
2736         'usr',
2737         NULL,
2738 $$
2739 [%- USE date -%]
2740 [%- SET user = target.0.usr -%]
2741 To: [%- params.recipient_email || user.email %]
2742 From: [%- params.sender_email || default_sender %]
2743 Subject: Circulation History
2744
2745     [% FOR circ IN target %]
2746             [% helpers.get_copy_bib_basics(circ.target_copy.id).title %]
2747             Barcode: [% circ.target_copy.barcode %]
2748             Checked Out: [% date.format(helpers.format_date(circ.xact_start), '%Y-%m-%d') %]
2749             Due Date: [% date.format(helpers.format_date(circ.due_date), '%Y-%m-%d') %]
2750             Returned: [% date.format(helpers.format_date(circ.checkin_time), '%Y-%m-%d') %]
2751     [% END %]
2752 $$
2753     )
2754     ,(
2755         26,
2756         TRUE,
2757         1,
2758         'circ.history.print',
2759         'circ.format.history.print',
2760         'NOOP_True',
2761         'ProcessTemplate',
2762         'usr',
2763         'print-on-demand',
2764 $$
2765 [%- USE date -%]
2766 <div>
2767     <style> li { padding: 8px; margin 5px; }</style>
2768     <div>[% date.format %]</div>
2769     <br/>
2770
2771     [% user.family_name %], [% user.first_given_name %]
2772     <ol>
2773     [% FOR circ IN target %]
2774         <li>
2775             <div>[% helpers.get_copy_bib_basics(circ.target_copy.id).title %]</div>
2776             <div>Barcode: [% circ.target_copy.barcode %]</div>
2777             <div>Checked Out: [% date.format(helpers.format_date(circ.xact_start), '%Y-%m-%d') %]</div>
2778             <div>Due Date: [% date.format(helpers.format_date(circ.due_date), '%Y-%m-%d') %]</div>
2779             <div>Returned: [% date.format(helpers.format_date(circ.checkin_time), '%Y-%m-%d') %]</div>
2780         </li>
2781     [% END %]
2782     </ol>
2783 </div>
2784 $$
2785     )
2786     ,(
2787         27,
2788         TRUE,
2789         1,
2790         'ahr.history.email',
2791         'ahr.format.history.email',
2792         'NOOP_True',
2793         'SendEmail',
2794         'usr',
2795         NULL,
2796 $$
2797 [%- USE date -%]
2798 [%- SET user = target.0.usr -%]
2799 To: [%- params.recipient_email || user.email %]
2800 From: [%- params.sender_email || default_sender %]
2801 Subject: Hold Request History
2802
2803     [% FOR hold IN target %]
2804             [% helpers.get_copy_bib_basics(hold.current_copy.id).title %]
2805             Requested: [% date.format(helpers.format_date(hold.request_time), '%Y-%m-%d') %]
2806             [% IF hold.fulfillment_time %]Fulfilled: [% date.format(helpers.format_date(hold.fulfillment_time), '%Y-%m-%d') %][% END %]
2807     [% END %]
2808 $$
2809     )
2810     ,(
2811         28,
2812         TRUE,
2813         1,
2814         'ahr.history.print',
2815         'ahr.format.history.print',
2816         'NOOP_True',
2817         'ProcessTemplate',
2818         'usr',
2819         'print-on-demand',
2820 $$
2821 [%- USE date -%]
2822 <div>
2823     <style> li { padding: 8px; margin 5px; }</style>
2824     <div>[% date.format %]</div>
2825     <br/>
2826
2827     [% user.family_name %], [% user.first_given_name %]
2828     <ol>
2829     [% FOR hold IN target %]
2830         <li>
2831             <div>[% helpers.get_copy_bib_basics(hold.current_copy.id).title %]</div>
2832             <div>Requested: [% date.format(helpers.format_date(hold.request_time), '%Y-%m-%d') %]</div>
2833             [% IF hold.fulfillment_time %]<div>Fulfilled: [% date.format(helpers.format_date(hold.fulfillment_time), '%Y-%m-%d') %]</div>[% END %]
2834         </li>
2835     [% END %]
2836     </ol>
2837 </div>
2838 $$
2839     )
2840
2841 ;
2842
2843 INSERT INTO action_trigger.environment (
2844         event_def,
2845         path
2846     ) VALUES 
2847          ( 25, 'target_copy')
2848         ,( 25, 'usr' )
2849         ,( 26, 'target_copy' )
2850         ,( 26, 'usr' )
2851         ,( 27, 'current_copy' )
2852         ,( 27, 'usr' )
2853         ,( 28, 'current_copy' )
2854         ,( 28, 'usr' )
2855 ;
2856
2857 INSERT INTO action_trigger.hook (key,core_type,description,passive) VALUES (
2858         'money.format.payment_receipt.email',
2859         'mp', 
2860         oils_i18n_gettext(
2861             'money.format.payment_receipt.email',
2862             'An email has been requested for a payment receipt.',
2863             'ath',
2864             'description'
2865         ), 
2866         FALSE
2867     )
2868     ,(
2869         'money.format.payment_receipt.print',
2870         'mp', 
2871         oils_i18n_gettext(
2872             'money.format.payment_receipt.print',
2873             'A payment receipt needs to be formatted for printing.',
2874             'ath',
2875             'description'
2876         ), 
2877         FALSE
2878     )
2879 ;
2880
2881 INSERT INTO action_trigger.event_definition (
2882         id,
2883         active,
2884         owner,
2885         name,
2886         hook,
2887         validator,
2888         reactor,
2889         group_field,
2890         granularity,
2891         template
2892     ) VALUES (
2893         29,
2894         TRUE,
2895         1,
2896         'money.payment_receipt.email',
2897         'money.format.payment_receipt.email',
2898         'NOOP_True',
2899         'SendEmail',
2900         'xact.usr',
2901         NULL,
2902 $$
2903 [%- USE date -%]
2904 [%- SET user = target.0.xact.usr -%]
2905 To: [%- params.recipient_email || user.email %]
2906 From: [%- params.sender_email || default_sender %]
2907 Subject: Payment Receipt
2908
2909 [% date.format -%]
2910 [%- SET xact_mp_hash = {} -%]
2911 [%- FOR mp IN target %][%# Template is hooked around payments, but let us make the receipt focused on transactions -%]
2912     [%- SET xact_id = mp.xact.id -%]
2913     [%- IF ! xact_mp_hash.defined( xact_id ) -%][%- xact_mp_hash.$xact_id = { 'xact' => mp.xact, 'payments' => [] } -%][%- END -%]
2914     [%- xact_mp_hash.$xact_id.payments.push(mp) -%]
2915 [%- END -%]
2916 [%- FOR xact_id IN xact_mp_hash.keys.sort -%]
2917     [%- SET xact = xact_mp_hash.$xact_id.xact %]
2918 Transaction ID: [% xact_id %]
2919     [% IF xact.circulation %][% helpers.get_copy_bib_basics(xact.circulation.target_copy).title %]
2920     [% ELSE %]Miscellaneous
2921     [% END %]
2922     Line item billings:
2923         [%- SET mb_type_hash = {} -%]
2924         [%- FOR mb IN xact.billings %][%# Group billings by their btype -%]
2925             [%- IF mb.voided == 'f' -%]
2926                 [%- SET mb_type = mb.btype.id -%]
2927                 [%- IF ! mb_type_hash.defined( mb_type ) -%][%- mb_type_hash.$mb_type = { 'sum' => 0.00, 'billings' => [] } -%][%- END -%]
2928                 [%- IF ! mb_type_hash.$mb_type.defined( 'first_ts' ) -%][%- mb_type_hash.$mb_type.first_ts = mb.billing_ts -%][%- END -%]
2929                 [%- mb_type_hash.$mb_type.last_ts = mb.billing_ts -%]
2930                 [%- mb_type_hash.$mb_type.sum = mb_type_hash.$mb_type.sum + mb.amount -%]
2931                 [%- mb_type_hash.$mb_type.billings.push( mb ) -%]
2932             [%- END -%]
2933         [%- END -%]
2934         [%- FOR mb_type IN mb_type_hash.keys.sort -%]
2935             [%- IF mb_type == 1 %][%-# Consolidated view of overdue billings -%]
2936                 $[% mb_type_hash.$mb_type.sum %] for [% mb_type_hash.$mb_type.billings.0.btype.name %] 
2937                     on [% mb_type_hash.$mb_type.first_ts %] through [% mb_type_hash.$mb_type.last_ts %]
2938             [%- ELSE -%][%# all other billings show individually %]
2939                 [% FOR mb IN mb_type_hash.$mb_type.billings %]
2940                     $[% mb.amount %] for [% mb.btype.name %] on [% mb.billing_ts %] [% mb.note %]
2941                 [% END %]
2942             [% END %]
2943         [% END %]
2944     Line item payments:
2945         [% FOR mp IN xact_mp_hash.$xact_id.payments %]
2946             Payment ID: [% mp.id %]
2947                 Paid [% mp.amount %] via [% SWITCH mp.payment_type -%]
2948                     [% CASE "cash_payment" %]cash
2949                     [% CASE "check_payment" %]check
2950                     [% CASE "credit_card_payment" %]credit card (
2951                         [%- SET cc_chunks = mp.credit_card_payment.cc_number.replace(' ','').chunk(4); -%]
2952                         [%- cc_chunks.slice(0, -1+cc_chunks.max).join.replace('\S','X') -%] 
2953                         [% cc_chunks.last -%]
2954                         exp [% mp.credit_card_payment.expire_month %]/[% mp.credit_card_payment.expire_year -%]
2955                     )
2956                     [% CASE "credit_payment" %]credit
2957                     [% CASE "forgive_payment" %]forgiveness
2958                     [% CASE "goods_payment" %]goods
2959                     [% CASE "work_payment" %]work
2960                 [%- END %] on [% mp.payment_ts %] [% mp.note %]
2961         [% END %]
2962 [% END %]
2963 $$
2964     )
2965     ,(
2966         30,
2967         TRUE,
2968         1,
2969         'money.payment_receipt.print',
2970         'money.format.payment_receipt.print',
2971         'NOOP_True',
2972         'ProcessTemplate',
2973         'xact.usr',
2974         'print-on-demand',
2975 $$
2976 [%- USE date -%][%- SET user = target.0.xact.usr -%]
2977 <div style="li { padding: 8px; margin 5px; }">
2978     <div>[% date.format %]</div><br/>
2979     <ol>
2980     [% SET xact_mp_hash = {} %]
2981     [% FOR mp IN target %][%# Template is hooked around payments, but let us make the receipt focused on transactions %]
2982         [% SET xact_id = mp.xact.id %]
2983         [% IF ! xact_mp_hash.defined( xact_id ) %][% xact_mp_hash.$xact_id = { 'xact' => mp.xact, 'payments' => [] } %][% END %]
2984         [% xact_mp_hash.$xact_id.payments.push(mp) %]
2985     [% END %]
2986     [% FOR xact_id IN xact_mp_hash.keys.sort %]
2987         [% SET xact = xact_mp_hash.$xact_id.xact %]
2988         <li>Transaction ID: [% xact_id %]
2989             [% IF xact.circulation %][% helpers.get_copy_bib_basics(xact.circulation.target_copy).title %]
2990             [% ELSE %]Miscellaneous
2991             [% END %]
2992             Line item billings:<ol>
2993                 [% SET mb_type_hash = {} %]
2994                 [% FOR mb IN xact.billings %][%# Group billings by their btype %]
2995                     [% IF mb.voided == 'f' %]
2996                         [% SET mb_type = mb.btype.id %]
2997                         [% IF ! mb_type_hash.defined( mb_type ) %][% mb_type_hash.$mb_type = { 'sum' => 0.00, 'billings' => [] } %][% END %]
2998                         [% IF ! mb_type_hash.$mb_type.defined( 'first_ts' ) %][% mb_type_hash.$mb_type.first_ts = mb.billing_ts %][% END %]
2999                         [% mb_type_hash.$mb_type.last_ts = mb.billing_ts %]
3000                         [% mb_type_hash.$mb_type.sum = mb_type_hash.$mb_type.sum + mb.amount %]
3001                         [% mb_type_hash.$mb_type.billings.push( mb ) %]
3002                     [% END %]
3003                 [% END %]
3004                 [% FOR mb_type IN mb_type_hash.keys.sort %]
3005                     <li>[% IF mb_type == 1 %][%# Consolidated view of overdue billings %]
3006                         $[% mb_type_hash.$mb_type.sum %] for [% mb_type_hash.$mb_type.billings.0.btype.name %] 
3007                             on [% mb_type_hash.$mb_type.first_ts %] through [% mb_type_hash.$mb_type.last_ts %]
3008                     [% ELSE %][%# all other billings show individually %]
3009                         [% FOR mb IN mb_type_hash.$mb_type.billings %]
3010                             $[% mb.amount %] for [% mb.btype.name %] on [% mb.billing_ts %] [% mb.note %]
3011                         [% END %]
3012                     [% END %]</li>
3013                 [% END %]
3014             </ol>
3015             Line item payments:<ol>
3016                 [% FOR mp IN xact_mp_hash.$xact_id.payments %]
3017                     <li>Payment ID: [% mp.id %]
3018                         Paid [% mp.amount %] via [% SWITCH mp.payment_type -%]
3019                             [% CASE "cash_payment" %]cash
3020                             [% CASE "check_payment" %]check
3021                             [% CASE "credit_card_payment" %]credit card (
3022                                 [%- SET cc_chunks = mp.credit_card_payment.cc_number.replace(' ','').chunk(4); -%]
3023                                 [%- cc_chunks.slice(0, -1+cc_chunks.max).join.replace('\S','X') -%] 
3024                                 [% cc_chunks.last -%]
3025                                 exp [% mp.credit_card_payment.expire_month %]/[% mp.credit_card_payment.expire_year -%]
3026                             )
3027                             [% CASE "credit_payment" %]credit
3028                             [% CASE "forgive_payment" %]forgiveness
3029                             [% CASE "goods_payment" %]goods
3030                             [% CASE "work_payment" %]work
3031                         [%- END %] on [% mp.payment_ts %] [% mp.note %]
3032                     </li>
3033                 [% END %]
3034             </ol>
3035         </li>
3036     [% END %]
3037     </ol>
3038 </div>
3039 $$
3040     )
3041 ;
3042
3043 INSERT INTO action_trigger.environment (
3044         event_def,
3045         path
3046     ) VALUES -- for fleshing mp objects
3047          ( 29, 'xact')
3048         ,( 29, 'xact.usr')
3049         ,( 29, 'xact.grocery' )
3050         ,( 29, 'xact.circulation' )
3051         ,( 29, 'xact.summary' )
3052         ,( 30, 'xact')
3053         ,( 30, 'xact.usr')
3054         ,( 30, 'xact.grocery' )
3055         ,( 30, 'xact.circulation' )
3056         ,( 30, 'xact.summary' )
3057 ;
3058
3059 INSERT INTO action_trigger.cleanup ( module, description ) VALUES (
3060     'DeleteTempBiblioBucket',
3061     oils_i18n_gettext(
3062         'DeleteTempBiblioBucket',
3063         'Deletes a cbreb object used as a target if it has a btype of "temp"',
3064         'atclean',
3065         'description'
3066     )
3067 );
3068
3069 INSERT INTO action_trigger.hook (key,core_type,description,passive) VALUES (
3070         'biblio.format.record_entry.email',
3071         'cbreb', 
3072         oils_i18n_gettext(
3073             'biblio.format.record_entry.email',
3074             'An email has been requested for one or more biblio record entries.',
3075             'ath',
3076             'description'
3077         ), 
3078         FALSE
3079     )
3080     ,(
3081         'biblio.format.record_entry.print',
3082         'cbreb', 
3083         oils_i18n_gettext(
3084             'biblio.format.record_entry.print',
3085             'One or more biblio record entries need to be formatted for printing.',
3086             'ath',
3087             'description'
3088         ), 
3089         FALSE
3090     )
3091 ;
3092
3093 INSERT INTO action_trigger.event_definition (
3094         id,
3095         active,
3096         owner,
3097         name,
3098         hook,
3099         validator,
3100         reactor,
3101         cleanup_success,
3102         cleanup_failure,
3103         group_field,
3104         granularity,
3105         template
3106     ) VALUES (
3107         31,
3108         TRUE,
3109         1,
3110         'biblio.record_entry.email',
3111         'biblio.format.record_entry.email',
3112         'NOOP_True',
3113         'SendEmail',
3114         'DeleteTempBiblioBucket',
3115         'DeleteTempBiblioBucket',
3116         'owner',
3117         NULL,
3118 $$
3119 [%- USE date -%]
3120 [%- SET user = target.0.owner -%]
3121 To: [%- params.recipient_email || user.email %]
3122 From: [%- params.sender_email || default_sender %]
3123 Subject: Bibliographic Records
3124
3125     [% FOR cbreb IN target %]
3126     [% FOR cbrebi IN cbreb.items %]
3127         Bib ID# [% cbrebi.target_biblio_record_entry.id %] ISBN: [% crebi.target_biblio_record_entry.simple_record.isbn %]
3128         Title: [% cbrebi.target_biblio_record_entry.simple_record.title %]
3129         Author: [% cbrebi.target_biblio_record_entry.simple_record.author %]
3130         Publication Year: [% cbrebi.target_biblio_record_entry.simple_record.pubdate %]
3131
3132     [% END %]
3133     [% END %]
3134 $$
3135     )
3136     ,(
3137         32,
3138         TRUE,
3139         1,
3140         'biblio.record_entry.print',
3141         'biblio.format.record_entry.print',
3142         'NOOP_True',
3143         'ProcessTemplate',
3144         'DeleteTempBiblioBucket',
3145         'DeleteTempBiblioBucket',
3146         'owner',
3147         'print-on-demand',
3148 $$
3149 [%- USE date -%]
3150 <div>
3151     <style> li { padding: 8px; margin 5px; }</style>
3152     <ol>
3153     [% FOR cbreb IN target %]
3154     [% FOR cbrebi IN cbreb.items %]
3155         <li>Bib ID# [% cbrebi.target_biblio_record_entry.id %] ISBN: [% crebi.target_biblio_record_entry.simple_record.isbn %]<br />
3156             Title: [% cbrebi.target_biblio_record_entry.simple_record.title %]<br />
3157             Author: [% cbrebi.target_biblio_record_entry.simple_record.author %]<br />
3158             Publication Year: [% cbrebi.target_biblio_record_entry.simple_record.pubdate %]
3159         </li>
3160     [% END %]
3161     [% END %]
3162     </ol>
3163 </div>
3164 $$
3165     )
3166 ;
3167
3168 INSERT INTO action_trigger.environment (
3169         event_def,
3170         path
3171     ) VALUES -- for fleshing cbreb objects
3172          ( 31, 'owner' )
3173         ,( 31, 'items' )
3174         ,( 31, 'items.target_biblio_record_entry' )
3175         ,( 31, 'items.target_biblio_record_entry.simple_record' )
3176         ,( 31, 'items.target_biblio_record_entry.call_numbers' )
3177         ,( 31, 'items.target_biblio_record_entry.fixed_fields' )
3178         ,( 31, 'items.target_biblio_record_entry.notes' )
3179         ,( 31, 'items.target_biblio_record_entry.full_record_entries' )
3180         ,( 32, 'owner' )
3181         ,( 32, 'items' )
3182         ,( 32, 'items.target_biblio_record_entry' )
3183         ,( 32, 'items.target_biblio_record_entry.simple_record' )
3184         ,( 32, 'items.target_biblio_record_entry.call_numbers' )
3185         ,( 32, 'items.target_biblio_record_entry.fixed_fields' )
3186         ,( 32, 'items.target_biblio_record_entry.notes' )
3187         ,( 32, 'items.target_biblio_record_entry.full_record_entries' )
3188 ;
3189
3190 INSERT INTO action_trigger.environment (
3191         event_def,
3192         path
3193     ) VALUES -- for fleshing mp objects
3194          ( 29, 'credit_card_payment')
3195         ,( 29, 'xact.billings')
3196         ,( 29, 'xact.billings.btype')
3197         ,( 30, 'credit_card_payment')
3198         ,( 30, 'xact.billings')
3199         ,( 30, 'xact.billings.btype')
3200 ;
3201
3202 INSERT INTO action_trigger.hook (key,core_type,description,passive) VALUES 
3203     (   'circ.format.missing_pieces.slip.print',
3204         'circ', 
3205         oils_i18n_gettext(
3206             'circ.format.missing_pieces.slip.print',
3207             'A missing pieces slip needs to be formatted for printing.',
3208             'ath',
3209             'description'
3210         ), 
3211         FALSE
3212     )
3213     ,(  'circ.format.missing_pieces.letter.print',
3214         'circ', 
3215         oils_i18n_gettext(
3216             'circ.format.missing_pieces.letter.print',
3217             'A missing pieces patron letter needs to be formatted for printing.',
3218             'ath',
3219             'description'
3220         ), 
3221         FALSE
3222     )
3223 ;
3224
3225 INSERT INTO action_trigger.event_definition (
3226         id,
3227         active,
3228         owner,
3229         name,
3230         hook,
3231         validator,
3232         reactor,
3233         group_field,
3234         granularity,
3235         template
3236     ) VALUES (
3237         33,
3238         TRUE,
3239         1,
3240         'circ.missing_pieces.slip.print',
3241         'circ.format.missing_pieces.slip.print',
3242         'NOOP_True',
3243         'ProcessTemplate',
3244         'usr',
3245         'print-on-demand',
3246 $$
3247 [%- USE date -%]
3248 [%- SET user = target.0.usr -%]
3249 <div style="li { padding: 8px; margin 5px; }">
3250     <div>[% date.format %]</div><br/>
3251     Missing pieces for:
3252     <ol>
3253     [% FOR circ IN target %]
3254         <li>Barcode: [% circ.target_copy.barcode %] Transaction ID: [% circ.id %] Due: [% circ.due_date.format %]<br />
3255             [% helpers.get_copy_bib_basics(circ.target_copy.id).title %]
3256         </li>
3257     [% END %]
3258     </ol>
3259 </div>
3260 $$
3261     )
3262     ,(
3263         34,
3264         TRUE,
3265         1,
3266         'circ.missing_pieces.letter.print',
3267         'circ.format.missing_pieces.letter.print',
3268         'NOOP_True',
3269         'ProcessTemplate',
3270         'usr',
3271         'print-on-demand',
3272 $$
3273 [%- USE date -%]
3274 [%- SET user = target.0.usr -%]
3275 [% date.format %]
3276 Dear [% user.prefix %] [% user.first_given_name %] [% user.family_name %],
3277
3278 We are missing pieces for the following returned items:
3279 [% FOR circ IN target %]
3280 Barcode: [% circ.target_copy.barcode %] Transaction ID: [% circ.id %] Due: [% circ.due_date.format %]
3281 [% helpers.get_copy_bib_basics(circ.target_copy.id).title %]
3282 [% END %]
3283
3284 Please return these pieces as soon as possible.
3285
3286 Thanks!
3287
3288 Library Staff
3289 $$
3290     )
3291 ;
3292
3293 INSERT INTO action_trigger.environment (
3294         event_def,
3295         path
3296     ) VALUES -- for fleshing circ objects
3297          ( 33, 'usr')
3298         ,( 33, 'target_copy')
3299         ,( 33, 'target_copy.circ_lib')
3300         ,( 33, 'target_copy.circ_lib.mailing_address')
3301         ,( 33, 'target_copy.circ_lib.billing_address')
3302         ,( 33, 'target_copy.call_number')
3303         ,( 33, 'target_copy.call_number.owning_lib')
3304         ,( 33, 'target_copy.call_number.owning_lib.mailing_address')
3305         ,( 33, 'target_copy.call_number.owning_lib.billing_address')
3306         ,( 33, 'circ_lib')
3307         ,( 33, 'circ_lib.mailing_address')
3308         ,( 33, 'circ_lib.billing_address')
3309         ,( 34, 'usr')
3310         ,( 34, 'target_copy')
3311         ,( 34, 'target_copy.circ_lib')
3312         ,( 34, 'target_copy.circ_lib.mailing_address')
3313         ,( 34, 'target_copy.circ_lib.billing_address')
3314         ,( 34, 'target_copy.call_number')
3315         ,( 34, 'target_copy.call_number.owning_lib')
3316         ,( 34, 'target_copy.call_number.owning_lib.mailing_address')
3317         ,( 34, 'target_copy.call_number.owning_lib.billing_address')
3318         ,( 34, 'circ_lib')
3319         ,( 34, 'circ_lib.mailing_address')
3320         ,( 34, 'circ_lib.billing_address')
3321 ;
3322
3323 INSERT INTO action_trigger.hook (key,core_type,description,passive) 
3324     VALUES (   
3325         'ahr.format.pull_list',
3326         'ahr', 
3327         oils_i18n_gettext(
3328             'ahr.format.pull_list',
3329             'Format holds pull list for printing',
3330             'ath',
3331             'description'
3332         ), 
3333         FALSE
3334     );
3335
3336 INSERT INTO action_trigger.event_definition (
3337         id,
3338         active,
3339         owner,
3340         name,
3341         hook,
3342         validator,
3343         reactor,
3344         group_field,
3345         granularity,
3346         template
3347     ) VALUES (
3348         35,
3349         TRUE,
3350         1,
3351         'Holds Pull List',
3352         'ahr.format.pull_list',
3353         'NOOP_True',
3354         'ProcessTemplate',
3355         'pickup_lib',
3356         'print-on-demand',
3357 $$
3358 [%- USE date -%]
3359 <style>
3360     table { border-collapse: collapse; } 
3361     td { padding: 5px; border-bottom: 1px solid #888; } 
3362     th { font-weight: bold; }
3363 </style>
3364 [% 
3365     # Sort the holds into copy-location buckets
3366     # In the main print loop, sort each bucket by callnumber before printing
3367     SET holds_list = [];
3368     SET loc_data = [];
3369     SET current_location = target.0.current_copy.location.id;
3370     FOR hold IN target;
3371         IF current_location != hold.current_copy.location.id;
3372             SET current_location = hold.current_copy.location.id;
3373             holds_list.push(loc_data);
3374             SET loc_data = [];
3375         END;
3376         SET hold_data = {
3377             'hold' => hold,
3378             'callnumber' => hold.current_copy.call_number.label
3379         };
3380         loc_data.push(hold_data);
3381     END;
3382     holds_list.push(loc_data)
3383 %]
3384 <table>
3385     <thead>
3386         <tr>
3387             <th>Title</th>
3388             <th>Author</th>
3389             <th>Shelving Location</th>
3390             <th>Call Number</th>
3391             <th>Barcode</th>
3392             <th>Patron</th>
3393         </tr>
3394     </thead>
3395     <tbody>
3396     [% FOR loc_data IN holds_list  %]
3397         [% FOR hold_data IN loc_data.sort('callnumber') %]
3398             [% 
3399                 SET hold = hold_data.hold;
3400                 SET copy_data = helpers.get_copy_bib_basics(hold.current_copy.id);
3401             %]
3402             <tr>
3403                 <td>[% copy_data.title | truncate %]</td>
3404                 <td>[% copy_data.author | truncate %]</td>
3405                 <td>[% hold.current_copy.location.name %]</td>
3406                 <td>[% hold.current_copy.call_number.label %]</td>
3407                 <td>[% hold.current_copy.barcode %]</td>
3408                 <td>[% hold.usr.card.barcode %]</td>
3409             </tr>
3410         [% END %]
3411     [% END %]
3412     <tbody>
3413 </table>
3414 $$
3415 );
3416
3417 INSERT INTO action_trigger.environment (
3418         event_def,
3419         path
3420     ) VALUES
3421         (35, 'current_copy.location'),
3422         (35, 'current_copy.call_number'),
3423         (35, 'usr.card'),
3424         (35, 'pickup_lib')
3425 ;
3426
3427 -- Create the query schema, and the tables and views therein
3428
3429 DROP SCHEMA IF EXISTS sql CASCADE;
3430 DROP SCHEMA IF EXISTS query CASCADE;
3431
3432 CREATE SCHEMA query;
3433
3434 CREATE TABLE query.datatype (
3435         id              SERIAL            PRIMARY KEY,
3436         datatype_name   TEXT              NOT NULL UNIQUE,
3437         is_numeric      BOOL              NOT NULL DEFAULT FALSE,
3438         is_composite    BOOL              NOT NULL DEFAULT FALSE,
3439         CONSTRAINT qdt_comp_not_num CHECK
3440         ( is_numeric IS FALSE OR is_composite IS FALSE )
3441 );
3442
3443 -- Define the most common datatypes in query.datatype.  Note that none of
3444 -- these stock datatypes specifies a width or precision.
3445
3446 -- Also: set the sequence for query.datatype to 1000, leaving plenty of
3447 -- room for more stock datatypes if we ever want to add them.
3448
3449 SELECT setval( 'query.datatype_id_seq', 1000 );
3450
3451 INSERT INTO query.datatype (id, datatype_name, is_numeric )
3452   VALUES (1, 'SMALLINT', true);
3453  
3454 INSERT INTO query.datatype (id, datatype_name, is_numeric )
3455   VALUES (2, 'INTEGER', true);
3456  
3457 INSERT INTO query.datatype (id, datatype_name, is_numeric )
3458   VALUES (3, 'BIGINT', true);
3459  
3460 INSERT INTO query.datatype (id, datatype_name, is_numeric )
3461   VALUES (4, 'DECIMAL', true);
3462  
3463 INSERT INTO query.datatype (id, datatype_name, is_numeric )
3464   VALUES (5, 'NUMERIC', true);
3465  
3466 INSERT INTO query.datatype (id, datatype_name, is_numeric )
3467   VALUES (6, 'REAL', true);
3468  
3469 INSERT INTO query.datatype (id, datatype_name, is_numeric )
3470   VALUES (7, 'DOUBLE PRECISION', true);
3471  
3472 INSERT INTO query.datatype (id, datatype_name, is_numeric )
3473   VALUES (8, 'SERIAL', true);
3474  
3475 INSERT INTO query.datatype (id, datatype_name, is_numeric )
3476   VALUES (9, 'BIGSERIAL', true);
3477  
3478 INSERT INTO query.datatype (id, datatype_name, is_numeric )
3479   VALUES (10, 'MONEY', false);
3480  
3481 INSERT INTO query.datatype (id, datatype_name, is_numeric )
3482   VALUES (11, 'VARCHAR', false);
3483  
3484 INSERT INTO query.datatype (id, datatype_name, is_numeric )
3485   VALUES (12, 'CHAR', false);
3486  
3487 INSERT INTO query.datatype (id, datatype_name, is_numeric )
3488   VALUES (13, 'TEXT', false);
3489  
3490 INSERT INTO query.datatype (id, datatype_name, is_numeric )
3491   VALUES (14, '"char"', false);
3492  
3493 INSERT INTO query.datatype (id, datatype_name, is_numeric )
3494   VALUES (15, 'NAME', false);
3495  
3496 INSERT INTO query.datatype (id, datatype_name, is_numeric )
3497   VALUES (16, 'BYTEA', false);
3498  
3499 INSERT INTO query.datatype (id, datatype_name, is_numeric )
3500   VALUES (17, 'TIMESTAMP WITHOUT TIME ZONE', false);
3501  
3502 INSERT INTO query.datatype (id, datatype_name, is_numeric )
3503   VALUES (18, 'TIMESTAMP WITH TIME ZONE', false);
3504  
3505 INSERT INTO query.datatype (id, datatype_name, is_numeric )
3506   VALUES (19, 'DATE', false);
3507  
3508 INSERT INTO query.datatype (id, datatype_name, is_numeric )
3509   VALUES (20, 'TIME WITHOUT TIME ZONE', false);
3510  
3511 INSERT INTO query.datatype (id, datatype_name, is_numeric )
3512   VALUES (21, 'TIME WITH TIME ZONE', false);
3513  
3514 INSERT INTO query.datatype (id, datatype_name, is_numeric )
3515   VALUES (22, 'INTERVAL', false);
3516  
3517 INSERT INTO query.datatype (id, datatype_name, is_numeric )
3518   VALUES (23, 'BOOLEAN', false);
3519  
3520 CREATE TABLE query.subfield (
3521         id              SERIAL            PRIMARY KEY,
3522         composite_type  INT               NOT NULL
3523                                           REFERENCES query.datatype(id)
3524                                           ON DELETE CASCADE
3525                                           DEFERRABLE INITIALLY DEFERRED,
3526         seq_no          INT               NOT NULL
3527                                           CONSTRAINT qsf_pos_seq_no
3528                                           CHECK( seq_no > 0 ),
3529         subfield_type   INT               NOT NULL
3530                                           REFERENCES query.datatype(id)
3531                                           DEFERRABLE INITIALLY DEFERRED,
3532         CONSTRAINT qsf_datatype_seq_no UNIQUE (composite_type, seq_no)
3533 );
3534
3535 CREATE TABLE query.function_sig (
3536         id              SERIAL            PRIMARY KEY,
3537         function_name   TEXT              NOT NULL,
3538         return_type     INT               REFERENCES query.datatype(id)
3539                                           DEFERRABLE INITIALLY DEFERRED,
3540         is_aggregate    BOOL              NOT NULL DEFAULT FALSE,
3541         CONSTRAINT qfd_rtn_or_aggr CHECK
3542         ( return_type IS NULL OR is_aggregate = FALSE )
3543 );
3544
3545 CREATE INDEX query_function_sig_name_idx 
3546         ON query.function_sig (function_name);
3547
3548 CREATE TABLE query.function_param_def (
3549         id              SERIAL            PRIMARY KEY,
3550         function_id     INT               NOT NULL
3551                                           REFERENCES query.function_sig( id )
3552                                           ON DELETE CASCADE
3553                                           DEFERRABLE INITIALLY DEFERRED,
3554         seq_no          INT               NOT NULL
3555                                           CONSTRAINT qfpd_pos_seq_no CHECK
3556                                           ( seq_no > 0 ),
3557         datatype        INT               NOT NULL
3558                                           REFERENCES query.datatype( id )
3559                                           DEFERRABLE INITIALLY DEFERRED,
3560         CONSTRAINT qfpd_function_param_seq UNIQUE (function_id, seq_no)
3561 );
3562
3563 CREATE TABLE  query.stored_query (
3564         id            SERIAL         PRIMARY KEY,
3565         type          TEXT           NOT NULL CONSTRAINT query_type CHECK
3566                                      ( type IN ( 'SELECT', 'UNION', 'INTERSECT', 'EXCEPT' ) ),
3567         use_all       BOOLEAN        NOT NULL DEFAULT FALSE,
3568         use_distinct  BOOLEAN        NOT NULL DEFAULT FALSE,
3569         from_clause   INT            , --REFERENCES query.from_clause
3570                                      --DEFERRABLE INITIALLY DEFERRED,
3571         where_clause  INT            , --REFERENCES query.expression
3572                                      --DEFERRABLE INITIALLY DEFERRED,
3573         having_clause INT            , --REFERENCES query.expression
3574                                      --DEFERRABLE INITIALLY DEFERRED
3575         limit_count   INT            , --REFERENCES query.expression( id )
3576                                      --DEFERRABLE INITIALLY DEFERRED,
3577         offset_count  INT            --REFERENCES query.expression( id )
3578                                      --DEFERRABLE INITIALLY DEFERRED
3579 );
3580
3581 -- (Foreign keys to be defined later after other tables are created)
3582
3583 CREATE TABLE query.query_sequence (
3584         id              SERIAL            PRIMARY KEY,
3585         parent_query    INT               NOT NULL
3586                                           REFERENCES query.stored_query
3587                                                                           ON DELETE CASCADE
3588                                                                           DEFERRABLE INITIALLY DEFERRED,
3589         seq_no          INT               NOT NULL,
3590         child_query     INT               NOT NULL
3591                                           REFERENCES query.stored_query
3592                                                                           ON DELETE CASCADE
3593                                                                           DEFERRABLE INITIALLY DEFERRED,
3594         CONSTRAINT query_query_seq UNIQUE( parent_query, seq_no )
3595 );
3596
3597 CREATE TABLE query.bind_variable (
3598         name          TEXT             PRIMARY KEY,
3599         type          TEXT             NOT NULL
3600                                            CONSTRAINT bind_variable_type CHECK
3601                                            ( type in ( 'string', 'number', 'string_list', 'number_list' )),
3602         description   TEXT             NOT NULL,
3603         default_value TEXT,            -- to be encoded in JSON
3604         label         TEXT             NOT NULL
3605 );
3606
3607 CREATE TABLE query.expression (
3608         id            SERIAL        PRIMARY KEY,
3609         type          TEXT          NOT NULL CONSTRAINT expression_type CHECK
3610                                     ( type IN (
3611                                     'xbet',    -- between
3612                                     'xbind',   -- bind variable
3613                                     'xbool',   -- boolean
3614                                     'xcase',   -- case
3615                                     'xcast',   -- cast
3616                                     'xcol',    -- column
3617                                     'xex',     -- exists
3618                                     'xfunc',   -- function
3619                                     'xin',     -- in
3620                                     'xisnull', -- is null
3621                                     'xnull',   -- null
3622                                     'xnum',    -- number
3623                                     'xop',     -- operator
3624                                     'xser',    -- series
3625                                     'xstr',    -- string
3626                                     'xsubq'    -- subquery
3627                                                                 ) ),
3628         parenthesize  BOOL          NOT NULL DEFAULT FALSE,
3629         parent_expr   INT           REFERENCES query.expression
3630                                     ON DELETE CASCADE
3631                                     DEFERRABLE INITIALLY DEFERRED,
3632         seq_no        INT           NOT NULL DEFAULT 1,
3633         literal       TEXT,
3634         table_alias   TEXT,
3635         column_name   TEXT,
3636         left_operand  INT           REFERENCES query.expression
3637                                     DEFERRABLE INITIALLY DEFERRED,
3638         operator      TEXT,
3639         right_operand INT           REFERENCES query.expression
3640                                     DEFERRABLE INITIALLY DEFERRED,
3641         function_id   INT           REFERENCES query.function_sig
3642                                     DEFERRABLE INITIALLY DEFERRED,
3643         subquery      INT           REFERENCES query.stored_query
3644                                     DEFERRABLE INITIALLY DEFERRED,
3645         cast_type     INT           REFERENCES query.datatype
3646                                     DEFERRABLE INITIALLY DEFERRED,
3647         negate        BOOL          NOT NULL DEFAULT FALSE,
3648         bind_variable TEXT          REFERENCES query.bind_variable
3649                                         DEFERRABLE INITIALLY DEFERRED
3650 );
3651
3652 CREATE UNIQUE INDEX query_expr_parent_seq
3653         ON query.expression( parent_expr, seq_no )
3654         WHERE parent_expr IS NOT NULL;
3655
3656 -- Due to some circular references, the following foreign key definitions
3657 -- had to be deferred until query.expression existed:
3658
3659 ALTER TABLE query.stored_query
3660         ADD FOREIGN KEY ( where_clause )
3661         REFERENCES query.expression( id )
3662         DEFERRABLE INITIALLY DEFERRED;
3663
3664 ALTER TABLE query.stored_query
3665         ADD FOREIGN KEY ( having_clause )
3666         REFERENCES query.expression( id )
3667         DEFERRABLE INITIALLY DEFERRED;
3668
3669 ALTER TABLE query.stored_query
3670     ADD FOREIGN KEY ( limit_count )
3671     REFERENCES query.expression( id )
3672     DEFERRABLE INITIALLY DEFERRED;
3673
3674 ALTER TABLE query.stored_query
3675     ADD FOREIGN KEY ( offset_count )
3676     REFERENCES query.expression( id )
3677     DEFERRABLE INITIALLY DEFERRED;
3678
3679 CREATE TABLE query.case_branch (
3680         id            SERIAL        PRIMARY KEY,
3681         parent_expr   INT           NOT NULL REFERENCES query.expression
3682                                     ON DELETE CASCADE
3683                                     DEFERRABLE INITIALLY DEFERRED,
3684         seq_no        INT           NOT NULL,
3685         condition     INT           REFERENCES query.expression
3686                                     DEFERRABLE INITIALLY DEFERRED,
3687         result        INT           NOT NULL REFERENCES query.expression
3688                                     DEFERRABLE INITIALLY DEFERRED,
3689         CONSTRAINT case_branch_parent_seq UNIQUE (parent_expr, seq_no)
3690 );
3691
3692 CREATE TABLE query.from_relation (
3693         id               SERIAL        PRIMARY KEY,
3694         type             TEXT          NOT NULL CONSTRAINT relation_type CHECK (
3695                                            type IN ( 'RELATION', 'SUBQUERY', 'FUNCTION' ) ),
3696         table_name       TEXT,
3697         class_name       TEXT,
3698         subquery         INT           REFERENCES query.stored_query,
3699         function_call    INT           REFERENCES query.expression,
3700         table_alias      TEXT,
3701         parent_relation  INT           REFERENCES query.from_relation
3702                                        ON DELETE CASCADE
3703                                        DEFERRABLE INITIALLY DEFERRED,
3704         seq_no           INT           NOT NULL DEFAULT 1,
3705         join_type        TEXT          CONSTRAINT good_join_type CHECK (
3706                                            join_type IS NULL OR join_type IN
3707                                            ( 'INNER', 'LEFT', 'RIGHT', 'FULL' )
3708                                        ),
3709         on_clause        INT           REFERENCES query.expression
3710                                        DEFERRABLE INITIALLY DEFERRED,
3711         CONSTRAINT join_or_core CHECK (
3712         ( parent_relation IS NULL AND join_type IS NULL
3713           AND on_clause IS NULL )
3714         OR
3715         ( parent_relation IS NOT NULL AND join_type IS NOT NULL
3716           AND on_clause IS NOT NULL )
3717         )
3718 );
3719
3720 CREATE UNIQUE INDEX from_parent_seq
3721         ON query.from_relation( parent_relation, seq_no )
3722         WHERE parent_relation IS NOT NULL;
3723
3724 -- The following foreign key had to be deferred until
3725 -- query.from_relation existed
3726
3727 ALTER TABLE query.stored_query
3728         ADD FOREIGN KEY (from_clause)
3729         REFERENCES query.from_relation
3730         DEFERRABLE INITIALLY DEFERRED;
3731
3732 CREATE TABLE query.record_column (
3733         id            SERIAL            PRIMARY KEY,
3734         from_relation INT               NOT NULL REFERENCES query.from_relation
3735                                         ON DELETE CASCADE
3736                                         DEFERRABLE INITIALLY DEFERRED,
3737         seq_no        INT               NOT NULL,
3738         column_name   TEXT              NOT NULL,
3739         column_type   INT               NOT NULL REFERENCES query.datatype
3740                                         ON DELETE CASCADE
3741                                                                         DEFERRABLE INITIALLY DEFERRED,
3742         CONSTRAINT column_sequence UNIQUE (from_relation, seq_no)
3743 );
3744
3745 CREATE TABLE query.select_item (
3746         id               SERIAL         PRIMARY KEY,
3747         stored_query     INT            NOT NULL REFERENCES query.stored_query
3748                                         ON DELETE CASCADE
3749                                         DEFERRABLE INITIALLY DEFERRED,
3750         seq_no           INT            NOT NULL,
3751         expression       INT            NOT NULL REFERENCES query.expression
3752                                         DEFERRABLE INITIALLY DEFERRED,
3753         column_alias     TEXT,
3754         grouped_by       BOOL           NOT NULL DEFAULT FALSE,
3755         CONSTRAINT select_sequence UNIQUE( stored_query, seq_no )
3756 );
3757
3758 CREATE TABLE query.order_by_item (
3759         id               SERIAL         PRIMARY KEY,
3760         stored_query     INT            NOT NULL REFERENCES query.stored_query
3761                                         ON DELETE CASCADE
3762                                         DEFERRABLE INITIALLY DEFERRED,
3763         seq_no           INT            NOT NULL,
3764         expression       INT            NOT NULL REFERENCES query.expression
3765                                         ON DELETE CASCADE
3766                                         DEFERRABLE INITIALLY DEFERRED,
3767         CONSTRAINT order_by_sequence UNIQUE( stored_query, seq_no )
3768 );
3769
3770 ------------------------------------------------------------
3771 -- Create updatable views for different kinds of expressions
3772 ------------------------------------------------------------
3773
3774 -- Create updatable view for BETWEEN expressions
3775
3776 CREATE OR REPLACE VIEW query.expr_xbet AS
3777     SELECT
3778                 id,
3779                 parenthesize,
3780                 parent_expr,
3781                 seq_no,
3782                 left_operand,
3783                 negate
3784     FROM
3785         query.expression
3786     WHERE
3787         type = 'xbet';
3788
3789 CREATE OR REPLACE RULE query_expr_xbet_insert_rule AS
3790     ON INSERT TO query.expr_xbet
3791     DO INSTEAD
3792     INSERT INTO query.expression (
3793                 id,
3794                 type,
3795                 parenthesize,
3796                 parent_expr,
3797                 seq_no,
3798                 left_operand,
3799                 negate
3800     ) VALUES (
3801         COALESCE(NEW.id, NEXTVAL('query.expression_id_seq'::REGCLASS)),
3802         'xbet',
3803         COALESCE(NEW.parenthesize, FALSE),
3804         NEW.parent_expr,
3805         COALESCE(NEW.seq_no, 1),
3806                 NEW.left_operand,
3807                 COALESCE(NEW.negate, false)
3808     );
3809
3810 CREATE OR REPLACE RULE query_expr_xbet_update_rule AS
3811     ON UPDATE TO query.expr_xbet
3812     DO INSTEAD
3813     UPDATE query.expression SET
3814         id = NEW.id,
3815         parenthesize = NEW.parenthesize,
3816         parent_expr = NEW.parent_expr,
3817         seq_no = NEW.seq_no,
3818                 left_operand = NEW.left_operand,
3819                 negate = NEW.negate
3820     WHERE
3821         id = OLD.id;
3822
3823 CREATE OR REPLACE RULE query_expr_xbet_delete_rule AS
3824     ON DELETE TO query.expr_xbet
3825     DO INSTEAD
3826     DELETE FROM query.expression WHERE id = OLD.id;
3827
3828 -- Create updatable view for bind variable expressions
3829
3830 CREATE OR REPLACE VIEW query.expr_xbind AS
3831     SELECT
3832                 id,
3833                 parenthesize,
3834                 parent_expr,
3835                 seq_no,
3836                 bind_variable
3837     FROM
3838         query.expression
3839     WHERE
3840         type = 'xbind';
3841
3842 CREATE OR REPLACE RULE query_expr_xbind_insert_rule AS
3843     ON INSERT TO query.expr_xbind
3844     DO INSTEAD
3845     INSERT INTO query.expression (
3846                 id,
3847                 type,
3848                 parenthesize,
3849                 parent_expr,
3850                 seq_no,
3851                 bind_variable
3852     ) VALUES (
3853         COALESCE(NEW.id, NEXTVAL('query.expression_id_seq'::REGCLASS)),
3854         'xbind',
3855         COALESCE(NEW.parenthesize, FALSE),
3856         NEW.parent_expr,
3857         COALESCE(NEW.seq_no, 1),
3858                 NEW.bind_variable
3859     );
3860
3861 CREATE OR REPLACE RULE query_expr_xbind_update_rule AS
3862     ON UPDATE TO query.expr_xbind
3863     DO INSTEAD
3864     UPDATE query.expression SET
3865         id = NEW.id,
3866         parenthesize = NEW.parenthesize,
3867         parent_expr = NEW.parent_expr,
3868         seq_no = NEW.seq_no,
3869                 bind_variable = NEW.bind_variable
3870     WHERE
3871         id = OLD.id;
3872
3873 CREATE OR REPLACE RULE query_expr_xbind_delete_rule AS
3874     ON DELETE TO query.expr_xbind
3875     DO INSTEAD
3876     DELETE FROM query.expression WHERE id = OLD.id;
3877
3878 -- Create updatable view for boolean expressions
3879
3880 CREATE OR REPLACE VIEW query.expr_xbool AS
3881     SELECT
3882                 id,
3883                 parenthesize,
3884                 parent_expr,
3885                 seq_no,
3886                 literal,
3887                 negate
3888     FROM
3889         query.expression
3890     WHERE
3891         type = 'xbool';
3892
3893 CREATE OR REPLACE RULE query_expr_xbool_insert_rule AS
3894     ON INSERT TO query.expr_xbool
3895     DO INSTEAD
3896     INSERT INTO query.expression (
3897                 id,
3898                 type,
3899                 parenthesize,
3900                 parent_expr,
3901                 seq_no,
3902                 literal,
3903                 negate
3904     ) VALUES (
3905         COALESCE(NEW.id, NEXTVAL('query.expression_id_seq'::REGCLASS)),
3906         'xbool',
3907         COALESCE(NEW.parenthesize, FALSE),
3908         NEW.parent_expr,
3909         COALESCE(NEW.seq_no, 1),
3910         NEW.literal,
3911                 COALESCE(NEW.negate, false)
3912     );
3913
3914 CREATE OR REPLACE RULE query_expr_xbool_update_rule AS
3915     ON UPDATE TO query.expr_xbool
3916     DO INSTEAD
3917     UPDATE query.expression SET
3918         id = NEW.id,
3919         parenthesize = NEW.parenthesize,
3920         parent_expr = NEW.parent_expr,
3921         seq_no = NEW.seq_no,
3922         literal = NEW.literal,
3923                 negate = NEW.negate
3924     WHERE
3925         id = OLD.id;
3926
3927 CREATE OR REPLACE RULE query_expr_xbool_delete_rule AS
3928     ON DELETE TO query.expr_xbool
3929     DO INSTEAD
3930     DELETE FROM query.expression WHERE id = OLD.id;
3931
3932 -- Create updatable view for CASE expressions
3933
3934 CREATE OR REPLACE VIEW query.expr_xcase AS
3935     SELECT
3936                 id,
3937                 parenthesize,
3938                 parent_expr,
3939                 seq_no,
3940                 left_operand,
3941                 negate
3942     FROM
3943         query.expression
3944     WHERE
3945         type = 'xcase';
3946
3947 CREATE OR REPLACE RULE query_expr_xcase_insert_rule AS
3948     ON INSERT TO query.expr_xcase
3949     DO INSTEAD
3950     INSERT INTO query.expression (
3951                 id,
3952                 type,
3953                 parenthesize,
3954                 parent_expr,
3955                 seq_no,
3956                 left_operand,
3957                 negate
3958     ) VALUES (
3959         COALESCE(NEW.id, NEXTVAL('query.expression_id_seq'::REGCLASS)),
3960         'xcase',
3961         COALESCE(NEW.parenthesize, FALSE),
3962         NEW.parent_expr,
3963         COALESCE(NEW.seq_no, 1),
3964                 NEW.left_operand,
3965                 COALESCE(NEW.negate, false)
3966     );
3967
3968 CREATE OR REPLACE RULE query_expr_xcase_update_rule AS
3969     ON UPDATE TO query.expr_xcase
3970     DO INSTEAD
3971     UPDATE query.expression SET
3972         id = NEW.id,
3973         parenthesize = NEW.parenthesize,
3974         parent_expr = NEW.parent_expr,
3975         seq_no = NEW.seq_no,
3976                 left_operand = NEW.left_operand,
3977                 negate = NEW.negate
3978     WHERE
3979         id = OLD.id;
3980
3981 CREATE OR REPLACE RULE query_expr_xcase_delete_rule AS
3982     ON DELETE TO query.expr_xcase
3983     DO INSTEAD
3984     DELETE FROM query.expression WHERE id = OLD.id;
3985
3986 -- Create updatable view for cast expressions
3987
3988 CREATE OR REPLACE VIEW query.expr_xcast AS
3989     SELECT
3990                 id,
3991                 parenthesize,
3992                 parent_expr,
3993                 seq_no,
3994                 left_operand,
3995                 cast_type,
3996                 negate
3997     FROM
3998         query.expression
3999     WHERE
4000         type = 'xcast';
4001
4002 CREATE OR REPLACE RULE query_expr_xcast_insert_rule AS
4003     ON INSERT TO query.expr_xcast
4004     DO INSTEAD
4005     INSERT INTO query.expression (
4006         id,
4007         type,
4008         parenthesize,
4009         parent_expr,
4010         seq_no,
4011         left_operand,
4012         cast_type,
4013         negate
4014     ) VALUES (
4015         COALESCE(NEW.id, NEXTVAL('query.expression_id_seq'::REGCLASS)),
4016         'xcast',
4017         COALESCE(NEW.parenthesize, FALSE),
4018         NEW.parent_expr,
4019         COALESCE(NEW.seq_no, 1),
4020         NEW.left_operand,
4021         NEW.cast_type,
4022         COALESCE(NEW.negate, false)
4023     );
4024
4025 CREATE OR REPLACE RULE query_expr_xcast_update_rule AS
4026     ON UPDATE TO query.expr_xcast
4027     DO INSTEAD
4028     UPDATE query.expression SET
4029         id = NEW.id,
4030         parenthesize = NEW.parenthesize,
4031         parent_expr = NEW.parent_expr,
4032         seq_no = NEW.seq_no,
4033                 left_operand = NEW.left_operand,
4034                 cast_type = NEW.cast_type,
4035                 negate = NEW.negate
4036     WHERE
4037         id = OLD.id;
4038
4039 CREATE OR REPLACE RULE query_expr_xcast_delete_rule AS
4040     ON DELETE TO query.expr_xcast
4041     DO INSTEAD
4042     DELETE FROM query.expression WHERE id = OLD.id;
4043
4044 -- Create updatable view for column expressions
4045
4046 CREATE OR REPLACE VIEW query.expr_xcol AS
4047     SELECT
4048                 id,
4049                 parenthesize,
4050                 parent_expr,
4051                 seq_no,
4052                 table_alias,
4053                 column_name,
4054                 negate
4055     FROM
4056         query.expression
4057     WHERE
4058         type = 'xcol';
4059
4060 CREATE OR REPLACE RULE query_expr_xcol_insert_rule AS
4061     ON INSERT TO query.expr_xcol
4062     DO INSTEAD
4063     INSERT INTO query.expression (
4064                 id,
4065                 type,
4066                 parenthesize,
4067                 parent_expr,
4068                 seq_no,
4069                 table_alias,
4070                 column_name,
4071                 negate
4072     ) VALUES (
4073         COALESCE(NEW.id, NEXTVAL('query.expression_id_seq'::REGCLASS)),
4074         'xcol',
4075         COALESCE(NEW.parenthesize, FALSE),
4076         NEW.parent_expr,
4077         COALESCE(NEW.seq_no, 1),
4078                 NEW.table_alias,
4079                 NEW.column_name,
4080                 COALESCE(NEW.negate, false)
4081     );
4082
4083 CREATE OR REPLACE RULE query_expr_xcol_update_rule AS
4084     ON UPDATE TO query.expr_xcol
4085     DO INSTEAD
4086     UPDATE query.expression SET
4087         id = NEW.id,
4088         parenthesize = NEW.parenthesize,
4089         parent_expr = NEW.parent_expr,
4090         seq_no = NEW.seq_no,
4091                 table_alias = NEW.table_alias,
4092                 column_name = NEW.column_name,
4093                 negate = NEW.negate
4094     WHERE
4095         id = OLD.id;
4096
4097 CREATE OR REPLACE RULE query_expr_xcol_delete_rule AS
4098     ON DELETE TO query.expr_xcol
4099     DO INSTEAD
4100     DELETE FROM query.expression WHERE id = OLD.id;
4101
4102 -- Create updatable view for EXISTS expressions
4103
4104 CREATE OR REPLACE VIEW query.expr_xex AS
4105     SELECT
4106                 id,
4107                 parenthesize,
4108                 parent_expr,
4109                 seq_no,
4110                 subquery,
4111                 negate
4112     FROM
4113         query.expression
4114     WHERE
4115         type = 'xex';
4116
4117 CREATE OR REPLACE RULE query_expr_xex_insert_rule AS
4118     ON INSERT TO query.expr_xex
4119     DO INSTEAD
4120     INSERT INTO query.expression (
4121                 id,
4122                 type,
4123                 parenthesize,
4124                 parent_expr,
4125                 seq_no,
4126                 subquery,
4127                 negate
4128     ) VALUES (
4129         COALESCE(NEW.id, NEXTVAL('query.expression_id_seq'::REGCLASS)),
4130         'xex',
4131         COALESCE(NEW.parenthesize, FALSE),
4132         NEW.parent_expr,
4133         COALESCE(NEW.seq_no, 1),
4134                 NEW.subquery,
4135                 COALESCE(NEW.negate, false)
4136     );
4137
4138 CREATE OR REPLACE RULE query_expr_xex_update_rule AS
4139     ON UPDATE TO query.expr_xex
4140     DO INSTEAD
4141     UPDATE query.expression SET
4142         id = NEW.id,
4143         parenthesize = NEW.parenthesize,
4144         parent_expr = NEW.parent_expr,
4145         seq_no = NEW.seq_no,
4146                 subquery = NEW.subquery,
4147                 negate = NEW.negate
4148     WHERE
4149         id = OLD.id;
4150
4151 CREATE OR REPLACE RULE query_expr_xex_delete_rule AS
4152     ON DELETE TO query.expr_xex
4153     DO INSTEAD
4154     DELETE FROM query.expression WHERE id = OLD.id;
4155
4156 -- Create updatable view for function call expressions
4157
4158 CREATE OR REPLACE VIEW query.expr_xfunc AS
4159     SELECT
4160         id,
4161         parenthesize,
4162         parent_expr,
4163         seq_no,
4164         column_name,
4165         function_id,
4166         negate
4167     FROM
4168         query.expression
4169     WHERE
4170         type = 'xfunc';
4171
4172 CREATE OR REPLACE RULE query_expr_xfunc_insert_rule AS
4173     ON INSERT TO query.expr_xfunc
4174     DO INSTEAD
4175     INSERT INTO query.expression (
4176         id,
4177         type,
4178         parenthesize,
4179         parent_expr,
4180         seq_no,
4181         column_name,
4182         function_id,
4183         negate
4184     ) VALUES (
4185         COALESCE(NEW.id, NEXTVAL('query.expression_id_seq'::REGCLASS)),
4186         'xfunc',
4187         COALESCE(NEW.parenthesize, FALSE),
4188         NEW.parent_expr,
4189         COALESCE(NEW.seq_no, 1),
4190         NEW.column_name,
4191         NEW.function_id,
4192         COALESCE(NEW.negate, false)
4193     );
4194
4195 CREATE OR REPLACE RULE query_expr_xfunc_update_rule AS
4196     ON UPDATE TO query.expr_xfunc
4197     DO INSTEAD
4198     UPDATE query.expression SET
4199         id = NEW.id,
4200         parenthesize = NEW.parenthesize,
4201         parent_expr = NEW.parent_expr,
4202         seq_no = NEW.seq_no,
4203         column_name = NEW.column_name,
4204         function_id = NEW.function_id,
4205         negate = NEW.negate
4206     WHERE
4207         id = OLD.id;
4208
4209 CREATE OR REPLACE RULE query_expr_xfunc_delete_rule AS
4210     ON DELETE TO query.expr_xfunc
4211     DO INSTEAD
4212     DELETE FROM query.expression WHERE id = OLD.id;
4213
4214 -- Create updatable view for IN expressions
4215
4216 CREATE OR REPLACE VIEW query.expr_xin AS
4217     SELECT
4218                 id,
4219                 parenthesize,
4220                 parent_expr,
4221                 seq_no,
4222                 left_operand,
4223                 subquery,
4224                 negate
4225     FROM
4226         query.expression
4227     WHERE
4228         type = 'xin';
4229
4230 CREATE OR REPLACE RULE query_expr_xin_insert_rule AS
4231     ON INSERT TO query.expr_xin
4232     DO INSTEAD
4233     INSERT INTO query.expression (
4234                 id,
4235                 type,
4236                 parenthesize,
4237                 parent_expr,
4238                 seq_no,
4239                 left_operand,
4240                 subquery,
4241                 negate
4242     ) VALUES (
4243         COALESCE(NEW.id, NEXTVAL('query.expression_id_seq'::REGCLASS)),
4244         'xin',
4245         COALESCE(NEW.parenthesize, FALSE),
4246         NEW.parent_expr,
4247         COALESCE(NEW.seq_no, 1),
4248                 NEW.left_operand,
4249                 NEW.subquery,
4250                 COALESCE(NEW.negate, false)
4251     );
4252
4253 CREATE OR REPLACE RULE query_expr_xin_update_rule AS
4254     ON UPDATE TO query.expr_xin
4255     DO INSTEAD
4256     UPDATE query.expression SET
4257         id = NEW.id,
4258         parenthesize = NEW.parenthesize,
4259         parent_expr = NEW.parent_expr,
4260         seq_no = NEW.seq_no,
4261                 left_operand = NEW.left_operand,
4262                 subquery = NEW.subquery,
4263                 negate = NEW.negate
4264     WHERE
4265         id = OLD.id;
4266
4267 CREATE OR REPLACE RULE query_expr_xin_delete_rule AS
4268     ON DELETE TO query.expr_xin
4269     DO INSTEAD
4270     DELETE FROM query.expression WHERE id = OLD.id;
4271
4272 -- Create updatable view for IS NULL expressions
4273
4274 CREATE OR REPLACE VIEW query.expr_xisnull AS
4275     SELECT
4276                 id,
4277                 parenthesize,
4278                 parent_expr,
4279                 seq_no,
4280                 left_operand,
4281                 negate
4282     FROM
4283         query.expression
4284     WHERE
4285         type = 'xisnull';
4286
4287 CREATE OR REPLACE RULE query_expr_xisnull_insert_rule AS
4288     ON INSERT TO query.expr_xisnull
4289     DO INSTEAD
4290     INSERT INTO query.expression (
4291                 id,
4292                 type,
4293                 parenthesize,
4294                 parent_expr,
4295                 seq_no,
4296                 left_operand,
4297                 negate
4298     ) VALUES (
4299         COALESCE(NEW.id, NEXTVAL('query.expression_id_seq'::REGCLASS)),
4300         'xisnull',
4301         COALESCE(NEW.parenthesize, FALSE),
4302         NEW.parent_expr,
4303         COALESCE(NEW.seq_no, 1),
4304                 NEW.left_operand,
4305                 COALESCE(NEW.negate, false)
4306     );
4307
4308 CREATE OR REPLACE RULE query_expr_xisnull_update_rule AS
4309     ON UPDATE TO query.expr_xisnull
4310     DO INSTEAD
4311     UPDATE query.expression SET
4312         id = NEW.id,
4313         parenthesize = NEW.parenthesize,
4314         parent_expr = NEW.parent_expr,
4315         seq_no = NEW.seq_no,
4316                 left_operand = NEW.left_operand,
4317                 negate = NEW.negate
4318     WHERE
4319         id = OLD.id;
4320
4321 CREATE OR REPLACE RULE query_expr_xisnull_delete_rule AS
4322     ON DELETE TO query.expr_xisnull
4323     DO INSTEAD
4324     DELETE FROM query.expression WHERE id = OLD.id;
4325
4326 -- Create updatable view for NULL expressions
4327
4328 CREATE OR REPLACE VIEW query.expr_xnull AS
4329     SELECT
4330                 id,
4331                 parenthesize,
4332                 parent_expr,
4333                 seq_no,
4334                 negate
4335     FROM
4336         query.expression
4337     WHERE
4338         type = 'xnull';
4339
4340 CREATE OR REPLACE RULE query_expr_xnull_insert_rule AS
4341     ON INSERT TO query.expr_xnull
4342     DO INSTEAD
4343     INSERT INTO query.expression (
4344                 id,
4345                 type,
4346                 parenthesize,
4347                 parent_expr,
4348                 seq_no,
4349                 negate
4350     ) VALUES (
4351         COALESCE(NEW.id, NEXTVAL('query.expression_id_seq'::REGCLASS)),
4352         'xnull',
4353         COALESCE(NEW.parenthesize, FALSE),
4354         NEW.parent_expr,
4355         COALESCE(NEW.seq_no, 1),
4356                 COALESCE(NEW.negate, false)
4357     );
4358
4359 CREATE OR REPLACE RULE query_expr_xnull_update_rule AS
4360     ON UPDATE TO query.expr_xnull
4361     DO INSTEAD
4362     UPDATE query.expression SET
4363         id = NEW.id,
4364         parenthesize = NEW.parenthesize,
4365         parent_expr = NEW.parent_expr,
4366         seq_no = NEW.seq_no,
4367                 negate = NEW.negate
4368     WHERE
4369         id = OLD.id;
4370
4371 CREATE OR REPLACE RULE query_expr_xnull_delete_rule AS
4372     ON DELETE TO query.expr_xnull
4373     DO INSTEAD
4374     DELETE FROM query.expression WHERE id = OLD.id;
4375
4376 -- Create updatable view for numeric literal expressions
4377
4378 CREATE OR REPLACE VIEW query.expr_xnum AS
4379     SELECT
4380                 id,
4381                 parenthesize,
4382                 parent_expr,
4383                 seq_no,
4384                 literal
4385     FROM
4386         query.expression
4387     WHERE
4388         type = 'xnum';
4389
4390 CREATE OR REPLACE RULE query_expr_xnum_insert_rule AS
4391     ON INSERT TO query.expr_xnum
4392     DO INSTEAD
4393     INSERT INTO query.expression (
4394                 id,
4395                 type,
4396                 parenthesize,
4397                 parent_expr,
4398                 seq_no,
4399                 literal
4400     ) VALUES (
4401         COALESCE(NEW.id, NEXTVAL('query.expression_id_seq'::REGCLASS)),
4402         'xnum',
4403         COALESCE(NEW.parenthesize, FALSE),
4404         NEW.parent_expr,
4405         COALESCE(NEW.seq_no, 1),
4406         NEW.literal
4407     );
4408
4409 CREATE OR REPLACE RULE query_expr_xnum_update_rule AS
4410     ON UPDATE TO query.expr_xnum
4411     DO INSTEAD
4412     UPDATE query.expression SET
4413         id = NEW.id,
4414         parenthesize = NEW.parenthesize,
4415         parent_expr = NEW.parent_expr,
4416         seq_no = NEW.seq_no,
4417         literal = NEW.literal
4418     WHERE
4419         id = OLD.id;
4420
4421 CREATE OR REPLACE RULE query_expr_xnum_delete_rule AS
4422     ON DELETE TO query.expr_xnum
4423     DO INSTEAD
4424     DELETE FROM query.expression WHERE id = OLD.id;
4425
4426 -- Create updatable view for operator expressions
4427
4428 CREATE OR REPLACE VIEW query.expr_xop AS
4429     SELECT
4430                 id,
4431                 parenthesize,
4432                 parent_expr,
4433                 seq_no,
4434                 left_operand,
4435                 operator,
4436                 right_operand,
4437                 negate
4438     FROM
4439         query.expression
4440     WHERE
4441         type = 'xop';
4442
4443 CREATE OR REPLACE RULE query_expr_xop_insert_rule AS
4444     ON INSERT TO query.expr_xop
4445     DO INSTEAD
4446     INSERT INTO query.expression (
4447                 id,
4448                 type,
4449                 parenthesize,
4450                 parent_expr,
4451                 seq_no,
4452                 left_operand,
4453                 operator,
4454                 right_operand,
4455                 negate
4456     ) VALUES (
4457         COALESCE(NEW.id, NEXTVAL('query.expression_id_seq'::REGCLASS)),
4458         'xop',
4459         COALESCE(NEW.parenthesize, FALSE),
4460         NEW.parent_expr,
4461         COALESCE(NEW.seq_no, 1),
4462                 NEW.left_operand,
4463                 NEW.operator,
4464                 NEW.right_operand,
4465                 COALESCE(NEW.negate, false)
4466     );
4467
4468 CREATE OR REPLACE RULE query_expr_xop_update_rule AS
4469     ON UPDATE TO query.expr_xop
4470     DO INSTEAD
4471     UPDATE query.expression SET
4472         id = NEW.id,
4473         parenthesize = NEW.parenthesize,
4474         parent_expr = NEW.parent_expr,
4475         seq_no = NEW.seq_no,
4476                 left_operand = NEW.left_operand,
4477                 operator = NEW.operator,
4478                 right_operand = NEW.right_operand,
4479                 negate = NEW.negate
4480     WHERE
4481         id = OLD.id;
4482
4483 CREATE OR REPLACE RULE query_expr_xop_delete_rule AS
4484     ON DELETE TO query.expr_xop
4485     DO INSTEAD
4486     DELETE FROM query.expression WHERE id = OLD.id;
4487
4488 -- Create updatable view for series expressions
4489 -- i.e. series of expressions separated by operators
4490
4491 CREATE OR REPLACE VIEW query.expr_xser AS
4492     SELECT
4493                 id,
4494                 parenthesize,
4495                 parent_expr,
4496                 seq_no,
4497                 operator,
4498                 negate
4499     FROM
4500         query.expression
4501     WHERE
4502         type = 'xser';
4503
4504 CREATE OR REPLACE RULE query_expr_xser_insert_rule AS
4505     ON INSERT TO query.expr_xser
4506     DO INSTEAD
4507     INSERT INTO query.expression (
4508                 id,
4509                 type,
4510                 parenthesize,
4511                 parent_expr,
4512                 seq_no,
4513                 operator,
4514                 negate
4515     ) VALUES (
4516         COALESCE(NEW.id, NEXTVAL('query.expression_id_seq'::REGCLASS)),
4517         'xser',
4518         COALESCE(NEW.parenthesize, FALSE),
4519         NEW.parent_expr,
4520         COALESCE(NEW.seq_no, 1),
4521                 NEW.operator,
4522                 COALESCE(NEW.negate, false)
4523     );
4524
4525 CREATE OR REPLACE RULE query_expr_xser_update_rule AS
4526     ON UPDATE TO query.expr_xser
4527     DO INSTEAD
4528     UPDATE query.expression SET
4529         id = NEW.id,
4530         parenthesize = NEW.parenthesize,
4531         parent_expr = NEW.parent_expr,
4532         seq_no = NEW.seq_no,
4533                 operator = NEW.operator,
4534                 negate = NEW.negate
4535     WHERE
4536         id = OLD.id;
4537
4538 CREATE OR REPLACE RULE query_expr_xser_delete_rule AS
4539     ON DELETE TO query.expr_xser
4540     DO INSTEAD
4541     DELETE FROM query.expression WHERE id = OLD.id;
4542
4543 -- Create updatable view for string literal expressions
4544
4545 CREATE OR REPLACE VIEW query.expr_xstr AS
4546     SELECT
4547         id,
4548         parenthesize,
4549         parent_expr,
4550         seq_no,
4551         literal
4552     FROM
4553         query.expression
4554     WHERE
4555         type = 'xstr';
4556
4557 CREATE OR REPLACE RULE query_expr_string_insert_rule AS
4558     ON INSERT TO query.expr_xstr
4559     DO INSTEAD
4560     INSERT INTO query.expression (
4561         id,
4562         type,
4563         parenthesize,
4564         parent_expr,
4565         seq_no,
4566         literal
4567     ) VALUES (
4568         COALESCE(NEW.id, NEXTVAL('query.expression_id_seq'::REGCLASS)),
4569         'xstr',
4570         COALESCE(NEW.parenthesize, FALSE),
4571         NEW.parent_expr,
4572         COALESCE(NEW.seq_no, 1),
4573         NEW.literal
4574     );
4575
4576 CREATE OR REPLACE RULE query_expr_string_update_rule AS
4577     ON UPDATE TO query.expr_xstr
4578     DO INSTEAD
4579     UPDATE query.expression SET
4580         id = NEW.id,
4581         parenthesize = NEW.parenthesize,
4582         parent_expr = NEW.parent_expr,
4583         seq_no = NEW.seq_no,
4584         literal = NEW.literal
4585     WHERE
4586         id = OLD.id;
4587
4588 CREATE OR REPLACE RULE query_expr_string_delete_rule AS
4589     ON DELETE TO query.expr_xstr
4590     DO INSTEAD
4591     DELETE FROM query.expression WHERE id = OLD.id;
4592
4593 -- Create updatable view for subquery expressions
4594
4595 CREATE OR REPLACE VIEW query.expr_xsubq AS
4596     SELECT
4597                 id,
4598                 parenthesize,
4599                 parent_expr,
4600                 seq_no,
4601                 subquery,
4602                 negate
4603     FROM
4604         query.expression
4605     WHERE
4606         type = 'xsubq';
4607
4608 CREATE OR REPLACE RULE query_expr_xsubq_insert_rule AS
4609     ON INSERT TO query.expr_xsubq
4610     DO INSTEAD
4611     INSERT INTO query.expression (
4612                 id,
4613                 type,
4614                 parenthesize,
4615                 parent_expr,
4616                 seq_no,
4617                 subquery,
4618                 negate
4619     ) VALUES (
4620         COALESCE(NEW.id, NEXTVAL('query.expression_id_seq'::REGCLASS)),
4621         'xsubq',
4622         COALESCE(NEW.parenthesize, FALSE),
4623         NEW.parent_expr,
4624         COALESCE(NEW.seq_no, 1),
4625                 NEW.subquery,
4626                 COALESCE(NEW.negate, false)
4627     );
4628
4629 CREATE OR REPLACE RULE query_expr_xsubq_update_rule AS
4630     ON UPDATE TO query.expr_xsubq
4631     DO INSTEAD
4632     UPDATE query.expression SET
4633         id = NEW.id,
4634         parenthesize = NEW.parenthesize,
4635         parent_expr = NEW.parent_expr,
4636         seq_no = NEW.seq_no,
4637                 subquery = NEW.subquery,
4638                 negate = NEW.negate
4639     WHERE
4640         id = OLD.id;
4641
4642 CREATE OR REPLACE RULE query_expr_xsubq_delete_rule AS
4643     ON DELETE TO query.expr_xsubq
4644     DO INSTEAD
4645     DELETE FROM query.expression WHERE id = OLD.id;
4646
4647 CREATE TABLE action.fieldset (
4648     id              SERIAL          PRIMARY KEY,
4649     owner           INT             NOT NULL REFERENCES actor.usr (id)
4650                                     DEFERRABLE INITIALLY DEFERRED,
4651     owning_lib      INT             NOT NULL REFERENCES actor.org_unit (id)
4652                                     DEFERRABLE INITIALLY DEFERRED,
4653     status          TEXT            NOT NULL
4654                                     CONSTRAINT valid_status CHECK ( status in
4655                                     ( 'PENDING', 'APPLIED', 'ERROR' )),
4656     creation_time   TIMESTAMPTZ     NOT NULL DEFAULT NOW(),
4657     scheduled_time  TIMESTAMPTZ,
4658     applied_time    TIMESTAMPTZ,
4659     classname       TEXT            NOT NULL, -- an IDL class name
4660     name            TEXT            NOT NULL,
4661     stored_query    INT             REFERENCES query.stored_query (id)
4662                                     DEFERRABLE INITIALLY DEFERRED,
4663     pkey_value      TEXT,
4664     CONSTRAINT lib_name_unique UNIQUE (owning_lib, name),
4665     CONSTRAINT fieldset_one_or_the_other CHECK (
4666         (stored_query IS NOT NULL AND pkey_value IS NULL) OR
4667         (pkey_value IS NOT NULL AND stored_query IS NULL)
4668     )
4669     -- the CHECK constraint means we can update the fields for a single
4670     -- row without all the extra overhead involved in a query
4671 );
4672
4673 CREATE INDEX action_fieldset_sched_time_idx ON action.fieldset( scheduled_time );
4674 CREATE INDEX action_owner_idx               ON action.fieldset( owner );
4675
4676 CREATE TABLE action.fieldset_col_val (
4677     id              SERIAL  PRIMARY KEY,
4678     fieldset        INT     NOT NULL REFERENCES action.fieldset
4679                                          ON DELETE CASCADE
4680                                          DEFERRABLE INITIALLY DEFERRED,
4681     col             TEXT    NOT NULL,  -- "field" from the idl ... the column on the table
4682     val             TEXT,              -- value for the column ... NULL means, well, NULL
4683     CONSTRAINT fieldset_col_once_per_set UNIQUE (fieldset, col)
4684 );
4685
4686 CREATE OR REPLACE FUNCTION action.apply_fieldset(
4687         fieldset_id IN INT,        -- id from action.fieldset
4688         table_name  IN TEXT,       -- table to be updated
4689         pkey_name   IN TEXT,       -- name of primary key column in that table
4690         query       IN TEXT        -- query constructed by qstore (for query-based
4691                                    --    fieldsets only; otherwise null
4692 )
4693 RETURNS TEXT AS $$
4694 DECLARE
4695         statement TEXT;
4696         fs_status TEXT;
4697         fs_pkey_value TEXT;
4698         fs_query TEXT;
4699         sep CHAR;
4700         status_code TEXT;
4701         msg TEXT;
4702         update_count INT;
4703         cv RECORD;
4704 BEGIN
4705         -- Sanity checks
4706         IF fieldset_id IS NULL THEN
4707                 RETURN 'Fieldset ID parameter is NULL';
4708         END IF;
4709         IF table_name IS NULL THEN
4710                 RETURN 'Table name parameter is NULL';
4711         END IF;
4712         IF pkey_name IS NULL THEN
4713                 RETURN 'Primary key name parameter is NULL';
4714         END IF;
4715         --
4716         statement := 'UPDATE ' || table_name || ' SET';
4717         --
4718         SELECT
4719                 status,
4720                 quote_literal( pkey_value )
4721         INTO
4722                 fs_status,
4723                 fs_pkey_value
4724         FROM
4725                 action.fieldset
4726         WHERE
4727                 id = fieldset_id;
4728         --
4729         IF fs_status IS NULL THEN
4730                 RETURN 'No fieldset found for id = ' || fieldset_id;
4731         ELSIF fs_status = 'APPLIED' THEN
4732                 RETURN 'Fieldset ' || fieldset_id || ' has already been applied';
4733         END IF;
4734         --
4735         sep := '';
4736         FOR cv IN
4737                 SELECT  col,
4738                                 val
4739                 FROM    action.fieldset_col_val
4740                 WHERE   fieldset = fieldset_id
4741         LOOP
4742                 statement := statement || sep || ' ' || cv.col
4743                                          || ' = ' || coalesce( quote_literal( cv.val ), 'NULL' );
4744                 sep := ',';
4745         END LOOP;
4746         --
4747         IF sep = '' THEN
4748                 RETURN 'Fieldset ' || fieldset_id || ' has no column values defined';
4749         END IF;
4750         --
4751         -- Add the WHERE clause.  This differs according to whether it's a
4752         -- single-row fieldset or a query-based fieldset.
4753         --
4754         IF query IS NULL        AND fs_pkey_value IS NULL THEN
4755                 RETURN 'Incomplete fieldset: neither a primary key nor a query available';
4756         ELSIF query IS NOT NULL AND fs_pkey_value IS NULL THEN
4757             fs_query := rtrim( query, ';' );
4758             statement := statement || ' WHERE ' || pkey_name || ' IN ( '
4759                          || fs_query || ' );';
4760         ELSIF query IS NULL     AND fs_pkey_value IS NOT NULL THEN
4761                 statement := statement || ' WHERE ' || pkey_name || ' = '
4762                                      || fs_pkey_value || ';';
4763         ELSE  -- both are not null
4764                 RETURN 'Ambiguous fieldset: both a primary key and a query provided';
4765         END IF;
4766         --
4767         -- Execute the update
4768         --
4769         BEGIN
4770                 EXECUTE statement;
4771                 GET DIAGNOSTICS update_count = ROW_COUNT;
4772                 --
4773                 IF UPDATE_COUNT > 0 THEN
4774                         status_code := 'APPLIED';
4775                         msg := NULL;
4776                 ELSE
4777                         status_code := 'ERROR';
4778                         msg := 'No eligible rows found for fieldset ' || fieldset_id;
4779         END IF;
4780         EXCEPTION WHEN OTHERS THEN
4781                 status_code := 'ERROR';
4782                 msg := 'Unable to apply fieldset ' || fieldset_id
4783                            || ': ' || sqlerrm;
4784         END;
4785         --
4786         -- Update fieldset status
4787         --
4788         UPDATE action.fieldset
4789         SET status       = status_code,
4790             applied_time = now()
4791         WHERE id = fieldset_id;
4792         --
4793         RETURN msg;
4794 END;
4795 $$ LANGUAGE plpgsql;
4796
4797 COMMENT ON FUNCTION action.apply_fieldset( INT, TEXT, TEXT, TEXT ) IS $$
4798 /**
4799  * Applies a specified fieldset, using a supplied table name and primary
4800  * key name.  The query parameter should be non-null only for
4801  * query-based fieldsets.
4802  *
4803  * Returns NULL if successful, or an error message if not.
4804  */
4805 $$;
4806
4807 -- Removed:
4808 -- stuff pertaining to settings and setting types
4809 -- almost everything pertaining to permission.perm_list
4810 -- everything pertaining to the action_trigger schema
4811 -- the query schema
4812 -- almost everything pertaining to fieldsets
4813
4814 CREATE INDEX uhr_hold_idx ON action.unfulfilled_hold_list (hold);
4815
4816 CREATE OR REPLACE VIEW action.unfulfilled_hold_loops AS
4817     SELECT  u.hold,
4818             c.circ_lib,
4819             count(*)
4820       FROM  action.unfulfilled_hold_list u
4821             JOIN asset.copy c ON (c.id = u.current_copy)
4822       GROUP BY 1,2;
4823
4824 CREATE OR REPLACE VIEW action.unfulfilled_hold_min_loop AS
4825     SELECT  hold,
4826             min(count)
4827       FROM  action.unfulfilled_hold_loops
4828       GROUP BY 1;
4829
4830 CREATE OR REPLACE VIEW action.unfulfilled_hold_innermost_loop AS
4831     SELECT  DISTINCT l.*
4832       FROM  action.unfulfilled_hold_loops l
4833             JOIN action.unfulfilled_hold_min_loop m USING (hold)
4834       WHERE l.count = m.min;
4835
4836 ALTER TABLE asset.copy
4837 ADD COLUMN dummy_isbn TEXT;
4838
4839 ALTER TABLE auditor.asset_copy_history
4840 ADD COLUMN dummy_isbn TEXT;
4841
4842 -- Add new column status_changed_date to asset.copy, with trigger to maintain it
4843 -- Add corresponding new column to auditor.asset_copy_history
4844
4845 ALTER TABLE asset.copy
4846         ADD COLUMN status_changed_time TIMESTAMPTZ;
4847
4848 ALTER TABLE auditor.asset_copy_history
4849         ADD COLUMN status_changed_time TIMESTAMPTZ;
4850
4851 CREATE OR REPLACE FUNCTION asset.acp_status_changed()
4852 RETURNS TRIGGER AS $$
4853 BEGIN
4854         IF NEW.status <> OLD.status THEN
4855                 NEW.status_changed_time := now();
4856         END IF;
4857         RETURN NEW;
4858 END;
4859 $$ LANGUAGE plpgsql;
4860
4861 CREATE TRIGGER acp_status_changed_trig
4862         BEFORE UPDATE ON asset.copy
4863         FOR EACH ROW EXECUTE PROCEDURE asset.acp_status_changed();
4864
4865 ALTER TABLE asset.copy
4866 ADD COLUMN mint_condition boolean NOT NULL DEFAULT TRUE;
4867
4868 ALTER TABLE auditor.asset_copy_history
4869 ADD COLUMN mint_condition boolean NOT NULL DEFAULT TRUE;
4870
4871 ALTER TABLE asset.copy ADD COLUMN floating BOOL NOT NULL DEFAULT FALSE;
4872 ALTER TABLE auditor.asset_copy_history ADD COLUMN floating BOOL;
4873
4874 DROP INDEX IF EXISTS asset.copy_barcode_key;
4875 CREATE UNIQUE INDEX copy_barcode_key ON asset.copy (barcode) WHERE deleted = FALSE OR deleted IS FALSE;
4876
4877 -- Note: later we create a trigger a_opac_vis_mat_view_tgr
4878 -- AFTER INSERT OR UPDATE ON asset.copy
4879
4880 ALTER TABLE asset.copy ADD COLUMN cost NUMERIC(8,2);
4881 ALTER TABLE auditor.asset_copy_history ADD COLUMN cost NUMERIC(8,2);
4882
4883 -- Moke mostly parallel changes to action.circulation
4884 -- and action.aged_circulation
4885
4886 ALTER TABLE action.circulation
4887 ADD COLUMN workstation INT
4888     REFERENCES actor.workstation
4889         ON DELETE SET NULL
4890         DEFERRABLE INITIALLY DEFERRED;
4891
4892 ALTER TABLE action.aged_circulation
4893 ADD COLUMN workstation INT;
4894
4895 ALTER TABLE action.circulation
4896 ADD COLUMN parent_circ BIGINT
4897         REFERENCES action.circulation(id)
4898         DEFERRABLE INITIALLY DEFERRED;
4899
4900 CREATE UNIQUE INDEX circ_parent_idx
4901 ON action.circulation( parent_circ )
4902 WHERE parent_circ IS NOT NULL;
4903
4904 ALTER TABLE action.aged_circulation
4905 ADD COLUMN parent_circ BIGINT;
4906
4907 ALTER TABLE action.circulation
4908 ADD COLUMN checkin_workstation INT
4909         REFERENCES actor.workstation(id)
4910         ON DELETE SET NULL
4911         DEFERRABLE INITIALLY DEFERRED;
4912
4913 ALTER TABLE action.aged_circulation
4914 ADD COLUMN checkin_workstation INT;
4915
4916 ALTER TABLE action.circulation
4917 ADD COLUMN checkin_scan_time TIMESTAMPTZ;
4918
4919 ALTER TABLE action.aged_circulation
4920 ADD COLUMN checkin_scan_time TIMESTAMPTZ;
4921
4922 CREATE INDEX action_circulation_target_copy_idx
4923 ON action.circulation (target_copy);
4924
4925 CREATE INDEX action_aged_circulation_target_copy_idx
4926 ON action.aged_circulation (target_copy);
4927
4928 ALTER TABLE action.circulation
4929 DROP CONSTRAINT circulation_stop_fines_check;
4930
4931 ALTER TABLE action.circulation
4932         ADD CONSTRAINT circulation_stop_fines_check
4933         CHECK (stop_fines IN (
4934         'CHECKIN','CLAIMSRETURNED','LOST','MAXFINES','RENEW','LONGOVERDUE','CLAIMSNEVERCHECKEDOUT'));
4935
4936 -- Correct some long-standing misspellings involving variations of "recur"
4937
4938 ALTER TABLE action.circulation RENAME COLUMN recuring_fine TO recurring_fine;
4939 ALTER TABLE action.circulation RENAME COLUMN recuring_fine_rule TO recurring_fine_rule;
4940
4941 ALTER TABLE action.aged_circulation RENAME COLUMN recuring_fine TO recurring_fine;
4942 ALTER TABLE action.aged_circulation RENAME COLUMN recuring_fine_rule TO recurring_fine_rule;
4943
4944 ALTER TABLE config.rule_recuring_fine RENAME TO rule_recurring_fine;
4945 ALTER TABLE config.rule_recuring_fine_id_seq RENAME TO rule_recurring_fine_id_seq;
4946
4947 ALTER TABLE config.rule_recurring_fine RENAME COLUMN recurance_interval TO recurrence_interval;
4948
4949 -- Might as well keep the comment in sync as well
4950 COMMENT ON TABLE config.rule_recurring_fine IS $$
4951 /*
4952  * Copyright (C) 2005  Georgia Public Library Service 
4953  * Mike Rylander <mrylander@gmail.com>
4954  *
4955  * Circulation Recurring Fine rules
4956  *
4957  * Each circulation is given a recurring fine amount based on one of
4958  * these rules.  The recurrence_interval should not be any shorter
4959  * than the interval between runs of the fine_processor.pl script
4960  * (which is run from CRON), or you could miss fines.
4961  * 
4962  *
4963  * ****
4964  *
4965  * This program is free software; you can redistribute it and/or
4966  * modify it under the terms of the GNU General Public License
4967  * as published by the Free Software Foundation; either version 2
4968  * of the License, or (at your option) any later version.
4969  *
4970  * This program is distributed in the hope that it will be useful,
4971  * but WITHOUT ANY WARRANTY; without even the implied warranty of
4972  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
4973  * GNU General Public License for more details.
4974  */
4975 $$;
4976
4977 -- Extend the name change to some related views:
4978
4979 -- You would think that CREATE OR REPLACE would be enough, but in testing
4980 -- PostgreSQL complained about renaming the columns in the view. So we
4981 -- drop the view first.
4982 DROP VIEW IF EXISTS action.all_circulation;
4983
4984 CREATE OR REPLACE VIEW action.all_circulation AS
4985     SELECT  id,usr_post_code, usr_home_ou, usr_profile, usr_birth_year, copy_call_number, copy_location,
4986         copy_owning_lib, copy_circ_lib, copy_bib_record, xact_start, xact_finish, target_copy,
4987         circ_lib, circ_staff, checkin_staff, checkin_lib, renewal_remaining, due_date,
4988         stop_fines_time, checkin_time, create_time, duration, fine_interval, recurring_fine,
4989         max_fine, phone_renewal, desk_renewal, opac_renewal, duration_rule, recurring_fine_rule,
4990         max_fine_rule, stop_fines, workstation, checkin_workstation, checkin_scan_time, parent_circ
4991       FROM  action.aged_circulation
4992             UNION ALL
4993     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,
4994         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,
4995         cn.record AS copy_bib_record, circ.xact_start, circ.xact_finish, circ.target_copy, circ.circ_lib, circ.circ_staff, circ.checkin_staff,
4996         circ.checkin_lib, circ.renewal_remaining, circ.due_date, circ.stop_fines_time, circ.checkin_time, circ.create_time, circ.duration,
4997         circ.fine_interval, circ.recurring_fine, circ.max_fine, circ.phone_renewal, circ.desk_renewal, circ.opac_renewal, circ.duration_rule,
4998         circ.recurring_fine_rule, circ.max_fine_rule, circ.stop_fines, circ.workstation, circ.checkin_workstation, circ.checkin_scan_time,
4999         circ.parent_circ
5000       FROM  action.circulation circ
5001         JOIN asset.copy cp ON (circ.target_copy = cp.id)
5002         JOIN asset.call_number cn ON (cp.call_number = cn.id)
5003         JOIN actor.usr p ON (circ.usr = p.id)
5004         LEFT JOIN actor.usr_address a ON (p.mailing_address = a.id)
5005         LEFT JOIN actor.usr_address b ON (p.billing_address = a.id);
5006
5007 CREATE UNIQUE INDEX only_one_concurrent_checkout_per_copy ON action.circulation(target_copy) WHERE checkin_time IS NULL;
5008
5009 ALTER TABLE action.circulation DROP CONSTRAINT action_circulation_target_copy_fkey;
5010
5011 CREATE OR REPLACE FUNCTION action.age_circ_on_delete () RETURNS TRIGGER AS $$
5012 DECLARE
5013 found char := 'N';
5014 BEGIN
5015
5016     -- If there are any renewals for this circulation, don't archive or delete
5017     -- it yet.   We'll do so later, when we archive and delete the renewals.
5018
5019     SELECT 'Y' INTO found
5020     FROM action.circulation
5021     WHERE parent_circ = OLD.id
5022     LIMIT 1;
5023
5024     IF found = 'Y' THEN
5025         RETURN NULL;  -- don't delete
5026         END IF;
5027
5028     -- Archive a copy of the old row to action.aged_circulation
5029
5030     INSERT INTO action.aged_circulation
5031         (id,usr_post_code, usr_home_ou, usr_profile, usr_birth_year, copy_call_number, copy_location,
5032         copy_owning_lib, copy_circ_lib, copy_bib_record, xact_start, xact_finish, target_copy,
5033         circ_lib, circ_staff, checkin_staff, checkin_lib, renewal_remaining, due_date,
5034         stop_fines_time, checkin_time, create_time, duration, fine_interval, recurring_fine,
5035         max_fine, phone_renewal, desk_renewal, opac_renewal, duration_rule, recurring_fine_rule,
5036         max_fine_rule, stop_fines, workstation, checkin_workstation, checkin_scan_time, parent_circ)
5037       SELECT
5038         id,usr_post_code, usr_home_ou, usr_profile, usr_birth_year, copy_call_number, copy_location,
5039         copy_owning_lib, copy_circ_lib, copy_bib_record, xact_start, xact_finish, target_copy,
5040         circ_lib, circ_staff, checkin_staff, checkin_lib, renewal_remaining, due_date,
5041         stop_fines_time, checkin_time, create_time, duration, fine_interval, recurring_fine,
5042         max_fine, phone_renewal, desk_renewal, opac_renewal, duration_rule, recurring_fine_rule,
5043         max_fine_rule, stop_fines, workstation, checkin_workstation, checkin_scan_time, parent_circ
5044         FROM action.all_circulation WHERE id = OLD.id;
5045
5046     RETURN OLD;
5047 END;
5048 $$ LANGUAGE 'plpgsql';
5049
5050 UPDATE config.z3950_attr SET truncation = 1 WHERE source = 'biblios' AND name = 'title';
5051
5052 UPDATE config.z3950_attr SET truncation = 1 WHERE source = 'biblios' AND truncation = 0;
5053
5054 -- Adding circ.holds.target_skip_me OU setting logic to the pre-matchpoint tests
5055
5056 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$
5057 DECLARE
5058     matchpoint_id        INT;
5059     user_object        actor.usr%ROWTYPE;
5060     age_protect_object    config.rule_age_hold_protect%ROWTYPE;
5061     standing_penalty    config.standing_penalty%ROWTYPE;
5062     transit_range_ou_type    actor.org_unit_type%ROWTYPE;
5063     transit_source        actor.org_unit%ROWTYPE;
5064     item_object        asset.copy%ROWTYPE;
5065     ou_skip              actor.org_unit_setting%ROWTYPE;
5066     result            action.matrix_test_result;
5067     hold_test        config.hold_matrix_matchpoint%ROWTYPE;
5068     hold_count        INT;
5069     hold_transit_prox    INT;
5070     frozen_hold_count    INT;
5071     context_org_list    INT[];
5072     done            BOOL := FALSE;
5073 BEGIN
5074     SELECT INTO user_object * FROM actor.usr WHERE id = match_user;
5075     SELECT INTO context_org_list ARRAY_ACCUM(id) FROM actor.org_unit_full_path( pickup_ou );
5076
5077     result.success := TRUE;
5078
5079     -- Fail if we couldn't find a user
5080     IF user_object.id IS NULL THEN
5081         result.fail_part := 'no_user';
5082         result.success := FALSE;
5083         done := TRUE;
5084         RETURN NEXT result;
5085         RETURN;
5086     END IF;
5087
5088     SELECT INTO item_object * FROM asset.copy WHERE id = match_item;
5089
5090     -- Fail if we couldn't find a copy
5091     IF item_object.id IS NULL THEN
5092         result.fail_part := 'no_item';
5093         result.success := FALSE;
5094         done := TRUE;
5095         RETURN NEXT result;
5096         RETURN;
5097     END IF;
5098
5099     SELECT INTO matchpoint_id action.find_hold_matrix_matchpoint(pickup_ou, request_ou, match_item, match_user, match_requestor);
5100     result.matchpoint := matchpoint_id;
5101
5102     SELECT INTO ou_skip * FROM actor.org_unit_setting WHERE name = 'circ.holds.target_skip_me' AND org_unit = item_object.circ_lib;
5103
5104     -- Fail if the circ_lib for the item has circ.holds.target_skip_me set to true
5105     IF ou_skip.id IS NOT NULL AND ou_skip.value = 'true' THEN
5106         result.fail_part := 'circ.holds.target_skip_me';
5107         result.success := FALSE;
5108         done := TRUE;
5109         RETURN NEXT result;
5110         RETURN;
5111     END IF;
5112
5113     -- Fail if user is barred
5114     IF user_object.barred IS TRUE THEN
5115         result.fail_part := 'actor.usr.barred';
5116         result.success := FALSE;
5117         done := TRUE;
5118         RETURN NEXT result;
5119         RETURN;
5120     END IF;
5121
5122     -- Fail if we couldn't find any matchpoint (requires a default)
5123     IF matchpoint_id IS NULL THEN
5124         result.fail_part := 'no_matchpoint';
5125         result.success := FALSE;
5126         done := TRUE;
5127         RETURN NEXT result;
5128         RETURN;
5129     END IF;
5130
5131     SELECT INTO hold_test * FROM config.hold_matrix_matchpoint WHERE id = matchpoint_id;
5132
5133     IF hold_test.holdable IS FALSE THEN
5134         result.fail_part := 'config.hold_matrix_test.holdable';
5135         result.success := FALSE;
5136         done := TRUE;
5137         RETURN NEXT result;
5138     END IF;
5139
5140     IF hold_test.transit_range IS NOT NULL THEN
5141         SELECT INTO transit_range_ou_type * FROM actor.org_unit_type WHERE id = hold_test.transit_range;
5142         IF hold_test.distance_is_from_owner THEN
5143             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;
5144         ELSE
5145             SELECT INTO transit_source * FROM actor.org_unit WHERE id = item_object.circ_lib;
5146         END IF;
5147
5148         PERFORM * FROM actor.org_unit_descendants( transit_source.id, transit_range_ou_type.depth ) WHERE id = pickup_ou;
5149
5150         IF NOT FOUND THEN
5151             result.fail_part := 'transit_range';
5152             result.success := FALSE;
5153             done := TRUE;
5154             RETURN NEXT result;
5155         END IF;
5156     END IF;
5157  
5158     FOR standing_penalty IN
5159         SELECT  DISTINCT csp.*
5160           FROM  actor.usr_standing_penalty usp
5161                 JOIN config.standing_penalty csp ON (csp.id = usp.standing_penalty)
5162           WHERE usr = match_user
5163                 AND usp.org_unit IN ( SELECT * FROM explode_array(context_org_list) )
5164                 AND (usp.stop_date IS NULL or usp.stop_date > NOW())
5165                 AND csp.block_list LIKE '%HOLD%' LOOP
5166
5167         result.fail_part := standing_penalty.name;
5168         result.success := FALSE;
5169         done := TRUE;
5170         RETURN NEXT result;
5171     END LOOP;
5172
5173     IF hold_test.stop_blocked_user IS TRUE THEN
5174         FOR standing_penalty IN
5175             SELECT  DISTINCT csp.*
5176               FROM  actor.usr_standing_penalty usp
5177                     JOIN config.standing_penalty csp ON (csp.id = usp.standing_penalty)
5178               WHERE usr = match_user
5179                     AND usp.org_unit IN ( SELECT * FROM explode_array(context_org_list) )
5180                     AND (usp.stop_date IS NULL or usp.stop_date > NOW())
5181                     AND csp.block_list LIKE '%CIRC%' LOOP
5182     
5183             result.fail_part := standing_penalty.name;
5184             result.success := FALSE;
5185             done := TRUE;
5186             RETURN NEXT result;
5187         END LOOP;
5188     END IF;
5189
5190     IF hold_test.max_holds IS NOT NULL THEN
5191         SELECT    INTO hold_count COUNT(*)
5192           FROM    action.hold_request
5193           WHERE    usr = match_user
5194             AND fulfillment_time IS NULL
5195             AND cancel_time IS NULL
5196             AND CASE WHEN hold_test.include_frozen_holds THEN TRUE ELSE frozen IS FALSE END;
5197
5198         IF hold_count >= hold_test.max_holds THEN
5199             result.fail_part := 'config.hold_matrix_test.max_holds';
5200             result.success := FALSE;
5201             done := TRUE;
5202             RETURN NEXT result;
5203         END IF;
5204     END IF;
5205
5206     IF item_object.age_protect IS NOT NULL THEN
5207         SELECT INTO age_protect_object * FROM config.rule_age_hold_protect WHERE id = item_object.age_protect;
5208
5209         IF item_object.create_date + age_protect_object.age > NOW() THEN
5210             IF hold_test.distance_is_from_owner THEN
5211                 SELECT INTO hold_transit_prox prox FROM actor.org_unit_proximity WHERE from_org = item_cn_object.owning_lib AND to_org = pickup_ou;
5212             ELSE
5213                 SELECT INTO hold_transit_prox prox FROM actor.org_unit_proximity WHERE from_org = item_object.circ_lib AND to_org = pickup_ou;
5214             END IF;
5215
5216             IF hold_transit_prox > age_protect_object.prox THEN
5217                 result.fail_part := 'config.rule_age_hold_protect.prox';
5218                 result.success := FALSE;
5219                 done := TRUE;
5220                 RETURN NEXT result;
5221             END IF;
5222         END IF;
5223     END IF;
5224
5225     IF NOT done THEN
5226         RETURN NEXT result;
5227     END IF;
5228
5229     RETURN;
5230 END;
5231 $func$ LANGUAGE plpgsql;
5232
5233 -- New post-delete trigger to propagate deletions to parent(s)
5234
5235 CREATE OR REPLACE FUNCTION action.age_parent_circ_on_delete () RETURNS TRIGGER AS $$
5236 BEGIN
5237
5238     -- Having deleted a renewal, we can delete the original circulation (or a previous
5239     -- renewal, if that's what parent_circ is pointing to).  That deletion will trigger
5240     -- deletion of any prior parents, etc. recursively.
5241
5242     IF OLD.parent_circ IS NOT NULL THEN
5243         DELETE FROM action.circulation
5244         WHERE id = OLD.parent_circ;
5245     END IF;
5246
5247     RETURN OLD;
5248 END;
5249 $$ LANGUAGE 'plpgsql';
5250
5251 CREATE TRIGGER age_parent_circ AFTER DELETE ON action.circulation
5252 FOR EACH ROW EXECUTE PROCEDURE action.age_parent_circ_on_delete ();
5253
5254 -- This only gets inserted if there are no other id > 100 billing types
5255 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;
5256 SELECT SETVAL('config.billing_type_id_seq'::TEXT, 101) FROM config.billing_type_id_seq WHERE last_value < 101;
5257
5258 -- Populate xact_type column in the materialized version of billable_xact_summary
5259
5260 CREATE OR REPLACE FUNCTION money.mat_summary_create () RETURNS TRIGGER AS $$
5261 BEGIN
5262         INSERT INTO money.materialized_billable_xact_summary (id, usr, xact_start, xact_finish, total_paid, total_owed, balance_owed, xact_type)
5263                 VALUES ( NEW.id, NEW.usr, NEW.xact_start, NEW.xact_finish, 0.0, 0.0, 0.0, TG_ARGV[0]);
5264         RETURN NEW;
5265 END;
5266 $$ LANGUAGE PLPGSQL;
5267  
5268 DROP TRIGGER IF EXISTS mat_summary_create_tgr ON action.circulation;
5269 CREATE TRIGGER mat_summary_create_tgr AFTER INSERT ON action.circulation FOR EACH ROW EXECUTE PROCEDURE money.mat_summary_create ('circulation');
5270  
5271 DROP TRIGGER IF EXISTS mat_summary_create_tgr ON money.grocery;
5272 CREATE TRIGGER mat_summary_create_tgr AFTER INSERT ON money.grocery FOR EACH ROW EXECUTE PROCEDURE money.mat_summary_create ('grocery');
5273
5274 CREATE RULE money_payment_view_update AS ON UPDATE TO money.payment_view DO INSTEAD 
5275     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;
5276
5277 -- Generate the equivalent of compound subject entries from the existing rows
5278 -- so that we don't have to laboriously reindex them
5279
5280 --INSERT INTO config.metabib_field (field_class, name, format, xpath ) VALUES
5281 --    ( 'subject', 'complete', 'mods32', $$//mods32:mods/mods32:subject//text()$$ );
5282 --
5283 --CREATE INDEX metabib_subject_field_entry_source_idx ON metabib.subject_field_entry (source);
5284 --
5285 --INSERT INTO metabib.subject_field_entry (source, field, value)
5286 --    SELECT source, (
5287 --            SELECT id 
5288 --            FROM config.metabib_field
5289 --            WHERE field_class = 'subject' AND name = 'complete'
5290 --        ), 
5291 --        ARRAY_TO_STRING ( 
5292 --            ARRAY (
5293 --                SELECT value 
5294 --                FROM metabib.subject_field_entry msfe
5295 --                WHERE msfe.source = groupee.source
5296 --                ORDER BY source 
5297 --            ), ' ' 
5298 --        ) AS grouped
5299 --    FROM ( 
5300 --        SELECT source
5301 --        FROM metabib.subject_field_entry
5302 --        GROUP BY source
5303 --    ) AS groupee;
5304
5305 CREATE OR REPLACE FUNCTION money.materialized_summary_billing_del () RETURNS TRIGGER AS $$
5306 DECLARE
5307         prev_billing    money.billing%ROWTYPE;
5308         old_billing     money.billing%ROWTYPE;
5309 BEGIN
5310         SELECT * INTO prev_billing FROM money.billing WHERE xact = OLD.xact AND NOT voided ORDER BY billing_ts DESC LIMIT 1 OFFSET 1;
5311         SELECT * INTO old_billing FROM money.billing WHERE xact = OLD.xact AND NOT voided ORDER BY billing_ts DESC LIMIT 1;
5312
5313         IF OLD.id = old_billing.id THEN
5314                 UPDATE  money.materialized_billable_xact_summary
5315                   SET   last_billing_ts = prev_billing.billing_ts,
5316                         last_billing_note = prev_billing.note,
5317                         last_billing_type = prev_billing.billing_type
5318                   WHERE id = OLD.xact;
5319         END IF;
5320
5321         IF NOT OLD.voided THEN
5322                 UPDATE  money.materialized_billable_xact_summary
5323                   SET   total_owed = total_owed - OLD.amount,
5324                         balance_owed = balance_owed + OLD.amount
5325                   WHERE id = OLD.xact;
5326         END IF;
5327
5328         RETURN OLD;
5329 END;
5330 $$ LANGUAGE PLPGSQL;
5331
5332 -- ARG! need to rid ourselves of the broken table definition ... this mechanism is not ideal, sorry.
5333 DROP TABLE IF EXISTS config.index_normalizer CASCADE;
5334
5335 CREATE OR REPLACE FUNCTION public.naco_normalize( TEXT, TEXT ) RETURNS TEXT AS $func$
5336         use Unicode::Normalize;
5337         use Encode;
5338
5339         # When working with Unicode data, the first step is to decode it to
5340         # a byte string; after that, lowercasing is safe
5341         my $txt = lc(decode_utf8(shift));
5342         my $sf = shift;
5343
5344         $txt = NFD($txt);
5345         $txt =~ s/\pM+//go;     # Remove diacritics
5346
5347         $txt =~ s/\xE6/AE/go;   # Convert ae digraph
5348         $txt =~ s/\x{153}/OE/go;# Convert oe digraph
5349         $txt =~ s/\xFE/TH/go;   # Convert Icelandic thorn
5350
5351         $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
5352         $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
5353
5354         $txt =~ tr/\x{0251}\x{03B1}\x{03B2}\x{0262}\x{03B3}/AABGG/;             # Convert Latin and Greek
5355         $txt =~ tr/\x{2113}\xF0\!\"\(\)\-\{\}\<\>\;\:\.\?\xA1\xBF\/\\\@\*\%\=\xB1\+\xAE\xA9\x{2117}\$\xA3\x{FFE1}\xB0\^\_\~\`/LD /;     # Convert Misc
5356         $txt =~ tr/\'\[\]\|//d;                                                 # Remove Misc
5357
5358         if ($sf && $sf =~ /^a/o) {
5359                 my $commapos = index($txt,',');
5360                 if ($commapos > -1) {
5361                         if ($commapos != length($txt) - 1) {
5362                                 my @list = split /,/, $txt;
5363                                 my $first = shift @list;
5364                                 $txt = $first . ',' . join(' ', @list);
5365                         } else {
5366                                 $txt =~ s/,/ /go;
5367                         }
5368                 }
5369         } else {
5370                 $txt =~ s/,/ /go;
5371         }
5372
5373         $txt =~ s/\s+/ /go;     # Compress multiple spaces
5374         $txt =~ s/^\s+//o;      # Remove leading space
5375         $txt =~ s/\s+$//o;      # Remove trailing space
5376
5377         # Encoding the outgoing string is good practice, but not strictly
5378         # necessary in this case because we've stripped everything from it
5379         return encode_utf8($txt);
5380 $func$ LANGUAGE 'plperlu' STRICT IMMUTABLE;
5381
5382 -- Some handy functions, based on existing ones, to provide optional ingest normalization
5383
5384 CREATE OR REPLACE FUNCTION public.left_trunc( TEXT, INT ) RETURNS TEXT AS $func$
5385         SELECT SUBSTRING($1,$2);
5386 $func$ LANGUAGE SQL STRICT IMMUTABLE;
5387
5388 CREATE OR REPLACE FUNCTION public.right_trunc( TEXT, INT ) RETURNS TEXT AS $func$
5389         SELECT SUBSTRING($1,1,$2);
5390 $func$ LANGUAGE SQL STRICT IMMUTABLE;
5391
5392 CREATE OR REPLACE FUNCTION public.naco_normalize_keep_comma( TEXT ) RETURNS TEXT AS $func$
5393         SELECT public.naco_normalize($1,'a');
5394 $func$ LANGUAGE SQL STRICT IMMUTABLE;
5395
5396 CREATE OR REPLACE FUNCTION public.split_date_range( TEXT ) RETURNS TEXT AS $func$
5397         SELECT REGEXP_REPLACE( $1, E'(\\d{4})-(\\d{4})', E'\\1 \\2', 'g' );
5398 $func$ LANGUAGE SQL STRICT IMMUTABLE;
5399
5400 -- And ... a table in which to register them
5401
5402 CREATE TABLE config.index_normalizer (
5403         id              SERIAL  PRIMARY KEY,
5404         name            TEXT    UNIQUE NOT NULL,
5405         description     TEXT    UNIQUE NOT NULL,
5406         func            TEXT    NOT NULL,
5407         param_count     INT     NOT NULL DEFAULT 0
5408 );
5409
5410 CREATE TABLE config.metabib_field_index_norm_map (
5411         id      SERIAL  PRIMARY KEY,
5412         field   INT     NOT NULL REFERENCES config.metabib_field (id) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED,
5413         norm    INT     NOT NULL REFERENCES config.index_normalizer (id) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED,
5414         params  TEXT,
5415         pos     INT     NOT NULL DEFAULT 0
5416 );
5417
5418 INSERT INTO config.index_normalizer (name, description, func, param_count) VALUES (
5419         'NACO Normalize',
5420         'Apply NACO normalization rules to the extracted text.  See http://www.loc.gov/catdir/pcc/naco/normrule-2.html for details.',
5421         'naco_normalize',
5422         0
5423 );
5424
5425 INSERT INTO config.index_normalizer (name, description, func, param_count) VALUES (
5426         'Normalize date range',
5427         'Split date ranges in the form of "XXXX-YYYY" into "XXXX YYYY" for proper index.',
5428         'split_date_range',
5429         1
5430 );
5431
5432 INSERT INTO config.index_normalizer (name, description, func, param_count) VALUES (
5433         'NACO Normalize -- retain first comma',
5434         '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.',
5435         'naco_normalize_keep_comma',
5436         0
5437 );
5438
5439 INSERT INTO config.index_normalizer (name, description, func, param_count) VALUES (
5440         'Strip Diacritics',
5441         'Convert text to NFD form and remove non-spacing combining marks.',
5442         'remove_diacritics',
5443         0
5444 );
5445
5446 INSERT INTO config.index_normalizer (name, description, func, param_count) VALUES (
5447         'Up-case',
5448         'Convert text upper case.',
5449         'uppercase',
5450         0
5451 );
5452
5453 INSERT INTO config.index_normalizer (name, description, func, param_count) VALUES (
5454         'Down-case',
5455         'Convert text lower case.',
5456         'lowercase',
5457         0
5458 );
5459
5460 INSERT INTO config.index_normalizer (name, description, func, param_count) VALUES (
5461         'Extract Dewey-like number',
5462         'Extract a string of numeric characters ther resembles a DDC number.',
5463         'call_number_dewey',
5464         0
5465 );
5466
5467 INSERT INTO config.index_normalizer (name, description, func, param_count) VALUES (
5468         'Left truncation',
5469         'Discard the specified number of characters from the left side of the string.',
5470         'left_trunc',
5471         1
5472 );
5473
5474 INSERT INTO config.index_normalizer (name, description, func, param_count) VALUES (
5475         'Right truncation',
5476         'Include only the specified number of characters from the left side of the string.',
5477         'right_trunc',
5478         1
5479 );
5480
5481 INSERT INTO config.index_normalizer (name, description, func, param_count) VALUES (
5482         'First word',
5483         'Include only the first space-separated word of a string.',
5484         'first_word',
5485         0
5486 );
5487
5488 INSERT INTO config.metabib_field_index_norm_map (field,norm)
5489         SELECT  m.id,
5490                 i.id
5491           FROM  config.metabib_field m,
5492                 config.index_normalizer i
5493           WHERE i.func IN ('naco_normalize','split_date_range');
5494
5495 CREATE OR REPLACE FUNCTION oils_tsearch2 () RETURNS TRIGGER AS $$
5496 DECLARE
5497     normalizer      RECORD;
5498     value           TEXT := '';
5499 BEGIN
5500
5501     value := NEW.value;
5502
5503     IF TG_TABLE_NAME::TEXT ~ 'field_entry$' THEN
5504         FOR normalizer IN
5505             SELECT  n.func AS func,
5506                     n.param_count AS param_count,
5507                     m.params AS params
5508               FROM  config.index_normalizer n
5509                     JOIN config.metabib_field_index_norm_map m ON (m.norm = n.id)
5510               WHERE field = NEW.field AND m.pos < 0
5511               ORDER BY m.pos LOOP
5512                 EXECUTE 'SELECT ' || normalizer.func || '(' ||
5513                     quote_literal( value ) ||
5514                     CASE
5515                         WHEN normalizer.param_count > 0
5516                             THEN ',' || REPLACE(REPLACE(BTRIM(normalizer.params,'[]'),E'\'',E'\\\''),E'"',E'\'')
5517                             ELSE ''
5518                         END ||
5519                     ')' INTO value;
5520
5521         END LOOP;
5522
5523         NEW.value := value;
5524     END IF;
5525
5526     IF NEW.index_vector = ''::tsvector THEN
5527         RETURN NEW;
5528     END IF;
5529
5530     IF TG_TABLE_NAME::TEXT ~ 'field_entry$' THEN
5531         FOR normalizer IN
5532             SELECT  n.func AS func,
5533                     n.param_count AS param_count,
5534                     m.params AS params
5535               FROM  config.index_normalizer n
5536                     JOIN config.metabib_field_index_norm_map m ON (m.norm = n.id)
5537               WHERE field = NEW.field AND m.pos >= 0
5538               ORDER BY m.pos LOOP
5539                 EXECUTE 'SELECT ' || normalizer.func || '(' ||
5540                     quote_literal( value ) ||
5541                     CASE
5542                         WHEN normalizer.param_count > 0
5543                             THEN ',' || REPLACE(REPLACE(BTRIM(normalizer.params,'[]'),E'\'',E'\\\''),E'"',E'\'')
5544                             ELSE ''
5545                         END ||
5546                     ')' INTO value;
5547
5548         END LOOP;
5549     END IF;
5550
5551     IF REGEXP_REPLACE(VERSION(),E'^.+?(\\d+\\.\\d+).*?$',E'\\1')::FLOAT > 8.2 THEN
5552         NEW.index_vector = to_tsvector((TG_ARGV[0])::regconfig, value);
5553     ELSE
5554         NEW.index_vector = to_tsvector(TG_ARGV[0], value);
5555     END IF;
5556
5557     RETURN NEW;
5558 END;
5559 $$ LANGUAGE PLPGSQL;
5560
5561 CREATE OR REPLACE FUNCTION oils_xpath ( TEXT, TEXT, ANYARRAY ) RETURNS TEXT[] AS 'SELECT XPATH( $1, $2::XML, $3 )::TEXT[];' LANGUAGE SQL IMMUTABLE;
5562
5563 CREATE OR REPLACE FUNCTION oils_xpath ( TEXT, TEXT ) RETURNS TEXT[] AS 'SELECT XPATH( $1, $2::XML )::TEXT[];' LANGUAGE SQL IMMUTABLE;
5564
5565 CREATE OR REPLACE FUNCTION oils_xpath_string ( TEXT, TEXT, TEXT, ANYARRAY ) RETURNS TEXT AS $func$
5566     SELECT  ARRAY_TO_STRING(
5567                 oils_xpath(
5568                     $1 ||
5569                         CASE WHEN $1 ~ $re$/[^/[]*@[^]]+$$re$ OR $1 ~ $re$text\(\)$$re$ THEN '' ELSE '//text()' END,
5570                     $2,
5571                     $4
5572                 ),
5573                 $3
5574             );
5575 $func$ LANGUAGE SQL IMMUTABLE;
5576
5577 CREATE OR REPLACE FUNCTION oils_xpath_string ( TEXT, TEXT, TEXT ) RETURNS TEXT AS $func$
5578     SELECT oils_xpath_string( $1, $2, $3, '{}'::TEXT[] );
5579 $func$ LANGUAGE SQL IMMUTABLE;
5580
5581 CREATE OR REPLACE FUNCTION oils_xpath_string ( TEXT, TEXT, ANYARRAY ) RETURNS TEXT AS $func$
5582     SELECT oils_xpath_string( $1, $2, '', $3 );
5583 $func$ LANGUAGE SQL IMMUTABLE;
5584
5585 CREATE OR REPLACE FUNCTION oils_xpath_string ( TEXT, TEXT ) RETURNS TEXT AS $func$
5586     SELECT oils_xpath_string( $1, $2, '{}'::TEXT[] );
5587 $func$ LANGUAGE SQL IMMUTABLE;
5588
5589 CREATE TYPE metabib.field_entry_template AS (
5590         field_class     TEXT,
5591         field           INT,
5592         source          BIGINT,
5593         value           TEXT
5594 );
5595
5596 CREATE OR REPLACE FUNCTION oils_xslt_process(TEXT, TEXT) RETURNS TEXT AS $func$
5597   use strict;
5598
5599   use XML::LibXSLT;
5600   use XML::LibXML;
5601
5602   my $doc = shift;
5603   my $xslt = shift;
5604
5605   # The following approach uses the older XML::LibXML 1.69 / XML::LibXSLT 1.68
5606   # methods of parsing XML documents and stylesheets, in the hopes of broader
5607   # compatibility with distributions
5608   my $parser = $_SHARED{'_xslt_process'}{parsers}{xml} || XML::LibXML->new();
5609
5610   # Cache the XML parser, if we do not already have one
5611   $_SHARED{'_xslt_process'}{parsers}{xml} = $parser
5612     unless ($_SHARED{'_xslt_process'}{parsers}{xml});
5613
5614   my $xslt_parser = $_SHARED{'_xslt_process'}{parsers}{xslt} || XML::LibXSLT->new();
5615
5616   # Cache the XSLT processor, if we do not already have one
5617   $_SHARED{'_xslt_process'}{parsers}{xslt} = $xslt_parser
5618     unless ($_SHARED{'_xslt_process'}{parsers}{xslt});
5619
5620   my $stylesheet = $_SHARED{'_xslt_process'}{stylesheets}{$xslt} ||
5621     $xslt_parser->parse_stylesheet( $parser->parse_string($xslt) );
5622
5623   $_SHARED{'_xslt_process'}{stylesheets}{$xslt} = $stylesheet
5624     unless ($_SHARED{'_xslt_process'}{stylesheets}{$xslt});
5625
5626   return $stylesheet->output_string(
5627     $stylesheet->transform(
5628       $parser->parse_string($doc)
5629     )
5630   );
5631
5632 $func$ LANGUAGE 'plperlu' STRICT IMMUTABLE;
5633
5634 -- Add two columns so that the following function will compile.
5635 -- Eventually the label column will be NOT NULL, but not yet.
5636 ALTER TABLE config.metabib_field ADD COLUMN label TEXT;
5637 ALTER TABLE config.metabib_field ADD COLUMN facet_xpath TEXT;
5638
5639 CREATE OR REPLACE FUNCTION biblio.extract_metabib_field_entry ( rid BIGINT, default_joiner TEXT ) RETURNS SETOF metabib.field_entry_template AS $func$
5640 DECLARE
5641     bib     biblio.record_entry%ROWTYPE;
5642     idx     config.metabib_field%ROWTYPE;
5643     xfrm        config.xml_transform%ROWTYPE;
5644     prev_xfrm   TEXT;
5645     transformed_xml TEXT;
5646     xml_node    TEXT;
5647     xml_node_list   TEXT[];
5648     facet_text  TEXT;
5649     raw_text    TEXT;
5650     curr_text   TEXT;
5651     joiner      TEXT := default_joiner; -- XXX will index defs supply a joiner?
5652     output_row  metabib.field_entry_template%ROWTYPE;
5653 BEGIN
5654
5655     -- Get the record
5656     SELECT INTO bib * FROM biblio.record_entry WHERE id = rid;
5657
5658     -- Loop over the indexing entries
5659     FOR idx IN SELECT * FROM config.metabib_field ORDER BY format LOOP
5660
5661         SELECT INTO xfrm * from config.xml_transform WHERE name = idx.format;
5662
5663         -- See if we can skip the XSLT ... it's expensive
5664         IF prev_xfrm IS NULL OR prev_xfrm <> xfrm.name THEN
5665             -- Can't skip the transform
5666             IF xfrm.xslt <> '---' THEN
5667                 transformed_xml := oils_xslt_process(bib.marc,xfrm.xslt);
5668             ELSE
5669                 transformed_xml := bib.marc;
5670             END IF;
5671
5672             prev_xfrm := xfrm.name;
5673         END IF;
5674
5675         xml_node_list := oils_xpath( idx.xpath, transformed_xml, ARRAY[ARRAY[xfrm.prefix, xfrm.namespace_uri]] );
5676
5677         raw_text := NULL;
5678         FOR xml_node IN SELECT x FROM explode_array(xml_node_list) AS x LOOP
5679             CONTINUE WHEN xml_node !~ E'^\\s*<';
5680
5681             curr_text := ARRAY_TO_STRING(
5682                 oils_xpath( '//text()',
5683                     REGEXP_REPLACE( -- This escapes all &s not followed by "amp;".  Data ise returned from oils_xpath (above) in UTF-8, not entity encoded
5684                         REGEXP_REPLACE( -- This escapes embeded <s
5685                             xml_node,
5686                             $re$(>[^<]+)(<)([^>]+<)$re$,
5687                             E'\\1&lt;\\3',
5688                             'g'
5689                         ),
5690                         '&(?!amp;)',
5691                         '&amp;',
5692                         'g'
5693                     )
5694                 ),
5695                 ' '
5696             );
5697
5698             CONTINUE WHEN curr_text IS NULL OR curr_text = '';
5699
5700             IF raw_text IS NOT NULL THEN
5701                 raw_text := raw_text || joiner;
5702             END IF;
5703
5704             raw_text := COALESCE(raw_text,'') || curr_text;
5705
5706             -- insert raw node text for faceting
5707             IF idx.facet_field THEN
5708
5709                 IF idx.facet_xpath IS NOT NULL AND idx.facet_xpath <> '' THEN
5710                     facet_text := oils_xpath_string( idx.facet_xpath, xml_node, joiner, ARRAY[ARRAY[xfrm.prefix, xfrm.namespace_uri]] );
5711                 ELSE
5712                     facet_text := curr_text;
5713                 END IF;
5714
5715                 output_row.field_class = idx.field_class;
5716                 output_row.field = -1 * idx.id;
5717                 output_row.source = rid;
5718                 output_row.value = BTRIM(REGEXP_REPLACE(facet_text, E'\\s+', ' ', 'g'));
5719
5720                 RETURN NEXT output_row;
5721             END IF;
5722
5723         END LOOP;
5724
5725         CONTINUE WHEN raw_text IS NULL OR raw_text = '';
5726
5727         -- insert combined node text for searching
5728         IF idx.search_field THEN
5729             output_row.field_class = idx.field_class;
5730             output_row.field = idx.id;
5731             output_row.source = rid;
5732             output_row.value = BTRIM(REGEXP_REPLACE(raw_text, E'\\s+', ' ', 'g'));
5733
5734             RETURN NEXT output_row;
5735         END IF;
5736
5737     END LOOP;
5738
5739 END;
5740 $func$ LANGUAGE PLPGSQL;
5741
5742 -- default to a space joiner
5743 CREATE OR REPLACE FUNCTION biblio.extract_metabib_field_entry ( BIGINT ) RETURNS SETOF metabib.field_entry_template AS $func$
5744         SELECT * FROM biblio.extract_metabib_field_entry($1, ' ');
5745 $func$ LANGUAGE SQL;
5746
5747 CREATE OR REPLACE FUNCTION biblio.flatten_marc ( TEXT ) RETURNS SETOF metabib.full_rec AS $func$
5748
5749 use MARC::Record;
5750 use MARC::File::XML (BinaryEncoding => 'UTF-8');
5751
5752 my $xml = shift;
5753 my $r = MARC::Record->new_from_xml( $xml );
5754
5755 return_next( { tag => 'LDR', value => $r->leader } );
5756
5757 for my $f ( $r->fields ) {
5758     if ($f->is_control_field) {
5759         return_next({ tag => $f->tag, value => $f->data });
5760     } else {
5761         for my $s ($f->subfields) {
5762             return_next({
5763                 tag      => $f->tag,
5764                 ind1     => $f->indicator(1),
5765                 ind2     => $f->indicator(2),
5766                 subfield => $s->[0],
5767                 value    => $s->[1]
5768             });
5769
5770             if ( $f->tag eq '245' and $s->[0] eq 'a' ) {
5771                 my $trim = $f->indicator(2) || 0;
5772                 return_next({
5773                     tag      => 'tnf',
5774                     ind1     => $f->indicator(1),
5775                     ind2     => $f->indicator(2),
5776                     subfield => 'a',
5777                     value    => substr( $s->[1], $trim )
5778                 });
5779             }
5780         }
5781     }
5782 }
5783
5784 return undef;
5785
5786 $func$ LANGUAGE PLPERLU;
5787
5788 CREATE OR REPLACE FUNCTION biblio.flatten_marc ( rid BIGINT ) RETURNS SETOF metabib.full_rec AS $func$
5789 DECLARE
5790     bib biblio.record_entry%ROWTYPE;
5791     output  metabib.full_rec%ROWTYPE;
5792     field   RECORD;
5793 BEGIN
5794     SELECT INTO bib * FROM biblio.record_entry WHERE id = rid;
5795
5796     FOR field IN SELECT * FROM biblio.flatten_marc( bib.marc ) LOOP
5797         output.record := rid;
5798         output.ind1 := field.ind1;
5799         output.ind2 := field.ind2;
5800         output.tag := field.tag;
5801         output.subfield := field.subfield;
5802         IF field.subfield IS NOT NULL AND field.tag NOT IN ('020','022','024') THEN -- exclude standard numbers and control fields
5803             output.value := naco_normalize(field.value, field.subfield);
5804         ELSE
5805             output.value := field.value;
5806         END IF;
5807
5808         CONTINUE WHEN output.value IS NULL;
5809
5810         RETURN NEXT output;
5811     END LOOP;
5812 END;
5813 $func$ LANGUAGE PLPGSQL;
5814
5815 ALTER TABLE action.hold_request ADD COLUMN cut_in_line BOOL;
5816
5817 ALTER TABLE action.hold_request
5818 ADD COLUMN mint_condition boolean NOT NULL DEFAULT TRUE;
5819
5820 ALTER TABLE action.hold_request
5821 ADD COLUMN shelf_expire_time TIMESTAMPTZ;
5822
5823 ALTER TABLE action.hold_request DROP CONSTRAINT hold_request_current_copy_fkey;
5824
5825 ALTER TABLE action.hold_request DROP CONSTRAINT hold_request_hold_type_check;
5826
5827 UPDATE config.index_normalizer SET param_count = 0 WHERE func = 'split_date_range';
5828
5829 ALTER TABLE actor.usr ADD COLUMN
5830         claims_never_checked_out_count  INT         NOT NULL DEFAULT 0;
5831
5832 ALTER TABLE AUDITOR.actor_usr_history ADD COLUMN 
5833         claims_never_checked_out_count INT NOT NULL DEFAULT 0;
5834
5835 CREATE OR REPLACE FUNCTION action.circulation_claims_returned () RETURNS TRIGGER AS $$
5836 BEGIN
5837         IF OLD.stop_fines IS NULL OR OLD.stop_fines <> NEW.stop_fines THEN
5838                 IF NEW.stop_fines = 'CLAIMSRETURNED' THEN
5839                         UPDATE actor.usr SET claims_returned_count = claims_returned_count + 1 WHERE id = NEW.usr;
5840                 END IF;
5841                 IF NEW.stop_fines = 'CLAIMSNEVERCHECKEDOUT' THEN
5842                         UPDATE actor.usr SET claims_never_checked_out_count = claims_never_checked_out_count + 1 WHERE id = NEW.usr;
5843                 END IF;
5844                 IF NEW.stop_fines = 'LOST' THEN
5845                         UPDATE asset.copy SET status = 3 WHERE id = NEW.target_copy;
5846                 END IF;
5847         END IF;
5848         RETURN NEW;
5849 END;
5850 $$ LANGUAGE 'plpgsql';
5851
5852 -- Create new table acq.fund_allocation_percent
5853 -- Populate it from acq.fund_allocation
5854 -- Convert all percentages to amounts in acq.fund_allocation
5855
5856 CREATE TABLE acq.fund_allocation_percent
5857 (
5858     id                   SERIAL            PRIMARY KEY,
5859     funding_source       INT               NOT NULL REFERENCES acq.funding_source
5860                                                DEFERRABLE INITIALLY DEFERRED,
5861     org                  INT               NOT NULL REFERENCES actor.org_unit
5862                                                DEFERRABLE INITIALLY DEFERRED,
5863     fund_code            TEXT,
5864     percent              NUMERIC           NOT NULL,
5865     allocator            INTEGER           NOT NULL REFERENCES actor.usr
5866                                                DEFERRABLE INITIALLY DEFERRED,
5867     note                 TEXT,
5868     create_time          TIMESTAMPTZ       NOT NULL DEFAULT now(),
5869     CONSTRAINT logical_key UNIQUE( funding_source, org, fund_code ),
5870     CONSTRAINT percentage_range CHECK( percent >= 0 AND percent <= 100 )
5871 );
5872
5873 -- Trigger function to validate combination of org_unit and fund_code
5874
5875 CREATE OR REPLACE FUNCTION acq.fund_alloc_percent_val()
5876 RETURNS TRIGGER AS $$
5877 --
5878 DECLARE
5879 --
5880 dummy int := 0;
5881 --
5882 BEGIN
5883     SELECT
5884         1
5885     INTO
5886         dummy
5887     FROM
5888         acq.fund
5889     WHERE
5890         org = NEW.org
5891         AND code = NEW.fund_code
5892         LIMIT 1;
5893     --
5894     IF dummy = 1 then
5895         RETURN NEW;
5896     ELSE
5897         RAISE EXCEPTION 'No fund exists for org % and code %', NEW.org, NEW.fund_code;
5898     END IF;
5899 END;
5900 $$ LANGUAGE plpgsql;
5901
5902 CREATE TRIGGER acq_fund_alloc_percent_val_trig
5903     BEFORE INSERT OR UPDATE ON acq.fund_allocation_percent
5904     FOR EACH ROW EXECUTE PROCEDURE acq.fund_alloc_percent_val();
5905
5906 CREATE OR REPLACE FUNCTION acq.fap_limit_100()
5907 RETURNS TRIGGER AS $$
5908 DECLARE
5909 --
5910 total_percent numeric;
5911 --
5912 BEGIN
5913     SELECT
5914         sum( percent )
5915     INTO
5916         total_percent
5917     FROM
5918         acq.fund_allocation_percent AS fap
5919     WHERE
5920         fap.funding_source = NEW.funding_source;
5921     --
5922     IF total_percent > 100 THEN
5923         RAISE EXCEPTION 'Total percentages exceed 100 for funding_source %',
5924             NEW.funding_source;
5925     ELSE
5926         RETURN NEW;
5927     END IF;
5928 END;
5929 $$ LANGUAGE plpgsql;
5930
5931 CREATE TRIGGER acqfap_limit_100_trig
5932     AFTER INSERT OR UPDATE ON acq.fund_allocation_percent
5933     FOR EACH ROW EXECUTE PROCEDURE acq.fap_limit_100();
5934
5935 -- Populate new table from acq.fund_allocation
5936
5937 INSERT INTO acq.fund_allocation_percent
5938 (
5939     funding_source,
5940     org,
5941     fund_code,
5942     percent,
5943     allocator,
5944     note,
5945     create_time
5946 )
5947     SELECT
5948         fa.funding_source,
5949         fund.org,
5950         fund.code,
5951         fa.percent,
5952         fa.allocator,
5953         fa.note,
5954         fa.create_time
5955     FROM
5956         acq.fund_allocation AS fa
5957             INNER JOIN acq.fund AS fund
5958                 ON ( fa.fund = fund.id )
5959     WHERE
5960         fa.percent is not null
5961     ORDER BY
5962         fund.org;
5963
5964 -- Temporary function to convert percentages to amounts in acq.fund_allocation
5965
5966 -- Algorithm to apply to each funding source:
5967
5968 -- 1. Add up the credits.
5969 -- 2. Add up the percentages.
5970 -- 3. Multiply the sum of the percentages times the sum of the credits.  Drop any
5971 --    fractional cents from the result.  This is the total amount to be allocated.
5972 -- 4. For each allocation: multiply the percentage by the total allocation.  Drop any
5973 --    fractional cents to get a preliminary amount.
5974 -- 5. Add up the preliminary amounts for all the allocations.
5975 -- 6. Subtract the results of step 5 from the result of step 3.  The difference is the
5976 --    number of residual cents (resulting from having dropped fractional cents) that
5977 --    must be distributed across the funds in order to make the total of the amounts
5978 --    match the total allocation.
5979 -- 7. Make a second pass through the allocations, in decreasing order of the fractional
5980 --    cents that were dropped from their amounts in step 4.  Add one cent to the amount
5981 --    for each successive fund, until all the residual cents have been exhausted.
5982
5983 -- Result: the sum of the individual allocations now equals the total to be allocated,
5984 -- to the penny.  The individual amounts match the percentages as closely as possible,
5985 -- given the constraint that the total must match.
5986
5987 CREATE OR REPLACE FUNCTION acq.apply_percents()
5988 RETURNS VOID AS $$
5989 declare
5990 --
5991 tot              RECORD;
5992 fund             RECORD;
5993 tot_cents        INTEGER;
5994 src              INTEGER;
5995 id               INTEGER[];
5996 curr_id          INTEGER;
5997 pennies          NUMERIC[];
5998 curr_amount      NUMERIC;
5999 i                INTEGER;
6000 total_of_floors  INTEGER;
6001 total_percent    NUMERIC;
6002 total_allocation INTEGER;
6003 residue          INTEGER;
6004 --
6005 begin
6006         RAISE NOTICE 'Applying percents';
6007         FOR tot IN
6008                 SELECT
6009                         fsrc.funding_source,
6010                         sum( fsrc.amount ) AS total
6011                 FROM
6012                         acq.funding_source_credit AS fsrc
6013                 WHERE fsrc.funding_source IN
6014                         ( SELECT DISTINCT fa.funding_source
6015                           FROM acq.fund_allocation AS fa
6016                           WHERE fa.percent IS NOT NULL )
6017                 GROUP BY
6018                         fsrc.funding_source
6019         LOOP
6020                 tot_cents = floor( tot.total * 100 );
6021                 src = tot.funding_source;
6022                 RAISE NOTICE 'Funding source % total %',
6023                         src, tot_cents;
6024                 i := 0;
6025                 total_of_floors := 0;
6026                 total_percent := 0;
6027                 --
6028                 FOR fund in
6029                         SELECT
6030                                 fa.id,
6031                                 fa.percent,
6032                                 floor( fa.percent * tot_cents / 100 ) as floor_pennies
6033                         FROM
6034                                 acq.fund_allocation AS fa
6035                         WHERE
6036                                 fa.funding_source = src
6037                                 AND fa.percent IS NOT NULL
6038                         ORDER BY
6039                                 mod( fa.percent * tot_cents / 100, 1 ),
6040                                 fa.fund,
6041                                 fa.id
6042                 LOOP
6043                         RAISE NOTICE '   %: %',
6044                                 fund.id,
6045                                 fund.floor_pennies;
6046                         i := i + 1;
6047                         id[i] = fund.id;
6048                         pennies[i] = fund.floor_pennies;
6049                         total_percent := total_percent + fund.percent;
6050                         total_of_floors := total_of_floors + pennies[i];
6051                 END LOOP;
6052                 total_allocation := floor( total_percent * tot_cents /100 );
6053                 RAISE NOTICE 'Total before distributing residue: %', total_of_floors;
6054                 residue := total_allocation - total_of_floors;
6055                 RAISE NOTICE 'Residue: %', residue;
6056                 --
6057                 -- Post the calculated amounts, revising as needed to
6058                 -- distribute the rounding error
6059                 --
6060                 WHILE i > 0 LOOP
6061                         IF residue > 0 THEN
6062                                 pennies[i] = pennies[i] + 1;
6063                                 residue := residue - 1;
6064                         END IF;
6065                         --
6066                         -- Post amount
6067                         --
6068                         curr_id     := id[i];
6069                         curr_amount := trunc( pennies[i] / 100, 2 );
6070                         --
6071                         UPDATE
6072                                 acq.fund_allocation AS fa
6073                         SET
6074                                 amount = curr_amount,
6075                                 percent = NULL
6076                         WHERE
6077                                 fa.id = curr_id;
6078                         --
6079                         RAISE NOTICE '   ID % and amount %',
6080                                 curr_id,
6081                                 curr_amount;
6082                         i = i - 1;
6083                 END LOOP;
6084         END LOOP;
6085 end;
6086 $$ LANGUAGE 'plpgsql';
6087
6088 -- Run the temporary function
6089
6090 select * from acq.apply_percents();
6091
6092 -- Drop the temporary function now that we're done with it
6093
6094 DROP FUNCTION IF EXISTS acq.apply_percents();
6095
6096 -- Eliminate acq.fund_allocation.percent, which has been moved to the acq.fund_allocation_percent table.
6097
6098 -- If the following step fails, it's probably because there are still some non-null percent values in
6099 -- acq.fund_allocation.  They should have all been converted to amounts, and then set to null, by a
6100 -- previous upgrade step based on 0049.schema.acq_funding_allocation_percent.sql.  If there are any
6101 -- non-null values, then either that step didn't run, or it didn't work, or some non-null values
6102 -- slipped in afterwards.
6103
6104 -- To convert any remaining percents to amounts: create, run, and then drop the temporary stored
6105 -- procedure acq.apply_percents() as defined above.
6106
6107 ALTER TABLE acq.fund_allocation
6108 ALTER COLUMN amount SET NOT NULL;
6109
6110 CREATE OR REPLACE VIEW acq.fund_allocation_total AS
6111     SELECT  fund,
6112             SUM(a.amount * acq.exchange_ratio(s.currency_type, f.currency_type))::NUMERIC(100,2) AS amount
6113     FROM acq.fund_allocation a
6114          JOIN acq.fund f ON (a.fund = f.id)
6115          JOIN acq.funding_source s ON (a.funding_source = s.id)
6116     GROUP BY 1;
6117
6118 CREATE OR REPLACE VIEW acq.funding_source_allocation_total AS
6119     SELECT  funding_source,
6120             SUM(a.amount)::NUMERIC(100,2) AS amount
6121     FROM  acq.fund_allocation a
6122     GROUP BY 1;
6123
6124 ALTER TABLE acq.fund_allocation
6125 DROP COLUMN percent;
6126
6127 CREATE TABLE asset.copy_location_order
6128 (
6129         id              SERIAL           PRIMARY KEY,
6130         location        INT              NOT NULL
6131                                              REFERENCES asset.copy_location
6132                                              ON DELETE CASCADE
6133                                              DEFERRABLE INITIALLY DEFERRED,
6134         org             INT              NOT NULL
6135                                              REFERENCES actor.org_unit
6136                                              ON DELETE CASCADE
6137                                              DEFERRABLE INITIALLY DEFERRED,
6138         position        INT              NOT NULL DEFAULT 0,
6139         CONSTRAINT acplo_once_per_org UNIQUE ( location, org )
6140 );
6141
6142 ALTER TABLE money.credit_card_payment ADD COLUMN cc_processor TEXT;
6143
6144 -- If you ran this before its most recent incarnation:
6145 -- delete from config.upgrade_log where version = '0328';
6146 -- alter table money.credit_card_payment drop column cc_name;
6147
6148 ALTER TABLE money.credit_card_payment ADD COLUMN cc_first_name TEXT;
6149 ALTER TABLE money.credit_card_payment ADD COLUMN cc_last_name TEXT;
6150
6151 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$
6152 DECLARE
6153     current_group    permission.grp_tree%ROWTYPE;
6154     user_object    actor.usr%ROWTYPE;
6155     item_object    asset.copy%ROWTYPE;
6156     cn_object    asset.call_number%ROWTYPE;
6157     rec_descriptor    metabib.rec_descriptor%ROWTYPE;
6158     current_mp    config.circ_matrix_matchpoint%ROWTYPE;
6159     matchpoint    config.circ_matrix_matchpoint%ROWTYPE;
6160 BEGIN
6161     SELECT INTO user_object * FROM actor.usr WHERE id = match_user;
6162     SELECT INTO item_object * FROM asset.copy WHERE id = match_item;
6163     SELECT INTO cn_object * FROM asset.call_number WHERE id = item_object.call_number;
6164     SELECT INTO rec_descriptor r.* FROM metabib.rec_descriptor r JOIN asset.call_number c USING (record) WHERE c.id = item_object.call_number;
6165     SELECT INTO current_group * FROM permission.grp_tree WHERE id = user_object.profile;
6166
6167     LOOP
6168         -- for each potential matchpoint for this ou and group ...
6169         FOR current_mp IN
6170             SELECT  m.*
6171               FROM  config.circ_matrix_matchpoint m
6172                     JOIN actor.org_unit_ancestors( context_ou ) d ON (m.org_unit = d.id)
6173                     LEFT JOIN actor.org_unit_proximity p ON (p.from_org = context_ou AND p.to_org = d.id)
6174               WHERE m.grp = current_group.id
6175                     AND m.active
6176                     AND (m.copy_owning_lib IS NULL OR cn_object.owning_lib IN ( SELECT id FROM actor.org_unit_descendants(m.copy_owning_lib) ))
6177                     AND (m.copy_circ_lib   IS NULL OR item_object.circ_lib IN ( SELECT id FROM actor.org_unit_descendants(m.copy_circ_lib)   ))
6178               ORDER BY    CASE WHEN p.prox        IS NULL THEN 999 ELSE p.prox END,
6179                     CASE WHEN m.copy_owning_lib IS NOT NULL
6180                         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 )
6181                         ELSE 0
6182                     END +
6183                     CASE WHEN m.copy_circ_lib IS NOT NULL
6184                         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 )
6185                         ELSE 0
6186                     END +
6187                     CASE WHEN m.is_renewal = renewal        THEN 128 ELSE 0 END +
6188                     CASE WHEN m.juvenile_flag    IS NOT NULL THEN 64 ELSE 0 END +
6189                     CASE WHEN m.circ_modifier    IS NOT NULL THEN 32 ELSE 0 END +
6190                     CASE WHEN m.marc_type        IS NOT NULL THEN 16 ELSE 0 END +
6191                     CASE WHEN m.marc_form        IS NOT NULL THEN 8 ELSE 0 END +
6192                     CASE WHEN m.marc_vr_format    IS NOT NULL THEN 4 ELSE 0 END +
6193                     CASE WHEN m.ref_flag        IS NOT NULL THEN 2 ELSE 0 END +
6194                     CASE WHEN m.usr_age_lower_bound    IS NOT NULL THEN 0.5 ELSE 0 END +
6195                     CASE WHEN m.usr_age_upper_bound    IS NOT NULL THEN 0.5 ELSE 0 END DESC LOOP
6196
6197             IF current_mp.circ_modifier IS NOT NULL THEN
6198                 CONTINUE WHEN current_mp.circ_modifier <> item_object.circ_modifier OR item_object.circ_modifier IS NULL;
6199             END IF;
6200
6201             IF current_mp.marc_type IS NOT NULL THEN
6202                 IF item_object.circ_as_type IS NOT NULL THEN
6203                     CONTINUE WHEN current_mp.marc_type <> item_object.circ_as_type;
6204                 ELSE
6205                     CONTINUE WHEN current_mp.marc_type <> rec_descriptor.item_type;
6206                 END IF;
6207             END IF;
6208
6209             IF current_mp.marc_form IS NOT NULL THEN
6210                 CONTINUE WHEN current_mp.marc_form <> rec_descriptor.item_form;
6211             END IF;
6212
6213             IF current_mp.marc_vr_format IS NOT NULL THEN
6214                 CONTINUE WHEN current_mp.marc_vr_format <> rec_descriptor.vr_format;
6215             END IF;
6216
6217             IF current_mp.ref_flag IS NOT NULL THEN
6218                 CONTINUE WHEN current_mp.ref_flag <> item_object.ref;
6219             END IF;
6220
6221             IF current_mp.juvenile_flag IS NOT NULL THEN
6222                 CONTINUE WHEN current_mp.juvenile_flag <> user_object.juvenile;
6223             END IF;
6224
6225             IF current_mp.usr_age_lower_bound IS NOT NULL THEN
6226                 CONTINUE WHEN user_object.dob IS NULL OR current_mp.usr_age_lower_bound < age(user_object.dob);
6227             END IF;
6228
6229             IF current_mp.usr_age_upper_bound IS NOT NULL THEN
6230                 CONTINUE WHEN user_object.dob IS NULL OR current_mp.usr_age_upper_bound > age(user_object.dob);
6231             END IF;
6232
6233
6234             -- everything was undefined or matched
6235             matchpoint = current_mp;
6236
6237             EXIT WHEN matchpoint.id IS NOT NULL;
6238         END LOOP;
6239
6240         EXIT WHEN current_group.parent IS NULL OR matchpoint.id IS NOT NULL;
6241
6242         SELECT INTO current_group * FROM permission.grp_tree WHERE id = current_group.parent;
6243     END LOOP;
6244
6245     RETURN matchpoint;
6246 END;
6247 $func$ LANGUAGE plpgsql;
6248
6249 CREATE TYPE action.hold_stats AS (
6250     hold_count              INT,
6251     copy_count              INT,
6252     available_count         INT,
6253     total_copy_ratio        FLOAT,
6254     available_copy_ratio    FLOAT
6255 );
6256
6257 CREATE OR REPLACE FUNCTION action.copy_related_hold_stats (copy_id INT) RETURNS action.hold_stats AS $func$
6258 DECLARE
6259     output          action.hold_stats%ROWTYPE;
6260     hold_count      INT := 0;
6261     copy_count      INT := 0;
6262     available_count INT := 0;
6263     hold_map_data   RECORD;
6264 BEGIN
6265
6266     output.hold_count := 0;
6267     output.copy_count := 0;
6268     output.available_count := 0;
6269
6270     SELECT  COUNT( DISTINCT m.hold ) INTO hold_count
6271       FROM  action.hold_copy_map m
6272             JOIN action.hold_request h ON (m.hold = h.id)
6273       WHERE m.target_copy = copy_id
6274             AND NOT h.frozen;
6275
6276     output.hold_count := hold_count;
6277
6278     IF output.hold_count > 0 THEN
6279         FOR hold_map_data IN
6280             SELECT  DISTINCT m.target_copy,
6281                     acp.status
6282               FROM  action.hold_copy_map m
6283                     JOIN asset.copy acp ON (m.target_copy = acp.id)
6284                     JOIN action.hold_request h ON (m.hold = h.id)
6285               WHERE m.hold IN ( SELECT DISTINCT hold FROM action.hold_copy_map WHERE target_copy = copy_id ) AND NOT h.frozen
6286         LOOP
6287             output.copy_count := output.copy_count + 1;
6288             IF hold_map_data.status IN (0,7,12) THEN
6289                 output.available_count := output.available_count + 1;
6290             END IF;
6291         END LOOP;
6292         output.total_copy_ratio = output.copy_count::FLOAT / output.hold_count::FLOAT;
6293         output.available_copy_ratio = output.available_count::FLOAT / output.hold_count::FLOAT;
6294
6295     END IF;
6296
6297     RETURN output;
6298
6299 END;
6300 $func$ LANGUAGE PLPGSQL;
6301
6302 ALTER TABLE config.circ_matrix_matchpoint ADD COLUMN total_copy_hold_ratio FLOAT;
6303 ALTER TABLE config.circ_matrix_matchpoint ADD COLUMN available_copy_hold_ratio FLOAT;
6304
6305 ALTER TABLE config.circ_matrix_matchpoint DROP CONSTRAINT ep_once_per_grp_loc_mod_marc;
6306
6307 ALTER TABLE config.circ_matrix_matchpoint ADD COLUMN copy_circ_lib   INT REFERENCES actor.org_unit (id) DEFERRABLE INITIALLY DEFERRED;
6308 ALTER TABLE config.circ_matrix_matchpoint ADD COLUMN copy_owning_lib INT REFERENCES actor.org_unit (id) DEFERRABLE INITIALLY DEFERRED;
6309
6310 ALTER TABLE config.circ_matrix_matchpoint ADD CONSTRAINT ep_once_per_grp_loc_mod_marc UNIQUE (
6311     grp, org_unit, circ_modifier, marc_type, marc_form, marc_vr_format, ref_flag,
6312     juvenile_flag, usr_age_lower_bound, usr_age_upper_bound, is_renewal, copy_circ_lib,
6313     copy_owning_lib
6314 );
6315
6316 -- Return the correct fail_part when the item can't be found
6317 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$
6318 DECLARE
6319     user_object        actor.usr%ROWTYPE;
6320     standing_penalty    config.standing_penalty%ROWTYPE;
6321     item_object        asset.copy%ROWTYPE;
6322     item_status_object    config.copy_status%ROWTYPE;
6323     item_location_object    asset.copy_location%ROWTYPE;
6324     result            action.matrix_test_result;
6325     circ_test        config.circ_matrix_matchpoint%ROWTYPE;
6326     out_by_circ_mod        config.circ_matrix_circ_mod_test%ROWTYPE;
6327     circ_mod_map        config.circ_matrix_circ_mod_test_map%ROWTYPE;
6328     hold_ratio          action.hold_stats%ROWTYPE;
6329     penalty_type         TEXT;
6330     tmp_grp         INT;
6331     items_out        INT;
6332     context_org_list        INT[];
6333     done            BOOL := FALSE;
6334 BEGIN
6335     result.success := TRUE;
6336
6337     -- Fail if the user is BARRED
6338     SELECT INTO user_object * FROM actor.usr WHERE id = match_user;
6339
6340     -- Fail if we couldn't find the user 
6341     IF user_object.id IS NULL THEN
6342         result.fail_part := 'no_user';
6343         result.success := FALSE;
6344         done := TRUE;
6345         RETURN NEXT result;
6346         RETURN;
6347     END IF;
6348
6349     SELECT INTO item_object * FROM asset.copy WHERE id = match_item;
6350
6351     -- Fail if we couldn't find the item 
6352     IF item_object.id IS NULL THEN
6353         result.fail_part := 'no_item';
6354         result.success := FALSE;
6355         done := TRUE;
6356         RETURN NEXT result;
6357         RETURN;
6358     END IF;
6359
6360     SELECT INTO circ_test * FROM action.find_circ_matrix_matchpoint(circ_ou, match_item, match_user, renewal);
6361     result.matchpoint := circ_test.id;
6362
6363     -- Fail if we couldn't find a matchpoint
6364     IF result.matchpoint IS NULL THEN
6365         result.fail_part := 'no_matchpoint';
6366         result.success := FALSE;
6367         done := TRUE;
6368         RETURN NEXT result;
6369     END IF;
6370
6371     IF user_object.barred IS TRUE THEN
6372         result.fail_part := 'actor.usr.barred';
6373         result.success := FALSE;
6374         done := TRUE;
6375         RETURN NEXT result;
6376     END IF;
6377
6378     -- Fail if the item can't circulate
6379     IF item_object.circulate IS FALSE THEN
6380         result.fail_part := 'asset.copy.circulate';
6381         result.success := FALSE;
6382         done := TRUE;
6383         RETURN NEXT result;
6384     END IF;
6385
6386     -- Fail if the item isn't in a circulateable status on a non-renewal
6387     IF NOT renewal AND item_object.status NOT IN ( 0, 7, 8 ) THEN
6388         result.fail_part := 'asset.copy.status';
6389         result.success := FALSE;
6390         done := TRUE;
6391         RETURN NEXT result;
6392     ELSIF renewal AND item_object.status <> 1 THEN
6393         result.fail_part := 'asset.copy.status';
6394         result.success := FALSE;
6395         done := TRUE;
6396         RETURN NEXT result;
6397     END IF;
6398
6399     -- Fail if the item can't circulate because of the shelving location
6400     SELECT INTO item_location_object * FROM asset.copy_location WHERE id = item_object.location;
6401     IF item_location_object.circulate IS FALSE THEN
6402         result.fail_part := 'asset.copy_location.circulate';
6403         result.success := FALSE;
6404         done := TRUE;
6405         RETURN NEXT result;
6406     END IF;
6407
6408     SELECT INTO context_org_list ARRAY_ACCUM(id) FROM actor.org_unit_full_path( circ_test.org_unit );
6409
6410     -- Fail if the test is set to hard non-circulating
6411     IF circ_test.circulate IS FALSE THEN
6412         result.fail_part := 'config.circ_matrix_test.circulate';
6413         result.success := FALSE;
6414         done := TRUE;
6415         RETURN NEXT result;
6416     END IF;
6417
6418     -- Fail if the total copy-hold ratio is too low
6419     IF circ_test.total_copy_hold_ratio IS NOT NULL THEN
6420         SELECT INTO hold_ratio * FROM action.copy_related_hold_stats(match_item);
6421         IF hold_ratio.total_copy_ratio IS NOT NULL AND hold_ratio.total_copy_ratio < circ_test.total_copy_hold_ratio THEN
6422             result.fail_part := 'config.circ_matrix_test.total_copy_hold_ratio';
6423             result.success := FALSE;
6424             done := TRUE;
6425             RETURN NEXT result;
6426         END IF;
6427     END IF;
6428
6429     -- Fail if the available copy-hold ratio is too low
6430     IF circ_test.available_copy_hold_ratio IS NOT NULL THEN
6431         SELECT INTO hold_ratio * FROM action.copy_related_hold_stats(match_item);
6432         IF hold_ratio.available_copy_ratio IS NOT NULL AND hold_ratio.available_copy_ratio < circ_test.available_copy_hold_ratio THEN
6433             result.fail_part := 'config.circ_matrix_test.available_copy_hold_ratio';
6434             result.success := FALSE;
6435             done := TRUE;
6436             RETURN NEXT result;
6437         END IF;
6438     END IF;
6439
6440     IF renewal THEN
6441         penalty_type = '%RENEW%';
6442     ELSE
6443         penalty_type = '%CIRC%';
6444     END IF;
6445
6446     FOR standing_penalty IN
6447         SELECT  DISTINCT csp.*
6448           FROM  actor.usr_standing_penalty usp
6449                 JOIN config.standing_penalty csp ON (csp.id = usp.standing_penalty)
6450           WHERE usr = match_user
6451                 AND usp.org_unit IN ( SELECT * FROM explode_array(context_org_list) )
6452                 AND (usp.stop_date IS NULL or usp.stop_date > NOW())
6453                 AND csp.block_list LIKE penalty_type LOOP
6454
6455         result.fail_part := standing_penalty.name;
6456         result.success := FALSE;
6457         done := TRUE;
6458         RETURN NEXT result;
6459     END LOOP;
6460
6461     -- Fail if the user has too many items with specific circ_modifiers checked out
6462     FOR out_by_circ_mod IN SELECT * FROM config.circ_matrix_circ_mod_test WHERE matchpoint = circ_test.id LOOP
6463         SELECT  INTO items_out COUNT(*)
6464           FROM  action.circulation circ
6465             JOIN asset.copy cp ON (cp.id = circ.target_copy)
6466           WHERE circ.usr = match_user
6467                AND circ.circ_lib IN ( SELECT * FROM explode_array(context_org_list) )
6468             AND circ.checkin_time IS NULL
6469             AND (circ.stop_fines IN ('MAXFINES','LONGOVERDUE') OR circ.stop_fines IS NULL)
6470             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);
6471         IF items_out >= out_by_circ_mod.items_out THEN
6472             result.fail_part := 'config.circ_matrix_circ_mod_test';
6473             result.success := FALSE;
6474             done := TRUE;
6475             RETURN NEXT result;
6476         END IF;
6477     END LOOP;
6478
6479     -- If we passed everything, return the successful matchpoint id
6480     IF NOT done THEN
6481         RETURN NEXT result;
6482     END IF;
6483
6484     RETURN;
6485 END;
6486 $func$ LANGUAGE plpgsql;
6487
6488 CREATE TABLE config.remote_account (
6489     id          SERIAL  PRIMARY KEY,
6490     label       TEXT    NOT NULL,
6491     host        TEXT    NOT NULL,   -- name or IP, :port optional
6492     username    TEXT,               -- optional, since we could default to $USER
6493     password    TEXT,               -- optional, since we could use SSH keys, or anonymous login.
6494     account     TEXT,               -- aka profile or FTP "account" command
6495     path        TEXT,               -- aka directory
6496     owner       INT     NOT NULL REFERENCES actor.org_unit (id) DEFERRABLE INITIALLY DEFERRED,
6497     last_activity TIMESTAMP WITH TIME ZONE
6498 );
6499
6500 CREATE TABLE acq.edi_account (      -- similar tables can extend remote_account for other parts of EG
6501     provider    INT     NOT NULL REFERENCES acq.provider          (id) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED,
6502     in_dir      TEXT,   -- incoming messages dir (probably different than config.remote_account.path, the outgoing dir)
6503         vendcode    TEXT,
6504         vendacct    TEXT
6505
6506 ) INHERITS (config.remote_account);
6507
6508 ALTER TABLE acq.edi_account ADD PRIMARY KEY (id);
6509
6510 CREATE TABLE acq.claim_type (
6511         id             SERIAL           PRIMARY KEY,
6512         org_unit       INT              NOT NULL REFERENCES actor.org_unit(id)
6513                                                  DEFERRABLE INITIALLY DEFERRED,
6514         code           TEXT             NOT NULL,
6515         description    TEXT             NOT NULL,
6516         CONSTRAINT claim_type_once_per_org UNIQUE ( org_unit, code )
6517 );
6518
6519 CREATE TABLE acq.claim (
6520         id             SERIAL           PRIMARY KEY,
6521         type           INT              NOT NULL REFERENCES acq.claim_type
6522                                                  DEFERRABLE INITIALLY DEFERRED,
6523         lineitem_detail BIGINT          NOT NULL REFERENCES acq.lineitem_detail
6524                                                  DEFERRABLE INITIALLY DEFERRED
6525 );
6526
6527 CREATE TABLE acq.claim_policy (
6528         id              SERIAL       PRIMARY KEY,
6529         org_unit        INT          NOT NULL REFERENCES actor.org_unit
6530                                      DEFERRABLE INITIALLY DEFERRED,
6531         name            TEXT         NOT NULL,
6532         description     TEXT         NOT NULL,
6533         CONSTRAINT name_once_per_org UNIQUE (org_unit, name)
6534 );
6535
6536 -- Add a san column for EDI. 
6537 -- See: http://isbn.org/standards/home/isbn/us/san/san-qa.asp
6538
6539 ALTER TABLE acq.provider ADD COLUMN san INT;
6540
6541 ALTER TABLE acq.provider ALTER COLUMN san TYPE TEXT USING lpad(text(san), 7, '0');
6542
6543 -- null edi_default is OK... it has to be, since we have no values in acq.edi_account yet
6544 ALTER TABLE acq.provider ADD COLUMN edi_default INT REFERENCES acq.edi_account (id) DEFERRABLE INITIALLY DEFERRED;
6545
6546 ALTER TABLE acq.provider
6547         ADD COLUMN active BOOL NOT NULL DEFAULT TRUE;
6548
6549 ALTER TABLE acq.provider
6550         ADD COLUMN prepayment_required BOOLEAN NOT NULL DEFAULT FALSE;
6551
6552 ALTER TABLE acq.provider
6553         ADD COLUMN url TEXT;
6554
6555 ALTER TABLE acq.provider
6556         ADD COLUMN email TEXT;
6557
6558 ALTER TABLE acq.provider
6559         ADD COLUMN phone TEXT;
6560
6561 ALTER TABLE acq.provider
6562         ADD COLUMN fax_phone TEXT;
6563
6564 ALTER TABLE acq.provider
6565         ADD COLUMN default_claim_policy INT
6566                 REFERENCES acq.claim_policy
6567                 DEFERRABLE INITIALLY DEFERRED;
6568
6569 ALTER TABLE action.transit_copy
6570 ADD COLUMN prev_dest INTEGER REFERENCES actor.org_unit( id )
6571                                                          DEFERRABLE INITIALLY DEFERRED;
6572
6573 DROP SCHEMA IF EXISTS booking CASCADE;
6574
6575 CREATE SCHEMA booking;
6576
6577 CREATE TABLE booking.resource_type (
6578         id             SERIAL          PRIMARY KEY,
6579         name           TEXT            NOT NULL,
6580         fine_interval  INTERVAL,
6581         fine_amount    DECIMAL(8,2)    NOT NULL DEFAULT 0,
6582         owner          INT             NOT NULL
6583                                        REFERENCES actor.org_unit( id )
6584                                        DEFERRABLE INITIALLY DEFERRED,
6585         catalog_item   BOOLEAN         NOT NULL DEFAULT FALSE,
6586         transferable   BOOLEAN         NOT NULL DEFAULT FALSE,
6587     record         BIGINT          REFERENCES biblio.record_entry (id)
6588                                        DEFERRABLE INITIALLY DEFERRED,
6589     max_fine       NUMERIC(8,2),
6590     elbow_room     INTERVAL,
6591     CONSTRAINT brt_name_or_record_once_per_owner UNIQUE(owner, name, record)
6592 );
6593
6594 CREATE TABLE booking.resource (
6595         id             SERIAL           PRIMARY KEY,
6596         owner          INT              NOT NULL
6597                                         REFERENCES actor.org_unit(id)
6598                                         DEFERRABLE INITIALLY DEFERRED,
6599         type           INT              NOT NULL
6600                                         REFERENCES booking.resource_type(id)
6601                                         DEFERRABLE INITIALLY DEFERRED,
6602         overbook       BOOLEAN          NOT NULL DEFAULT FALSE,
6603         barcode        TEXT             NOT NULL,
6604         deposit        BOOLEAN          NOT NULL DEFAULT FALSE,
6605         deposit_amount DECIMAL(8,2)     NOT NULL DEFAULT 0.00,
6606         user_fee       DECIMAL(8,2)     NOT NULL DEFAULT 0.00,
6607         CONSTRAINT br_unique UNIQUE (owner, barcode)
6608 );
6609
6610 -- For non-catalog items: hijack barcode for name/description
6611
6612 CREATE TABLE booking.resource_attr (
6613         id              SERIAL          PRIMARY KEY,
6614         owner           INT             NOT NULL
6615                                         REFERENCES actor.org_unit(id)
6616                                         DEFERRABLE INITIALLY DEFERRED,
6617         name            TEXT            NOT NULL,
6618         resource_type   INT             NOT NULL
6619                                         REFERENCES booking.resource_type(id)
6620                                         ON DELETE CASCADE
6621                                         DEFERRABLE INITIALLY DEFERRED,
6622         required        BOOLEAN         NOT NULL DEFAULT FALSE,
6623         CONSTRAINT bra_name_once_per_type UNIQUE(resource_type, name)
6624 );
6625
6626 CREATE TABLE booking.resource_attr_value (
6627         id               SERIAL         PRIMARY KEY,
6628         owner            INT            NOT NULL
6629                                         REFERENCES actor.org_unit(id)
6630                                         DEFERRABLE INITIALLY DEFERRED,
6631         attr             INT            NOT NULL
6632                                         REFERENCES booking.resource_attr(id)
6633                                         DEFERRABLE INITIALLY DEFERRED,
6634         valid_value      TEXT           NOT NULL,
6635         CONSTRAINT brav_logical_key UNIQUE(owner, attr, valid_value)
6636 );
6637
6638 CREATE TABLE booking.resource_attr_map (
6639         id               SERIAL         PRIMARY KEY,
6640         resource         INT            NOT NULL
6641                                         REFERENCES booking.resource(id)
6642                                         ON DELETE CASCADE
6643                                         DEFERRABLE INITIALLY DEFERRED,
6644         resource_attr    INT            NOT NULL
6645                                         REFERENCES booking.resource_attr(id)
6646                                         ON DELETE CASCADE
6647                                         DEFERRABLE INITIALLY DEFERRED,
6648         value            INT            NOT NULL
6649                                         REFERENCES booking.resource_attr_value(id)
6650                                         DEFERRABLE INITIALLY DEFERRED,
6651         CONSTRAINT bram_one_value_per_attr UNIQUE(resource, resource_attr)
6652 );
6653
6654 CREATE TABLE booking.reservation (
6655         request_time     TIMESTAMPTZ   NOT NULL DEFAULT now(),
6656         start_time       TIMESTAMPTZ,
6657         end_time         TIMESTAMPTZ,
6658         capture_time     TIMESTAMPTZ,
6659         cancel_time      TIMESTAMPTZ,
6660         pickup_time      TIMESTAMPTZ,
6661         return_time      TIMESTAMPTZ,
6662         booking_interval INTERVAL,
6663         fine_interval    INTERVAL,
6664         fine_amount      DECIMAL(8,2),
6665         target_resource_type  INT       NOT NULL
6666                                         REFERENCES booking.resource_type(id)
6667                                         ON DELETE CASCADE
6668                                         DEFERRABLE INITIALLY DEFERRED,
6669         target_resource  INT            REFERENCES booking.resource(id)
6670                                         ON DELETE CASCADE
6671                                         DEFERRABLE INITIALLY DEFERRED,
6672         current_resource INT            REFERENCES booking.resource(id)
6673                                         ON DELETE CASCADE
6674                                         DEFERRABLE INITIALLY DEFERRED,
6675         request_lib      INT            NOT NULL
6676                                         REFERENCES actor.org_unit(id)
6677                                         DEFERRABLE INITIALLY DEFERRED,
6678         pickup_lib       INT            REFERENCES actor.org_unit(id)
6679                                         DEFERRABLE INITIALLY DEFERRED,
6680         capture_staff    INT            REFERENCES actor.usr(id)
6681                                         DEFERRABLE INITIALLY DEFERRED,
6682     max_fine         NUMERIC(8,2)
6683 ) INHERITS (money.billable_xact);
6684
6685 ALTER TABLE booking.reservation ADD PRIMARY KEY (id);
6686
6687 ALTER TABLE booking.reservation
6688         ADD CONSTRAINT booking_reservation_usr_fkey
6689         FOREIGN KEY (usr) REFERENCES actor.usr (id)
6690         DEFERRABLE INITIALLY DEFERRED;
6691
6692 CREATE TABLE booking.reservation_attr_value_map (
6693         id               SERIAL         PRIMARY KEY,
6694         reservation      INT            NOT NULL
6695                                         REFERENCES booking.reservation(id)
6696                                         ON DELETE CASCADE
6697                                         DEFERRABLE INITIALLY DEFERRED,
6698         attr_value       INT            NOT NULL
6699                                         REFERENCES booking.resource_attr_value(id)
6700                                         ON DELETE CASCADE
6701                                         DEFERRABLE INITIALLY DEFERRED,
6702         CONSTRAINT bravm_logical_key UNIQUE(reservation, attr_value)
6703 );
6704
6705 CREATE FUNCTION auditor.create_auditor_seq     ( sch TEXT, tbl TEXT ) RETURNS BOOL AS $creator$
6706 BEGIN
6707     EXECUTE $$
6708         CREATE SEQUENCE auditor.$$ || sch || $$_$$ || tbl || $$_pkey_seq;
6709     $$;
6710         RETURN TRUE;
6711 END;
6712 $creator$ LANGUAGE 'plpgsql';
6713
6714 CREATE FUNCTION auditor.create_auditor_history ( sch TEXT, tbl TEXT ) RETURNS BOOL AS $creator$
6715 BEGIN
6716     EXECUTE $$
6717         CREATE TABLE auditor.$$ || sch || $$_$$ || tbl || $$_history (
6718             audit_id    BIGINT                          PRIMARY KEY,
6719             audit_time  TIMESTAMP WITH TIME ZONE        NOT NULL,
6720             audit_action        TEXT                            NOT NULL,
6721             LIKE $$ || sch || $$.$$ || tbl || $$
6722         );
6723     $$;
6724         RETURN TRUE;
6725 END;
6726 $creator$ LANGUAGE 'plpgsql';
6727
6728 CREATE FUNCTION auditor.create_auditor_func    ( sch TEXT, tbl TEXT ) RETURNS BOOL AS $creator$
6729 BEGIN
6730     EXECUTE $$
6731         CREATE FUNCTION auditor.audit_$$ || sch || $$_$$ || tbl || $$_func ()
6732         RETURNS TRIGGER AS $func$
6733         BEGIN
6734             INSERT INTO auditor.$$ || sch || $$_$$ || tbl || $$_history
6735                 SELECT  nextval('auditor.$$ || sch || $$_$$ || tbl || $$_pkey_seq'),
6736                     now(),
6737                     SUBSTR(TG_OP,1,1),
6738                     OLD.*;
6739             RETURN NULL;
6740         END;
6741         $func$ LANGUAGE 'plpgsql';
6742     $$;
6743     RETURN TRUE;
6744 END;
6745 $creator$ LANGUAGE 'plpgsql';
6746
6747 CREATE FUNCTION auditor.create_auditor_update_trigger ( sch TEXT, tbl TEXT ) RETURNS BOOL AS $creator$
6748 BEGIN
6749     EXECUTE $$
6750         CREATE TRIGGER audit_$$ || sch || $$_$$ || tbl || $$_update_trigger
6751             AFTER UPDATE OR DELETE ON $$ || sch || $$.$$ || tbl || $$ FOR EACH ROW
6752             EXECUTE PROCEDURE auditor.audit_$$ || sch || $$_$$ || tbl || $$_func ();
6753     $$;
6754         RETURN TRUE;
6755 END;
6756 $creator$ LANGUAGE 'plpgsql';
6757
6758 CREATE FUNCTION auditor.create_auditor_lifecycle     ( sch TEXT, tbl TEXT ) RETURNS BOOL AS $creator$
6759 BEGIN
6760     EXECUTE $$
6761         CREATE VIEW auditor.$$ || sch || $$_$$ || tbl || $$_lifecycle AS
6762             SELECT      -1, now() as audit_time, '-' as audit_action, *
6763               FROM      $$ || sch || $$.$$ || tbl || $$
6764                 UNION ALL
6765             SELECT      *
6766               FROM      auditor.$$ || sch || $$_$$ || tbl || $$_history;
6767     $$;
6768         RETURN TRUE;
6769 END;
6770 $creator$ LANGUAGE 'plpgsql';
6771
6772 DROP FUNCTION IF EXISTS auditor.create_auditor (TEXT, TEXT);
6773
6774 -- The main event
6775
6776 CREATE FUNCTION auditor.create_auditor ( sch TEXT, tbl TEXT ) RETURNS BOOL AS $creator$
6777 BEGIN
6778     PERFORM auditor.create_auditor_seq(sch, tbl);
6779     PERFORM auditor.create_auditor_history(sch, tbl);
6780     PERFORM auditor.create_auditor_func(sch, tbl);
6781     PERFORM auditor.create_auditor_update_trigger(sch, tbl);
6782     PERFORM auditor.create_auditor_lifecycle(sch, tbl);
6783         RETURN TRUE;
6784 END;
6785 $creator$ LANGUAGE 'plpgsql';
6786
6787 -- represents a circ chain summary
6788 CREATE TYPE action.circ_chain_summary AS (
6789     num_circs INTEGER,
6790     start_time TIMESTAMP WITH TIME ZONE,
6791     checkout_workstation TEXT,
6792     last_renewal_time TIMESTAMP WITH TIME ZONE, -- NULL if no renewals
6793     last_stop_fines TEXT,
6794     last_stop_fines_time TIMESTAMP WITH TIME ZONE,
6795     last_renewal_workstation TEXT, -- NULL if no renewals
6796     last_checkin_workstation TEXT,
6797     last_checkin_time TIMESTAMP WITH TIME ZONE,
6798     last_checkin_scan_time TIMESTAMP WITH TIME ZONE
6799 );
6800
6801 CREATE OR REPLACE FUNCTION action.circ_chain ( ctx_circ_id INTEGER ) RETURNS SETOF action.circulation AS $$
6802 DECLARE
6803     tmp_circ action.circulation%ROWTYPE;
6804     circ_0 action.circulation%ROWTYPE;
6805 BEGIN
6806
6807     SELECT INTO tmp_circ * FROM action.circulation WHERE id = ctx_circ_id;
6808
6809     IF tmp_circ IS NULL THEN
6810         RETURN NEXT tmp_circ;
6811     END IF;
6812     circ_0 := tmp_circ;
6813
6814     -- find the front of the chain
6815     WHILE TRUE LOOP
6816         SELECT INTO tmp_circ * FROM action.circulation WHERE id = tmp_circ.parent_circ;
6817         IF tmp_circ IS NULL THEN
6818             EXIT;
6819         END IF;
6820         circ_0 := tmp_circ;
6821     END LOOP;
6822
6823     -- now send the circs to the caller, oldest to newest
6824     tmp_circ := circ_0;
6825     WHILE TRUE LOOP
6826         IF tmp_circ IS NULL THEN
6827             EXIT;
6828         END IF;
6829         RETURN NEXT tmp_circ;
6830         SELECT INTO tmp_circ * FROM action.circulation WHERE parent_circ = tmp_circ.id;
6831     END LOOP;
6832
6833 END;
6834 $$ LANGUAGE 'plpgsql';
6835
6836 CREATE OR REPLACE FUNCTION action.summarize_circ_chain ( ctx_circ_id INTEGER ) RETURNS action.circ_chain_summary AS $$
6837
6838 DECLARE
6839
6840     -- first circ in the chain
6841     circ_0 action.circulation%ROWTYPE;
6842
6843     -- last circ in the chain
6844     circ_n action.circulation%ROWTYPE;
6845
6846     -- circ chain under construction
6847     chain action.circ_chain_summary;
6848     tmp_circ action.circulation%ROWTYPE;
6849
6850 BEGIN
6851     
6852     chain.num_circs := 0;
6853     FOR tmp_circ IN SELECT * FROM action.circ_chain(ctx_circ_id) LOOP
6854
6855         IF chain.num_circs = 0 THEN
6856             circ_0 := tmp_circ;
6857         END IF;
6858
6859         chain.num_circs := chain.num_circs + 1;
6860         circ_n := tmp_circ;
6861     END LOOP;
6862
6863     chain.start_time := circ_0.xact_start;
6864     chain.last_stop_fines := circ_n.stop_fines;
6865     chain.last_stop_fines_time := circ_n.stop_fines_time;
6866     chain.last_checkin_time := circ_n.checkin_time;
6867     chain.last_checkin_scan_time := circ_n.checkin_scan_time;
6868     SELECT INTO chain.checkout_workstation name FROM actor.workstation WHERE id = circ_0.workstation;
6869     SELECT INTO chain.last_checkin_workstation name FROM actor.workstation WHERE id = circ_n.checkin_workstation;
6870
6871     IF chain.num_circs > 1 THEN
6872         chain.last_renewal_time := circ_n.xact_start;
6873         SELECT INTO chain.last_renewal_workstation name FROM actor.workstation WHERE id = circ_n.workstation;
6874     END IF;
6875
6876     RETURN chain;
6877
6878 END;
6879 $$ LANGUAGE 'plpgsql';
6880
6881 CREATE TRIGGER mat_summary_create_tgr AFTER INSERT ON booking.reservation FOR EACH ROW EXECUTE PROCEDURE money.mat_summary_create ('reservation');
6882 CREATE TRIGGER mat_summary_change_tgr AFTER UPDATE ON booking.reservation FOR EACH ROW EXECUTE PROCEDURE money.mat_summary_update ();
6883 CREATE TRIGGER mat_summary_remove_tgr AFTER DELETE ON booking.reservation FOR EACH ROW EXECUTE PROCEDURE money.mat_summary_delete ();
6884
6885 ALTER TABLE config.standing_penalty
6886         ADD COLUMN org_depth   INTEGER;
6887
6888 CREATE OR REPLACE FUNCTION actor.calculate_system_penalties( match_user INT, context_org INT ) RETURNS SETOF actor.usr_standing_penalty AS $func$
6889 DECLARE
6890     user_object         actor.usr%ROWTYPE;
6891     new_sp_row          actor.usr_standing_penalty%ROWTYPE;
6892     existing_sp_row     actor.usr_standing_penalty%ROWTYPE;
6893     collections_fines   permission.grp_penalty_threshold%ROWTYPE;
6894     max_fines           permission.grp_penalty_threshold%ROWTYPE;
6895     max_overdue         permission.grp_penalty_threshold%ROWTYPE;
6896     max_items_out       permission.grp_penalty_threshold%ROWTYPE;
6897     tmp_grp             INT;
6898     items_overdue       INT;
6899     items_out           INT;
6900     context_org_list    INT[];
6901     current_fines        NUMERIC(8,2) := 0.0;
6902     tmp_fines            NUMERIC(8,2);
6903     tmp_groc            RECORD;
6904     tmp_circ            RECORD;
6905     tmp_org             actor.org_unit%ROWTYPE;
6906     tmp_penalty         config.standing_penalty%ROWTYPE;
6907     tmp_depth           INTEGER;
6908 BEGIN
6909     SELECT INTO user_object * FROM actor.usr WHERE id = match_user;
6910
6911     -- Max fines
6912     SELECT INTO tmp_org * FROM actor.org_unit WHERE id = context_org;
6913
6914     -- Fail if the user has a high fine balance
6915     LOOP
6916         tmp_grp := user_object.profile;
6917         LOOP
6918             SELECT * INTO max_fines FROM permission.grp_penalty_threshold WHERE grp = tmp_grp AND penalty = 1 AND org_unit = tmp_org.id;
6919
6920             IF max_fines.threshold IS NULL THEN
6921                 SELECT parent INTO tmp_grp FROM permission.grp_tree WHERE id = tmp_grp;
6922             ELSE
6923                 EXIT;
6924             END IF;
6925
6926             IF tmp_grp IS NULL THEN
6927                 EXIT;
6928             END IF;
6929         END LOOP;
6930
6931         IF max_fines.threshold IS NOT NULL OR tmp_org.parent_ou IS NULL THEN
6932             EXIT;
6933         END IF;
6934
6935         SELECT * INTO tmp_org FROM actor.org_unit WHERE id = tmp_org.parent_ou;
6936
6937     END LOOP;
6938
6939     IF max_fines.threshold IS NOT NULL THEN
6940
6941         FOR existing_sp_row IN
6942                 SELECT  *
6943                   FROM  actor.usr_standing_penalty
6944                   WHERE usr = match_user
6945                         AND org_unit = max_fines.org_unit
6946                         AND (stop_date IS NULL or stop_date > NOW())
6947                         AND standing_penalty = 1
6948                 LOOP
6949             RETURN NEXT existing_sp_row;
6950         END LOOP;
6951
6952         SELECT  SUM(f.balance_owed) INTO current_fines
6953           FROM  money.materialized_billable_xact_summary f
6954                 JOIN (
6955                     SELECT  r.id
6956                       FROM  booking.reservation r
6957                             JOIN  actor.org_unit_full_path( max_fines.org_unit ) fp ON (r.pickup_lib = fp.id)
6958                       WHERE usr = match_user
6959                             AND xact_finish IS NULL
6960                                 UNION ALL
6961                     SELECT  g.id
6962                       FROM  money.grocery g
6963                             JOIN  actor.org_unit_full_path( max_fines.org_unit ) fp ON (g.billing_location = fp.id)
6964                       WHERE usr = match_user
6965                             AND xact_finish IS NULL
6966                                 UNION ALL
6967                     SELECT  circ.id
6968                       FROM  action.circulation circ
6969                             JOIN  actor.org_unit_full_path( max_fines.org_unit ) fp ON (circ.circ_lib = fp.id)
6970                       WHERE usr = match_user
6971                             AND xact_finish IS NULL ) l USING (id);
6972
6973         IF current_fines >= max_fines.threshold THEN
6974             new_sp_row.usr := match_user;
6975             new_sp_row.org_unit := max_fines.org_unit;
6976             new_sp_row.standing_penalty := 1;
6977             RETURN NEXT new_sp_row;
6978         END IF;
6979     END IF;
6980
6981     -- Start over for max overdue
6982     SELECT INTO tmp_org * FROM actor.org_unit WHERE id = context_org;
6983
6984     -- Fail if the user has too many overdue items
6985     LOOP
6986         tmp_grp := user_object.profile;
6987         LOOP
6988
6989             SELECT * INTO max_overdue FROM permission.grp_penalty_threshold WHERE grp = tmp_grp AND penalty = 2 AND org_unit = tmp_org.id;
6990
6991             IF max_overdue.threshold IS NULL THEN
6992                 SELECT parent INTO tmp_grp FROM permission.grp_tree WHERE id = tmp_grp;
6993             ELSE
6994                 EXIT;
6995             END IF;
6996
6997             IF tmp_grp IS NULL THEN
6998                 EXIT;
6999             END IF;
7000         END LOOP;
7001
7002         IF max_overdue.threshold IS NOT NULL OR tmp_org.parent_ou IS NULL THEN
7003             EXIT;
7004         END IF;
7005
7006         SELECT INTO tmp_org * FROM actor.org_unit WHERE id = tmp_org.parent_ou;
7007
7008     END LOOP;
7009
7010     IF max_overdue.threshold IS NOT NULL THEN
7011
7012         FOR existing_sp_row IN
7013                 SELECT  *
7014                   FROM  actor.usr_standing_penalty
7015                   WHERE usr = match_user
7016                         AND org_unit = max_overdue.org_unit
7017                         AND (stop_date IS NULL or stop_date > NOW())
7018                         AND standing_penalty = 2
7019                 LOOP
7020             RETURN NEXT existing_sp_row;
7021         END LOOP;
7022
7023         SELECT  INTO items_overdue COUNT(*)
7024           FROM  action.circulation circ
7025                 JOIN  actor.org_unit_full_path( max_overdue.org_unit ) fp ON (circ.circ_lib = fp.id)
7026           WHERE circ.usr = match_user
7027             AND circ.checkin_time IS NULL
7028             AND circ.due_date < NOW()
7029             AND (circ.stop_fines = 'MAXFINES' OR circ.stop_fines IS NULL);
7030
7031         IF items_overdue >= max_overdue.threshold::INT THEN
7032             new_sp_row.usr := match_user;
7033             new_sp_row.org_unit := max_overdue.org_unit;
7034             new_sp_row.standing_penalty := 2;
7035             RETURN NEXT new_sp_row;
7036         END IF;
7037     END IF;
7038
7039     -- Start over for max out
7040     SELECT INTO tmp_org * FROM actor.org_unit WHERE id = context_org;
7041
7042     -- Fail if the user has too many checked out items
7043     LOOP
7044         tmp_grp := user_object.profile;
7045         LOOP
7046             SELECT * INTO max_items_out FROM permission.grp_penalty_threshold WHERE grp = tmp_grp AND penalty = 3 AND org_unit = tmp_org.id;
7047
7048             IF max_items_out.threshold IS NULL THEN
7049                 SELECT parent INTO tmp_grp FROM permission.grp_tree WHERE id = tmp_grp;
7050             ELSE
7051                 EXIT;
7052             END IF;
7053
7054             IF tmp_grp IS NULL THEN
7055                 EXIT;
7056             END IF;
7057         END LOOP;
7058
7059         IF max_items_out.threshold IS NOT NULL OR tmp_org.parent_ou IS NULL THEN
7060             EXIT;
7061         END IF;
7062
7063         SELECT INTO tmp_org * FROM actor.org_unit WHERE id = tmp_org.parent_ou;
7064
7065     END LOOP;
7066
7067
7068     -- Fail if the user has too many items checked out
7069     IF max_items_out.threshold IS NOT NULL THEN
7070
7071         FOR existing_sp_row IN
7072                 SELECT  *
7073                   FROM  actor.usr_standing_penalty
7074                   WHERE usr = match_user
7075                         AND org_unit = max_items_out.org_unit
7076                         AND (stop_date IS NULL or stop_date > NOW())
7077                         AND standing_penalty = 3
7078                 LOOP
7079             RETURN NEXT existing_sp_row;
7080         END LOOP;
7081
7082         SELECT  INTO items_out COUNT(*)
7083           FROM  action.circulation circ
7084                 JOIN  actor.org_unit_full_path( max_items_out.org_unit ) fp ON (circ.circ_lib = fp.id)
7085           WHERE circ.usr = match_user
7086                 AND circ.checkin_time IS NULL
7087                 AND (circ.stop_fines IN ('MAXFINES','LONGOVERDUE') OR circ.stop_fines IS NULL);
7088
7089            IF items_out >= max_items_out.threshold::INT THEN
7090             new_sp_row.usr := match_user;
7091             new_sp_row.org_unit := max_items_out.org_unit;
7092             new_sp_row.standing_penalty := 3;
7093             RETURN NEXT new_sp_row;
7094            END IF;
7095     END IF;
7096
7097     -- Start over for collections warning
7098     SELECT INTO tmp_org * FROM actor.org_unit WHERE id = context_org;
7099
7100     -- Fail if the user has a collections-level fine balance
7101     LOOP
7102         tmp_grp := user_object.profile;
7103         LOOP
7104             SELECT * INTO max_fines FROM permission.grp_penalty_threshold WHERE grp = tmp_grp AND penalty = 4 AND org_unit = tmp_org.id;
7105
7106             IF max_fines.threshold IS NULL THEN
7107                 SELECT parent INTO tmp_grp FROM permission.grp_tree WHERE id = tmp_grp;
7108             ELSE
7109                 EXIT;
7110             END IF;
7111
7112             IF tmp_grp IS NULL THEN
7113                 EXIT;
7114             END IF;
7115         END LOOP;
7116
7117         IF max_fines.threshold IS NOT NULL OR tmp_org.parent_ou IS NULL THEN
7118             EXIT;
7119         END IF;
7120
7121         SELECT * INTO tmp_org FROM actor.org_unit WHERE id = tmp_org.parent_ou;
7122
7123     END LOOP;
7124
7125     IF max_fines.threshold IS NOT NULL THEN
7126
7127         FOR existing_sp_row IN
7128                 SELECT  *
7129                   FROM  actor.usr_standing_penalty
7130                   WHERE usr = match_user
7131                         AND org_unit = max_fines.org_unit
7132                         AND (stop_date IS NULL or stop_date > NOW())
7133                         AND standing_penalty = 4
7134                 LOOP
7135             RETURN NEXT existing_sp_row;
7136         END LOOP;
7137
7138         SELECT  SUM(f.balance_owed) INTO current_fines
7139           FROM  money.materialized_billable_xact_summary f
7140                 JOIN (
7141                     SELECT  r.id
7142                       FROM  booking.reservation r
7143                             JOIN  actor.org_unit_full_path( max_fines.org_unit ) fp ON (r.pickup_lib = fp.id)
7144                       WHERE usr = match_user
7145                             AND xact_finish IS NULL
7146                                 UNION ALL
7147                     SELECT  g.id
7148                       FROM  money.grocery g
7149                             JOIN  actor.org_unit_full_path( max_fines.org_unit ) fp ON (g.billing_location = fp.id)
7150                       WHERE usr = match_user
7151                             AND xact_finish IS NULL
7152                                 UNION ALL
7153                     SELECT  circ.id
7154                       FROM  action.circulation circ
7155                             JOIN  actor.org_unit_full_path( max_fines.org_unit ) fp ON (circ.circ_lib = fp.id)
7156                       WHERE usr = match_user
7157                             AND xact_finish IS NULL ) l USING (id);
7158
7159         IF current_fines >= max_fines.threshold THEN
7160             new_sp_row.usr := match_user;
7161             new_sp_row.org_unit := max_fines.org_unit;
7162             new_sp_row.standing_penalty := 4;
7163             RETURN NEXT new_sp_row;
7164         END IF;
7165     END IF;
7166
7167     -- Start over for in collections
7168     SELECT INTO tmp_org * FROM actor.org_unit WHERE id = context_org;
7169
7170     -- Remove the in-collections penalty if the user has paid down enough
7171     -- This penalty is different, because this code is not responsible for creating 
7172     -- new in-collections penalties, only for removing them
7173     LOOP
7174         tmp_grp := user_object.profile;
7175         LOOP
7176             SELECT * INTO max_fines FROM permission.grp_penalty_threshold WHERE grp = tmp_grp AND penalty = 30 AND org_unit = tmp_org.id;
7177
7178             IF max_fines.threshold IS NULL THEN
7179                 SELECT parent INTO tmp_grp FROM permission.grp_tree WHERE id = tmp_grp;
7180             ELSE
7181                 EXIT;
7182             END IF;
7183
7184             IF tmp_grp IS NULL THEN
7185                 EXIT;
7186             END IF;
7187         END LOOP;
7188
7189         IF max_fines.threshold IS NOT NULL OR tmp_org.parent_ou IS NULL THEN
7190             EXIT;
7191         END IF;
7192
7193         SELECT * INTO tmp_org FROM actor.org_unit WHERE id = tmp_org.parent_ou;
7194
7195     END LOOP;
7196
7197     IF max_fines.threshold IS NOT NULL THEN
7198
7199         -- first, see if the user had paid down to the threshold
7200         SELECT  SUM(f.balance_owed) INTO current_fines
7201           FROM  money.materialized_billable_xact_summary f
7202                 JOIN (
7203                     SELECT  r.id
7204                       FROM  booking.reservation r
7205                             JOIN  actor.org_unit_full_path( max_fines.org_unit ) fp ON (r.pickup_lib = fp.id)
7206                       WHERE usr = match_user
7207                             AND xact_finish IS NULL
7208                                 UNION ALL
7209                     SELECT  g.id
7210                       FROM  money.grocery g
7211                             JOIN  actor.org_unit_full_path( max_fines.org_unit ) fp ON (g.billing_location = fp.id)
7212                       WHERE usr = match_user
7213                             AND xact_finish IS NULL
7214                                 UNION ALL
7215                     SELECT  circ.id
7216                       FROM  action.circulation circ
7217                             JOIN  actor.org_unit_full_path( max_fines.org_unit ) fp ON (circ.circ_lib = fp.id)
7218                       WHERE usr = match_user
7219                             AND xact_finish IS NULL ) l USING (id);
7220
7221         IF current_fines IS NULL OR current_fines <= max_fines.threshold THEN
7222             -- patron has paid down enough
7223
7224             SELECT INTO tmp_penalty * FROM config.standing_penalty WHERE id = 30;
7225
7226             IF tmp_penalty.org_depth IS NOT NULL THEN
7227
7228                 -- since this code is not responsible for applying the penalty, it can't 
7229                 -- guarantee the current context org will match the org at which the penalty 
7230                 --- was applied.  search up the org tree until we hit the configured penalty depth
7231                 SELECT INTO tmp_org * FROM actor.org_unit WHERE id = context_org;
7232                 SELECT INTO tmp_depth depth FROM actor.org_unit_type WHERE id = tmp_org.ou_type;
7233
7234                 WHILE tmp_depth >= tmp_penalty.org_depth LOOP
7235
7236                     FOR existing_sp_row IN
7237                             SELECT  *
7238                             FROM  actor.usr_standing_penalty
7239                             WHERE usr = match_user
7240                                     AND org_unit = tmp_org.id
7241                                     AND (stop_date IS NULL or stop_date > NOW())
7242                                     AND standing_penalty = 30 
7243                             LOOP
7244
7245                         -- Penalty exists, return it for removal
7246                         RETURN NEXT existing_sp_row;
7247                     END LOOP;
7248
7249                     IF tmp_org.parent_ou IS NULL THEN
7250                         EXIT;
7251                     END IF;
7252
7253                     SELECT * INTO tmp_org FROM actor.org_unit WHERE id = tmp_org.parent_ou;
7254                     SELECT INTO tmp_depth depth FROM actor.org_unit_type WHERE id = tmp_org.ou_type;
7255                 END LOOP;
7256
7257             ELSE
7258
7259                 -- no penalty depth is defined, look for exact matches
7260
7261                 FOR existing_sp_row IN
7262                         SELECT  *
7263                         FROM  actor.usr_standing_penalty
7264                         WHERE usr = match_user
7265                                 AND org_unit = max_fines.org_unit
7266                                 AND (stop_date IS NULL or stop_date > NOW())
7267                                 AND standing_penalty = 30 
7268                         LOOP
7269                     -- Penalty exists, return it for removal
7270                     RETURN NEXT existing_sp_row;
7271                 END LOOP;
7272             END IF;
7273     
7274         END IF;
7275
7276     END IF;
7277
7278     RETURN;
7279 END;
7280 $func$ LANGUAGE plpgsql;
7281
7282 -- Create a default row in acq.fiscal_calendar
7283 -- Add a column in actor.org_unit to point to it
7284
7285 INSERT INTO acq.fiscal_calendar ( id, name ) VALUES ( 1, 'Default' );
7286
7287 ALTER TABLE actor.org_unit
7288 ADD COLUMN fiscal_calendar INT NOT NULL
7289         REFERENCES acq.fiscal_calendar( id )
7290         DEFERRABLE INITIALLY DEFERRED
7291         DEFAULT 1;
7292
7293 ALTER TABLE auditor.actor_org_unit_history
7294         ADD COLUMN fiscal_calendar INT;
7295
7296 ALTER TABLE acq.funding_source_credit
7297 ADD COLUMN deadline_date TIMESTAMPTZ;
7298
7299 ALTER TABLE acq.funding_source_credit
7300 ADD COLUMN effective_date TIMESTAMPTZ NOT NULL DEFAULT now();
7301
7302 INSERT INTO config.standing_penalty (id,name,label) VALUES (30,'PATRON_IN_COLLECTIONS','Patron has been referred to a collections agency');
7303
7304 CREATE TABLE acq.fund_transfer (
7305         id               SERIAL         PRIMARY KEY,
7306         src_fund         INT            NOT NULL REFERENCES acq.fund( id )
7307                                         DEFERRABLE INITIALLY DEFERRED,
7308         src_amount       NUMERIC        NOT NULL,
7309         dest_fund        INT            REFERENCES acq.fund( id )
7310                                         DEFERRABLE INITIALLY DEFERRED,
7311         dest_amount      NUMERIC,
7312         transfer_time    TIMESTAMPTZ    NOT NULL DEFAULT now(),
7313         transfer_user    INT            NOT NULL REFERENCES actor.usr( id )
7314                                         DEFERRABLE INITIALLY DEFERRED,
7315         note             TEXT,
7316     funding_source_credit INTEGER   NOT NULL
7317                                         REFERENCES acq.funding_source_credit(id)
7318                                         DEFERRABLE INITIALLY DEFERRED
7319 );
7320
7321 CREATE INDEX acqftr_usr_idx
7322 ON acq.fund_transfer( transfer_user );
7323
7324 COMMENT ON TABLE acq.fund_transfer IS $$
7325 /*
7326  * Copyright (C) 2009  Georgia Public Library Service
7327  * Scott McKellar <scott@esilibrary.com>
7328  *
7329  * Fund Transfer
7330  *
7331  * Each row represents the transfer of money from a source fund
7332  * to a destination fund.  There should be corresponding entries
7333  * in acq.fund_allocation.  The purpose of acq.fund_transfer is
7334  * to record how much money moved from which fund to which other
7335  * fund.
7336  * 
7337  * The presence of two amount fields, rather than one, reflects
7338  * the possibility that the two funds are denominated in different
7339  * currencies.  If they use the same currency type, the two
7340  * amounts should be the same.
7341  *
7342  * ****
7343  *
7344  * This program is free software; you can redistribute it and/or
7345  * modify it under the terms of the GNU General Public License
7346  * as published by the Free Software Foundation; either version 2
7347  * of the License, or (at your option) any later version.
7348  *
7349  * This program is distributed in the hope that it will be useful,
7350  * but WITHOUT ANY WARRANTY; without even the implied warranty of
7351  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
7352  * GNU General Public License for more details.
7353  */
7354 $$;
7355
7356 CREATE TABLE acq.claim_event_type (
7357         id             SERIAL           PRIMARY KEY,
7358         org_unit       INT              NOT NULL REFERENCES actor.org_unit(id)
7359                                                  DEFERRABLE INITIALLY DEFERRED,
7360         code           TEXT             NOT NULL,
7361         description    TEXT             NOT NULL,
7362         library_initiated BOOL          NOT NULL DEFAULT FALSE,
7363         CONSTRAINT event_type_once_per_org UNIQUE ( org_unit, code )
7364 );
7365
7366 CREATE TABLE acq.claim_event (
7367         id             BIGSERIAL        PRIMARY KEY,
7368         type           INT              NOT NULL REFERENCES acq.claim_event_type
7369                                                  DEFERRABLE INITIALLY DEFERRED,
7370         claim          SERIAL           NOT NULL REFERENCES acq.claim
7371                                                  DEFERRABLE INITIALLY DEFERRED,
7372         event_date     TIMESTAMPTZ      NOT NULL DEFAULT now(),
7373         creator        INT              NOT NULL REFERENCES actor.usr
7374                                                  DEFERRABLE INITIALLY DEFERRED,
7375         note           TEXT
7376 );
7377
7378 CREATE INDEX claim_event_claim_date_idx ON acq.claim_event( claim, event_date );
7379
7380 CREATE OR REPLACE FUNCTION actor.usr_purge_data(
7381         src_usr  IN INTEGER,
7382         dest_usr IN INTEGER
7383 ) RETURNS VOID AS $$
7384 DECLARE
7385         suffix TEXT;
7386         renamable_row RECORD;
7387 BEGIN
7388
7389         UPDATE actor.usr SET
7390                 active = FALSE,
7391                 card = NULL,
7392                 mailing_address = NULL,
7393                 billing_address = NULL
7394         WHERE id = src_usr;
7395
7396         -- acq.*
7397         UPDATE acq.fund_allocation SET allocator = dest_usr WHERE allocator = src_usr;
7398         UPDATE acq.lineitem SET creator = dest_usr WHERE creator = src_usr;
7399         UPDATE acq.lineitem SET editor = dest_usr WHERE editor = src_usr;
7400         UPDATE acq.lineitem SET selector = dest_usr WHERE selector = src_usr;
7401         UPDATE acq.lineitem_note SET creator = dest_usr WHERE creator = src_usr;
7402         UPDATE acq.lineitem_note SET editor = dest_usr WHERE editor = src_usr;
7403         DELETE FROM acq.lineitem_usr_attr_definition WHERE usr = src_usr;
7404
7405         -- Update with a rename to avoid collisions
7406         FOR renamable_row in
7407                 SELECT id, name
7408                 FROM   acq.picklist
7409                 WHERE  owner = src_usr
7410         LOOP
7411                 suffix := ' (' || src_usr || ')';
7412                 LOOP
7413                         BEGIN
7414                                 UPDATE  acq.picklist
7415                                 SET     owner = dest_usr, name = name || suffix
7416                                 WHERE   id = renamable_row.id;
7417                         EXCEPTION WHEN unique_violation THEN
7418                                 suffix := suffix || ' ';
7419                                 CONTINUE;
7420                         END;
7421                         EXIT;
7422                 END LOOP;
7423         END LOOP;
7424
7425         UPDATE acq.picklist SET creator = dest_usr WHERE creator = src_usr;
7426         UPDATE acq.picklist SET editor = dest_usr WHERE editor = src_usr;
7427         UPDATE acq.po_note SET creator = dest_usr WHERE creator = src_usr;
7428         UPDATE acq.po_note SET editor = dest_usr WHERE editor = src_usr;
7429         UPDATE acq.purchase_order SET owner = dest_usr WHERE owner = src_usr;
7430         UPDATE acq.purchase_order SET creator = dest_usr WHERE creator = src_usr;
7431         UPDATE acq.purchase_order SET editor = dest_usr WHERE editor = src_usr;
7432         UPDATE acq.claim_event SET creator = dest_usr WHERE creator = src_usr;
7433
7434         -- action.*
7435         DELETE FROM action.circulation WHERE usr = src_usr;
7436         UPDATE action.circulation SET circ_staff = dest_usr WHERE circ_staff = src_usr;
7437         UPDATE action.circulation SET checkin_staff = dest_usr WHERE checkin_staff = src_usr;
7438         UPDATE action.hold_notification SET notify_staff = dest_usr WHERE notify_staff = src_usr;
7439         UPDATE action.hold_request SET fulfillment_staff = dest_usr WHERE fulfillment_staff = src_usr;
7440         UPDATE action.hold_request SET requestor = dest_usr WHERE requestor = src_usr;
7441         DELETE FROM action.hold_request WHERE usr = src_usr;
7442         UPDATE action.in_house_use SET staff = dest_usr WHERE staff = src_usr;
7443         UPDATE action.non_cat_in_house_use SET staff = dest_usr WHERE staff = src_usr;
7444         DELETE FROM action.non_cataloged_circulation WHERE patron = src_usr;
7445         UPDATE action.non_cataloged_circulation SET staff = dest_usr WHERE staff = src_usr;
7446         DELETE FROM action.survey_response WHERE usr = src_usr;
7447         UPDATE action.fieldset SET owner = dest_usr WHERE owner = src_usr;
7448
7449         -- actor.*
7450         DELETE FROM actor.card WHERE usr = src_usr;
7451         DELETE FROM actor.stat_cat_entry_usr_map WHERE target_usr = src_usr;
7452
7453         -- The following update is intended to avoid transient violations of a foreign
7454         -- key constraint, whereby actor.usr_address references itself.  It may not be
7455         -- necessary, but it does no harm.
7456         UPDATE actor.usr_address SET replaces = NULL
7457                 WHERE usr = src_usr AND replaces IS NOT NULL;
7458         DELETE FROM actor.usr_address WHERE usr = src_usr;
7459         DELETE FROM actor.usr_note WHERE usr = src_usr;
7460         UPDATE actor.usr_note SET creator = dest_usr WHERE creator = src_usr;
7461         DELETE FROM actor.usr_org_unit_opt_in WHERE usr = src_usr;
7462         UPDATE actor.usr_org_unit_opt_in SET staff = dest_usr WHERE staff = src_usr;
7463         DELETE FROM actor.usr_setting WHERE usr = src_usr;
7464         DELETE FROM actor.usr_standing_penalty WHERE usr = src_usr;
7465         UPDATE actor.usr_standing_penalty SET staff = dest_usr WHERE staff = src_usr;
7466
7467         -- asset.*
7468         UPDATE asset.call_number SET creator = dest_usr WHERE creator = src_usr;
7469         UPDATE asset.call_number SET editor = dest_usr WHERE editor = src_usr;
7470         UPDATE asset.call_number_note SET creator = dest_usr WHERE creator = src_usr;
7471         UPDATE asset.copy SET creator = dest_usr WHERE creator = src_usr;
7472         UPDATE asset.copy SET editor = dest_usr WHERE editor = src_usr;
7473         UPDATE asset.copy_note SET creator = dest_usr WHERE creator = src_usr;
7474
7475         -- auditor.*
7476         DELETE FROM auditor.actor_usr_address_history WHERE id = src_usr;
7477         DELETE FROM auditor.actor_usr_history WHERE id = src_usr;
7478         UPDATE auditor.asset_call_number_history SET creator = dest_usr WHERE creator = src_usr;
7479         UPDATE auditor.asset_call_number_history SET editor  = dest_usr WHERE editor  = src_usr;
7480         UPDATE auditor.asset_copy_history SET creator = dest_usr WHERE creator = src_usr;
7481         UPDATE auditor.asset_copy_history SET editor  = dest_usr WHERE editor  = src_usr;
7482         UPDATE auditor.biblio_record_entry_history SET creator = dest_usr WHERE creator = src_usr;
7483         UPDATE auditor.biblio_record_entry_history SET editor  = dest_usr WHERE editor  = src_usr;
7484
7485         -- biblio.*
7486         UPDATE biblio.record_entry SET creator = dest_usr WHERE creator = src_usr;
7487         UPDATE biblio.record_entry SET editor = dest_usr WHERE editor = src_usr;
7488         UPDATE biblio.record_note SET creator = dest_usr WHERE creator = src_usr;
7489         UPDATE biblio.record_note SET editor = dest_usr WHERE editor = src_usr;
7490
7491         -- container.*
7492         -- Update buckets with a rename to avoid collisions
7493         FOR renamable_row in
7494                 SELECT id, name
7495                 FROM   container.biblio_record_entry_bucket
7496                 WHERE  owner = src_usr
7497         LOOP
7498                 suffix := ' (' || src_usr || ')';
7499                 LOOP
7500                         BEGIN
7501                                 UPDATE  container.biblio_record_entry_bucket
7502                                 SET     owner = dest_usr, name = name || suffix
7503                                 WHERE   id = renamable_row.id;
7504                         EXCEPTION WHEN unique_violation THEN
7505                                 suffix := suffix || ' ';
7506                                 CONTINUE;
7507                         END;
7508                         EXIT;
7509                 END LOOP;
7510         END LOOP;
7511
7512         FOR renamable_row in
7513                 SELECT id, name
7514                 FROM   container.call_number_bucket
7515                 WHERE  owner = src_usr
7516         LOOP
7517                 suffix := ' (' || src_usr || ')';
7518                 LOOP
7519                         BEGIN
7520                                 UPDATE  container.call_number_bucket
7521                                 SET     owner = dest_usr, name = name || suffix
7522                                 WHERE   id = renamable_row.id;
7523                         EXCEPTION WHEN unique_violation THEN
7524                                 suffix := suffix || ' ';
7525                                 CONTINUE;
7526                         END;
7527                         EXIT;
7528                 END LOOP;
7529         END LOOP;
7530
7531         FOR renamable_row in
7532                 SELECT id, name
7533                 FROM   container.copy_bucket
7534                 WHERE  owner = src_usr
7535         LOOP
7536                 suffix := ' (' || src_usr || ')';
7537                 LOOP
7538                         BEGIN
7539                                 UPDATE  container.copy_bucket
7540                                 SET     owner = dest_usr, name = name || suffix
7541                                 WHERE   id = renamable_row.id;
7542                         EXCEPTION WHEN unique_violation THEN
7543                                 suffix := suffix || ' ';
7544                                 CONTINUE;
7545                         END;
7546                         EXIT;
7547                 END LOOP;
7548         END LOOP;
7549
7550         FOR renamable_row in
7551                 SELECT id, name
7552                 FROM   container.user_bucket
7553                 WHERE  owner = src_usr
7554         LOOP
7555                 suffix := ' (' || src_usr || ')';
7556                 LOOP
7557                         BEGIN
7558                                 UPDATE  container.user_bucket
7559                                 SET     owner = dest_usr, name = name || suffix
7560                                 WHERE   id = renamable_row.id;
7561                         EXCEPTION WHEN unique_violation THEN
7562                                 suffix := suffix || ' ';
7563                                 CONTINUE;
7564                         END;
7565                         EXIT;
7566                 END LOOP;
7567         END LOOP;
7568
7569         DELETE FROM container.user_bucket_item WHERE target_user = src_usr;
7570
7571         -- money.*
7572         DELETE FROM money.billable_xact WHERE usr = src_usr;
7573         DELETE FROM money.collections_tracker WHERE usr = src_usr;
7574         UPDATE money.collections_tracker SET collector = dest_usr WHERE collector = src_usr;
7575
7576         -- permission.*
7577         DELETE FROM permission.usr_grp_map WHERE usr = src_usr;
7578         DELETE FROM permission.usr_object_perm_map WHERE usr = src_usr;
7579         DELETE FROM permission.usr_perm_map WHERE usr = src_usr;
7580         DELETE FROM permission.usr_work_ou_map WHERE usr = src_usr;
7581
7582         -- reporter.*
7583         -- Update with a rename to avoid collisions
7584         BEGIN
7585                 FOR renamable_row in
7586                         SELECT id, name
7587                         FROM   reporter.output_folder
7588                         WHERE  owner = src_usr
7589                 LOOP
7590                         suffix := ' (' || src_usr || ')';
7591                         LOOP
7592                                 BEGIN
7593                                         UPDATE  reporter.output_folder
7594                                         SET     owner = dest_usr, name = name || suffix
7595                                         WHERE   id = renamable_row.id;
7596                                 EXCEPTION WHEN unique_violation THEN
7597                                         suffix := suffix || ' ';
7598                                         CONTINUE;
7599                                 END;
7600                                 EXIT;
7601                         END LOOP;
7602                 END LOOP;
7603         EXCEPTION WHEN undefined_table THEN
7604                 -- do nothing
7605         END;
7606
7607         BEGIN
7608                 UPDATE reporter.report SET owner = dest_usr WHERE owner = src_usr;
7609         EXCEPTION WHEN undefined_table THEN
7610                 -- do nothing
7611         END;
7612
7613         -- Update with a rename to avoid collisions
7614         BEGIN
7615                 FOR renamable_row in
7616                         SELECT id, name
7617                         FROM   reporter.report_folder
7618                         WHERE  owner = src_usr
7619                 LOOP
7620                         suffix := ' (' || src_usr || ')';
7621                         LOOP
7622                                 BEGIN
7623                                         UPDATE  reporter.report_folder
7624                                         SET     owner = dest_usr, name = name || suffix
7625                                         WHERE   id = renamable_row.id;
7626                                 EXCEPTION WHEN unique_violation THEN
7627                                         suffix := suffix || ' ';
7628                                         CONTINUE;
7629                                 END;
7630                                 EXIT;
7631                         END LOOP;
7632                 END LOOP;
7633         EXCEPTION WHEN undefined_table THEN
7634                 -- do nothing
7635         END;
7636
7637         BEGIN
7638                 UPDATE reporter.schedule SET runner = dest_usr WHERE runner = src_usr;
7639         EXCEPTION WHEN undefined_table THEN
7640                 -- do nothing
7641         END;
7642
7643         BEGIN
7644                 UPDATE reporter.template SET owner = dest_usr WHERE owner = src_usr;
7645         EXCEPTION WHEN undefined_table THEN
7646                 -- do nothing
7647         END;
7648
7649         -- Update with a rename to avoid collisions
7650         BEGIN
7651                 FOR renamable_row in
7652                         SELECT id, name
7653                         FROM   reporter.template_folder
7654                         WHERE  owner = src_usr
7655                 LOOP
7656                         suffix := ' (' || src_usr || ')';
7657                         LOOP
7658                                 BEGIN
7659                                         UPDATE  reporter.template_folder
7660                                         SET     owner = dest_usr, name = name || suffix
7661                                         WHERE   id = renamable_row.id;
7662                                 EXCEPTION WHEN unique_violation THEN
7663                                         suffix := suffix || ' ';
7664                                         CONTINUE;
7665                                 END;
7666                                 EXIT;
7667                         END LOOP;
7668                 END LOOP;
7669         EXCEPTION WHEN undefined_table THEN
7670         -- do nothing
7671         END;
7672
7673         -- vandelay.*
7674         -- Update with a rename to avoid collisions
7675         FOR renamable_row in
7676                 SELECT id, name
7677                 FROM   vandelay.queue
7678                 WHERE  owner = src_usr
7679         LOOP
7680                 suffix := ' (' || src_usr || ')';
7681                 LOOP
7682                         BEGIN
7683                                 UPDATE  vandelay.queue
7684                                 SET     owner = dest_usr, name = name || suffix
7685                                 WHERE   id = renamable_row.id;
7686                         EXCEPTION WHEN unique_violation THEN
7687                                 suffix := suffix || ' ';
7688                                 CONTINUE;
7689                         END;
7690                         EXIT;
7691                 END LOOP;
7692         END LOOP;
7693
7694 END;
7695 $$ LANGUAGE plpgsql;
7696
7697 -- INSERT INTO config.copy_status (id,name) VALUES (15,oils_i18n_gettext(15, 'On reservation shelf', 'ccs', 'name'));
7698
7699 ALTER TABLE acq.fund
7700 ADD COLUMN rollover BOOL NOT NULL DEFAULT FALSE;
7701
7702 ALTER TABLE acq.fund
7703         ADD COLUMN propagate BOOLEAN NOT NULL DEFAULT TRUE;
7704
7705 -- A fund can't roll over if it doesn't propagate from one year to the next
7706
7707 ALTER TABLE acq.fund
7708         ADD CONSTRAINT acq_fund_rollover_implies_propagate CHECK
7709         ( propagate OR NOT rollover );
7710
7711 ALTER TABLE acq.fund
7712         ADD COLUMN active BOOL NOT NULL DEFAULT TRUE;
7713
7714 ALTER TABLE acq.fund
7715     ADD COLUMN balance_warning_percent INT
7716     CONSTRAINT balance_warning_percent_limit
7717         CHECK( balance_warning_percent <= 100 );
7718
7719 ALTER TABLE acq.fund
7720     ADD COLUMN balance_stop_percent INT
7721     CONSTRAINT balance_stop_percent_limit
7722         CHECK( balance_stop_percent <= 100 );
7723
7724 CREATE VIEW acq.ordered_funding_source_credit AS
7725         SELECT
7726                 CASE WHEN deadline_date IS NULL THEN
7727                         2
7728                 ELSE
7729                         1
7730                 END AS sort_priority,
7731                 CASE WHEN deadline_date IS NULL THEN
7732                         effective_date
7733                 ELSE
7734                         deadline_date
7735                 END AS sort_date,
7736                 id,
7737                 funding_source,
7738                 amount,
7739                 note
7740         FROM
7741                 acq.funding_source_credit;
7742
7743 COMMENT ON VIEW acq.ordered_funding_source_credit IS $$
7744 /*
7745  * Copyright (C) 2009  Georgia Public Library Service
7746  * Scott McKellar <scott@gmail.com>
7747  *
7748  * The acq.ordered_funding_source_credit view is a prioritized
7749  * ordering of funding source credits.  When ordered by the first
7750  * three columns, this view defines the order in which the various
7751  * credits are to be tapped for spending, subject to the allocations
7752  * in the acq.fund_allocation table.
7753  *
7754  * The first column reflects the principle that we should spend
7755  * money with deadlines before spending money without deadlines.
7756  *
7757  * The second column reflects the principle that we should spend the
7758  * oldest money first.  For money with deadlines, that means that we
7759  * spend first from the credit with the earliest deadline.  For
7760  * money without deadlines, we spend first from the credit with the
7761  * earliest effective date.  
7762  *
7763  * The third column is a tie breaker to ensure a consistent
7764  * ordering.
7765  *
7766  * ****
7767  *
7768  * This program is free software; you can redistribute it and/or
7769  * modify it under the terms of the GNU General Public License
7770  * as published by the Free Software Foundation; either version 2
7771  * of the License, or (at your option) any later version.
7772  *
7773  * This program is distributed in the hope that it will be useful,
7774  * but WITHOUT ANY WARRANTY; without even the implied warranty of
7775  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
7776  * GNU General Public License for more details.
7777  */
7778 $$;
7779
7780 CREATE OR REPLACE VIEW money.billable_xact_summary_location_view AS
7781     SELECT  m.*, COALESCE(c.circ_lib, g.billing_location, r.pickup_lib) AS billing_location
7782       FROM  money.materialized_billable_xact_summary m
7783             LEFT JOIN action.circulation c ON (c.id = m.id)
7784             LEFT JOIN money.grocery g ON (g.id = m.id)
7785             LEFT JOIN booking.reservation r ON (r.id = m.id);
7786
7787 CREATE TABLE config.marc21_rec_type_map (
7788     code        TEXT    PRIMARY KEY,
7789     type_val    TEXT    NOT NULL,
7790     blvl_val    TEXT    NOT NULL
7791 );
7792
7793 CREATE TABLE config.marc21_ff_pos_map (
7794     id          SERIAL  PRIMARY KEY,
7795     fixed_field TEXT    NOT NULL,
7796     tag         TEXT    NOT NULL,
7797     rec_type    TEXT    NOT NULL,
7798     start_pos   INT     NOT NULL,
7799     length      INT     NOT NULL,
7800     default_val TEXT    NOT NULL DEFAULT ' '
7801 );
7802
7803 CREATE TABLE config.marc21_physical_characteristic_type_map (
7804     ptype_key   TEXT    PRIMARY KEY,
7805     label       TEXT    NOT NULL -- I18N
7806 );
7807
7808 CREATE TABLE config.marc21_physical_characteristic_subfield_map (
7809     id          SERIAL  PRIMARY KEY,
7810     ptype_key   TEXT    NOT NULL REFERENCES config.marc21_physical_characteristic_type_map (ptype_key) ON DELETE CASCADE ON UPDATE CASCADE,
7811     subfield    TEXT    NOT NULL,
7812     start_pos   INT     NOT NULL,
7813     length      INT     NOT NULL,
7814     label       TEXT    NOT NULL -- I18N
7815 );
7816
7817 CREATE TABLE config.marc21_physical_characteristic_value_map (
7818     id              SERIAL  PRIMARY KEY,
7819     value           TEXT    NOT NULL,
7820     ptype_subfield  INT     NOT NULL REFERENCES config.marc21_physical_characteristic_subfield_map (id),
7821     label           TEXT    NOT NULL -- I18N
7822 );
7823
7824 ----------------------------------
7825 -- MARC21 record structure data --
7826 ----------------------------------
7827
7828 -- Record type map
7829 INSERT INTO config.marc21_rec_type_map (code, type_val, blvl_val) VALUES ('BKS','at','acdm');
7830 INSERT INTO config.marc21_rec_type_map (code, type_val, blvl_val) VALUES ('SER','a','bsi');
7831 INSERT INTO config.marc21_rec_type_map (code, type_val, blvl_val) VALUES ('VIS','gkro','abcdmsi');
7832 INSERT INTO config.marc21_rec_type_map (code, type_val, blvl_val) VALUES ('MIX','p','cdi');
7833 INSERT INTO config.marc21_rec_type_map (code, type_val, blvl_val) VALUES ('MAP','ef','abcdmsi');
7834 INSERT INTO config.marc21_rec_type_map (code, type_val, blvl_val) VALUES ('SCO','cd','abcdmsi');
7835 INSERT INTO config.marc21_rec_type_map (code, type_val, blvl_val) VALUES ('REC','ij','abcdmsi');
7836 INSERT INTO config.marc21_rec_type_map (code, type_val, blvl_val) VALUES ('COM','m','abcdmsi');
7837
7838 ------ Physical Characteristics
7839
7840 -- Map
7841 INSERT INTO config.marc21_physical_characteristic_type_map (ptype_key, label) VALUES ('a','Map');
7842 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('a','b','1','1','SMD');
7843 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('d',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Atlas');
7844 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('g',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Diagram');
7845 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('j',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Map');
7846 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('k',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Profile');
7847 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('q',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Model');
7848 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');
7849 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('s',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Section');
7850 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unspecified');
7851 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('y',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'View');
7852 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
7853 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('a','d','3','1','Color');
7854 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');
7855 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('c',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Multicolored');
7856 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('a','e','4','1','Physical medium');
7857 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('a',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Paper');
7858 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('b',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Wood');
7859 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('c',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Stone');
7860 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('d',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Metal');
7861 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('e',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Synthetics');
7862 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('f',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Skins');
7863 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('g',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Textile');
7864 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('p',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Plaster');
7865 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');
7866 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');
7867 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');
7868 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');
7869 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
7870 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');
7871 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
7872 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('a','f','5','1','Type of reproduction');
7873 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('f',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Facsimile');
7874 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');
7875 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
7876 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
7877 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('a','g','6','1','Production/reproduction details');
7878 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');
7879 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('b',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Photocopy');
7880 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');
7881 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('d',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Film');
7882 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
7883 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
7884 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('a','h','7','1','Positive/negative');
7885 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('a',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Positive');
7886 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('b',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Negative');
7887 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('m',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Mixed');
7888 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');
7889
7890 -- Electronic Resource
7891 INSERT INTO config.marc21_physical_characteristic_type_map (ptype_key, label) VALUES ('c','Electronic Resource');
7892 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('c','b','1','1','SMD');
7893 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');
7894 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');
7895 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');
7896 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');
7897 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');
7898 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');
7899 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');
7900 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');
7901 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('r',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Remote');
7902 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unspecified');
7903 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
7904 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('c','d','3','1','Color');
7905 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');
7906 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');
7907 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('c',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Multicolored');
7908 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');
7909 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('m',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Mixed');
7910 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');
7911 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
7912 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
7913 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('c','e','4','1','Dimensions');
7914 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.');
7915 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.');
7916 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.');
7917 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.');
7918 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.');
7919 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');
7920 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.');
7921 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
7922 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.');
7923 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
7924 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('c','f','5','1','Sound');
7925 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)');
7926 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('a',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Sound');
7927 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
7928 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('c','g','6','3','Image bit depth');
7929 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('---',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
7930 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('mmm',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Multiple');
7931 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');
7932 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('c','h','9','1','File formats');
7933 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');
7934 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');
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','i','10','1','Quality assurance target(s)');
7937 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('a',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Absent');
7938 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');
7939 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('p',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Present');
7940 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
7941 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('c','j','11','1','Antecedent/Source');
7942 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');
7943 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');
7944 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');
7945 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)');
7946 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('m',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Mixed');
7947 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');
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','k','12','1','Level of compression');
7950 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('a',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Uncompressed');
7951 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('b',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Lossless');
7952 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('d',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Lossy');
7953 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('m',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Mixed');
7954 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
7955 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('c','l','13','1','Reformatting quality');
7956 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('a',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Access');
7957 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');
7958 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('p',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Preservation');
7959 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('r',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Replacement');
7960 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
7961
7962 -- Globe
7963 INSERT INTO config.marc21_physical_characteristic_type_map (ptype_key, label) VALUES ('d','Globe');
7964 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('d','b','1','1','SMD');
7965 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');
7966 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');
7967 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');
7968 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');
7969 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unspecified');
7970 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
7971 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('d','d','3','1','Color');
7972 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');
7973 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('c',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Multicolored');
7974 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('d','e','4','1','Physical medium');
7975 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('a',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Paper');
7976 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('b',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Wood');
7977 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('c',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Stone');
7978 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('d',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Metal');
7979 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('e',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Synthetics');
7980 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('f',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Skins');
7981 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('g',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Textile');
7982 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('p',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Plaster');
7983 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
7984 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
7985 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('d','f','5','1','Type of reproduction');
7986 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('f',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Facsimile');
7987 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');
7988 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
7989 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
7990
7991 -- Tactile Material
7992 INSERT INTO config.marc21_physical_characteristic_type_map (ptype_key, label) VALUES ('f','Tactile Material');
7993 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('f','b','1','1','SMD');
7994 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('a',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Moon');
7995 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('b',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Braille');
7996 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('c',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Combination');
7997 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');
7998 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unspecified');
7999 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
8000 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('f','d','3','2','Class of braille writing');
8001 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');
8002 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');
8003 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');
8004 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');
8005 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');
8006 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');
8007 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');
8008 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
8009 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
8010 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('f','e','4','1','Level of contraction');
8011 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('a',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Uncontracted');
8012 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('b',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Contracted');
8013 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('m',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Combination');
8014 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');
8015 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
8016 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
8017 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('f','f','6','3','Braille music format');
8018 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');
8019 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');
8020 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');
8021 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('d',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Paragraph');
8022 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');
8023 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');
8024 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');
8025 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');
8026 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');
8027 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');
8028 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('k',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Outline');
8029 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');
8030 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');
8031 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
8032 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
8033 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('f','g','9','1','Special physical characteristics');
8034 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');
8035 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');
8036 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');
8037 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
8038 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
8039
8040 -- Projected Graphic
8041 INSERT INTO config.marc21_physical_characteristic_type_map (ptype_key, label) VALUES ('g','Projected Graphic');
8042 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('g','b','1','1','SMD');
8043 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');
8044 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('d',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Filmstrip');
8045 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');
8046 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');
8047 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('s',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Slide');
8048 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('t',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Transparency');
8049 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
8050 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('g','d','3','1','Color');
8051 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');
8052 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('c',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Multicolored');
8053 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');
8054 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('m',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Mixed');
8055 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');
8056 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
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','e','4','1','Base of emulsion');
8059 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('d',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Glass');
8060 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('e',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Synthetics');
8061 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');
8062 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');
8063 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');
8064 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('o',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Paper');
8065 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
8066 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
8067 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');
8068 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');
8069 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');
8070 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
8071 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('g','g','6','1','Medium for sound');
8072 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');
8073 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');
8074 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');
8075 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');
8076 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');
8077 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');
8078 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');
8079 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('h',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Videotape');
8080 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('i',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Videodisc');
8081 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
8082 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
8083 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('g','h','7','1','Dimensions');
8084 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.');
8085 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.');
8086 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.');
8087 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.');
8088 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.');
8089 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.');
8090 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.');
8091 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.)');
8092 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.)');
8093 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.)');
8094 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.)');
8095 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
8096 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.)');
8097 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.)');
8098 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.)');
8099 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.)');
8100 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
8101 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('g','i','8','1','Secondary support material');
8102 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('c',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Cardboard');
8103 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('d',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Glass');
8104 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('e',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Synthetics');
8105 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('h',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'metal');
8106 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');
8107 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');
8108 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');
8109 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
8110 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
8111
8112 -- Microform
8113 INSERT INTO config.marc21_physical_characteristic_type_map (ptype_key, label) VALUES ('h','Microform');
8114 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('h','b','1','1','SMD');
8115 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');
8116 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');
8117 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');
8118 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');
8119 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('e',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Microfiche');
8120 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');
8121 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('g',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Microopaque');
8122 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unspecified');
8123 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
8124 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('h','d','3','1','Positive/negative');
8125 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('a',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Positive');
8126 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('b',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Negative');
8127 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('m',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Mixed');
8128 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
8129 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('h','e','4','1','Dimensions');
8130 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.');
8131 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.');
8132 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.');
8133 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('g',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'70mm.');
8134 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.');
8135 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.)');
8136 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.)');
8137 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.)');
8138 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.)');
8139 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
8140 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
8141 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');
8142 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)');
8143 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)');
8144 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)');
8145 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)');
8146 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-)');
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 ('v',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Reduction ratio varies');
8149 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('h','g','9','1','Color');
8150 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');
8151 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('c',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Multicolored');
8152 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('m',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Mixed');
8153 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
8154 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
8155 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('h','h','10','1','Emulsion on film');
8156 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');
8157 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('b',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Diazo');
8158 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('c',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Vesicular');
8159 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('m',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Mixed');
8160 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');
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','i','11','1','Quality assurance target(s)');
8164 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');
8165 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');
8166 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');
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 generation');
8168 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
8169 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('h','j','12','1','Base of film');
8170 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');
8171 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');
8172 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');
8173 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');
8174 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');
8175 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');
8176 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');
8177 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');
8178 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');
8179 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
8180 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
8181
8182 -- Non-projected Graphic
8183 INSERT INTO config.marc21_physical_characteristic_type_map (ptype_key, label) VALUES ('k','Non-projected Graphic');
8184 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('k','b','1','1','SMD');
8185 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('c',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Collage');
8186 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('d',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Drawing');
8187 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('e',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Painting');
8188 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');
8189 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('g',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Photonegative');
8190 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('h',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Photoprint');
8191 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('i',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Picture');
8192 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('j',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Print');
8193 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');
8194 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('n',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Chart');
8195 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');
8196 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unspecified');
8197 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
8198 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('k','d','3','1','Color');
8199 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');
8200 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');
8201 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('c',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Multicolored');
8202 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');
8203 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('m',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Mixed');
8204 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
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','e','4','1','Primary support material');
8207 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('a',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Canvas');
8208 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');
8209 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');
8210 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('d',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Glass');
8211 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('e',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Synthetics');
8212 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('f',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Skins');
8213 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('g',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Textile');
8214 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('h',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Metal');
8215 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');
8216 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('o',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Paper');
8217 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('p',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Plaster');
8218 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('q',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Hardboard');
8219 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('r',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Porcelain');
8220 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('s',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Stone');
8221 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('t',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Wood');
8222 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
8223 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
8224 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('k','f','5','1','Secondary support material');
8225 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('a',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Canvas');
8226 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');
8227 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');
8228 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('d',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Glass');
8229 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('e',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Synthetics');
8230 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('f',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Skins');
8231 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('g',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Textile');
8232 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('h',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Metal');
8233 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');
8234 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('o',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Paper');
8235 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('p',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Plaster');
8236 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('q',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Hardboard');
8237 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('r',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Porcelain');
8238 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('s',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Stone');
8239 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('t',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Wood');
8240 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
8241 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
8242
8243 -- Motion Picture
8244 INSERT INTO config.marc21_physical_characteristic_type_map (ptype_key, label) VALUES ('m','Motion Picture');
8245 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('m','b','1','1','SMD');
8246 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');
8247 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');
8248 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');
8249 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unspecified');
8250 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
8251 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('m','d','3','1','Color');
8252 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');
8253 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('c',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Multicolored');
8254 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');
8255 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('m',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Mixed');
8256 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
8257 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
8258 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('m','e','4','1','Motion picture presentation format');
8259 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');
8260 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)');
8261 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('c',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'3D');
8262 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)');
8263 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');
8264 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');
8265 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
8266 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
8267 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');
8268 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');
8269 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');
8270 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
8271 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('m','g','6','1','Medium for sound');
8272 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');
8273 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');
8274 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');
8275 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');
8276 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');
8277 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');
8278 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');
8279 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('h',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Videotape');
8280 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('i',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Videodisc');
8281 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
8282 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
8283 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('m','h','7','1','Dimensions');
8284 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.');
8285 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.');
8286 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.');
8287 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.');
8288 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.');
8289 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.');
8290 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.');
8291 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
8292 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
8293 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('m','i','8','1','Configuration of playback channels');
8294 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('k',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Mixed');
8295 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('m',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Monaural');
8296 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');
8297 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');
8298 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('s',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Stereophonic');
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','j','9','1','Production elements');
8302 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');
8303 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('b',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Trims');
8304 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('c',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Outtakes');
8305 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('d',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Rushes');
8306 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');
8307 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');
8308 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');
8309 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');
8310 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
8311
8312 -- Remote-sensing Image
8313 INSERT INTO config.marc21_physical_characteristic_type_map (ptype_key, label) VALUES ('r','Remote-sensing Image');
8314 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('r','b','1','1','SMD');
8315 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unspecified');
8316 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('r','d','3','1','Altitude of sensor');
8317 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('a',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Surface');
8318 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('b',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Airborne');
8319 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('c',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Spaceborne');
8320 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');
8321 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
8322 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
8323 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('r','e','4','1','Attitude of sensor');
8324 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');
8325 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');
8326 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('c',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Vertical');
8327 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');
8328 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
8329 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('r','f','5','1','Cloud cover');
8330 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%');
8331 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%');
8332 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%');
8333 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%');
8334 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%');
8335 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%');
8336 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%');
8337 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%');
8338 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%');
8339 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%');
8340 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');
8341 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
8342 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('r','g','6','1','Platform construction type');
8343 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('a',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Balloon');
8344 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');
8345 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');
8346 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');
8347 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');
8348 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');
8349 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');
8350 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');
8351 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');
8352 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');
8353 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
8354 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
8355 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('r','h','7','1','Platform use category');
8356 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('a',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Meteorological');
8357 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');
8358 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');
8359 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');
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','i','8','1','Sensor type');
8364 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('a',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Active');
8365 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('b',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Passive');
8366 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
8367 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
8368 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('r','j','9','2','Data type');
8369 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');
8370 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');
8371 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');
8372 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');
8373 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');
8374 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)');
8375 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');
8376 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('dv',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Combinations');
8377 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');
8378 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)');
8379 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)');
8380 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)');
8381 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');
8382 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');
8383 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');
8384 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');
8385 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');
8386 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');
8387 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');
8388 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');
8389 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');
8390 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');
8391 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');
8392 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');
8393 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');
8394 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');
8395 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');
8396 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');
8397 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');
8398 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');
8399 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');
8400 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');
8401 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');
8402 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)');
8403 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');
8404 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('rc',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Bouger');
8405 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('rd',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Isostatic');
8406 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');
8407 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');
8408 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('uu',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
8409 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('zz',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
8410
8411 -- Sound Recording
8412 INSERT INTO config.marc21_physical_characteristic_type_map (ptype_key, label) VALUES ('s','Sound Recording');
8413 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('s','b','1','1','SMD');
8414 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');
8415 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('e',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Cylinder');
8416 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');
8417 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');
8418 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('q',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Roll');
8419 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');
8420 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');
8421 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unspecified');
8422 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');
8423 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
8424 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('s','d','3','1','Speed');
8425 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');
8426 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');
8427 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');
8428 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');
8429 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');
8430 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');
8431 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');
8432 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');
8433 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');
8434 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');
8435 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');
8436 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');
8437 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');
8438 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');
8439 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
8440 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
8441 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('s','e','4','1','Configuration of playback channels');
8442 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('m',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Monaural');
8443 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('q',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Quadraphonic');
8444 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('s',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Stereophonic');
8445 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
8446 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
8447 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('s','f','5','1','Groove width or pitch');
8448 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');
8449 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');
8450 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');
8451 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
8452 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
8453 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('s','g','6','1','Dimensions');
8454 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.');
8455 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.');
8456 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.');
8457 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.');
8458 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.');
8459 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.');
8460 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.)');
8461 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.');
8462 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');
8463 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.');
8464 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.');
8465 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
8466 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
8467 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('s','h','7','1','Tape width');
8468 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.');
8469 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.');
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'),'1/2 in.');
8472 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.');
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','i','8','1','Tape configuration ');
8476 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');
8477 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');
8478 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');
8479 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');
8480 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');
8481 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');
8482 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');
8483 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
8484 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
8485 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('s','m','12','1','Special playback');
8486 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');
8487 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');
8488 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');
8489 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');
8490 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');
8491 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');
8492 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');
8493 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');
8494 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');
8495 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
8496 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
8497 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('s','n','13','1','Capture and storage');
8498 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');
8499 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');
8500 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');
8501 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');
8502 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
8503 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
8504
8505 -- Videorecording
8506 INSERT INTO config.marc21_physical_characteristic_type_map (ptype_key, label) VALUES ('v','Videorecording');
8507 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('v','b','1','1','SMD');
8508 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('c',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Videocartridge');
8509 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('d',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Videodisc');
8510 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('f',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Videocassette');
8511 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('r',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Videoreel');
8512 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unspecified');
8513 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
8514 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('v','d','3','1','Color');
8515 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');
8516 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('c',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Multicolored');
8517 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('m',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Mixed');
8518 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');
8519 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
8520 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
8521 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('v','e','4','1','Videorecording format');
8522 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('a',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Beta');
8523 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('b',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'VHS');
8524 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');
8525 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('d',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'EIAJ');
8526 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');
8527 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('f',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Quadruplex');
8528 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('g',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Laserdisc');
8529 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('h',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'CED');
8530 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('i',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Betacam');
8531 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');
8532 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');
8533 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');
8534 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');
8535 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.');
8536 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.');
8537 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
8538 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('v',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'DVD');
8539 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
8540 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');
8541 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');
8542 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');
8543 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
8544 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('v','g','6','1','Medium for sound');
8545 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');
8546 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');
8547 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');
8548 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');
8549 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');
8550 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');
8551 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');
8552 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('h',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Videotape');
8553 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('i',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Videodisc');
8554 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
8555 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
8556 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('v','h','7','1','Dimensions');
8557 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.');
8558 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.');
8559 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.');
8560 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.');
8561 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.');
8562 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.');
8563 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
8564 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
8565 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('v','i','8','1','Configuration of playback channel');
8566 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('k',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Mixed');
8567 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('m',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Monaural');
8568 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');
8569 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');
8570 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('s',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Stereophonic');
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
8574 -- Fixed Field position data -- 0-based!
8575 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Alph', '006', 'SER', 16, 1, ' ');
8576 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Alph', '008', 'SER', 33, 1, ' ');
8577 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Audn', '006', 'BKS', 5, 1, ' ');
8578 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Audn', '006', 'COM', 5, 1, ' ');
8579 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Audn', '006', 'REC', 5, 1, ' ');
8580 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Audn', '006', 'SCO', 5, 1, ' ');
8581 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Audn', '006', 'SER', 5, 1, ' ');
8582 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Audn', '006', 'VIS', 5, 1, ' ');
8583 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Audn', '008', 'BKS', 22, 1, ' ');
8584 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Audn', '008', 'COM', 22, 1, ' ');
8585 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Audn', '008', 'REC', 22, 1, ' ');
8586 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Audn', '008', 'SCO', 22, 1, ' ');
8587 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Audn', '008', 'SER', 22, 1, ' ');
8588 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Audn', '008', 'VIS', 22, 1, ' ');
8589 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('BLvl', 'ldr', 'BKS', 7, 1, 'm');
8590 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('BLvl', 'ldr', 'COM', 7, 1, 'm');
8591 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('BLvl', 'ldr', 'MAP', 7, 1, 'm');
8592 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('BLvl', 'ldr', 'MIX', 7, 1, 'c');
8593 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('BLvl', 'ldr', 'REC', 7, 1, 'm');
8594 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('BLvl', 'ldr', 'SCO', 7, 1, 'm');
8595 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('BLvl', 'ldr', 'SER', 7, 1, 's');
8596 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('BLvl', 'ldr', 'VIS', 7, 1, 'm');
8597 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Biog', '006', 'BKS', 17, 1, ' ');
8598 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Biog', '008', 'BKS', 34, 1, ' ');
8599 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Conf', '006', 'BKS', 7, 4, ' ');
8600 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Conf', '006', 'SER', 8, 3, ' ');
8601 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Conf', '008', 'BKS', 24, 4, ' ');
8602 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Conf', '008', 'SER', 25, 3, ' ');
8603 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Ctrl', 'ldr', 'BKS', 8, 1, ' ');
8604 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Ctrl', 'ldr', 'COM', 8, 1, ' ');
8605 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Ctrl', 'ldr', 'MAP', 8, 1, ' ');
8606 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Ctrl', 'ldr', 'MIX', 8, 1, ' ');
8607 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Ctrl', 'ldr', 'REC', 8, 1, ' ');
8608 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Ctrl', 'ldr', 'SCO', 8, 1, ' ');
8609 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Ctrl', 'ldr', 'SER', 8, 1, ' ');
8610 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Ctrl', 'ldr', 'VIS', 8, 1, ' ');
8611 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Ctry', '008', 'BKS', 15, 3, ' ');
8612 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Ctry', '008', 'COM', 15, 3, ' ');
8613 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Ctry', '008', 'MAP', 15, 3, ' ');
8614 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Ctry', '008', 'MIX', 15, 3, ' ');
8615 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Ctry', '008', 'REC', 15, 3, ' ');
8616 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Ctry', '008', 'SCO', 15, 3, ' ');
8617 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Ctry', '008', 'SER', 15, 3, ' ');
8618 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Ctry', '008', 'VIS', 15, 3, ' ');
8619 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Date1', '008', 'BKS', 7, 4, ' ');
8620 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Date1', '008', 'COM', 7, 4, ' ');
8621 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Date1', '008', 'MAP', 7, 4, ' ');
8622 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Date1', '008', 'MIX', 7, 4, ' ');
8623 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Date1', '008', 'REC', 7, 4, ' ');
8624 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Date1', '008', 'SCO', 7, 4, ' ');
8625 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Date1', '008', 'SER', 7, 4, ' ');
8626 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Date1', '008', 'VIS', 7, 4, ' ');
8627 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Date2', '008', 'BKS', 11, 4, ' ');
8628 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Date2', '008', 'COM', 11, 4, ' ');
8629 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Date2', '008', 'MAP', 11, 4, ' ');
8630 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Date2', '008', 'MIX', 11, 4, ' ');
8631 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Date2', '008', 'REC', 11, 4, ' ');
8632 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Date2', '008', 'SCO', 11, 4, ' ');
8633 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Date2', '008', 'SER', 11, 4, '9');
8634 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Date2', '008', 'VIS', 11, 4, ' ');
8635 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Desc', 'ldr', 'BKS', 18, 1, ' ');
8636 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Desc', 'ldr', 'COM', 18, 1, ' ');
8637 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Desc', 'ldr', 'MAP', 18, 1, ' ');
8638 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Desc', 'ldr', 'MIX', 18, 1, ' ');
8639 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Desc', 'ldr', 'REC', 18, 1, ' ');
8640 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Desc', 'ldr', 'SCO', 18, 1, ' ');
8641 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Desc', 'ldr', 'SER', 18, 1, ' ');
8642 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Desc', 'ldr', 'VIS', 18, 1, ' ');
8643 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('DtSt', '008', 'BKS', 6, 1, ' ');
8644 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('DtSt', '008', 'COM', 6, 1, ' ');
8645 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('DtSt', '008', 'MAP', 6, 1, ' ');
8646 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('DtSt', '008', 'MIX', 6, 1, ' ');
8647 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('DtSt', '008', 'REC', 6, 1, ' ');
8648 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('DtSt', '008', 'SCO', 6, 1, ' ');
8649 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('DtSt', '008', 'SER', 6, 1, 'c');
8650 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('DtSt', '008', 'VIS', 6, 1, ' ');
8651 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('ELvl', 'ldr', 'BKS', 17, 1, ' ');
8652 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('ELvl', 'ldr', 'COM', 17, 1, ' ');
8653 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('ELvl', 'ldr', 'MAP', 17, 1, ' ');
8654 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('ELvl', 'ldr', 'MIX', 17, 1, ' ');
8655 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('ELvl', 'ldr', 'REC', 17, 1, ' ');
8656 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('ELvl', 'ldr', 'SCO', 17, 1, ' ');
8657 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('ELvl', 'ldr', 'SER', 17, 1, ' ');
8658 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('ELvl', 'ldr', 'VIS', 17, 1, ' ');
8659 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Fest', '006', 'BKS', 13, 1, '0');
8660 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Fest', '008', 'BKS', 30, 1, '0');
8661 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Form', '006', 'BKS', 6, 1, ' ');
8662 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Form', '006', 'MAP', 12, 1, ' ');
8663 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Form', '006', 'MIX', 6, 1, ' ');
8664 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Form', '006', 'REC', 6, 1, ' ');
8665 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Form', '006', 'SCO', 6, 1, ' ');
8666 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Form', '006', 'SER', 6, 1, ' ');
8667 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Form', '006', 'VIS', 12, 1, ' ');
8668 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Form', '008', 'BKS', 23, 1, ' ');
8669 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Form', '008', 'MAP', 29, 1, ' ');
8670 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Form', '008', 'MIX', 23, 1, ' ');
8671 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Form', '008', 'REC', 23, 1, ' ');
8672 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Form', '008', 'SCO', 23, 1, ' ');
8673 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Form', '008', 'SER', 23, 1, ' ');
8674 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Form', '008', 'VIS', 29, 1, ' ');
8675 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('GPub', '006', 'BKS', 11, 1, ' ');
8676 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('GPub', '006', 'COM', 11, 1, ' ');
8677 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('GPub', '006', 'MAP', 11, 1, ' ');
8678 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('GPub', '006', 'SER', 11, 1, ' ');
8679 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('GPub', '006', 'VIS', 11, 1, ' ');
8680 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('GPub', '008', 'BKS', 28, 1, ' ');
8681 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('GPub', '008', 'COM', 28, 1, ' ');
8682 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('GPub', '008', 'MAP', 28, 1, ' ');
8683 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('GPub', '008', 'SER', 28, 1, ' ');
8684 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('GPub', '008', 'VIS', 28, 1, ' ');
8685 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Ills', '006', 'BKS', 1, 4, ' ');
8686 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Ills', '008', 'BKS', 18, 4, ' ');
8687 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Indx', '006', 'BKS', 14, 1, '0');
8688 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Indx', '006', 'MAP', 14, 1, '0');
8689 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Indx', '008', 'BKS', 31, 1, '0');
8690 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Indx', '008', 'MAP', 31, 1, '0');
8691 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Lang', '008', 'BKS', 35, 3, ' ');
8692 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Lang', '008', 'COM', 35, 3, ' ');
8693 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Lang', '008', 'MAP', 35, 3, ' ');
8694 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Lang', '008', 'MIX', 35, 3, ' ');
8695 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Lang', '008', 'REC', 35, 3, ' ');
8696 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Lang', '008', 'SCO', 35, 3, ' ');
8697 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Lang', '008', 'SER', 35, 3, ' ');
8698 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Lang', '008', 'VIS', 35, 3, ' ');
8699 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('LitF', '006', 'BKS', 16, 1, '0');
8700 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('LitF', '008', 'BKS', 33, 1, '0');
8701 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('MRec', '008', 'BKS', 38, 1, ' ');
8702 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('MRec', '008', 'COM', 38, 1, ' ');
8703 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('MRec', '008', 'MAP', 38, 1, ' ');
8704 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('MRec', '008', 'MIX', 38, 1, ' ');
8705 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('MRec', '008', 'REC', 38, 1, ' ');
8706 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('MRec', '008', 'SCO', 38, 1, ' ');
8707 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('MRec', '008', 'SER', 38, 1, ' ');
8708 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('MRec', '008', 'VIS', 38, 1, ' ');
8709 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');
8710 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');
8711 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('TMat', '006', 'VIS', 16, 1, ' ');
8712 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('TMat', '008', 'VIS', 33, 1, ' ');
8713 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Type', 'ldr', 'BKS', 6, 1, 'a');
8714 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Type', 'ldr', 'COM', 6, 1, 'm');
8715 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Type', 'ldr', 'MAP', 6, 1, 'e');
8716 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Type', 'ldr', 'MIX', 6, 1, 'p');
8717 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Type', 'ldr', 'REC', 6, 1, 'i');
8718 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Type', 'ldr', 'SCO', 6, 1, 'c');
8719 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Type', 'ldr', 'SER', 6, 1, 'a');
8720 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Type', 'ldr', 'VIS', 6, 1, 'g');
8721
8722 CREATE OR REPLACE FUNCTION biblio.marc21_record_type( rid BIGINT ) RETURNS config.marc21_rec_type_map AS $func$
8723 DECLARE
8724         ldr         RECORD;
8725         tval        TEXT;
8726         tval_rec    RECORD;
8727         bval        TEXT;
8728         bval_rec    RECORD;
8729     retval      config.marc21_rec_type_map%ROWTYPE;
8730 BEGIN
8731     SELECT * INTO ldr FROM metabib.full_rec WHERE record = rid AND tag = 'LDR' LIMIT 1;
8732
8733     IF ldr.id IS NULL THEN
8734         SELECT * INTO retval FROM config.marc21_rec_type_map WHERE code = 'BKS';
8735         RETURN retval;
8736     END IF;
8737
8738     SELECT * INTO tval_rec FROM config.marc21_ff_pos_map WHERE fixed_field = 'Type' LIMIT 1; -- They're all the same
8739     SELECT * INTO bval_rec FROM config.marc21_ff_pos_map WHERE fixed_field = 'BLvl' LIMIT 1; -- They're all the same
8740
8741
8742     tval := SUBSTRING( ldr.value, tval_rec.start_pos + 1, tval_rec.length );
8743     bval := SUBSTRING( ldr.value, bval_rec.start_pos + 1, bval_rec.length );
8744
8745     -- RAISE NOTICE 'type %, blvl %, ldr %', tval, bval, ldr.value;
8746
8747     SELECT * INTO retval FROM config.marc21_rec_type_map WHERE type_val LIKE '%' || tval || '%' AND blvl_val LIKE '%' || bval || '%';
8748
8749
8750     IF retval.code IS NULL THEN
8751         SELECT * INTO retval FROM config.marc21_rec_type_map WHERE code = 'BKS';
8752     END IF;
8753
8754     RETURN retval;
8755 END;
8756 $func$ LANGUAGE PLPGSQL;
8757
8758 CREATE OR REPLACE FUNCTION biblio.marc21_extract_fixed_field( rid BIGINT, ff TEXT ) RETURNS TEXT AS $func$
8759 DECLARE
8760     rtype       TEXT;
8761     ff_pos      RECORD;
8762     tag_data    RECORD;
8763     val         TEXT;
8764 BEGIN
8765     rtype := (biblio.marc21_record_type( rid )).code;
8766     FOR ff_pos IN SELECT * FROM config.marc21_ff_pos_map WHERE fixed_field = ff AND rec_type = rtype ORDER BY tag DESC LOOP
8767         FOR tag_data IN SELECT * FROM metabib.full_rec WHERE tag = UPPER(ff_pos.tag) AND record = rid LOOP
8768             val := SUBSTRING( tag_data.value, ff_pos.start_pos + 1, ff_pos.length );
8769             RETURN val;
8770         END LOOP;
8771         val := REPEAT( ff_pos.default_val, ff_pos.length );
8772         RETURN val;
8773     END LOOP;
8774
8775     RETURN NULL;
8776 END;
8777 $func$ LANGUAGE PLPGSQL;
8778
8779 CREATE TYPE biblio.marc21_physical_characteristics AS ( id INT, record BIGINT, ptype TEXT, subfield INT, value INT );
8780 CREATE OR REPLACE FUNCTION biblio.marc21_physical_characteristics( rid BIGINT ) RETURNS SETOF biblio.marc21_physical_characteristics AS $func$
8781 DECLARE
8782     rowid   INT := 0;
8783     _007    RECORD;
8784     ptype   config.marc21_physical_characteristic_type_map%ROWTYPE;
8785     psf     config.marc21_physical_characteristic_subfield_map%ROWTYPE;
8786     pval    config.marc21_physical_characteristic_value_map%ROWTYPE;
8787     retval  biblio.marc21_physical_characteristics%ROWTYPE;
8788 BEGIN
8789
8790     SELECT * INTO _007 FROM metabib.full_rec WHERE record = rid AND tag = '007' LIMIT 1;
8791
8792     IF _007.id IS NOT NULL THEN
8793         SELECT * INTO ptype FROM config.marc21_physical_characteristic_type_map WHERE ptype_key = SUBSTRING( _007.value, 1, 1 );
8794
8795         IF ptype.ptype_key IS NOT NULL THEN
8796             FOR psf IN SELECT * FROM config.marc21_physical_characteristic_subfield_map WHERE ptype_key = ptype.ptype_key LOOP
8797                 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 );
8798
8799                 IF pval.id IS NOT NULL THEN
8800                     rowid := rowid + 1;
8801                     retval.id := rowid;
8802                     retval.record := rid;
8803                     retval.ptype := ptype.ptype_key;
8804                     retval.subfield := psf.id;
8805                     retval.value := pval.id;
8806                     RETURN NEXT retval;
8807                 END IF;
8808
8809             END LOOP;
8810         END IF;
8811     END IF;
8812
8813     RETURN;
8814 END;
8815 $func$ LANGUAGE PLPGSQL;
8816
8817 DROP VIEW IF EXISTS money.open_usr_circulation_summary;
8818 DROP VIEW IF EXISTS money.open_usr_summary;
8819 DROP VIEW IF EXISTS money.open_billable_xact_summary;
8820
8821 -- The view should supply defaults for numeric (amount) columns
8822 CREATE OR REPLACE VIEW money.billable_xact_summary AS
8823     SELECT  xact.id,
8824         xact.usr,
8825         xact.xact_start,
8826         xact.xact_finish,
8827         COALESCE(credit.amount, 0.0::numeric) AS total_paid,
8828         credit.payment_ts AS last_payment_ts,
8829         credit.note AS last_payment_note,
8830         credit.payment_type AS last_payment_type,
8831         COALESCE(debit.amount, 0.0::numeric) AS total_owed,
8832         debit.billing_ts AS last_billing_ts,
8833         debit.note AS last_billing_note,
8834         debit.billing_type AS last_billing_type,
8835         COALESCE(debit.amount, 0.0::numeric) - COALESCE(credit.amount, 0.0::numeric) AS balance_owed,
8836         p.relname AS xact_type
8837       FROM  money.billable_xact xact
8838         JOIN pg_class p ON xact.tableoid = p.oid
8839         LEFT JOIN (
8840             SELECT  billing.xact,
8841                 sum(billing.amount) AS amount,
8842                 max(billing.billing_ts) AS billing_ts,
8843                 last(billing.note) AS note,
8844                 last(billing.billing_type) AS billing_type
8845               FROM  money.billing
8846               WHERE billing.voided IS FALSE
8847               GROUP BY billing.xact
8848             ) debit ON xact.id = debit.xact
8849         LEFT JOIN (
8850             SELECT  payment_view.xact,
8851                 sum(payment_view.amount) AS amount,
8852                 max(payment_view.payment_ts) AS payment_ts,
8853                 last(payment_view.note) AS note,
8854                 last(payment_view.payment_type) AS payment_type
8855               FROM  money.payment_view
8856               WHERE payment_view.voided IS FALSE
8857               GROUP BY payment_view.xact
8858             ) credit ON xact.id = credit.xact
8859       ORDER BY debit.billing_ts, credit.payment_ts;
8860
8861 CREATE OR REPLACE VIEW money.open_billable_xact_summary AS 
8862     SELECT * FROM money.billable_xact_summary_location_view
8863     WHERE xact_finish IS NULL;
8864
8865 CREATE OR REPLACE VIEW money.open_usr_circulation_summary AS
8866     SELECT 
8867         usr,
8868         SUM(total_paid) AS total_paid,
8869         SUM(total_owed) AS total_owed,
8870         SUM(balance_owed) AS balance_owed
8871     FROM  money.materialized_billable_xact_summary
8872     WHERE xact_type = 'circulation' AND xact_finish IS NULL
8873     GROUP BY usr;
8874
8875 CREATE OR REPLACE VIEW money.usr_summary AS
8876     SELECT 
8877         usr, 
8878         sum(total_paid) AS total_paid, 
8879         sum(total_owed) AS total_owed, 
8880         sum(balance_owed) AS balance_owed
8881     FROM money.materialized_billable_xact_summary
8882     GROUP BY usr;
8883
8884 CREATE OR REPLACE VIEW money.open_usr_summary AS
8885     SELECT 
8886         usr, 
8887         sum(total_paid) AS total_paid, 
8888         sum(total_owed) AS total_owed, 
8889         sum(balance_owed) AS balance_owed
8890     FROM money.materialized_billable_xact_summary
8891     WHERE xact_finish IS NULL
8892     GROUP BY usr;
8893
8894 -- 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;
8895
8896 CREATE TABLE config.biblio_fingerprint (
8897         id                      SERIAL  PRIMARY KEY,
8898         name            TEXT    NOT NULL, 
8899         xpath           TEXT    NOT NULL,
8900     first_word  BOOL    NOT NULL DEFAULT FALSE,
8901         format          TEXT    NOT NULL DEFAULT 'marcxml'
8902 );
8903
8904 INSERT INTO config.biblio_fingerprint (name, xpath, format)
8905     VALUES (
8906         'Title',
8907         '//marc:datafield[@tag="700"]/marc:subfield[@code="t"]|' ||
8908             '//marc:datafield[@tag="240"]/marc:subfield[@code="a"]|' ||
8909             '//marc:datafield[@tag="242"]/marc:subfield[@code="a"]|' ||
8910             '//marc:datafield[@tag="246"]/marc:subfield[@code="a"]|' ||
8911             '//marc:datafield[@tag="245"]/marc:subfield[@code="a"]',
8912         'marcxml'
8913     );
8914
8915 INSERT INTO config.biblio_fingerprint (name, xpath, format, first_word)
8916     VALUES (
8917         'Author',
8918         '//marc:datafield[@tag="700" and ./*[@code="t"]]/marc:subfield[@code="a"]|'
8919             '//marc:datafield[@tag="100"]/marc:subfield[@code="a"]|'
8920             '//marc:datafield[@tag="110"]/marc:subfield[@code="a"]|'
8921             '//marc:datafield[@tag="111"]/marc:subfield[@code="a"]|'
8922             '//marc:datafield[@tag="260"]/marc:subfield[@code="b"]',
8923         'marcxml',
8924         TRUE
8925     );
8926
8927 CREATE OR REPLACE FUNCTION biblio.extract_quality ( marc TEXT, best_lang TEXT, best_type TEXT ) RETURNS INT AS $func$
8928 DECLARE
8929     qual        INT;
8930     ldr         TEXT;
8931     tval        TEXT;
8932     tval_rec    RECORD;
8933     bval        TEXT;
8934     bval_rec    RECORD;
8935     type_map    RECORD;
8936     ff_pos      RECORD;
8937     ff_tag_data TEXT;
8938 BEGIN
8939
8940     IF marc IS NULL OR marc = '' THEN
8941         RETURN NULL;
8942     END IF;
8943
8944     -- First, the count of tags
8945     qual := ARRAY_UPPER(oils_xpath('*[local-name()="datafield"]', marc), 1);
8946
8947     -- now go through a bunch of pain to get the record type
8948     IF best_type IS NOT NULL THEN
8949         ldr := (oils_xpath('//*[local-name()="leader"]/text()', marc))[1];
8950
8951         IF ldr IS NOT NULL THEN
8952             SELECT * INTO tval_rec FROM config.marc21_ff_pos_map WHERE fixed_field = 'Type' LIMIT 1; -- They're all the same
8953             SELECT * INTO bval_rec FROM config.marc21_ff_pos_map WHERE fixed_field = 'BLvl' LIMIT 1; -- They're all the same
8954
8955
8956             tval := SUBSTRING( ldr, tval_rec.start_pos + 1, tval_rec.length );
8957             bval := SUBSTRING( ldr, bval_rec.start_pos + 1, bval_rec.length );
8958
8959             -- RAISE NOTICE 'type %, blvl %, ldr %', tval, bval, ldr;
8960
8961             SELECT * INTO type_map FROM config.marc21_rec_type_map WHERE type_val LIKE '%' || tval || '%' AND blvl_val LIKE '%' || bval || '%';
8962
8963             IF type_map.code IS NOT NULL THEN
8964                 IF best_type = type_map.code THEN
8965                     qual := qual + qual / 2;
8966                 END IF;
8967
8968                 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
8969                     ff_tag_data := SUBSTRING((oils_xpath('//*[@tag="' || ff_pos.tag || '"]/text()',marc))[1], ff_pos.start_pos + 1, ff_pos.length);
8970                     IF ff_tag_data = best_lang THEN
8971                             qual := qual + 100;
8972                     END IF;
8973                 END LOOP;
8974             END IF;
8975         END IF;
8976     END IF;
8977
8978     -- Now look for some quality metrics
8979     -- DCL record?
8980     IF ARRAY_UPPER(oils_xpath('//*[@tag="040"]/*[@code="a" and contains(.,"DLC")]', marc), 1) = 1 THEN
8981         qual := qual + 10;
8982     END IF;
8983
8984     -- From OCLC?
8985     IF (oils_xpath('//*[@tag="003"]/text()', marc))[1] ~* E'oclo?c' THEN
8986         qual := qual + 10;
8987     END IF;
8988
8989     RETURN qual;
8990
8991 END;
8992 $func$ LANGUAGE PLPGSQL;
8993
8994 CREATE OR REPLACE FUNCTION biblio.extract_fingerprint ( marc text ) RETURNS TEXT AS $func$
8995 DECLARE
8996     idx     config.biblio_fingerprint%ROWTYPE;
8997     xfrm        config.xml_transform%ROWTYPE;
8998     prev_xfrm   TEXT;
8999     transformed_xml TEXT;
9000     xml_node    TEXT;
9001     xml_node_list   TEXT[];
9002     raw_text    TEXT;
9003     output_text TEXT := '';
9004 BEGIN
9005
9006     IF marc IS NULL OR marc = '' THEN
9007         RETURN NULL;
9008     END IF;
9009
9010     -- Loop over the indexing entries
9011     FOR idx IN SELECT * FROM config.biblio_fingerprint ORDER BY format, id LOOP
9012
9013         SELECT INTO xfrm * from config.xml_transform WHERE name = idx.format;
9014
9015         -- See if we can skip the XSLT ... it's expensive
9016         IF prev_xfrm IS NULL OR prev_xfrm <> xfrm.name THEN
9017             -- Can't skip the transform
9018             IF xfrm.xslt <> '---' THEN
9019                 transformed_xml := oils_xslt_process(marc,xfrm.xslt);
9020             ELSE
9021                 transformed_xml := marc;
9022             END IF;
9023
9024             prev_xfrm := xfrm.name;
9025         END IF;
9026
9027         raw_text := COALESCE(
9028             naco_normalize(
9029                 ARRAY_TO_STRING(
9030                     oils_xpath(
9031                         '//text()',
9032                         (oils_xpath(
9033                             idx.xpath,
9034                             transformed_xml,
9035                             ARRAY[ARRAY[xfrm.prefix, xfrm.namespace_uri]]
9036                         ))[1]
9037                     ),
9038                     ''
9039                 )
9040             ),
9041             ''
9042         );
9043
9044         raw_text := REGEXP_REPLACE(raw_text, E'\\[.+?\\]', E'');
9045         raw_text := REGEXP_REPLACE(raw_text, E'\\mthe\\M|\\man?d?d\\M', E'', 'g'); -- arg! the pain!
9046
9047         IF idx.first_word IS TRUE THEN
9048             raw_text := REGEXP_REPLACE(raw_text, E'^(\\w+).*?$', E'\\1');
9049         END IF;
9050
9051         output_text := output_text || REGEXP_REPLACE(raw_text, E'\\s+', '', 'g');
9052
9053     END LOOP;
9054
9055     RETURN output_text;
9056
9057 END;
9058 $func$ LANGUAGE PLPGSQL;
9059
9060 -- BEFORE UPDATE OR INSERT trigger for biblio.record_entry
9061 CREATE OR REPLACE FUNCTION biblio.fingerprint_trigger () RETURNS TRIGGER AS $func$
9062 BEGIN
9063
9064     -- For TG_ARGV, first param is language (like 'eng'), second is record type (like 'BKS')
9065
9066     IF NEW.deleted IS TRUE THEN -- we don't much care, then, do we?
9067         RETURN NEW;
9068     END IF;
9069
9070     NEW.fingerprint := biblio.extract_fingerprint(NEW.marc);
9071     NEW.quality := biblio.extract_quality(NEW.marc, TG_ARGV[0], TG_ARGV[1]);
9072
9073     RETURN NEW;
9074
9075 END;
9076 $func$ LANGUAGE PLPGSQL;
9077
9078 CREATE TABLE config.internal_flag (
9079     name    TEXT    PRIMARY KEY,
9080     value   TEXT,
9081     enabled BOOL    NOT NULL DEFAULT FALSE
9082 );
9083 INSERT INTO config.internal_flag (name) VALUES ('ingest.metarecord_mapping.skip_on_insert');
9084 INSERT INTO config.internal_flag (name) VALUES ('ingest.reingest.force_on_same_marc');
9085 INSERT INTO config.internal_flag (name) VALUES ('ingest.reingest.skip_located_uri');
9086 INSERT INTO config.internal_flag (name) VALUES ('ingest.disable_located_uri');
9087 INSERT INTO config.internal_flag (name) VALUES ('ingest.disable_metabib_full_rec');
9088 INSERT INTO config.internal_flag (name) VALUES ('ingest.disable_metabib_rec_descriptor');
9089 INSERT INTO config.internal_flag (name) VALUES ('ingest.disable_metabib_field_entry');
9090 INSERT INTO config.internal_flag (name) VALUES ('ingest.disable_authority_linking');
9091
9092 CREATE TABLE authority.bib_linking (
9093     id          BIGSERIAL   PRIMARY KEY,
9094     bib         BIGINT      NOT NULL REFERENCES biblio.record_entry (id),
9095     authority   BIGINT      NOT NULL REFERENCES authority.record_entry (id)
9096 );
9097 CREATE INDEX authority_bl_bib_idx ON authority.bib_linking ( bib );
9098 CREATE UNIQUE INDEX authority_bl_bib_authority_once_idx ON authority.bib_linking ( authority, bib );
9099
9100 CREATE OR REPLACE FUNCTION public.remove_paren_substring( TEXT ) RETURNS TEXT AS $func$
9101     SELECT regexp_replace($1, $$\([^)]+\)$$, '', 'g');
9102 $func$ LANGUAGE SQL STRICT IMMUTABLE;
9103
9104 CREATE OR REPLACE FUNCTION biblio.map_authority_linking (bibid BIGINT, marc TEXT) RETURNS BIGINT AS $func$
9105     DELETE FROM authority.bib_linking WHERE bib = $1;
9106     INSERT INTO authority.bib_linking (bib, authority)
9107         SELECT  y.bib,
9108                 y.authority
9109           FROM (    SELECT  DISTINCT $1 AS bib,
9110                             BTRIM(remove_paren_substring(txt))::BIGINT AS authority
9111                       FROM  explode_array(oils_xpath('//*[@code="0"]/text()',$2)) x(txt)
9112                       WHERE BTRIM(remove_paren_substring(txt)) ~ $re$^\d+$$re$
9113                 ) y JOIN authority.record_entry r ON r.id = y.authority;
9114     SELECT $1;
9115 $func$ LANGUAGE SQL;
9116
9117 CREATE OR REPLACE FUNCTION metabib.reingest_metabib_rec_descriptor( bib_id BIGINT ) RETURNS VOID AS $func$
9118 BEGIN
9119     DELETE FROM metabib.rec_descriptor WHERE record = bib_id;
9120     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)
9121         SELECT  bib_id,
9122                 biblio.marc21_extract_fixed_field( bib_id, 'Type' ),
9123                 biblio.marc21_extract_fixed_field( bib_id, 'Form' ),
9124                 biblio.marc21_extract_fixed_field( bib_id, 'BLvl' ),
9125                 biblio.marc21_extract_fixed_field( bib_id, 'Ctrl' ),
9126                 biblio.marc21_extract_fixed_field( bib_id, 'ELvl' ),
9127                 biblio.marc21_extract_fixed_field( bib_id, 'Audn' ),
9128                 biblio.marc21_extract_fixed_field( bib_id, 'LitF' ),
9129                 biblio.marc21_extract_fixed_field( bib_id, 'TMat' ),
9130                 biblio.marc21_extract_fixed_field( bib_id, 'Desc' ),
9131                 biblio.marc21_extract_fixed_field( bib_id, 'DtSt' ),
9132                 biblio.marc21_extract_fixed_field( bib_id, 'Lang' ),
9133                 (   SELECT  v.value
9134                       FROM  biblio.marc21_physical_characteristics( bib_id) p
9135                             JOIN config.marc21_physical_characteristic_subfield_map s ON (s.id = p.subfield)
9136                             JOIN config.marc21_physical_characteristic_value_map v ON (v.id = p.value)
9137                       WHERE p.ptype = 'v' AND s.subfield = 'e'    ),
9138                 biblio.marc21_extract_fixed_field( bib_id, 'Date1'),
9139                 biblio.marc21_extract_fixed_field( bib_id, 'Date2');
9140
9141     RETURN;
9142 END;
9143 $func$ LANGUAGE PLPGSQL;
9144
9145 CREATE TABLE config.metabib_class (
9146     name    TEXT    PRIMARY KEY,
9147     label   TEXT    NOT NULL UNIQUE
9148 );
9149
9150 INSERT INTO config.metabib_class ( name, label ) VALUES ( 'keyword', oils_i18n_gettext('keyword', 'Keyword', 'cmc', 'label') );
9151 INSERT INTO config.metabib_class ( name, label ) VALUES ( 'title', oils_i18n_gettext('title', 'Title', 'cmc', 'label') );
9152 INSERT INTO config.metabib_class ( name, label ) VALUES ( 'author', oils_i18n_gettext('author', 'Author', 'cmc', 'label') );
9153 INSERT INTO config.metabib_class ( name, label ) VALUES ( 'subject', oils_i18n_gettext('subject', 'Subject', 'cmc', 'label') );
9154 INSERT INTO config.metabib_class ( name, label ) VALUES ( 'series', oils_i18n_gettext('series', 'Series', 'cmc', 'label') );
9155
9156 CREATE TABLE metabib.facet_entry (
9157         id              BIGSERIAL       PRIMARY KEY,
9158         source          BIGINT          NOT NULL,
9159         field           INT             NOT NULL,
9160         value           TEXT            NOT NULL
9161 );
9162
9163 CREATE OR REPLACE FUNCTION metabib.reingest_metabib_field_entries( bib_id BIGINT ) RETURNS VOID AS $func$
9164 DECLARE
9165     fclass          RECORD;
9166     ind_data        metabib.field_entry_template%ROWTYPE;
9167 BEGIN
9168     FOR fclass IN SELECT * FROM config.metabib_class LOOP
9169         -- RAISE NOTICE 'Emptying out %', fclass.name;
9170         EXECUTE $$DELETE FROM metabib.$$ || fclass.name || $$_field_entry WHERE source = $$ || bib_id;
9171     END LOOP;
9172
9173     DELETE FROM metabib.facet_entry WHERE source = bib_id;
9174
9175     FOR ind_data IN SELECT * FROM biblio.extract_metabib_field_entry( bib_id ) LOOP
9176         IF ind_data.field < 0 THEN
9177             ind_data.field = -1 * ind_data.field;
9178             INSERT INTO metabib.facet_entry (field, source, value)
9179                 VALUES (ind_data.field, ind_data.source, ind_data.value);
9180         ELSE
9181             EXECUTE $$
9182                 INSERT INTO metabib.$$ || ind_data.field_class || $$_field_entry (field, source, value)
9183                     VALUES ($$ ||
9184                         quote_literal(ind_data.field) || $$, $$ ||
9185                         quote_literal(ind_data.source) || $$, $$ ||
9186                         quote_literal(ind_data.value) ||
9187                     $$);$$;
9188         END IF;
9189
9190     END LOOP;
9191
9192     RETURN;
9193 END;
9194 $func$ LANGUAGE PLPGSQL;
9195
9196 CREATE OR REPLACE FUNCTION biblio.extract_located_uris( bib_id BIGINT, marcxml TEXT, editor_id INT ) RETURNS VOID AS $func$
9197 DECLARE
9198     uris            TEXT[];
9199     uri_xml         TEXT;
9200     uri_label       TEXT;
9201     uri_href        TEXT;
9202     uri_use         TEXT;
9203     uri_owner       TEXT;
9204     uri_owner_id    INT;
9205     uri_id          INT;
9206     uri_cn_id       INT;
9207     uri_map_id      INT;
9208 BEGIN
9209
9210     uris := oils_xpath('//*[@tag="856" and (@ind1="4" or @ind1="1") and (@ind2="0" or @ind2="1")]',marcxml);
9211     IF ARRAY_UPPER(uris,1) > 0 THEN
9212         FOR i IN 1 .. ARRAY_UPPER(uris, 1) LOOP
9213             -- First we pull info out of the 856
9214             uri_xml     := uris[i];
9215
9216             uri_href    := (oils_xpath('//*[@code="u"]/text()',uri_xml))[1];
9217             CONTINUE WHEN uri_href IS NULL;
9218
9219             uri_label   := (oils_xpath('//*[@code="y"]/text()|//*[@code="3"]/text()|//*[@code="u"]/text()',uri_xml))[1];
9220             CONTINUE WHEN uri_label IS NULL;
9221
9222             uri_owner   := (oils_xpath('//*[@code="9"]/text()|//*[@code="w"]/text()|//*[@code="n"]/text()',uri_xml))[1];
9223             CONTINUE WHEN uri_owner IS NULL;
9224
9225             uri_use     := (oils_xpath('//*[@code="z"]/text()|//*[@code="2"]/text()|//*[@code="n"]/text()',uri_xml))[1];
9226
9227             uri_owner := REGEXP_REPLACE(uri_owner, $re$^.*?\((\w+)\).*$$re$, E'\\1');
9228
9229             SELECT id INTO uri_owner_id FROM actor.org_unit WHERE shortname = uri_owner;
9230             CONTINUE WHEN NOT FOUND;
9231
9232             -- now we look for a matching uri
9233             SELECT id INTO uri_id FROM asset.uri WHERE label = uri_label AND href = uri_href AND use_restriction = uri_use AND active;
9234             IF NOT FOUND THEN -- create one
9235                 INSERT INTO asset.uri (label, href, use_restriction) VALUES (uri_label, uri_href, uri_use);
9236                 SELECT id INTO uri_id FROM asset.uri WHERE label = uri_label AND href = uri_href AND use_restriction = uri_use AND active;
9237             END IF;
9238
9239             -- we need a call number to link through
9240             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;
9241             IF NOT FOUND THEN
9242                 INSERT INTO asset.call_number (owning_lib, record, create_date, edit_date, creator, editor, label)
9243                     VALUES (uri_owner_id, bib_id, 'now', 'now', editor_id, editor_id, '##URI##');
9244                 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;
9245             END IF;
9246
9247             -- now, link them if they're not already
9248             SELECT id INTO uri_map_id FROM asset.uri_call_number_map WHERE call_number = uri_cn_id AND uri = uri_id;
9249             IF NOT FOUND THEN
9250                 INSERT INTO asset.uri_call_number_map (call_number, uri) VALUES (uri_cn_id, uri_id);
9251             END IF;
9252
9253         END LOOP;
9254     END IF;
9255
9256     RETURN;
9257 END;
9258 $func$ LANGUAGE PLPGSQL;
9259
9260 CREATE OR REPLACE FUNCTION metabib.remap_metarecord_for_bib( bib_id BIGINT, fp TEXT ) RETURNS BIGINT AS $func$
9261 DECLARE
9262     source_count    INT;
9263     old_mr          BIGINT;
9264     tmp_mr          metabib.metarecord%ROWTYPE;
9265     deleted_mrs     BIGINT[];
9266 BEGIN
9267
9268     DELETE FROM metabib.metarecord_source_map WHERE source = bib_id; -- Rid ourselves of the search-estimate-killing linkage
9269
9270     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
9271
9272         IF old_mr IS NULL AND fp = tmp_mr.fingerprint THEN -- Find the first fingerprint-matching
9273             old_mr := tmp_mr.id;
9274         ELSE
9275             SELECT COUNT(*) INTO source_count FROM metabib.metarecord_source_map WHERE metarecord = tmp_mr.id;
9276             IF source_count = 0 THEN -- No other records
9277                 deleted_mrs := ARRAY_APPEND(deleted_mrs, tmp_mr.id);
9278                 DELETE FROM metabib.metarecord WHERE id = tmp_mr.id;
9279             END IF;
9280         END IF;
9281
9282     END LOOP;
9283
9284     IF old_mr IS NULL THEN -- we found no suitable, preexisting MR based on old source maps
9285         SELECT id INTO old_mr FROM metabib.metarecord WHERE fingerprint = fp; -- is there one for our current fingerprint?
9286         IF old_mr IS NULL THEN -- nope, create one and grab its id
9287             INSERT INTO metabib.metarecord ( fingerprint, master_record ) VALUES ( fp, bib_id );
9288             SELECT id INTO old_mr FROM metabib.metarecord WHERE fingerprint = fp;
9289         ELSE -- indeed there is. update it with a null cache and recalcualated master record
9290             UPDATE  metabib.metarecord
9291               SET   mods = NULL,
9292                     master_record = ( SELECT id FROM biblio.record_entry WHERE fingerprint = fp ORDER BY quality DESC LIMIT 1)
9293               WHERE id = old_mr;
9294         END IF;
9295     ELSE -- there was one we already attached to, update its mods cache and master_record
9296         UPDATE  metabib.metarecord
9297           SET   mods = NULL,
9298                 master_record = ( SELECT id FROM biblio.record_entry WHERE fingerprint = fp ORDER BY quality DESC LIMIT 1)
9299           WHERE id = old_mr;
9300     END IF;
9301
9302     INSERT INTO metabib.metarecord_source_map (metarecord, source) VALUES (old_mr, bib_id); -- new source mapping
9303
9304     IF ARRAY_UPPER(deleted_mrs,1) > 0 THEN
9305         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
9306     END IF;
9307
9308     RETURN old_mr;
9309
9310 END;
9311 $func$ LANGUAGE PLPGSQL;
9312
9313 CREATE OR REPLACE FUNCTION metabib.reingest_metabib_full_rec( bib_id BIGINT ) RETURNS VOID AS $func$
9314 BEGIN
9315     DELETE FROM metabib.real_full_rec WHERE record = bib_id;
9316     INSERT INTO metabib.real_full_rec (record, tag, ind1, ind2, subfield, value)
9317         SELECT record, tag, ind1, ind2, subfield, value FROM biblio.flatten_marc( bib_id );
9318
9319     RETURN;
9320 END;
9321 $func$ LANGUAGE PLPGSQL;
9322
9323 -- AFTER UPDATE OR INSERT trigger for biblio.record_entry
9324 CREATE OR REPLACE FUNCTION biblio.indexing_ingest_or_delete () RETURNS TRIGGER AS $func$
9325 BEGIN
9326
9327     IF NEW.deleted IS TRUE THEN -- If this bib is deleted
9328         DELETE FROM metabib.metarecord_source_map WHERE source = NEW.id; -- Rid ourselves of the search-estimate-killing linkage
9329         DELETE FROM authority.bib_linking WHERE bib = NEW.id; -- Avoid updating fields in bibs that are no longer visible
9330         RETURN NEW; -- and we're done
9331     END IF;
9332
9333     IF TG_OP = 'UPDATE' THEN -- re-ingest?
9334         PERFORM * FROM config.internal_flag WHERE name = 'ingest.reingest.force_on_same_marc' AND enabled;
9335
9336         IF NOT FOUND AND OLD.marc = NEW.marc THEN -- don't do anything if the MARC didn't change
9337             RETURN NEW;
9338         END IF;
9339     END IF;
9340
9341     -- Record authority linking
9342     PERFORM * FROM config.internal_flag WHERE name = 'ingest.disable_authority_linking' AND enabled;
9343     IF NOT FOUND THEN
9344         PERFORM biblio.map_authority_linking( NEW.id, NEW.marc );
9345     END IF;
9346
9347     -- Flatten and insert the mfr data
9348     PERFORM * FROM config.internal_flag WHERE name = 'ingest.disable_metabib_full_rec' AND enabled;
9349     IF NOT FOUND THEN
9350         PERFORM metabib.reingest_metabib_full_rec(NEW.id);
9351         PERFORM * FROM config.internal_flag WHERE name = 'ingest.disable_metabib_rec_descriptor' AND enabled;
9352         IF NOT FOUND THEN
9353             PERFORM metabib.reingest_metabib_rec_descriptor(NEW.id);
9354         END IF;
9355     END IF;
9356
9357     -- Gather and insert the field entry data
9358     PERFORM metabib.reingest_metabib_field_entries(NEW.id);
9359
9360     -- Located URI magic
9361     IF TG_OP = 'INSERT' THEN
9362         PERFORM * FROM config.internal_flag WHERE name = 'ingest.disable_located_uri' AND enabled;
9363         IF NOT FOUND THEN
9364             PERFORM biblio.extract_located_uris( NEW.id, NEW.marc, NEW.editor );
9365         END IF;
9366     ELSE
9367         PERFORM * FROM config.internal_flag WHERE name = 'ingest.disable_located_uri' AND enabled;
9368         IF NOT FOUND THEN
9369             PERFORM biblio.extract_located_uris( NEW.id, NEW.marc, NEW.editor );
9370         END IF;
9371     END IF;
9372
9373     -- (re)map metarecord-bib linking
9374     IF TG_OP = 'INSERT' THEN -- if not deleted and performing an insert, check for the flag
9375         PERFORM * FROM config.internal_flag WHERE name = 'ingest.metarecord_mapping.skip_on_insert' AND enabled;
9376         IF NOT FOUND THEN
9377             PERFORM metabib.remap_metarecord_for_bib( NEW.id, NEW.fingerprint );
9378         END IF;
9379     ELSE -- we're doing an update, and we're not deleted, remap
9380         PERFORM metabib.remap_metarecord_for_bib( NEW.id, NEW.fingerprint );
9381     END IF;
9382
9383     RETURN NEW;
9384 END;
9385 $func$ LANGUAGE PLPGSQL;
9386
9387 CREATE TRIGGER fingerprint_tgr BEFORE INSERT OR UPDATE ON biblio.record_entry FOR EACH ROW EXECUTE PROCEDURE biblio.fingerprint_trigger ('eng','BKS');
9388 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 ();
9389
9390 DROP TRIGGER IF EXISTS zzz_update_materialized_simple_rec_delete_tgr ON biblio.record_entry;
9391
9392 CREATE OR REPLACE FUNCTION oils_xpath_table ( key TEXT, document_field TEXT, relation_name TEXT, xpaths TEXT, criteria TEXT )
9393 RETURNS SETOF RECORD AS $func$
9394 DECLARE
9395     xpath_list  TEXT[];
9396     select_list TEXT[];
9397     where_list  TEXT[];
9398     q           TEXT;
9399     out_record  RECORD;
9400     empty_test  RECORD;
9401 BEGIN
9402     xpath_list := STRING_TO_ARRAY( xpaths, '|' );
9403
9404     select_list := ARRAY_APPEND( select_list, key || '::INT AS key' );
9405
9406     FOR i IN 1 .. ARRAY_UPPER(xpath_list,1) LOOP
9407         select_list := ARRAY_APPEND(
9408             select_list,
9409             $sel$
9410             EXPLODE_ARRAY(
9411                 COALESCE(
9412                     NULLIF(
9413                         oils_xpath(
9414                             $sel$ ||
9415                                 quote_literal(
9416                                     CASE
9417                                         WHEN xpath_list[i] ~ $re$/[^/[]*@[^/]+$$re$ OR xpath_list[i] ~ $re$text\(\)$$re$ THEN xpath_list[i]
9418                                         ELSE xpath_list[i] || '//text()'
9419                                     END
9420                                 ) ||
9421                             $sel$,
9422                             $sel$ || document_field || $sel$
9423                         ),
9424                        '{}'::TEXT[]
9425                     ),
9426                     '{NULL}'::TEXT[]
9427                 )
9428             ) AS c_$sel$ || i
9429         );
9430         where_list := ARRAY_APPEND(
9431             where_list,
9432             'c_' || i || ' IS NOT NULL'
9433         );
9434     END LOOP;
9435
9436     q := $q$
9437 SELECT * FROM (
9438     SELECT $q$ || ARRAY_TO_STRING( select_list, ', ' ) || $q$ FROM $q$ || relation_name || $q$ WHERE ($q$ || criteria || $q$)
9439 )x WHERE $q$ || ARRAY_TO_STRING( where_list, ' AND ' );
9440     -- RAISE NOTICE 'query: %', q;
9441
9442     FOR out_record IN EXECUTE q LOOP
9443         RETURN NEXT out_record;
9444     END LOOP;
9445
9446     RETURN;
9447 END;
9448 $func$ LANGUAGE PLPGSQL;
9449
9450 CREATE OR REPLACE FUNCTION vandelay.ingest_items ( import_id BIGINT, attr_def_id BIGINT ) RETURNS SETOF vandelay.import_item AS $$
9451 DECLARE
9452
9453     owning_lib      TEXT;
9454     circ_lib        TEXT;
9455     call_number     TEXT;
9456     copy_number     TEXT;
9457     status          TEXT;
9458     location        TEXT;
9459     circulate       TEXT;
9460     deposit         TEXT;
9461     deposit_amount  TEXT;
9462     ref             TEXT;
9463     holdable        TEXT;
9464     price           TEXT;
9465     barcode         TEXT;
9466     circ_modifier   TEXT;
9467     circ_as_type    TEXT;
9468     alert_message   TEXT;
9469     opac_visible    TEXT;
9470     pub_note        TEXT;
9471     priv_note       TEXT;
9472
9473     attr_def        RECORD;
9474     tmp_attr_set    RECORD;
9475     attr_set        vandelay.import_item%ROWTYPE;
9476
9477     xpath           TEXT;
9478
9479 BEGIN
9480
9481     SELECT * INTO attr_def FROM vandelay.import_item_attr_definition WHERE id = attr_def_id;
9482
9483     IF FOUND THEN
9484
9485         attr_set.definition := attr_def.id; 
9486     
9487         -- Build the combined XPath
9488     
9489         owning_lib :=
9490             CASE
9491                 WHEN attr_def.owning_lib IS NULL THEN 'null()'
9492                 WHEN LENGTH( attr_def.owning_lib ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.owning_lib || '"]'
9493                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.owning_lib
9494             END;
9495     
9496         circ_lib :=
9497             CASE
9498                 WHEN attr_def.circ_lib IS NULL THEN 'null()'
9499                 WHEN LENGTH( attr_def.circ_lib ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.circ_lib || '"]'
9500                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.circ_lib
9501             END;
9502     
9503         call_number :=
9504             CASE
9505                 WHEN attr_def.call_number IS NULL THEN 'null()'
9506                 WHEN LENGTH( attr_def.call_number ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.call_number || '"]'
9507                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.call_number
9508             END;
9509     
9510         copy_number :=
9511             CASE
9512                 WHEN attr_def.copy_number IS NULL THEN 'null()'
9513                 WHEN LENGTH( attr_def.copy_number ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.copy_number || '"]'
9514                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.copy_number
9515             END;
9516     
9517         status :=
9518             CASE
9519                 WHEN attr_def.status IS NULL THEN 'null()'
9520                 WHEN LENGTH( attr_def.status ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.status || '"]'
9521                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.status
9522             END;
9523     
9524         location :=
9525             CASE
9526                 WHEN attr_def.location IS NULL THEN 'null()'
9527                 WHEN LENGTH( attr_def.location ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.location || '"]'
9528                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.location
9529             END;
9530     
9531         circulate :=
9532             CASE
9533                 WHEN attr_def.circulate IS NULL THEN 'null()'
9534                 WHEN LENGTH( attr_def.circulate ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.circulate || '"]'
9535                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.circulate
9536             END;
9537     
9538         deposit :=
9539             CASE
9540                 WHEN attr_def.deposit IS NULL THEN 'null()'
9541                 WHEN LENGTH( attr_def.deposit ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.deposit || '"]'
9542                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.deposit
9543             END;
9544     
9545         deposit_amount :=
9546             CASE
9547                 WHEN attr_def.deposit_amount IS NULL THEN 'null()'
9548                 WHEN LENGTH( attr_def.deposit_amount ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.deposit_amount || '"]'
9549                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.deposit_amount
9550             END;
9551     
9552         ref :=
9553             CASE
9554                 WHEN attr_def.ref IS NULL THEN 'null()'
9555                 WHEN LENGTH( attr_def.ref ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.ref || '"]'
9556                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.ref
9557             END;
9558     
9559         holdable :=
9560             CASE
9561                 WHEN attr_def.holdable IS NULL THEN 'null()'
9562                 WHEN LENGTH( attr_def.holdable ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.holdable || '"]'
9563                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.holdable
9564             END;
9565     
9566         price :=
9567             CASE
9568                 WHEN attr_def.price IS NULL THEN 'null()'
9569                 WHEN LENGTH( attr_def.price ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.price || '"]'
9570                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.price
9571             END;
9572     
9573         barcode :=
9574             CASE
9575                 WHEN attr_def.barcode IS NULL THEN 'null()'
9576                 WHEN LENGTH( attr_def.barcode ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.barcode || '"]'
9577                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.barcode
9578             END;
9579     
9580         circ_modifier :=
9581             CASE
9582                 WHEN attr_def.circ_modifier IS NULL THEN 'null()'
9583                 WHEN LENGTH( attr_def.circ_modifier ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.circ_modifier || '"]'
9584                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.circ_modifier
9585             END;
9586     
9587         circ_as_type :=
9588             CASE
9589                 WHEN attr_def.circ_as_type IS NULL THEN 'null()'
9590                 WHEN LENGTH( attr_def.circ_as_type ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.circ_as_type || '"]'
9591                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.circ_as_type
9592             END;
9593     
9594         alert_message :=
9595             CASE
9596                 WHEN attr_def.alert_message IS NULL THEN 'null()'
9597                 WHEN LENGTH( attr_def.alert_message ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.alert_message || '"]'
9598                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.alert_message
9599             END;
9600     
9601         opac_visible :=
9602             CASE
9603                 WHEN attr_def.opac_visible IS NULL THEN 'null()'
9604                 WHEN LENGTH( attr_def.opac_visible ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.opac_visible || '"]'
9605                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.opac_visible
9606             END;
9607
9608         pub_note :=
9609             CASE
9610                 WHEN attr_def.pub_note IS NULL THEN 'null()'
9611                 WHEN LENGTH( attr_def.pub_note ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.pub_note || '"]'
9612                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.pub_note
9613             END;
9614         priv_note :=
9615             CASE
9616                 WHEN attr_def.priv_note IS NULL THEN 'null()'
9617                 WHEN LENGTH( attr_def.priv_note ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.priv_note || '"]'
9618                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.priv_note
9619             END;
9620     
9621     
9622         xpath := 
9623             owning_lib      || '|' || 
9624             circ_lib        || '|' || 
9625             call_number     || '|' || 
9626             copy_number     || '|' || 
9627             status          || '|' || 
9628             location        || '|' || 
9629             circulate       || '|' || 
9630             deposit         || '|' || 
9631             deposit_amount  || '|' || 
9632             ref             || '|' || 
9633             holdable        || '|' || 
9634             price           || '|' || 
9635             barcode         || '|' || 
9636             circ_modifier   || '|' || 
9637             circ_as_type    || '|' || 
9638             alert_message   || '|' || 
9639             pub_note        || '|' || 
9640             priv_note       || '|' || 
9641             opac_visible;
9642
9643         -- RAISE NOTICE 'XPath: %', xpath;
9644         
9645         FOR tmp_attr_set IN
9646                 SELECT  *
9647                   FROM  oils_xpath_table( 'id', 'marc', 'vandelay.queued_bib_record', xpath, 'id = ' || import_id )
9648                             AS t( id INT, ol TEXT, clib TEXT, cn TEXT, cnum TEXT, cs TEXT, cl TEXT, circ TEXT,
9649                                   dep TEXT, dep_amount TEXT, r TEXT, hold TEXT, pr TEXT, bc TEXT, circ_mod TEXT,
9650                                   circ_as TEXT, amessage TEXT, note TEXT, pnote TEXT, opac_vis TEXT )
9651         LOOP
9652     
9653             tmp_attr_set.pr = REGEXP_REPLACE(tmp_attr_set.pr, E'[^0-9\\.]', '', 'g');
9654             tmp_attr_set.dep_amount = REGEXP_REPLACE(tmp_attr_set.dep_amount, E'[^0-9\\.]', '', 'g');
9655
9656             tmp_attr_set.pr := NULLIF( tmp_attr_set.pr, '' );
9657             tmp_attr_set.dep_amount := NULLIF( tmp_attr_set.dep_amount, '' );
9658     
9659             SELECT id INTO attr_set.owning_lib FROM actor.org_unit WHERE shortname = UPPER(tmp_attr_set.ol); -- INT
9660             SELECT id INTO attr_set.circ_lib FROM actor.org_unit WHERE shortname = UPPER(tmp_attr_set.clib); -- INT
9661             SELECT id INTO attr_set.status FROM config.copy_status WHERE LOWER(name) = LOWER(tmp_attr_set.cs); -- INT
9662     
9663             SELECT  id INTO attr_set.location
9664               FROM  asset.copy_location
9665               WHERE LOWER(name) = LOWER(tmp_attr_set.cl)
9666                     AND asset.copy_location.owning_lib = COALESCE(attr_set.owning_lib, attr_set.circ_lib); -- INT
9667     
9668             attr_set.circulate      :=
9669                 LOWER( SUBSTRING( tmp_attr_set.circ, 1, 1)) IN ('t','y','1')
9670                 OR LOWER(tmp_attr_set.circ) = 'circulating'; -- BOOL
9671
9672             attr_set.deposit        :=
9673                 LOWER( SUBSTRING( tmp_attr_set.dep, 1, 1 ) ) IN ('t','y','1')
9674                 OR LOWER(tmp_attr_set.dep) = 'deposit'; -- BOOL
9675
9676             attr_set.holdable       :=
9677                 LOWER( SUBSTRING( tmp_attr_set.hold, 1, 1 ) ) IN ('t','y','1')
9678                 OR LOWER(tmp_attr_set.hold) = 'holdable'; -- BOOL
9679
9680             attr_set.opac_visible   :=
9681                 LOWER( SUBSTRING( tmp_attr_set.opac_vis, 1, 1 ) ) IN ('t','y','1')
9682                 OR LOWER(tmp_attr_set.opac_vis) = 'visible'; -- BOOL
9683
9684             attr_set.ref            :=
9685                 LOWER( SUBSTRING( tmp_attr_set.r, 1, 1 ) ) IN ('t','y','1')
9686                 OR LOWER(tmp_attr_set.r) = 'reference'; -- BOOL
9687     
9688             attr_set.copy_number    := tmp_attr_set.cnum::INT; -- INT,
9689             attr_set.deposit_amount := tmp_attr_set.dep_amount::NUMERIC(6,2); -- NUMERIC(6,2),
9690             attr_set.price          := tmp_attr_set.pr::NUMERIC(8,2); -- NUMERIC(8,2),
9691     
9692             attr_set.call_number    := tmp_attr_set.cn; -- TEXT
9693             attr_set.barcode        := tmp_attr_set.bc; -- TEXT,
9694             attr_set.circ_modifier  := tmp_attr_set.circ_mod; -- TEXT,
9695             attr_set.circ_as_type   := tmp_attr_set.circ_as; -- TEXT,
9696             attr_set.alert_message  := tmp_attr_set.amessage; -- TEXT,
9697             attr_set.pub_note       := tmp_attr_set.note; -- TEXT,
9698             attr_set.priv_note      := tmp_attr_set.pnote; -- TEXT,
9699             attr_set.alert_message  := tmp_attr_set.amessage; -- TEXT,
9700     
9701             RETURN NEXT attr_set;
9702     
9703         END LOOP;
9704     
9705     END IF;
9706
9707     RETURN;
9708
9709 END;
9710 $$ LANGUAGE PLPGSQL;
9711
9712 CREATE OR REPLACE FUNCTION vandelay.ingest_bib_items ( ) RETURNS TRIGGER AS $func$
9713 DECLARE
9714     attr_def    BIGINT;
9715     item_data   vandelay.import_item%ROWTYPE;
9716 BEGIN
9717
9718     SELECT item_attr_def INTO attr_def FROM vandelay.bib_queue WHERE id = NEW.queue;
9719
9720     FOR item_data IN SELECT * FROM vandelay.ingest_items( NEW.id::BIGINT, attr_def ) LOOP
9721         INSERT INTO vandelay.import_item (
9722             record,
9723             definition,
9724             owning_lib,
9725             circ_lib,
9726             call_number,
9727             copy_number,
9728             status,
9729             location,
9730             circulate,
9731             deposit,
9732             deposit_amount,
9733             ref,
9734             holdable,
9735             price,
9736             barcode,
9737             circ_modifier,
9738             circ_as_type,
9739             alert_message,
9740             pub_note,
9741             priv_note,
9742             opac_visible
9743         ) VALUES (
9744             NEW.id,
9745             item_data.definition,
9746             item_data.owning_lib,
9747             item_data.circ_lib,
9748             item_data.call_number,
9749             item_data.copy_number,
9750             item_data.status,
9751             item_data.location,
9752             item_data.circulate,
9753             item_data.deposit,
9754             item_data.deposit_amount,
9755             item_data.ref,
9756             item_data.holdable,
9757             item_data.price,
9758             item_data.barcode,
9759             item_data.circ_modifier,
9760             item_data.circ_as_type,
9761             item_data.alert_message,
9762             item_data.pub_note,
9763             item_data.priv_note,
9764             item_data.opac_visible
9765         );
9766     END LOOP;
9767
9768     RETURN NULL;
9769 END;
9770 $func$ LANGUAGE PLPGSQL;
9771
9772 CREATE OR REPLACE FUNCTION acq.create_acq_seq     ( sch TEXT, tbl TEXT ) RETURNS BOOL AS $creator$
9773 BEGIN
9774     EXECUTE $$
9775         CREATE SEQUENCE acq.$$ || sch || $$_$$ || tbl || $$_pkey_seq;
9776     $$;
9777         RETURN TRUE;
9778 END;
9779 $creator$ LANGUAGE 'plpgsql';
9780
9781 CREATE OR REPLACE FUNCTION acq.create_acq_history ( sch TEXT, tbl TEXT ) RETURNS BOOL AS $creator$
9782 BEGIN
9783     EXECUTE $$
9784         CREATE TABLE acq.$$ || sch || $$_$$ || tbl || $$_history (
9785             audit_id    BIGINT                          PRIMARY KEY,
9786             audit_time  TIMESTAMP WITH TIME ZONE        NOT NULL,
9787             audit_action        TEXT                            NOT NULL,
9788             LIKE $$ || sch || $$.$$ || tbl || $$
9789         );
9790     $$;
9791         RETURN TRUE;
9792 END;
9793 $creator$ LANGUAGE 'plpgsql';
9794
9795 CREATE OR REPLACE FUNCTION acq.create_acq_func    ( sch TEXT, tbl TEXT ) RETURNS BOOL AS $creator$
9796 BEGIN
9797     EXECUTE $$
9798         CREATE OR REPLACE FUNCTION acq.audit_$$ || sch || $$_$$ || tbl || $$_func ()
9799         RETURNS TRIGGER AS $func$
9800         BEGIN
9801             INSERT INTO acq.$$ || sch || $$_$$ || tbl || $$_history
9802                 SELECT  nextval('acq.$$ || sch || $$_$$ || tbl || $$_pkey_seq'),
9803                     now(),
9804                     SUBSTR(TG_OP,1,1),
9805                     OLD.*;
9806             RETURN NULL;
9807         END;
9808         $func$ LANGUAGE 'plpgsql';
9809     $$;
9810         RETURN TRUE;
9811 END;
9812 $creator$ LANGUAGE 'plpgsql';
9813
9814 CREATE OR REPLACE FUNCTION acq.create_acq_update_trigger ( sch TEXT, tbl TEXT ) RETURNS BOOL AS $creator$
9815 BEGIN
9816     EXECUTE $$
9817         CREATE TRIGGER audit_$$ || sch || $$_$$ || tbl || $$_update_trigger
9818             AFTER UPDATE OR DELETE ON $$ || sch || $$.$$ || tbl || $$ FOR EACH ROW
9819             EXECUTE PROCEDURE acq.audit_$$ || sch || $$_$$ || tbl || $$_func ();
9820     $$;
9821         RETURN TRUE;
9822 END;
9823 $creator$ LANGUAGE 'plpgsql';
9824
9825 CREATE OR REPLACE FUNCTION acq.create_acq_lifecycle     ( sch TEXT, tbl TEXT ) RETURNS BOOL AS $creator$
9826 BEGIN
9827     EXECUTE $$
9828         CREATE OR REPLACE VIEW acq.$$ || sch || $$_$$ || tbl || $$_lifecycle AS
9829             SELECT      -1, now() as audit_time, '-' as audit_action, *
9830               FROM      $$ || sch || $$.$$ || tbl || $$
9831                 UNION ALL
9832             SELECT      *
9833               FROM      acq.$$ || sch || $$_$$ || tbl || $$_history;
9834     $$;
9835         RETURN TRUE;
9836 END;
9837 $creator$ LANGUAGE 'plpgsql';
9838
9839 -- The main event
9840
9841 CREATE OR REPLACE FUNCTION acq.create_acq_auditor ( sch TEXT, tbl TEXT ) RETURNS BOOL AS $creator$
9842 BEGIN
9843     PERFORM acq.create_acq_seq(sch, tbl);
9844     PERFORM acq.create_acq_history(sch, tbl);
9845     PERFORM acq.create_acq_func(sch, tbl);
9846     PERFORM acq.create_acq_update_trigger(sch, tbl);
9847     PERFORM acq.create_acq_lifecycle(sch, tbl);
9848     RETURN TRUE;
9849 END;
9850 $creator$ LANGUAGE 'plpgsql';
9851
9852 SELECT acq.create_acq_auditor ( 'acq', 'purchase_order' );
9853 CREATE INDEX acq_po_hist_id_idx            ON acq.acq_purchase_order_history( id );
9854
9855 SELECT acq.create_acq_auditor ( 'acq', 'lineitem' );
9856 CREATE INDEX acq_lineitem_hist_id_idx            ON acq.acq_lineitem_history( id );
9857
9858 CREATE OR REPLACE VIEW acq.fund_debit_total AS
9859     SELECT  fund.id AS fund,
9860             fund_debit.encumbrance AS encumbrance,
9861             SUM( COALESCE( fund_debit.amount, 0 ) ) AS amount
9862       FROM acq.fund AS fund
9863                         LEFT JOIN acq.fund_debit AS fund_debit
9864                                 ON ( fund.id = fund_debit.fund )
9865       GROUP BY 1,2;
9866
9867 CREATE TABLE acq.debit_attribution (
9868         id                     INT         NOT NULL PRIMARY KEY,
9869         fund_debit             INT         NOT NULL
9870                                            REFERENCES acq.fund_debit
9871                                            DEFERRABLE INITIALLY DEFERRED,
9872     debit_amount           NUMERIC     NOT NULL,
9873         funding_source_credit  INT         REFERENCES acq.funding_source_credit
9874                                            DEFERRABLE INITIALLY DEFERRED,
9875     credit_amount          NUMERIC
9876 );
9877
9878 CREATE INDEX acq_attribution_debit_idx
9879         ON acq.debit_attribution( fund_debit );
9880
9881 CREATE INDEX acq_attribution_credit_idx
9882         ON acq.debit_attribution( funding_source_credit );
9883
9884 CREATE OR REPLACE FUNCTION acq.attribute_debits() RETURNS VOID AS $$
9885 /*
9886 Function to attribute expenditures and encumbrances to funding source credits,
9887 and thereby to funding sources.
9888
9889 Read the debits in chonological order, attributing each one to one or
9890 more funding source credits.  Constraints:
9891
9892 1. Don't attribute more to a credit than the amount of the credit.
9893
9894 2. For a given fund, don't attribute more to a funding source than the
9895 source has allocated to that fund.
9896
9897 3. Attribute debits to credits with deadlines before attributing them to
9898 credits without deadlines.  Otherwise attribute to the earliest credits
9899 first, based on the deadline date when present, or on the effective date
9900 when there is no deadline.  Use funding_source_credit.id as a tie-breaker.
9901 This ordering is defined by an ORDER BY clause on the view
9902 acq.ordered_funding_source_credit.
9903
9904 Start by truncating the table acq.debit_attribution.  Then insert a row
9905 into that table for each attribution.  If a debit cannot be fully
9906 attributed, insert a row for the unattributable balance, with the 
9907 funding_source_credit and credit_amount columns NULL.
9908 */
9909 DECLARE
9910         curr_fund_source_bal RECORD;
9911         seqno                INT;     -- sequence num for credits applicable to a fund
9912         fund_credit          RECORD;  -- current row in temp t_fund_credit table
9913         fc                   RECORD;  -- used for loading t_fund_credit table
9914         sc                   RECORD;  -- used for loading t_fund_credit table
9915         --
9916         -- Used exclusively in the main loop:
9917         --
9918         deb                 RECORD;   -- current row from acq.fund_debit table
9919         curr_credit_bal     RECORD;   -- current row from temp t_credit table
9920         debit_balance       NUMERIC;  -- amount left to attribute for current debit
9921         conv_debit_balance  NUMERIC;  -- debit balance in currency of the fund
9922         attr_amount         NUMERIC;  -- amount being attributed, in currency of debit
9923         conv_attr_amount    NUMERIC;  -- amount being attributed, in currency of source
9924         conv_cred_balance   NUMERIC;  -- credit_balance in the currency of the fund
9925         conv_alloc_balance  NUMERIC;  -- allocated balance in the currency of the fund
9926         attrib_count        INT;      -- populates id of acq.debit_attribution
9927 BEGIN
9928         --
9929         -- Load a temporary table.  For each combination of fund and funding source,
9930         -- load an entry with the total amount allocated to that fund by that source.
9931         -- This sum may reflect transfers as well as original allocations.  We will
9932         -- reduce this balance whenever we attribute debits to it.
9933         --
9934         CREATE TEMP TABLE t_fund_source_bal
9935         ON COMMIT DROP AS
9936                 SELECT
9937                         fund AS fund,
9938                         funding_source AS source,
9939                         sum( amount ) AS balance
9940                 FROM
9941                         acq.fund_allocation
9942                 GROUP BY
9943                         fund,
9944                         funding_source
9945                 HAVING
9946                         sum( amount ) > 0;
9947         --
9948         CREATE INDEX t_fund_source_bal_idx
9949                 ON t_fund_source_bal( fund, source );
9950         -------------------------------------------------------------------------------
9951         --
9952         -- Load another temporary table.  For each fund, load zero or more
9953         -- funding source credits from which that fund can get money.
9954         --
9955         CREATE TEMP TABLE t_fund_credit (
9956                 fund        INT,
9957                 seq         INT,
9958                 credit      INT
9959         ) ON COMMIT DROP;
9960         --
9961         FOR fc IN
9962                 SELECT DISTINCT fund
9963                 FROM acq.fund_allocation
9964                 ORDER BY fund
9965         LOOP                  -- Loop over the funds
9966                 seqno := 1;
9967                 FOR sc IN
9968                         SELECT
9969                                 ofsc.id
9970                         FROM
9971                                 acq.ordered_funding_source_credit AS ofsc
9972                         WHERE
9973                                 ofsc.funding_source IN
9974                                 (
9975                                         SELECT funding_source
9976                                         FROM acq.fund_allocation
9977                                         WHERE fund = fc.fund
9978                                 )
9979                 ORDER BY
9980                     ofsc.sort_priority,
9981                     ofsc.sort_date,
9982                     ofsc.id
9983                 LOOP                        -- Add each credit to the list
9984                         INSERT INTO t_fund_credit (
9985                                 fund,
9986                                 seq,
9987                                 credit
9988                         ) VALUES (
9989                                 fc.fund,
9990                                 seqno,
9991                                 sc.id
9992                         );
9993                         --RAISE NOTICE 'Fund % credit %', fc.fund, sc.id;
9994                         seqno := seqno + 1;
9995                 END LOOP;     -- Loop over credits for a given fund
9996         END LOOP;         -- Loop over funds
9997         --
9998         CREATE INDEX t_fund_credit_idx
9999                 ON t_fund_credit( fund, seq );
10000         -------------------------------------------------------------------------------
10001         --
10002         -- Load yet another temporary table.  This one is a list of funding source
10003         -- credits, with their balances.  We shall reduce those balances as we
10004         -- attribute debits to them.
10005         --
10006         CREATE TEMP TABLE t_credit
10007         ON COMMIT DROP AS
10008         SELECT
10009             fsc.id AS credit,
10010             fsc.funding_source AS source,
10011             fsc.amount AS balance,
10012             fs.currency_type AS currency_type
10013         FROM
10014             acq.funding_source_credit AS fsc,
10015             acq.funding_source fs
10016         WHERE
10017             fsc.funding_source = fs.id
10018                         AND fsc.amount > 0;
10019         --
10020         CREATE INDEX t_credit_idx
10021                 ON t_credit( credit );
10022         --
10023         -------------------------------------------------------------------------------
10024         --
10025         -- Now that we have loaded the lookup tables: loop through the debits,
10026         -- attributing each one to one or more funding source credits.
10027         -- 
10028         truncate table acq.debit_attribution;
10029         --
10030         attrib_count := 0;
10031         FOR deb in
10032                 SELECT
10033                         fd.id,
10034                         fd.fund,
10035                         fd.amount,
10036                         f.currency_type,
10037                         fd.encumbrance
10038                 FROM
10039                         acq.fund_debit fd,
10040                         acq.fund f
10041                 WHERE
10042                         fd.fund = f.id
10043                 ORDER BY
10044                         fd.id
10045         LOOP
10046                 --RAISE NOTICE 'Debit %, fund %', deb.id, deb.fund;
10047                 --
10048                 debit_balance := deb.amount;
10049                 --
10050                 -- Loop over the funding source credits that are eligible
10051                 -- to pay for this debit
10052                 --
10053                 FOR fund_credit IN
10054                         SELECT
10055                                 credit
10056                         FROM
10057                                 t_fund_credit
10058                         WHERE
10059                                 fund = deb.fund
10060                         ORDER BY
10061                                 seq
10062                 LOOP
10063                         --RAISE NOTICE '   Examining credit %', fund_credit.credit;
10064                         --
10065                         -- Look up the balance for this credit.  If it's zero, then
10066                         -- it's not useful, so treat it as if you didn't find it.
10067                         -- (Actually there shouldn't be any zero balances in the table,
10068                         -- but we check just to make sure.)
10069                         --
10070                         SELECT *
10071                         INTO curr_credit_bal
10072                         FROM t_credit
10073                         WHERE
10074                                 credit = fund_credit.credit
10075                                 AND balance > 0;
10076                         --
10077                         IF curr_credit_bal IS NULL THEN
10078                                 --
10079                                 -- This credit is exhausted; try the next one.
10080                                 --
10081                                 CONTINUE;
10082                         END IF;
10083                         --
10084                         --
10085                         -- At this point we have an applicable credit with some money left.
10086                         -- Now see if the relevant funding_source has any money left.
10087                         --
10088                         -- Look up the balance of the allocation for this combination of
10089                         -- fund and source.  If you find such an entry, but it has a zero
10090                         -- balance, then it's not useful, so treat it as unfound.
10091                         -- (Actually there shouldn't be any zero balances in the table,
10092                         -- but we check just to make sure.)
10093                         --
10094                         SELECT *
10095                         INTO curr_fund_source_bal
10096                         FROM t_fund_source_bal
10097                         WHERE
10098                                 fund = deb.fund
10099                                 AND source = curr_credit_bal.source
10100                                 AND balance > 0;
10101                         --
10102                         IF curr_fund_source_bal IS NULL THEN
10103                                 --
10104                                 -- This fund/source doesn't exist or is already exhausted,
10105                                 -- so we can't use this credit.  Go on to the next one.
10106                                 --
10107                                 CONTINUE;
10108                         END IF;
10109                         --
10110                         -- Convert the available balances to the currency of the fund
10111                         --
10112                         conv_alloc_balance := curr_fund_source_bal.balance * acq.exchange_ratio(
10113                                 curr_credit_bal.currency_type, deb.currency_type );
10114                         conv_cred_balance := curr_credit_bal.balance * acq.exchange_ratio(
10115                                 curr_credit_bal.currency_type, deb.currency_type );
10116                         --
10117                         -- Determine how much we can attribute to this credit: the minimum
10118                         -- of the debit amount, the fund/source balance, and the
10119                         -- credit balance
10120                         --
10121                         --RAISE NOTICE '   deb bal %', debit_balance;
10122                         --RAISE NOTICE '      source % balance %', curr_credit_bal.source, conv_alloc_balance;
10123                         --RAISE NOTICE '      credit % balance %', curr_credit_bal.credit, conv_cred_balance;
10124                         --
10125                         conv_attr_amount := NULL;
10126                         attr_amount := debit_balance;
10127                         --
10128                         IF attr_amount > conv_alloc_balance THEN
10129                                 attr_amount := conv_alloc_balance;
10130                                 conv_attr_amount := curr_fund_source_bal.balance;
10131                         END IF;
10132                         IF attr_amount > conv_cred_balance THEN
10133                                 attr_amount := conv_cred_balance;
10134                                 conv_attr_amount := curr_credit_bal.balance;
10135                         END IF;
10136                         --
10137                         -- If we're attributing all of one of the balances, then that's how
10138                         -- much we will deduct from the balances, and we already captured
10139                         -- that amount above.  Otherwise we must convert the amount of the
10140                         -- attribution from the currency of the fund back to the currency of
10141                         -- the funding source.
10142                         --
10143                         IF conv_attr_amount IS NULL THEN
10144                                 conv_attr_amount := attr_amount * acq.exchange_ratio(
10145                                         deb.currency_type, curr_credit_bal.currency_type );
10146                         END IF;
10147                         --
10148                         -- Insert a row to record the attribution
10149                         --
10150                         attrib_count := attrib_count + 1;
10151                         INSERT INTO acq.debit_attribution (
10152                                 id,
10153                                 fund_debit,
10154                                 debit_amount,
10155                                 funding_source_credit,
10156                                 credit_amount
10157                         ) VALUES (
10158                                 attrib_count,
10159                                 deb.id,
10160                                 attr_amount,
10161                                 curr_credit_bal.credit,
10162                                 conv_attr_amount
10163                         );
10164                         --
10165                         -- Subtract the attributed amount from the various balances
10166                         --
10167                         debit_balance := debit_balance - attr_amount;
10168                         curr_fund_source_bal.balance := curr_fund_source_bal.balance - conv_attr_amount;
10169                         --
10170                         IF curr_fund_source_bal.balance <= 0 THEN
10171                                 --
10172                                 -- This allocation is exhausted.  Delete it so
10173                                 -- that we don't waste time looking at it again.
10174                                 --
10175                                 DELETE FROM t_fund_source_bal
10176                                 WHERE
10177                                         fund = curr_fund_source_bal.fund
10178                                         AND source = curr_fund_source_bal.source;
10179                         ELSE
10180                                 UPDATE t_fund_source_bal
10181                                 SET balance = balance - conv_attr_amount
10182                                 WHERE
10183                                         fund = curr_fund_source_bal.fund
10184                                         AND source = curr_fund_source_bal.source;
10185                         END IF;
10186                         --
10187                         IF curr_credit_bal.balance <= 0 THEN
10188                                 --
10189                                 -- This funding source credit is exhausted.  Delete it
10190                                 -- so that we don't waste time looking at it again.
10191                                 --
10192                                 --DELETE FROM t_credit
10193                                 --WHERE
10194                                 --      credit = curr_credit_bal.credit;
10195                                 --
10196                                 DELETE FROM t_fund_credit
10197                                 WHERE
10198                                         credit = curr_credit_bal.credit;
10199                         ELSE
10200                                 UPDATE t_credit
10201                                 SET balance = curr_credit_bal.balance
10202                                 WHERE
10203                                         credit = curr_credit_bal.credit;
10204                         END IF;
10205                         --
10206                         -- Are we done with this debit yet?
10207                         --
10208                         IF debit_balance <= 0 THEN
10209                                 EXIT;       -- We've fully attributed this debit; stop looking at credits.
10210                         END IF;
10211                 END LOOP;       -- End loop over credits
10212                 --
10213                 IF debit_balance <> 0 THEN
10214                         --
10215                         -- We weren't able to attribute this debit, or at least not
10216                         -- all of it.  Insert a row for the unattributed balance.
10217                         --
10218                         attrib_count := attrib_count + 1;
10219                         INSERT INTO acq.debit_attribution (
10220                                 id,
10221                                 fund_debit,
10222                                 debit_amount,
10223                                 funding_source_credit,
10224                                 credit_amount
10225                         ) VALUES (
10226                                 attrib_count,
10227                                 deb.id,
10228                                 debit_balance,
10229                                 NULL,
10230                                 NULL
10231                         );
10232                 END IF;
10233         END LOOP;   -- End of loop over debits
10234 END;
10235 $$ LANGUAGE 'plpgsql';
10236
10237 CREATE OR REPLACE FUNCTION extract_marc_field ( TEXT, BIGINT, TEXT, TEXT ) RETURNS TEXT AS $$
10238 DECLARE
10239     query TEXT;
10240     output TEXT;
10241 BEGIN
10242     query := $q$
10243         SELECT  regexp_replace(
10244                     oils_xpath_string(
10245                         $q$ || quote_literal($3) || $q$,
10246                         marc,
10247                         ' '
10248                     ),
10249                     $q$ || quote_literal($4) || $q$,
10250                     '',
10251                     'g')
10252           FROM  $q$ || $1 || $q$
10253           WHERE id = $q$ || $2;
10254
10255     EXECUTE query INTO output;
10256
10257     -- RAISE NOTICE 'query: %, output; %', query, output;
10258
10259     RETURN output;
10260 END;
10261 $$ LANGUAGE PLPGSQL IMMUTABLE;
10262
10263 CREATE OR REPLACE FUNCTION extract_marc_field ( TEXT, BIGINT, TEXT ) RETURNS TEXT AS $$
10264     SELECT extract_marc_field($1,$2,$3,'');
10265 $$ LANGUAGE SQL IMMUTABLE;
10266
10267 CREATE OR REPLACE FUNCTION asset.merge_record_assets( target_record BIGINT, source_record BIGINT ) RETURNS INT AS $func$
10268 DECLARE
10269     moved_objects INT := 0;
10270     source_cn     asset.call_number%ROWTYPE;
10271     target_cn     asset.call_number%ROWTYPE;
10272     metarec       metabib.metarecord%ROWTYPE;
10273     hold          action.hold_request%ROWTYPE;
10274     ser_rec       serial.record_entry%ROWTYPE;
10275     uri_count     INT := 0;
10276     counter       INT := 0;
10277     uri_datafield TEXT;
10278     uri_text      TEXT := '';
10279 BEGIN
10280
10281     -- move any 856 entries on records that have at least one MARC-mapped URI entry
10282     SELECT  INTO uri_count COUNT(*)
10283       FROM  asset.uri_call_number_map m
10284             JOIN asset.call_number cn ON (m.call_number = cn.id)
10285       WHERE cn.record = source_record;
10286
10287     IF uri_count > 0 THEN
10288
10289         SELECT  COUNT(*) INTO counter
10290           FROM  oils_xpath_table(
10291                     'id',
10292                     'marc',
10293                     'biblio.record_entry',
10294                     '//*[@tag="856"]',
10295                     'id=' || source_record
10296                 ) as t(i int,c text);
10297
10298         FOR i IN 1 .. counter LOOP
10299             SELECT  '<datafield xmlns="http://www.loc.gov/MARC21/slim"' ||
10300                         ' tag="856"' || 
10301                         ' ind1="' || FIRST(ind1) || '"'  || 
10302                         ' ind2="' || FIRST(ind2) || '">' || 
10303                         array_to_string(
10304                             array_accum(
10305                                 '<subfield code="' || subfield || '">' ||
10306                                 regexp_replace(
10307                                     regexp_replace(
10308                                         regexp_replace(data,'&','&amp;','g'),
10309                                         '>', '&gt;', 'g'
10310                                     ),
10311                                     '<', '&lt;', 'g'
10312                                 ) || '</subfield>'
10313                             ), ''
10314                         ) || '</datafield>' INTO uri_datafield
10315               FROM  oils_xpath_table(
10316                         'id',
10317                         'marc',
10318                         'biblio.record_entry',
10319                         '//*[@tag="856"][position()=' || i || ']/@ind1|' || 
10320                         '//*[@tag="856"][position()=' || i || ']/@ind2|' || 
10321                         '//*[@tag="856"][position()=' || i || ']/*/@code|' ||
10322                         '//*[@tag="856"][position()=' || i || ']/*[@code]',
10323                         'id=' || source_record
10324                     ) as t(id int,ind1 text, ind2 text,subfield text,data text);
10325
10326             uri_text := uri_text || uri_datafield;
10327         END LOOP;
10328
10329         IF uri_text <> '' THEN
10330             UPDATE  biblio.record_entry
10331               SET   marc = regexp_replace(marc,'(</[^>]*record>)', uri_text || E'\\1')
10332               WHERE id = target_record;
10333         END IF;
10334
10335     END IF;
10336
10337     -- Find and move metarecords to the target record
10338     SELECT  INTO metarec *
10339       FROM  metabib.metarecord
10340       WHERE master_record = source_record;
10341
10342     IF FOUND THEN
10343         UPDATE  metabib.metarecord
10344           SET   master_record = target_record,
10345             mods = NULL
10346           WHERE id = metarec.id;
10347
10348         moved_objects := moved_objects + 1;
10349     END IF;
10350
10351     -- Find call numbers attached to the source ...
10352     FOR source_cn IN SELECT * FROM asset.call_number WHERE record = source_record LOOP
10353
10354         SELECT  INTO target_cn *
10355           FROM  asset.call_number
10356           WHERE label = source_cn.label
10357             AND owning_lib = source_cn.owning_lib
10358             AND record = target_record;
10359
10360         -- ... and if there's a conflicting one on the target ...
10361         IF FOUND THEN
10362
10363             -- ... move the copies to that, and ...
10364             UPDATE  asset.copy
10365               SET   call_number = target_cn.id
10366               WHERE call_number = source_cn.id;
10367
10368             -- ... move V holds to the move-target call number
10369             FOR hold IN SELECT * FROM action.hold_request WHERE target = source_cn.id AND hold_type = 'V' LOOP
10370
10371                 UPDATE  action.hold_request
10372                   SET   target = target_cn.id
10373                   WHERE id = hold.id;
10374
10375                 moved_objects := moved_objects + 1;
10376             END LOOP;
10377
10378         -- ... if not ...
10379         ELSE
10380             -- ... just move the call number to the target record
10381             UPDATE  asset.call_number
10382               SET   record = target_record
10383               WHERE id = source_cn.id;
10384         END IF;
10385
10386         moved_objects := moved_objects + 1;
10387     END LOOP;
10388
10389     -- Find T holds targeting the source record ...
10390     FOR hold IN SELECT * FROM action.hold_request WHERE target = source_record AND hold_type = 'T' LOOP
10391
10392         -- ... and move them to the target record
10393         UPDATE  action.hold_request
10394           SET   target = target_record
10395           WHERE id = hold.id;
10396
10397         moved_objects := moved_objects + 1;
10398     END LOOP;
10399
10400     -- Find serial records targeting the source record ...
10401     FOR ser_rec IN SELECT * FROM serial.record_entry WHERE record = source_record LOOP
10402         -- ... and move them to the target record
10403         UPDATE  serial.record_entry
10404           SET   record = target_record
10405           WHERE id = ser_rec.id;
10406
10407         moved_objects := moved_objects + 1;
10408     END LOOP;
10409
10410     -- Finally, "delete" the source record
10411     DELETE FROM biblio.record_entry WHERE id = source_record;
10412
10413     -- That's all, folks!
10414     RETURN moved_objects;
10415 END;
10416 $func$ LANGUAGE plpgsql;
10417
10418 CREATE OR REPLACE FUNCTION acq.transfer_fund(
10419         old_fund   IN INT,
10420         old_amount IN NUMERIC,     -- in currency of old fund
10421         new_fund   IN INT,
10422         new_amount IN NUMERIC,     -- in currency of new fund
10423         user_id    IN INT,
10424         xfer_note  IN TEXT         -- to be recorded in acq.fund_transfer
10425         -- ,funding_source_in IN INT  -- if user wants to specify a funding source (see notes)
10426 ) RETURNS VOID AS $$
10427 /* -------------------------------------------------------------------------------
10428
10429 Function to transfer money from one fund to another.
10430
10431 A transfer is represented as a pair of entries in acq.fund_allocation, with a
10432 negative amount for the old (losing) fund and a positive amount for the new
10433 (gaining) fund.  In some cases there may be more than one such pair of entries
10434 in order to pull the money from different funding sources, or more specifically
10435 from different funding source credits.  For each such pair there is also an
10436 entry in acq.fund_transfer.
10437
10438 Since funding_source is a non-nullable column in acq.fund_allocation, we must
10439 choose a funding source for the transferred money to come from.  This choice
10440 must meet two constraints, so far as possible:
10441
10442 1. The amount transferred from a given funding source must not exceed the
10443 amount allocated to the old fund by the funding source.  To that end we
10444 compare the amount being transferred to the amount allocated.
10445
10446 2. We shouldn't transfer money that has already been spent or encumbered, as
10447 defined by the funding attribution process.  We attribute expenses to the
10448 oldest funding source credits first.  In order to avoid transferring that
10449 attributed money, we reverse the priority, transferring from the newest funding
10450 source credits first.  There can be no guarantee that this approach will
10451 avoid overcommitting a fund, but no other approach can do any better.
10452
10453 In this context the age of a funding source credit is defined by the
10454 deadline_date for credits with deadline_dates, and by the effective_date for
10455 credits without deadline_dates, with the proviso that credits with deadline_dates
10456 are all considered "older" than those without.
10457
10458 ----------
10459
10460 In the signature for this function, there is one last parameter commented out,
10461 named "funding_source_in".  Correspondingly, the WHERE clause for the query
10462 driving the main loop has an OR clause commented out, which references the
10463 funding_source_in parameter.
10464
10465 If these lines are uncommented, this function will allow the user optionally to
10466 restrict a fund transfer to a specified funding source.  If the source
10467 parameter is left NULL, then there will be no such restriction.
10468
10469 ------------------------------------------------------------------------------- */ 
10470 DECLARE
10471         same_currency      BOOLEAN;
10472         currency_ratio     NUMERIC;
10473         old_fund_currency  TEXT;
10474         old_remaining      NUMERIC;  -- in currency of old fund
10475         new_fund_currency  TEXT;
10476         new_fund_active    BOOLEAN;
10477         new_remaining      NUMERIC;  -- in currency of new fund
10478         curr_old_amt       NUMERIC;  -- in currency of old fund
10479         curr_new_amt       NUMERIC;  -- in currency of new fund
10480         source_addition    NUMERIC;  -- in currency of funding source
10481         source_deduction   NUMERIC;  -- in currency of funding source
10482         orig_allocated_amt NUMERIC;  -- in currency of funding source
10483         allocated_amt      NUMERIC;  -- in currency of fund
10484         source             RECORD;
10485 BEGIN
10486         --
10487         -- Sanity checks
10488         --
10489         IF old_fund IS NULL THEN
10490                 RAISE EXCEPTION 'acq.transfer_fund: old fund id is NULL';
10491         END IF;
10492         --
10493         IF old_amount IS NULL THEN
10494                 RAISE EXCEPTION 'acq.transfer_fund: amount to transfer is NULL';
10495         END IF;
10496         --
10497         -- The new fund and its amount must be both NULL or both not NULL.
10498         --
10499         IF new_fund IS NOT NULL AND new_amount IS NULL THEN
10500                 RAISE EXCEPTION 'acq.transfer_fund: amount to transfer to receiving fund is NULL';
10501         END IF;
10502         --
10503         IF new_fund IS NULL AND new_amount IS NOT NULL THEN
10504                 RAISE EXCEPTION 'acq.transfer_fund: receiving fund is NULL, its amount is not NULL';
10505         END IF;
10506         --
10507         IF user_id IS NULL THEN
10508                 RAISE EXCEPTION 'acq.transfer_fund: user id is NULL';
10509         END IF;
10510         --
10511         -- Initialize the amounts to be transferred, each denominated
10512         -- in the currency of its respective fund.  They will be
10513         -- reduced on each iteration of the loop.
10514         --
10515         old_remaining := old_amount;
10516         new_remaining := new_amount;
10517         --
10518         -- RAISE NOTICE 'Transferring % in fund % to % in fund %',
10519         --      old_amount, old_fund, new_amount, new_fund;
10520         --
10521         -- Get the currency types of the old and new funds.
10522         --
10523         SELECT
10524                 currency_type
10525         INTO
10526                 old_fund_currency
10527         FROM
10528                 acq.fund
10529         WHERE
10530                 id = old_fund;
10531         --
10532         IF old_fund_currency IS NULL THEN
10533                 RAISE EXCEPTION 'acq.transfer_fund: old fund id % is not defined', old_fund;
10534         END IF;
10535         --
10536         IF new_fund IS NOT NULL THEN
10537                 SELECT
10538                         currency_type,
10539                         active
10540                 INTO
10541                         new_fund_currency,
10542                         new_fund_active
10543                 FROM
10544                         acq.fund
10545                 WHERE
10546                         id = new_fund;
10547                 --
10548                 IF new_fund_currency IS NULL THEN
10549                         RAISE EXCEPTION 'acq.transfer_fund: new fund id % is not defined', new_fund;
10550                 ELSIF NOT new_fund_active THEN
10551                         --
10552                         -- No point in putting money into a fund from whence you can't spend it
10553                         --
10554                         RAISE EXCEPTION 'acq.transfer_fund: new fund id % is inactive', new_fund;
10555                 END IF;
10556                 --
10557                 IF new_amount = old_amount THEN
10558                         same_currency := true;
10559                         currency_ratio := 1;
10560                 ELSE
10561                         --
10562                         -- We'll have to translate currency between funds.  We presume that
10563                         -- the calling code has already applied an appropriate exchange rate,
10564                         -- so we'll apply the same conversion to each sub-transfer.
10565                         --
10566                         same_currency := false;
10567                         currency_ratio := new_amount / old_amount;
10568                 END IF;
10569         END IF;
10570         --
10571         -- Identify the funding source(s) from which we want to transfer the money.
10572         -- The principle is that we want to transfer the newest money first, because
10573         -- we spend the oldest money first.  The priority for spending is defined
10574         -- by a sort of the view acq.ordered_funding_source_credit.
10575         --
10576         FOR source in
10577                 SELECT
10578                         ofsc.id,
10579                         ofsc.funding_source,
10580                         ofsc.amount,
10581                         ofsc.amount * acq.exchange_ratio( fs.currency_type, old_fund_currency )
10582                                 AS converted_amt,
10583                         fs.currency_type
10584                 FROM
10585                         acq.ordered_funding_source_credit AS ofsc,
10586                         acq.funding_source fs
10587                 WHERE
10588                         ofsc.funding_source = fs.id
10589                         and ofsc.funding_source IN
10590                         (
10591                                 SELECT funding_source
10592                                 FROM acq.fund_allocation
10593                                 WHERE fund = old_fund
10594                         )
10595                         -- and
10596                         -- (
10597                         --      ofsc.funding_source = funding_source_in
10598                         --      OR funding_source_in IS NULL
10599                         -- )
10600                 ORDER BY
10601                         ofsc.sort_priority desc,
10602                         ofsc.sort_date desc,
10603                         ofsc.id desc
10604         LOOP
10605                 --
10606                 -- Determine how much money the old fund got from this funding source,
10607                 -- denominated in the currency types of the source and of the fund.
10608                 -- This result may reflect transfers from previous iterations.
10609                 --
10610                 SELECT
10611                         COALESCE( sum( amount ), 0 ),
10612                         COALESCE( sum( amount )
10613                                 * acq.exchange_ratio( source.currency_type, old_fund_currency ), 0 )
10614                 INTO
10615                         orig_allocated_amt,     -- in currency of the source
10616                         allocated_amt           -- in currency of the old fund
10617                 FROM
10618                         acq.fund_allocation
10619                 WHERE
10620                         fund = old_fund
10621                         and funding_source = source.funding_source;
10622                 --      
10623                 -- Determine how much to transfer from this credit, in the currency
10624                 -- of the fund.   Begin with the amount remaining to be attributed:
10625                 --
10626                 curr_old_amt := old_remaining;
10627                 --
10628                 -- Can't attribute more than was allocated from the fund:
10629                 --
10630                 IF curr_old_amt > allocated_amt THEN
10631                         curr_old_amt := allocated_amt;
10632                 END IF;
10633                 --
10634                 -- Can't attribute more than the amount of the current credit:
10635                 --
10636                 IF curr_old_amt > source.converted_amt THEN
10637                         curr_old_amt := source.converted_amt;
10638                 END IF;
10639                 --
10640                 curr_old_amt := trunc( curr_old_amt, 2 );
10641                 --
10642                 old_remaining := old_remaining - curr_old_amt;
10643                 --
10644                 -- Determine the amount to be deducted, if any,
10645                 -- from the old allocation.
10646                 --
10647                 IF old_remaining > 0 THEN
10648                         --
10649                         -- In this case we're using the whole allocation, so use that
10650                         -- amount directly instead of applying a currency translation
10651                         -- and thereby inviting round-off errors.
10652                         --
10653                         source_deduction := - orig_allocated_amt;
10654                 ELSE 
10655                         source_deduction := trunc(
10656                                 ( - curr_old_amt ) *
10657                                         acq.exchange_ratio( old_fund_currency, source.currency_type ),
10658                                 2 );
10659                 END IF;
10660                 --
10661                 IF source_deduction <> 0 THEN
10662                         --
10663                         -- Insert negative allocation for old fund in fund_allocation,
10664                         -- converted into the currency of the funding source
10665                         --
10666                         INSERT INTO acq.fund_allocation (
10667                                 funding_source,
10668                                 fund,
10669                                 amount,
10670                                 allocator,
10671                                 note
10672                         ) VALUES (
10673                                 source.funding_source,
10674                                 old_fund,
10675                                 source_deduction,
10676                                 user_id,
10677                                 'Transfer to fund ' || new_fund
10678                         );
10679                 END IF;
10680                 --
10681                 IF new_fund IS NOT NULL THEN
10682                         --
10683                         -- Determine how much to add to the new fund, in
10684                         -- its currency, and how much remains to be added:
10685                         --
10686                         IF same_currency THEN
10687                                 curr_new_amt := curr_old_amt;
10688                         ELSE
10689                                 IF old_remaining = 0 THEN
10690                                         --
10691                                         -- This is the last iteration, so nothing should be left
10692                                         --
10693                                         curr_new_amt := new_remaining;
10694                                         new_remaining := 0;
10695                                 ELSE
10696                                         curr_new_amt := trunc( curr_old_amt * currency_ratio, 2 );
10697                                         new_remaining := new_remaining - curr_new_amt;
10698                                 END IF;
10699                         END IF;
10700                         --
10701                         -- Determine how much to add, if any,
10702                         -- to the new fund's allocation.
10703                         --
10704                         IF old_remaining > 0 THEN
10705                                 --
10706                                 -- In this case we're using the whole allocation, so use that amount
10707                                 -- amount directly instead of applying a currency translation and
10708                                 -- thereby inviting round-off errors.
10709                                 --
10710                                 source_addition := orig_allocated_amt;
10711                         ELSIF source.currency_type = old_fund_currency THEN
10712                                 --
10713                                 -- In this case we don't need a round trip currency translation,
10714                                 -- thereby inviting round-off errors:
10715                                 --
10716                                 source_addition := curr_old_amt;
10717                         ELSE 
10718                                 source_addition := trunc(
10719                                         curr_new_amt *
10720                                                 acq.exchange_ratio( new_fund_currency, source.currency_type ),
10721                                         2 );
10722                         END IF;
10723                         --
10724                         IF source_addition <> 0 THEN
10725                                 --
10726                                 -- Insert positive allocation for new fund in fund_allocation,
10727                                 -- converted to the currency of the founding source
10728                                 --
10729                                 INSERT INTO acq.fund_allocation (
10730                                         funding_source,
10731                                         fund,
10732                                         amount,
10733                                         allocator,
10734                                         note
10735                                 ) VALUES (
10736                                         source.funding_source,
10737                                         new_fund,
10738                                         source_addition,
10739                                         user_id,
10740                                         'Transfer from fund ' || old_fund
10741                                 );
10742                         END IF;
10743                 END IF;
10744                 --
10745                 IF trunc( curr_old_amt, 2 ) <> 0
10746                 OR trunc( curr_new_amt, 2 ) <> 0 THEN
10747                         --
10748                         -- Insert row in fund_transfer, using amounts in the currency of the funds
10749                         --
10750                         INSERT INTO acq.fund_transfer (
10751                                 src_fund,
10752                                 src_amount,
10753                                 dest_fund,
10754                                 dest_amount,
10755                                 transfer_user,
10756                                 note,
10757                                 funding_source_credit
10758                         ) VALUES (
10759                                 old_fund,
10760                                 trunc( curr_old_amt, 2 ),
10761                                 new_fund,
10762                                 trunc( curr_new_amt, 2 ),
10763                                 user_id,
10764                                 xfer_note,
10765                                 source.id
10766                         );
10767                 END IF;
10768                 --
10769                 if old_remaining <= 0 THEN
10770                         EXIT;                   -- Nothing more to be transferred
10771                 END IF;
10772         END LOOP;
10773 END;
10774 $$ LANGUAGE plpgsql;
10775
10776 CREATE OR REPLACE FUNCTION acq.propagate_funds_by_org_unit(
10777         old_year INTEGER,
10778         user_id INTEGER,
10779         org_unit_id INTEGER
10780 ) RETURNS VOID AS $$
10781 DECLARE
10782 --
10783 new_id      INT;
10784 old_fund    RECORD;
10785 org_found   BOOLEAN;
10786 --
10787 BEGIN
10788         --
10789         -- Sanity checks
10790         --
10791         IF old_year IS NULL THEN
10792                 RAISE EXCEPTION 'Input year argument is NULL';
10793         ELSIF old_year NOT BETWEEN 2008 and 2200 THEN
10794                 RAISE EXCEPTION 'Input year is out of range';
10795         END IF;
10796         --
10797         IF user_id IS NULL THEN
10798                 RAISE EXCEPTION 'Input user id argument is NULL';
10799         END IF;
10800         --
10801         IF org_unit_id IS NULL THEN
10802                 RAISE EXCEPTION 'Org unit id argument is NULL';
10803         ELSE
10804                 SELECT TRUE INTO org_found
10805                 FROM actor.org_unit
10806                 WHERE id = org_unit_id;
10807                 --
10808                 IF org_found IS NULL THEN
10809                         RAISE EXCEPTION 'Org unit id is invalid';
10810                 END IF;
10811         END IF;
10812         --
10813         -- Loop over the applicable funds
10814         --
10815         FOR old_fund in SELECT * FROM acq.fund
10816         WHERE
10817                 year = old_year
10818                 AND propagate
10819                 AND org = org_unit_id
10820         LOOP
10821                 BEGIN
10822                         INSERT INTO acq.fund (
10823                                 org,
10824                                 name,
10825                                 year,
10826                                 currency_type,
10827                                 code,
10828                                 rollover,
10829                                 propagate,
10830                                 balance_warning_percent,
10831                                 balance_stop_percent
10832                         ) VALUES (
10833                                 old_fund.org,
10834                                 old_fund.name,
10835                                 old_year + 1,
10836                                 old_fund.currency_type,
10837                                 old_fund.code,
10838                                 old_fund.rollover,
10839                                 true,
10840                                 old_fund.balance_warning_percent,
10841                                 old_fund.balance_stop_percent
10842                         )
10843                         RETURNING id INTO new_id;
10844                 EXCEPTION
10845                         WHEN unique_violation THEN
10846                                 --RAISE NOTICE 'Fund % already propagated', old_fund.id;
10847                                 CONTINUE;
10848                 END;
10849                 --RAISE NOTICE 'Propagating fund % to fund %',
10850                 --      old_fund.code, new_id;
10851         END LOOP;
10852 END;
10853 $$ LANGUAGE plpgsql;
10854
10855 CREATE OR REPLACE FUNCTION acq.propagate_funds_by_org_tree(
10856         old_year INTEGER,
10857         user_id INTEGER,
10858         org_unit_id INTEGER
10859 ) RETURNS VOID AS $$
10860 DECLARE
10861 --
10862 new_id      INT;
10863 old_fund    RECORD;
10864 org_found   BOOLEAN;
10865 --
10866 BEGIN
10867         --
10868         -- Sanity checks
10869         --
10870         IF old_year IS NULL THEN
10871                 RAISE EXCEPTION 'Input year argument is NULL';
10872         ELSIF old_year NOT BETWEEN 2008 and 2200 THEN
10873                 RAISE EXCEPTION 'Input year is out of range';
10874         END IF;
10875         --
10876         IF user_id IS NULL THEN
10877                 RAISE EXCEPTION 'Input user id argument is NULL';
10878         END IF;
10879         --
10880         IF org_unit_id IS NULL THEN
10881                 RAISE EXCEPTION 'Org unit id argument is NULL';
10882         ELSE
10883                 SELECT TRUE INTO org_found
10884                 FROM actor.org_unit
10885                 WHERE id = org_unit_id;
10886                 --
10887                 IF org_found IS NULL THEN
10888                         RAISE EXCEPTION 'Org unit id is invalid';
10889                 END IF;
10890         END IF;
10891         --
10892         -- Loop over the applicable funds
10893         --
10894         FOR old_fund in SELECT * FROM acq.fund
10895         WHERE
10896                 year = old_year
10897                 AND propagate
10898                 AND org in (
10899                         SELECT id FROM actor.org_unit_descendants( org_unit_id )
10900                 )
10901         LOOP
10902                 BEGIN
10903                         INSERT INTO acq.fund (
10904                                 org,
10905                                 name,
10906                                 year,
10907                                 currency_type,
10908                                 code,
10909                                 rollover,
10910                                 propagate,
10911                                 balance_warning_percent,
10912                                 balance_stop_percent
10913                         ) VALUES (
10914                                 old_fund.org,
10915                                 old_fund.name,
10916                                 old_year + 1,
10917                                 old_fund.currency_type,
10918                                 old_fund.code,
10919                                 old_fund.rollover,
10920                                 true,
10921                                 old_fund.balance_warning_percent,
10922                                 old_fund.balance_stop_percent
10923                         )
10924                         RETURNING id INTO new_id;
10925                 EXCEPTION
10926                         WHEN unique_violation THEN
10927                                 --RAISE NOTICE 'Fund % already propagated', old_fund.id;
10928                                 CONTINUE;
10929                 END;
10930                 --RAISE NOTICE 'Propagating fund % to fund %',
10931                 --      old_fund.code, new_id;
10932         END LOOP;
10933 END;
10934 $$ LANGUAGE plpgsql;
10935
10936 CREATE OR REPLACE FUNCTION acq.rollover_funds_by_org_unit(
10937         old_year INTEGER,
10938         user_id INTEGER,
10939         org_unit_id INTEGER
10940 ) RETURNS VOID AS $$
10941 DECLARE
10942 --
10943 new_fund    INT;
10944 new_year    INT := old_year + 1;
10945 org_found   BOOL;
10946 xfer_amount NUMERIC;
10947 roll_fund   RECORD;
10948 deb         RECORD;
10949 detail      RECORD;
10950 --
10951 BEGIN
10952         --
10953         -- Sanity checks
10954         --
10955         IF old_year IS NULL THEN
10956                 RAISE EXCEPTION 'Input year argument is NULL';
10957     ELSIF old_year NOT BETWEEN 2008 and 2200 THEN
10958         RAISE EXCEPTION 'Input year is out of range';
10959         END IF;
10960         --
10961         IF user_id IS NULL THEN
10962                 RAISE EXCEPTION 'Input user id argument is NULL';
10963         END IF;
10964         --
10965         IF org_unit_id IS NULL THEN
10966                 RAISE EXCEPTION 'Org unit id argument is NULL';
10967         ELSE
10968                 --
10969                 -- Validate the org unit
10970                 --
10971                 SELECT TRUE
10972                 INTO org_found
10973                 FROM actor.org_unit
10974                 WHERE id = org_unit_id;
10975                 --
10976                 IF org_found IS NULL THEN
10977                         RAISE EXCEPTION 'Org unit id % is invalid', org_unit_id;
10978                 END IF;
10979         END IF;
10980         --
10981         -- Loop over the propagable funds to identify the details
10982         -- from the old fund plus the id of the new one, if it exists.
10983         --
10984         FOR roll_fund in
10985         SELECT
10986             oldf.id AS old_fund,
10987             oldf.org,
10988             oldf.name,
10989             oldf.currency_type,
10990             oldf.code,
10991                 oldf.rollover,
10992             newf.id AS new_fund_id
10993         FROM
10994         acq.fund AS oldf
10995         LEFT JOIN acq.fund AS newf
10996                 ON ( oldf.code = newf.code )
10997         WHERE
10998                     oldf.org = org_unit_id
10999                 and oldf.year = old_year
11000                 and oldf.propagate
11001         and newf.year = new_year
11002         LOOP
11003                 --RAISE NOTICE 'Processing fund %', roll_fund.old_fund;
11004                 --
11005                 IF roll_fund.new_fund_id IS NULL THEN
11006                         --
11007                         -- The old fund hasn't been propagated yet.  Propagate it now.
11008                         --
11009                         INSERT INTO acq.fund (
11010                                 org,
11011                                 name,
11012                                 year,
11013                                 currency_type,
11014                                 code,
11015                                 rollover,
11016                                 propagate,
11017                                 balance_warning_percent,
11018                                 balance_stop_percent
11019                         ) VALUES (
11020                                 roll_fund.org,
11021                                 roll_fund.name,
11022                                 new_year,
11023                                 roll_fund.currency_type,
11024                                 roll_fund.code,
11025                                 true,
11026                                 true,
11027                                 roll_fund.balance_warning_percent,
11028                                 roll_fund.balance_stop_percent
11029                         )
11030                         RETURNING id INTO new_fund;
11031                 ELSE
11032                         new_fund = roll_fund.new_fund_id;
11033                 END IF;
11034                 --
11035                 -- Determine the amount to transfer
11036                 --
11037                 SELECT amount
11038                 INTO xfer_amount
11039                 FROM acq.fund_spent_balance
11040                 WHERE fund = roll_fund.old_fund;
11041                 --
11042                 IF xfer_amount <> 0 THEN
11043                         IF roll_fund.rollover THEN
11044                                 --
11045                                 -- Transfer balance from old fund to new
11046                                 --
11047                                 --RAISE NOTICE 'Transferring % from fund % to %', xfer_amount, roll_fund.old_fund, new_fund;
11048                                 --
11049                                 PERFORM acq.transfer_fund(
11050                                         roll_fund.old_fund,
11051                                         xfer_amount,
11052                                         new_fund,
11053                                         xfer_amount,
11054                                         user_id,
11055                                         'Rollover'
11056                                 );
11057                         ELSE
11058                                 --
11059                                 -- Transfer balance from old fund to the void
11060                                 --
11061                                 -- RAISE NOTICE 'Transferring % from fund % to the void', xfer_amount, roll_fund.old_fund;
11062                                 --
11063                                 PERFORM acq.transfer_fund(
11064                                         roll_fund.old_fund,
11065                                         xfer_amount,
11066                                         NULL,
11067                                         NULL,
11068                                         user_id,
11069                                         'Rollover'
11070                                 );
11071                         END IF;
11072                 END IF;
11073                 --
11074                 IF roll_fund.rollover THEN
11075                         --
11076                         -- Move any lineitems from the old fund to the new one
11077                         -- where the associated debit is an encumbrance.
11078                         --
11079                         -- Any other tables tying expenditure details to funds should
11080                         -- receive similar treatment.  At this writing there are none.
11081                         --
11082                         UPDATE acq.lineitem_detail
11083                         SET fund = new_fund
11084                         WHERE
11085                         fund = roll_fund.old_fund -- this condition may be redundant
11086                         AND fund_debit in
11087                         (
11088                                 SELECT id
11089                                 FROM acq.fund_debit
11090                                 WHERE
11091                                 fund = roll_fund.old_fund
11092                                 AND encumbrance
11093                         );
11094                         --
11095                         -- Move encumbrance debits from the old fund to the new fund
11096                         --
11097                         UPDATE acq.fund_debit
11098                         SET fund = new_fund
11099                         wHERE
11100                                 fund = roll_fund.old_fund
11101                                 AND encumbrance;
11102                 END IF;
11103                 --
11104                 -- Mark old fund as inactive, now that we've closed it
11105                 --
11106                 UPDATE acq.fund
11107                 SET active = FALSE
11108                 WHERE id = roll_fund.old_fund;
11109         END LOOP;
11110 END;
11111 $$ LANGUAGE plpgsql;
11112
11113 CREATE OR REPLACE FUNCTION acq.rollover_funds_by_org_tree(
11114         old_year INTEGER,
11115         user_id INTEGER,
11116         org_unit_id INTEGER
11117 ) RETURNS VOID AS $$
11118 DECLARE
11119 --
11120 new_fund    INT;
11121 new_year    INT := old_year + 1;
11122 org_found   BOOL;
11123 xfer_amount NUMERIC;
11124 roll_fund   RECORD;
11125 deb         RECORD;
11126 detail      RECORD;
11127 --
11128 BEGIN
11129         --
11130         -- Sanity checks
11131         --
11132         IF old_year IS NULL THEN
11133                 RAISE EXCEPTION 'Input year argument is NULL';
11134     ELSIF old_year NOT BETWEEN 2008 and 2200 THEN
11135         RAISE EXCEPTION 'Input year is out of range';
11136         END IF;
11137         --
11138         IF user_id IS NULL THEN
11139                 RAISE EXCEPTION 'Input user id argument is NULL';
11140         END IF;
11141         --
11142         IF org_unit_id IS NULL THEN
11143                 RAISE EXCEPTION 'Org unit id argument is NULL';
11144         ELSE
11145                 --
11146                 -- Validate the org unit
11147                 --
11148                 SELECT TRUE
11149                 INTO org_found
11150                 FROM actor.org_unit
11151                 WHERE id = org_unit_id;
11152                 --
11153                 IF org_found IS NULL THEN
11154                         RAISE EXCEPTION 'Org unit id % is invalid', org_unit_id;
11155                 END IF;
11156         END IF;
11157         --
11158         -- Loop over the propagable funds to identify the details
11159         -- from the old fund plus the id of the new one, if it exists.
11160         --
11161         FOR roll_fund in
11162         SELECT
11163             oldf.id AS old_fund,
11164             oldf.org,
11165             oldf.name,
11166             oldf.currency_type,
11167             oldf.code,
11168                 oldf.rollover,
11169             newf.id AS new_fund_id
11170         FROM
11171         acq.fund AS oldf
11172         LEFT JOIN acq.fund AS newf
11173                 ON ( oldf.code = newf.code )
11174         WHERE
11175                     oldf.year = old_year
11176                 AND oldf.propagate
11177         AND newf.year = new_year
11178                 AND oldf.org in (
11179                         SELECT id FROM actor.org_unit_descendants( org_unit_id )
11180                 )
11181         LOOP
11182                 --RAISE NOTICE 'Processing fund %', roll_fund.old_fund;
11183                 --
11184                 IF roll_fund.new_fund_id IS NULL THEN
11185                         --
11186                         -- The old fund hasn't been propagated yet.  Propagate it now.
11187                         --
11188                         INSERT INTO acq.fund (
11189                                 org,
11190                                 name,
11191                                 year,
11192                                 currency_type,
11193                                 code,
11194                                 rollover,
11195                                 propagate,
11196                                 balance_warning_percent,
11197                                 balance_stop_percent
11198                         ) VALUES (
11199                                 roll_fund.org,
11200                                 roll_fund.name,
11201                                 new_year,
11202                                 roll_fund.currency_type,
11203                                 roll_fund.code,
11204                                 true,
11205                                 true,
11206                                 roll_fund.balance_warning_percent,
11207                                 roll_fund.balance_stop_percent
11208                         )
11209                         RETURNING id INTO new_fund;
11210                 ELSE
11211                         new_fund = roll_fund.new_fund_id;
11212                 END IF;
11213                 --
11214                 -- Determine the amount to transfer
11215                 --
11216                 SELECT amount
11217                 INTO xfer_amount
11218                 FROM acq.fund_spent_balance
11219                 WHERE fund = roll_fund.old_fund;
11220                 --
11221                 IF xfer_amount <> 0 THEN
11222                         IF roll_fund.rollover THEN
11223                                 --
11224                                 -- Transfer balance from old fund to new
11225                                 --
11226                                 --RAISE NOTICE 'Transferring % from fund % to %', xfer_amount, roll_fund.old_fund, new_fund;
11227                                 --
11228                                 PERFORM acq.transfer_fund(
11229                                         roll_fund.old_fund,
11230                                         xfer_amount,
11231                                         new_fund,
11232                                         xfer_amount,
11233                                         user_id,
11234                                         'Rollover'
11235                                 );
11236                         ELSE
11237                                 --
11238                                 -- Transfer balance from old fund to the void
11239                                 --
11240                                 -- RAISE NOTICE 'Transferring % from fund % to the void', xfer_amount, roll_fund.old_fund;
11241                                 --
11242                                 PERFORM acq.transfer_fund(
11243                                         roll_fund.old_fund,
11244                                         xfer_amount,
11245                                         NULL,
11246                                         NULL,
11247                                         user_id,
11248                                         'Rollover'
11249                                 );
11250                         END IF;
11251                 END IF;
11252                 --
11253                 IF roll_fund.rollover THEN
11254                         --
11255                         -- Move any lineitems from the old fund to the new one
11256                         -- where the associated debit is an encumbrance.
11257                         --
11258                         -- Any other tables tying expenditure details to funds should
11259                         -- receive similar treatment.  At this writing there are none.
11260                         --
11261                         UPDATE acq.lineitem_detail
11262                         SET fund = new_fund
11263                         WHERE
11264                         fund = roll_fund.old_fund -- this condition may be redundant
11265                         AND fund_debit in
11266                         (
11267                                 SELECT id
11268                                 FROM acq.fund_debit
11269                                 WHERE
11270                                 fund = roll_fund.old_fund
11271                                 AND encumbrance
11272                         );
11273                         --
11274                         -- Move encumbrance debits from the old fund to the new fund
11275                         --
11276                         UPDATE acq.fund_debit
11277                         SET fund = new_fund
11278                         wHERE
11279                                 fund = roll_fund.old_fund
11280                                 AND encumbrance;
11281                 END IF;
11282                 --
11283                 -- Mark old fund as inactive, now that we've closed it
11284                 --
11285                 UPDATE acq.fund
11286                 SET active = FALSE
11287                 WHERE id = roll_fund.old_fund;
11288         END LOOP;
11289 END;
11290 $$ LANGUAGE plpgsql;
11291
11292 CREATE OR REPLACE FUNCTION public.remove_commas( TEXT ) RETURNS TEXT AS $$
11293     SELECT regexp_replace($1, ',', '', 'g');
11294 $$ LANGUAGE SQL STRICT IMMUTABLE;
11295
11296 CREATE OR REPLACE FUNCTION public.remove_whitespace( TEXT ) RETURNS TEXT AS $$
11297     SELECT regexp_replace(normalize_space($1), E'\\s+', '', 'g');
11298 $$ LANGUAGE SQL STRICT IMMUTABLE;
11299
11300 CREATE TABLE acq.distribution_formula_application (
11301     id BIGSERIAL PRIMARY KEY,
11302     creator INT NOT NULL REFERENCES actor.usr(id) DEFERRABLE INITIALLY DEFERRED,
11303     create_time TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(),
11304     formula INT NOT NULL
11305         REFERENCES acq.distribution_formula(id) DEFERRABLE INITIALLY DEFERRED,
11306     lineitem INT NOT NULL
11307         REFERENCES acq.lineitem( id )
11308                 ON DELETE CASCADE
11309                 DEFERRABLE INITIALLY DEFERRED
11310 );
11311
11312 CREATE INDEX acqdfa_df_idx
11313     ON acq.distribution_formula_application(formula);
11314 CREATE INDEX acqdfa_li_idx
11315     ON acq.distribution_formula_application(lineitem);
11316 CREATE INDEX acqdfa_creator_idx
11317     ON acq.distribution_formula_application(creator);
11318
11319 CREATE TABLE acq.user_request_type (
11320     id      SERIAL  PRIMARY KEY,
11321     label   TEXT    NOT NULL UNIQUE -- i18n-ize
11322 );
11323
11324 INSERT INTO acq.user_request_type (id,label) VALUES (1, oils_i18n_gettext('1', 'Books', 'aurt', 'label'));
11325 INSERT INTO acq.user_request_type (id,label) VALUES (2, oils_i18n_gettext('2', 'Journal/Magazine & Newspaper Articles', 'aurt', 'label'));
11326 INSERT INTO acq.user_request_type (id,label) VALUES (3, oils_i18n_gettext('3', 'Audiobooks', 'aurt', 'label'));
11327 INSERT INTO acq.user_request_type (id,label) VALUES (4, oils_i18n_gettext('4', 'Music', 'aurt', 'label'));
11328 INSERT INTO acq.user_request_type (id,label) VALUES (5, oils_i18n_gettext('5', 'DVDs', 'aurt', 'label'));
11329
11330 SELECT SETVAL('acq.user_request_type_id_seq'::TEXT, 6);
11331
11332 CREATE TABLE acq.cancel_reason (
11333         id            SERIAL            PRIMARY KEY,
11334         org_unit      INTEGER           NOT NULL REFERENCES actor.org_unit( id )
11335                                         DEFERRABLE INITIALLY DEFERRED,
11336         label         TEXT              NOT NULL,
11337         description   TEXT              NOT NULL,
11338         keep_debits   BOOL              NOT NULL DEFAULT FALSE,
11339         CONSTRAINT acq_cancel_reason_one_per_org_unit UNIQUE( org_unit, label )
11340 );
11341
11342 -- Reserve ids 1-999 for stock reasons
11343 -- Reserve ids 1000-1999 for EDI reasons
11344 -- 2000+ are available for staff to create
11345
11346 SELECT SETVAL('acq.cancel_reason_id_seq'::TEXT, 2000);
11347
11348 CREATE TABLE acq.user_request (
11349     id                  SERIAL  PRIMARY KEY,
11350     usr                 INT     NOT NULL REFERENCES actor.usr (id), -- requesting user
11351     hold                BOOL    NOT NULL DEFAULT TRUE,
11352
11353     pickup_lib          INT     NOT NULL REFERENCES actor.org_unit (id), -- pickup lib
11354     holdable_formats    TEXT,           -- nullable, for use in hold creation
11355     phone_notify        TEXT,
11356     email_notify        BOOL    NOT NULL DEFAULT TRUE,
11357     lineitem            INT     REFERENCES acq.lineitem (id) ON DELETE CASCADE,
11358     eg_bib              BIGINT  REFERENCES biblio.record_entry (id) ON DELETE CASCADE,
11359     request_date        TIMESTAMPTZ NOT NULL DEFAULT NOW(), -- when they requested it
11360     need_before         TIMESTAMPTZ,    -- don't create holds after this
11361     max_fee             TEXT,
11362
11363     request_type        INT     NOT NULL REFERENCES acq.user_request_type (id), 
11364     isxn                TEXT,
11365     title               TEXT,
11366     volume              TEXT,
11367     author              TEXT,
11368     article_title       TEXT,
11369     article_pages       TEXT,
11370     publisher           TEXT,
11371     location            TEXT,
11372     pubdate             TEXT,
11373     mentioned           TEXT,
11374     other_info          TEXT,
11375         cancel_reason       INT              REFERENCES acq.cancel_reason( id )
11376                                              DEFERRABLE INITIALLY DEFERRED
11377 );
11378
11379 CREATE TABLE acq.lineitem_alert_text (
11380         id               SERIAL         PRIMARY KEY,
11381         code             TEXT           UNIQUE NOT NULL,
11382         description      TEXT,
11383         owning_lib       INT            NOT NULL
11384                                         REFERENCES actor.org_unit(id)
11385                                         DEFERRABLE INITIALLY DEFERRED,
11386         CONSTRAINT alert_one_code_per_org UNIQUE (code, owning_lib)
11387 );
11388
11389 ALTER TABLE acq.lineitem_note
11390         ADD COLUMN alert_text    INT     REFERENCES acq.lineitem_alert_text(id)
11391                                          DEFERRABLE INITIALLY DEFERRED;
11392
11393 -- add ON DELETE CASCADE clause
11394
11395 ALTER TABLE acq.lineitem_note
11396         DROP CONSTRAINT lineitem_note_lineitem_fkey;
11397
11398 ALTER TABLE acq.lineitem_note
11399         ADD FOREIGN KEY (lineitem) REFERENCES acq.lineitem( id )
11400                 ON DELETE CASCADE
11401                 DEFERRABLE INITIALLY DEFERRED;
11402
11403 ALTER TABLE acq.lineitem_note
11404         ADD COLUMN vendor_public BOOLEAN NOT NULL DEFAULT FALSE;
11405
11406 CREATE TABLE acq.invoice_method (
11407     code    TEXT    PRIMARY KEY,
11408     name    TEXT    NOT NULL -- i18n-ize
11409 );
11410 INSERT INTO acq.invoice_method (code,name) VALUES ('EDI',oils_i18n_gettext('EDI', 'EDI', 'acqim', 'name'));
11411 INSERT INTO acq.invoice_method (code,name) VALUES ('PPR',oils_i18n_gettext('PPR', 'Paper', 'acqit', 'name'));
11412
11413 CREATE TABLE acq.invoice_payment_method (
11414         code      TEXT     PRIMARY KEY,
11415         name      TEXT     NOT NULL
11416 );
11417
11418 CREATE TABLE acq.invoice (
11419     id             SERIAL      PRIMARY KEY,
11420     receiver       INT         NOT NULL REFERENCES actor.org_unit (id),
11421     provider       INT         NOT NULL REFERENCES acq.provider (id),
11422     shipper        INT         NOT NULL REFERENCES acq.provider (id),
11423     recv_date      TIMESTAMPTZ NOT NULL DEFAULT NOW(),
11424     recv_method    TEXT        NOT NULL REFERENCES acq.invoice_method (code) DEFAULT 'EDI',
11425     inv_type       TEXT,       -- A "type" field is desired, but no idea what goes here
11426     inv_ident      TEXT        NOT NULL, -- vendor-supplied invoice id/number
11427         payment_auth   TEXT,
11428         payment_method TEXT        REFERENCES acq.invoice_payment_method (code)
11429                                    DEFERRABLE INITIALLY DEFERRED,
11430         note           TEXT,
11431     complete       BOOL        NOT NULL DEFAULT FALSE,
11432     CONSTRAINT inv_ident_once_per_provider UNIQUE(provider, inv_ident)
11433 );
11434
11435 CREATE TABLE acq.invoice_entry (
11436     id              SERIAL      PRIMARY KEY,
11437     invoice         INT         NOT NULL REFERENCES acq.invoice (id) ON DELETE CASCADE,
11438     purchase_order  INT         REFERENCES acq.purchase_order (id) ON UPDATE CASCADE ON DELETE SET NULL,
11439     lineitem        INT         REFERENCES acq.lineitem (id) ON UPDATE CASCADE ON DELETE SET NULL,
11440     inv_item_count  INT         NOT NULL, -- How many acqlids did they say they sent
11441     phys_item_count INT, -- and how many did staff count
11442     note            TEXT,
11443     billed_per_item BOOL,
11444     cost_billed     NUMERIC(8,2),
11445     actual_cost     NUMERIC(8,2),
11446         amount_paid     NUMERIC (8,2)
11447 );
11448
11449 CREATE TABLE acq.invoice_item_type (
11450     code    TEXT    PRIMARY KEY,
11451     name    TEXT    NOT NULL, -- i18n-ize
11452         prorate BOOL    NOT NULL DEFAULT FALSE
11453 );
11454
11455 INSERT INTO acq.invoice_item_type (code,name) VALUES ('TAX',oils_i18n_gettext('TAX', 'Tax', 'aiit', 'name'));
11456 INSERT INTO acq.invoice_item_type (code,name) VALUES ('PRO',oils_i18n_gettext('PRO', 'Processing Fee', 'aiit', 'name'));
11457 INSERT INTO acq.invoice_item_type (code,name) VALUES ('SHP',oils_i18n_gettext('SHP', 'Shipping Charge', 'aiit', 'name'));
11458 INSERT INTO acq.invoice_item_type (code,name) VALUES ('HND',oils_i18n_gettext('HND', 'Handling Charge', 'aiit', 'name'));
11459 INSERT INTO acq.invoice_item_type (code,name) VALUES ('ITM',oils_i18n_gettext('ITM', 'Non-library Item', 'aiit', 'name'));
11460 INSERT INTO acq.invoice_item_type (code,name) VALUES ('SUB',oils_i18n_gettext('SUB', 'Searial Subscription', 'aiit', 'name'));
11461
11462 CREATE TABLE acq.po_item (
11463         id              SERIAL      PRIMARY KEY,
11464         purchase_order  INT         REFERENCES acq.purchase_order (id)
11465                                     ON UPDATE CASCADE ON DELETE SET NULL
11466                                     DEFERRABLE INITIALLY DEFERRED,
11467         fund_debit      INT         REFERENCES acq.fund_debit (id)
11468                                     DEFERRABLE INITIALLY DEFERRED,
11469         inv_item_type   TEXT        NOT NULL
11470                                     REFERENCES acq.invoice_item_type (code)
11471                                     DEFERRABLE INITIALLY DEFERRED,
11472         title           TEXT,
11473         author          TEXT,
11474         note            TEXT,
11475         estimated_cost  NUMERIC(8,2),
11476         fund            INT         REFERENCES acq.fund (id)
11477                                     DEFERRABLE INITIALLY DEFERRED,
11478         target          BIGINT
11479 );
11480
11481 CREATE TABLE acq.invoice_item ( -- for invoice-only debits: taxes/fees/non-bib items/etc
11482     id              SERIAL      PRIMARY KEY,
11483     invoice         INT         NOT NULL REFERENCES acq.invoice (id) ON UPDATE CASCADE ON DELETE CASCADE,
11484     purchase_order  INT         REFERENCES acq.purchase_order (id) ON UPDATE CASCADE ON DELETE SET NULL,
11485     fund_debit      INT         REFERENCES acq.fund_debit (id),
11486     inv_item_type   TEXT        NOT NULL REFERENCES acq.invoice_item_type (code),
11487     title           TEXT,
11488     author          TEXT,
11489     note            TEXT,
11490     cost_billed     NUMERIC(8,2),
11491     actual_cost     NUMERIC(8,2),
11492     fund            INT         REFERENCES acq.fund (id)
11493                                 DEFERRABLE INITIALLY DEFERRED,
11494     amount_paid     NUMERIC (8,2),
11495     po_item         INT         REFERENCES acq.po_item (id)
11496                                 DEFERRABLE INITIALLY DEFERRED,
11497     target          BIGINT
11498 );
11499
11500 CREATE TABLE acq.edi_message (
11501     id               SERIAL          PRIMARY KEY,
11502     account          INTEGER         REFERENCES acq.edi_account(id)
11503                                      DEFERRABLE INITIALLY DEFERRED,
11504     remote_file      TEXT,
11505     create_time      TIMESTAMPTZ     NOT NULL DEFAULT now(),
11506     translate_time   TIMESTAMPTZ,
11507     process_time     TIMESTAMPTZ,
11508     error_time       TIMESTAMPTZ,
11509     status           TEXT            NOT NULL DEFAULT 'new'
11510                                      CONSTRAINT status_value CHECK
11511                                      ( status IN (
11512                                         'new',          -- needs to be translated
11513                                         'translated',   -- needs to be processed
11514                                         'trans_error',  -- error in translation step
11515                                         'processed',    -- needs to have remote_file deleted
11516                                         'proc_error',   -- error in processing step
11517                                         'delete_error', -- error in deletion
11518                                         'retry',        -- need to retry
11519                                         'complete'      -- done
11520                                      )),
11521     edi              TEXT,
11522     jedi             TEXT,
11523     error            TEXT,
11524     purchase_order   INT             REFERENCES acq.purchase_order
11525                                      DEFERRABLE INITIALLY DEFERRED,
11526     message_type     TEXT            NOT NULL CONSTRAINT valid_message_type
11527                                      CHECK ( message_type IN (
11528                                         'ORDERS',
11529                                         'ORDRSP',
11530                                         'INVOIC',
11531                                         'OSTENQ',
11532                                         'OSTRPT'
11533                                      ))
11534 );
11535
11536 ALTER TABLE actor.org_address ADD COLUMN san TEXT;
11537
11538 ALTER TABLE acq.provider_address
11539         ADD COLUMN fax_phone TEXT;
11540
11541 ALTER TABLE acq.provider_contact_address
11542         ADD COLUMN fax_phone TEXT;
11543
11544 CREATE TABLE acq.provider_note (
11545     id      SERIAL              PRIMARY KEY,
11546     provider    INT             NOT NULL REFERENCES acq.provider (id) DEFERRABLE INITIALLY DEFERRED,
11547     creator     INT             NOT NULL REFERENCES actor.usr (id) DEFERRABLE INITIALLY DEFERRED,
11548     editor      INT             NOT NULL REFERENCES actor.usr (id) DEFERRABLE INITIALLY DEFERRED,
11549     create_time TIMESTAMP WITH TIME ZONE    NOT NULL DEFAULT NOW(),
11550     edit_time   TIMESTAMP WITH TIME ZONE    NOT NULL DEFAULT NOW(),
11551     value       TEXT            NOT NULL
11552 );
11553 CREATE INDEX acq_pro_note_pro_idx      ON acq.provider_note ( provider );
11554 CREATE INDEX acq_pro_note_creator_idx  ON acq.provider_note ( creator );
11555 CREATE INDEX acq_pro_note_editor_idx   ON acq.provider_note ( editor );
11556
11557 -- For each fund: the total allocation from all sources, in the
11558 -- currency of the fund (or 0 if there are no allocations)
11559
11560 CREATE VIEW acq.all_fund_allocation_total AS
11561 SELECT
11562     f.id AS fund,
11563     COALESCE( SUM( a.amount * acq.exchange_ratio(
11564         s.currency_type, f.currency_type))::numeric(100,2), 0 )
11565     AS amount
11566 FROM
11567     acq.fund f
11568         LEFT JOIN acq.fund_allocation a
11569             ON a.fund = f.id
11570         LEFT JOIN acq.funding_source s
11571             ON a.funding_source = s.id
11572 GROUP BY
11573     f.id;
11574
11575 -- For every fund: the total encumbrances (or 0 if none),
11576 -- in the currency of the fund.
11577
11578 CREATE VIEW acq.all_fund_encumbrance_total AS
11579 SELECT
11580         f.id AS fund,
11581         COALESCE( encumb.amount, 0 ) AS amount
11582 FROM
11583         acq.fund AS f
11584                 LEFT JOIN (
11585                         SELECT
11586                                 fund,
11587                                 sum( amount ) AS amount
11588                         FROM
11589                                 acq.fund_debit
11590                         WHERE
11591                                 encumbrance
11592                         GROUP BY fund
11593                 ) AS encumb
11594                         ON f.id = encumb.fund;
11595
11596 -- For every fund: the total spent (or 0 if none),
11597 -- in the currency of the fund.
11598
11599 CREATE VIEW acq.all_fund_spent_total AS
11600 SELECT
11601     f.id AS fund,
11602     COALESCE( spent.amount, 0 ) AS amount
11603 FROM
11604     acq.fund AS f
11605         LEFT JOIN (
11606             SELECT
11607                 fund,
11608                 sum( amount ) AS amount
11609             FROM
11610                 acq.fund_debit
11611             WHERE
11612                 NOT encumbrance
11613             GROUP BY fund
11614         ) AS spent
11615             ON f.id = spent.fund;
11616
11617 -- For each fund: the amount not yet spent, in the currency
11618 -- of the fund.  May include encumbrances.
11619
11620 CREATE VIEW acq.all_fund_spent_balance AS
11621 SELECT
11622         c.fund,
11623         c.amount - d.amount AS amount
11624 FROM acq.all_fund_allocation_total c
11625     LEFT JOIN acq.all_fund_spent_total d USING (fund);
11626
11627 -- For each fund: the amount neither spent nor encumbered,
11628 -- in the currency of the fund
11629
11630 CREATE VIEW acq.all_fund_combined_balance AS
11631 SELECT
11632      a.fund,
11633      a.amount - COALESCE( c.amount, 0 ) AS amount
11634 FROM
11635      acq.all_fund_allocation_total a
11636         LEFT OUTER JOIN (
11637             SELECT
11638                 fund,
11639                 SUM( amount ) AS amount
11640             FROM
11641                 acq.fund_debit
11642             GROUP BY
11643                 fund
11644         ) AS c USING ( fund );
11645
11646 CREATE OR REPLACE FUNCTION actor.usr_merge(
11647         src_usr INT,
11648         dest_usr INT,
11649         del_addrs BOOLEAN,
11650         del_cards BOOLEAN,
11651         deactivate_cards BOOLEAN
11652 ) RETURNS VOID AS $$
11653 DECLARE
11654         suffix TEXT;
11655         bucket_row RECORD;
11656         picklist_row RECORD;
11657         queue_row RECORD;
11658         folder_row RECORD;
11659 BEGIN
11660
11661     -- do some initial cleanup 
11662     UPDATE actor.usr SET card = NULL WHERE id = src_usr;
11663     UPDATE actor.usr SET mailing_address = NULL WHERE id = src_usr;
11664     UPDATE actor.usr SET billing_address = NULL WHERE id = src_usr;
11665
11666     -- actor.*
11667     IF del_cards THEN
11668         DELETE FROM actor.card where usr = src_usr;
11669     ELSE
11670         IF deactivate_cards THEN
11671             UPDATE actor.card SET active = 'f' WHERE usr = src_usr;
11672         END IF;
11673         UPDATE actor.card SET usr = dest_usr WHERE usr = src_usr;
11674     END IF;
11675
11676
11677     IF del_addrs THEN
11678         DELETE FROM actor.usr_address WHERE usr = src_usr;
11679     ELSE
11680         UPDATE actor.usr_address SET usr = dest_usr WHERE usr = src_usr;
11681     END IF;
11682
11683     UPDATE actor.usr_note SET usr = dest_usr WHERE usr = src_usr;
11684     -- dupes are technically OK in actor.usr_standing_penalty, should manually delete them...
11685     UPDATE actor.usr_standing_penalty SET usr = dest_usr WHERE usr = src_usr;
11686     PERFORM actor.usr_merge_rows('actor.usr_org_unit_opt_in', 'usr', src_usr, dest_usr);
11687     PERFORM actor.usr_merge_rows('actor.usr_setting', 'usr', src_usr, dest_usr);
11688
11689     -- permission.*
11690     PERFORM actor.usr_merge_rows('permission.usr_perm_map', 'usr', src_usr, dest_usr);
11691     PERFORM actor.usr_merge_rows('permission.usr_object_perm_map', 'usr', src_usr, dest_usr);
11692     PERFORM actor.usr_merge_rows('permission.usr_grp_map', 'usr', src_usr, dest_usr);
11693     PERFORM actor.usr_merge_rows('permission.usr_work_ou_map', 'usr', src_usr, dest_usr);
11694
11695
11696     -- container.*
11697         
11698         -- For each *_bucket table: transfer every bucket belonging to src_usr
11699         -- into the custody of dest_usr.
11700         --
11701         -- In order to avoid colliding with an existing bucket owned by
11702         -- the destination user, append the source user's id (in parenthesese)
11703         -- to the name.  If you still get a collision, add successive
11704         -- spaces to the name and keep trying until you succeed.
11705         --
11706         FOR bucket_row in
11707                 SELECT id, name
11708                 FROM   container.biblio_record_entry_bucket
11709                 WHERE  owner = src_usr
11710         LOOP
11711                 suffix := ' (' || src_usr || ')';
11712                 LOOP
11713                         BEGIN
11714                                 UPDATE  container.biblio_record_entry_bucket
11715                                 SET     owner = dest_usr, name = name || suffix
11716                                 WHERE   id = bucket_row.id;
11717                         EXCEPTION WHEN unique_violation THEN
11718                                 suffix := suffix || ' ';
11719                                 CONTINUE;
11720                         END;
11721                         EXIT;
11722                 END LOOP;
11723         END LOOP;
11724
11725         FOR bucket_row in
11726                 SELECT id, name
11727                 FROM   container.call_number_bucket
11728                 WHERE  owner = src_usr
11729         LOOP
11730                 suffix := ' (' || src_usr || ')';
11731                 LOOP
11732                         BEGIN
11733                                 UPDATE  container.call_number_bucket
11734                                 SET     owner = dest_usr, name = name || suffix
11735                                 WHERE   id = bucket_row.id;
11736                         EXCEPTION WHEN unique_violation THEN
11737                                 suffix := suffix || ' ';
11738                                 CONTINUE;
11739                         END;
11740                         EXIT;
11741                 END LOOP;
11742         END LOOP;
11743
11744         FOR bucket_row in
11745                 SELECT id, name
11746                 FROM   container.copy_bucket
11747                 WHERE  owner = src_usr
11748         LOOP
11749                 suffix := ' (' || src_usr || ')';
11750                 LOOP
11751                         BEGIN
11752                                 UPDATE  container.copy_bucket
11753                                 SET     owner = dest_usr, name = name || suffix
11754                                 WHERE   id = bucket_row.id;
11755                         EXCEPTION WHEN unique_violation THEN
11756                                 suffix := suffix || ' ';
11757                                 CONTINUE;
11758                         END;
11759                         EXIT;
11760                 END LOOP;
11761         END LOOP;
11762
11763         FOR bucket_row in
11764                 SELECT id, name
11765                 FROM   container.user_bucket
11766                 WHERE  owner = src_usr
11767         LOOP
11768                 suffix := ' (' || src_usr || ')';
11769                 LOOP
11770                         BEGIN
11771                                 UPDATE  container.user_bucket
11772                                 SET     owner = dest_usr, name = name || suffix
11773                                 WHERE   id = bucket_row.id;
11774                         EXCEPTION WHEN unique_violation THEN
11775                                 suffix := suffix || ' ';
11776                                 CONTINUE;
11777                         END;
11778                         EXIT;
11779                 END LOOP;
11780         END LOOP;
11781
11782         UPDATE container.user_bucket_item SET target_user = dest_usr WHERE target_user = src_usr;
11783
11784     -- vandelay.*
11785         -- transfer queues the same way we transfer buckets (see above)
11786         FOR queue_row in
11787                 SELECT id, name
11788                 FROM   vandelay.queue
11789                 WHERE  owner = src_usr
11790         LOOP
11791                 suffix := ' (' || src_usr || ')';
11792                 LOOP
11793                         BEGIN
11794                                 UPDATE  vandelay.queue
11795                                 SET     owner = dest_usr, name = name || suffix
11796                                 WHERE   id = queue_row.id;
11797                         EXCEPTION WHEN unique_violation THEN
11798                                 suffix := suffix || ' ';
11799                                 CONTINUE;
11800                         END;
11801                         EXIT;
11802                 END LOOP;
11803         END LOOP;
11804
11805     -- money.*
11806     PERFORM actor.usr_merge_rows('money.collections_tracker', 'usr', src_usr, dest_usr);
11807     PERFORM actor.usr_merge_rows('money.collections_tracker', 'collector', src_usr, dest_usr);
11808     UPDATE money.billable_xact SET usr = dest_usr WHERE usr = src_usr;
11809     UPDATE money.billing SET voider = dest_usr WHERE voider = src_usr;
11810     UPDATE money.bnm_payment SET accepting_usr = dest_usr WHERE accepting_usr = src_usr;
11811
11812     -- action.*
11813     UPDATE action.circulation SET usr = dest_usr WHERE usr = src_usr;
11814     UPDATE action.circulation SET circ_staff = dest_usr WHERE circ_staff = src_usr;
11815     UPDATE action.circulation SET checkin_staff = dest_usr WHERE checkin_staff = src_usr;
11816
11817     UPDATE action.hold_request SET usr = dest_usr WHERE usr = src_usr;
11818     UPDATE action.hold_request SET fulfillment_staff = dest_usr WHERE fulfillment_staff = src_usr;
11819     UPDATE action.hold_request SET requestor = dest_usr WHERE requestor = src_usr;
11820     UPDATE action.hold_notification SET notify_staff = dest_usr WHERE notify_staff = src_usr;
11821
11822     UPDATE action.in_house_use SET staff = dest_usr WHERE staff = src_usr;
11823     UPDATE action.non_cataloged_circulation SET staff = dest_usr WHERE staff = src_usr;
11824     UPDATE action.non_cataloged_circulation SET patron = dest_usr WHERE patron = src_usr;
11825     UPDATE action.non_cat_in_house_use SET staff = dest_usr WHERE staff = src_usr;
11826     UPDATE action.survey_response SET usr = dest_usr WHERE usr = src_usr;
11827
11828     -- acq.*
11829     UPDATE acq.fund_allocation SET allocator = dest_usr WHERE allocator = src_usr;
11830     UPDATE acq.fund_transfer SET transfer_user = dest_usr WHERE transfer_user = src_usr;
11831
11832         -- transfer picklists the same way we transfer buckets (see above)
11833         FOR picklist_row in
11834                 SELECT id, name
11835                 FROM   acq.picklist
11836                 WHERE  owner = src_usr
11837         LOOP
11838                 suffix := ' (' || src_usr || ')';
11839                 LOOP
11840                         BEGIN
11841                                 UPDATE  acq.picklist
11842                                 SET     owner = dest_usr, name = name || suffix
11843                                 WHERE   id = picklist_row.id;
11844                         EXCEPTION WHEN unique_violation THEN
11845                                 suffix := suffix || ' ';
11846                                 CONTINUE;
11847                         END;
11848                         EXIT;
11849                 END LOOP;
11850         END LOOP;
11851
11852     UPDATE acq.purchase_order SET owner = dest_usr WHERE owner = src_usr;
11853     UPDATE acq.po_note SET creator = dest_usr WHERE creator = src_usr;
11854     UPDATE acq.po_note SET editor = dest_usr WHERE editor = src_usr;
11855         UPDATE acq.provider_note SET creator = dest_usr WHERE creator = src_usr;
11856         UPDATE acq.provider_note SET editor = dest_usr WHERE editor = src_usr;
11857     UPDATE acq.lineitem_note SET creator = dest_usr WHERE creator = src_usr;
11858     UPDATE acq.lineitem_note SET editor = dest_usr WHERE editor = src_usr;
11859     UPDATE acq.lineitem_usr_attr_definition SET usr = dest_usr WHERE usr = src_usr;
11860
11861     -- asset.*
11862     UPDATE asset.copy SET creator = dest_usr WHERE creator = src_usr;
11863     UPDATE asset.copy SET editor = dest_usr WHERE editor = src_usr;
11864     UPDATE asset.copy_note SET creator = dest_usr WHERE creator = src_usr;
11865     UPDATE asset.call_number SET creator = dest_usr WHERE creator = src_usr;
11866     UPDATE asset.call_number SET editor = dest_usr WHERE editor = src_usr;
11867     UPDATE asset.call_number_note SET creator = dest_usr WHERE creator = src_usr;
11868
11869     -- serial.*
11870     UPDATE serial.record_entry SET creator = dest_usr WHERE creator = src_usr;
11871     UPDATE serial.record_entry SET editor = dest_usr WHERE editor = src_usr;
11872
11873     -- reporter.*
11874     -- It's not uncommon to define the reporter schema in a replica 
11875     -- DB only, so don't assume these tables exist in the write DB.
11876     BEGIN
11877         UPDATE reporter.template SET owner = dest_usr WHERE owner = src_usr;
11878     EXCEPTION WHEN undefined_table THEN
11879         -- do nothing
11880     END;
11881     BEGIN
11882         UPDATE reporter.report SET owner = dest_usr WHERE owner = src_usr;
11883     EXCEPTION WHEN undefined_table THEN
11884         -- do nothing
11885     END;
11886     BEGIN
11887         UPDATE reporter.schedule SET runner = dest_usr WHERE runner = src_usr;
11888     EXCEPTION WHEN undefined_table THEN
11889         -- do nothing
11890     END;
11891     BEGIN
11892                 -- transfer folders the same way we transfer buckets (see above)
11893                 FOR folder_row in
11894                         SELECT id, name
11895                         FROM   reporter.template_folder
11896                         WHERE  owner = src_usr
11897                 LOOP
11898                         suffix := ' (' || src_usr || ')';
11899                         LOOP
11900                                 BEGIN
11901                                         UPDATE  reporter.template_folder
11902                                         SET     owner = dest_usr, name = name || suffix
11903                                         WHERE   id = folder_row.id;
11904                                 EXCEPTION WHEN unique_violation THEN
11905                                         suffix := suffix || ' ';
11906                                         CONTINUE;
11907                                 END;
11908                                 EXIT;
11909                         END LOOP;
11910                 END LOOP;
11911     EXCEPTION WHEN undefined_table THEN
11912         -- do nothing
11913     END;
11914     BEGIN
11915                 -- transfer folders the same way we transfer buckets (see above)
11916                 FOR folder_row in
11917                         SELECT id, name
11918                         FROM   reporter.report_folder
11919                         WHERE  owner = src_usr
11920                 LOOP
11921                         suffix := ' (' || src_usr || ')';
11922                         LOOP
11923                                 BEGIN
11924                                         UPDATE  reporter.report_folder
11925                                         SET     owner = dest_usr, name = name || suffix
11926                                         WHERE   id = folder_row.id;
11927                                 EXCEPTION WHEN unique_violation THEN
11928                                         suffix := suffix || ' ';
11929                                         CONTINUE;
11930                                 END;
11931                                 EXIT;
11932                         END LOOP;
11933                 END LOOP;
11934     EXCEPTION WHEN undefined_table THEN
11935         -- do nothing
11936     END;
11937     BEGIN
11938                 -- transfer folders the same way we transfer buckets (see above)
11939                 FOR folder_row in
11940                         SELECT id, name
11941                         FROM   reporter.output_folder
11942                         WHERE  owner = src_usr
11943                 LOOP
11944                         suffix := ' (' || src_usr || ')';
11945                         LOOP
11946                                 BEGIN
11947                                         UPDATE  reporter.output_folder
11948                                         SET     owner = dest_usr, name = name || suffix
11949                                         WHERE   id = folder_row.id;
11950                                 EXCEPTION WHEN unique_violation THEN
11951                                         suffix := suffix || ' ';
11952                                         CONTINUE;
11953                                 END;
11954                                 EXIT;
11955                         END LOOP;
11956                 END LOOP;
11957     EXCEPTION WHEN undefined_table THEN
11958         -- do nothing
11959     END;
11960
11961     -- Finally, delete the source user
11962     DELETE FROM actor.usr WHERE id = src_usr;
11963
11964 END;
11965 $$ LANGUAGE plpgsql;
11966
11967 -- The "add" trigger functions should protect against existing NULLed values, just in case
11968 CREATE OR REPLACE FUNCTION money.materialized_summary_billing_add () RETURNS TRIGGER AS $$
11969 BEGIN
11970     IF NOT NEW.voided THEN
11971         UPDATE  money.materialized_billable_xact_summary
11972           SET   total_owed = COALESCE(total_owed, 0.0::numeric) + NEW.amount,
11973             last_billing_ts = NEW.billing_ts,
11974             last_billing_note = NEW.note,
11975             last_billing_type = NEW.billing_type,
11976             balance_owed = balance_owed + NEW.amount
11977           WHERE id = NEW.xact;
11978     END IF;
11979
11980     RETURN NEW;
11981 END;
11982 $$ LANGUAGE PLPGSQL;
11983
11984 CREATE OR REPLACE FUNCTION money.materialized_summary_payment_add () RETURNS TRIGGER AS $$
11985 BEGIN
11986     IF NOT NEW.voided THEN
11987         UPDATE  money.materialized_billable_xact_summary
11988           SET   total_paid = COALESCE(total_paid, 0.0::numeric) + NEW.amount,
11989             last_payment_ts = NEW.payment_ts,
11990             last_payment_note = NEW.note,
11991             last_payment_type = TG_ARGV[0],
11992             balance_owed = balance_owed - NEW.amount
11993           WHERE id = NEW.xact;
11994     END IF;
11995
11996     RETURN NEW;
11997 END;
11998 $$ LANGUAGE PLPGSQL;
11999
12000 -- Refresh the mat view with the corrected underlying view
12001 TRUNCATE money.materialized_billable_xact_summary;
12002 INSERT INTO money.materialized_billable_xact_summary SELECT * FROM money.billable_xact_summary;
12003
12004 CREATE OR REPLACE FUNCTION permission.usr_has_perm_at_nd(
12005     user_id    IN INTEGER,
12006     perm_code  IN TEXT
12007 )
12008 RETURNS SETOF INTEGER AS $$
12009 --
12010 -- Return a set of all the org units for which a given user has a given
12011 -- permission, granted directly (not through inheritance from a parent
12012 -- org unit).
12013 --
12014 -- The permissions apply to a minimum depth of the org unit hierarchy,
12015 -- for the org unit(s) to which the user is assigned.  (They also apply
12016 -- to the subordinates of those org units, but we don't report the
12017 -- subordinates here.)
12018 --
12019 -- For purposes of this function, the permission.usr_work_ou_map table
12020 -- defines which users belong to which org units.  I.e. we ignore the
12021 -- home_ou column of actor.usr.
12022 --
12023 -- The result set may contain duplicates, which should be eliminated
12024 -- by a DISTINCT clause.
12025 --
12026 DECLARE
12027     b_super       BOOLEAN;
12028     n_perm        INTEGER;
12029     n_min_depth   INTEGER;
12030     n_work_ou     INTEGER;
12031     n_curr_ou     INTEGER;
12032     n_depth       INTEGER;
12033     n_curr_depth  INTEGER;
12034 BEGIN
12035     --
12036     -- Check for superuser
12037     --
12038     SELECT INTO b_super
12039         super_user
12040     FROM
12041         actor.usr
12042     WHERE
12043         id = user_id;
12044     --
12045     IF NOT FOUND THEN
12046         return;             -- No user?  No permissions.
12047     ELSIF b_super THEN
12048         --
12049         -- Super user has all permissions everywhere
12050         --
12051         FOR n_work_ou IN
12052             SELECT
12053                 id
12054             FROM
12055                 actor.org_unit
12056             WHERE
12057                 parent_ou IS NULL
12058         LOOP
12059             RETURN NEXT n_work_ou;
12060         END LOOP;
12061         RETURN;
12062     END IF;
12063     --
12064     -- Translate the permission name
12065     -- to a numeric permission id
12066     --
12067     SELECT INTO n_perm
12068         id
12069     FROM
12070         permission.perm_list
12071     WHERE
12072         code = perm_code;
12073     --
12074     IF NOT FOUND THEN
12075         RETURN;               -- No such permission
12076     END IF;
12077     --
12078     -- Find the highest-level org unit (i.e. the minimum depth)
12079     -- to which the permission is applied for this user
12080     --
12081     -- This query is modified from the one in permission.usr_perms().
12082     --
12083     SELECT INTO n_min_depth
12084         min( depth )
12085     FROM    (
12086         SELECT depth
12087           FROM permission.usr_perm_map upm
12088          WHERE upm.usr = user_id
12089            AND (upm.perm = n_perm OR upm.perm = -1)
12090                     UNION
12091         SELECT  gpm.depth
12092           FROM  permission.grp_perm_map gpm
12093           WHERE (gpm.perm = n_perm OR gpm.perm = -1)
12094             AND gpm.grp IN (
12095                SELECT   (permission.grp_ancestors(
12096                     (SELECT profile FROM actor.usr WHERE id = user_id)
12097                 )).id
12098             )
12099                     UNION
12100         SELECT  p.depth
12101           FROM  permission.grp_perm_map p
12102           WHERE (p.perm = n_perm OR p.perm = -1)
12103             AND p.grp IN (
12104                 SELECT (permission.grp_ancestors(m.grp)).id
12105                 FROM   permission.usr_grp_map m
12106                 WHERE  m.usr = user_id
12107             )
12108     ) AS x;
12109     --
12110     IF NOT FOUND THEN
12111         RETURN;                -- No such permission for this user
12112     END IF;
12113     --
12114     -- Identify the org units to which the user is assigned.  Note that
12115     -- we pay no attention to the home_ou column in actor.usr.
12116     --
12117     FOR n_work_ou IN
12118         SELECT
12119             work_ou
12120         FROM
12121             permission.usr_work_ou_map
12122         WHERE
12123             usr = user_id
12124     LOOP            -- For each org unit to which the user is assigned
12125         --
12126         -- Determine the level of the org unit by a lookup in actor.org_unit_type.
12127         -- We take it on faith that this depth agrees with the actual hierarchy
12128         -- defined in actor.org_unit.
12129         --
12130         SELECT INTO n_depth
12131             type.depth
12132         FROM
12133             actor.org_unit_type type
12134                 INNER JOIN actor.org_unit ou
12135                     ON ( ou.ou_type = type.id )
12136         WHERE
12137             ou.id = n_work_ou;
12138         --
12139         IF NOT FOUND THEN
12140             CONTINUE;        -- Maybe raise exception?
12141         END IF;
12142         --
12143         -- Compare the depth of the work org unit to the
12144         -- minimum depth, and branch accordingly
12145         --
12146         IF n_depth = n_min_depth THEN
12147             --
12148             -- The org unit is at the right depth, so return it.
12149             --
12150             RETURN NEXT n_work_ou;
12151         ELSIF n_depth > n_min_depth THEN
12152             --
12153             -- Traverse the org unit tree toward the root,
12154             -- until you reach the minimum depth determined above
12155             --
12156             n_curr_depth := n_depth;
12157             n_curr_ou := n_work_ou;
12158             WHILE n_curr_depth > n_min_depth LOOP
12159                 SELECT INTO n_curr_ou
12160                     parent_ou
12161                 FROM
12162                     actor.org_unit
12163                 WHERE
12164                     id = n_curr_ou;
12165                 --
12166                 IF FOUND THEN
12167                     n_curr_depth := n_curr_depth - 1;
12168                 ELSE
12169                     --
12170                     -- This can happen only if the hierarchy defined in
12171                     -- actor.org_unit is corrupted, or out of sync with
12172                     -- the depths defined in actor.org_unit_type.
12173                     -- Maybe we should raise an exception here, instead
12174                     -- of silently ignoring the problem.
12175                     --
12176                     n_curr_ou = NULL;
12177                     EXIT;
12178                 END IF;
12179             END LOOP;
12180             --
12181             IF n_curr_ou IS NOT NULL THEN
12182                 RETURN NEXT n_curr_ou;
12183             END IF;
12184         ELSE
12185             --
12186             -- The permission applies only at a depth greater than the work org unit.
12187             -- Use connectby() to find all dependent org units at the specified depth.
12188             --
12189             FOR n_curr_ou IN
12190                 SELECT ou::INTEGER
12191                 FROM connectby(
12192                         'actor.org_unit',         -- table name
12193                         'id',                     -- key column
12194                         'parent_ou',              -- recursive foreign key
12195                         n_work_ou::TEXT,          -- id of starting point
12196                         (n_min_depth - n_depth)   -- max depth to search, relative
12197                     )                             --   to starting point
12198                     AS t(
12199                         ou text,            -- dependent org unit
12200                         parent_ou text,     -- (ignore)
12201                         level int           -- depth relative to starting point
12202                     )
12203                 WHERE
12204                     level = n_min_depth - n_depth
12205             LOOP
12206                 RETURN NEXT n_curr_ou;
12207             END LOOP;
12208         END IF;
12209         --
12210     END LOOP;
12211     --
12212     RETURN;
12213     --
12214 END;
12215 $$ LANGUAGE 'plpgsql';
12216
12217 ALTER TABLE acq.purchase_order
12218         ADD COLUMN cancel_reason        INT REFERENCES acq.cancel_reason( id )
12219                                             DEFERRABLE INITIALLY DEFERRED;
12220
12221 ALTER TABLE acq.acq_purchase_order_history
12222         ADD COLUMN cancel_reason INTEGER;
12223
12224 ALTER TABLE acq.purchase_order
12225         ADD COLUMN prepayment_required BOOLEAN NOT NULL DEFAULT FALSE;
12226
12227 ALTER TABLE acq.acq_purchase_order_history
12228         ADD COLUMN prepayment_required BOOLEAN NOT NULL DEFAULT FALSE;
12229
12230 ALTER TABLE acq.lineitem
12231         ADD COLUMN cancel_reason        INT REFERENCES acq.cancel_reason( id )
12232                                             DEFERRABLE INITIALLY DEFERRED;
12233
12234 ALTER TABLE acq.acq_lineitem_history
12235         ADD COLUMN cancel_reason INTEGER;
12236
12237 ALTER TABLE acq.lineitem
12238         ADD COLUMN estimated_unit_price NUMERIC;
12239
12240 ALTER TABLE acq.acq_lineitem_history
12241         ADD COLUMN estimated_unit_price NUMERIC;
12242
12243 ALTER TABLE acq.lineitem
12244         ADD COLUMN claim_policy INT
12245                 REFERENCES acq.claim_policy
12246                 DEFERRABLE INITIALLY DEFERRED;
12247
12248 ALTER TABLE acq.acq_lineitem_history
12249         ADD COLUMN claim_policy INT
12250                 REFERENCES acq.claim_policy
12251                 DEFERRABLE INITIALLY DEFERRED;
12252
12253 ALTER TABLE acq.lineitem_detail
12254         ADD COLUMN cancel_reason        INT REFERENCES acq.cancel_reason( id )
12255                                             DEFERRABLE INITIALLY DEFERRED;
12256
12257 ALTER TABLE acq.lineitem_detail
12258         DROP CONSTRAINT lineitem_detail_lineitem_fkey;
12259
12260 ALTER TABLE acq.lineitem_detail
12261         ADD FOREIGN KEY (lineitem) REFERENCES acq.lineitem( id )
12262                 ON DELETE CASCADE
12263                 DEFERRABLE INITIALLY DEFERRED;
12264
12265 ALTER TABLE acq.lineitem_detail DROP CONSTRAINT lineitem_detail_eg_copy_id_fkey;
12266
12267 INSERT INTO acq.cancel_reason ( id, org_unit, label, description ) VALUES (
12268         1, 1, 'invalid_isbn', oils_i18n_gettext( 1, 'ISBN is unrecognizable', 'acqcr', 'label' ));
12269
12270 INSERT INTO acq.cancel_reason ( id, org_unit, label, description ) VALUES (
12271         2, 1, 'postpone', oils_i18n_gettext( 2, 'Title has been postponed', 'acqcr', 'label' ));
12272
12273 CREATE OR REPLACE FUNCTION vandelay.add_field ( target_xml TEXT, source_xml TEXT, field TEXT ) RETURNS TEXT AS $_$
12274
12275     use MARC::Record;
12276     use MARC::File::XML (BinaryEncoding => 'UTF-8');
12277     use strict;
12278
12279     my $target_xml = shift;
12280     my $source_xml = shift;
12281     my $field_spec = shift;
12282
12283     my $target_r = MARC::Record->new_from_xml( $target_xml );
12284     my $source_r = MARC::Record->new_from_xml( $source_xml );
12285
12286     return $target_xml unless ($target_r && $source_r);
12287
12288     my @field_list = split(',', $field_spec);
12289
12290     my %fields;
12291     for my $f (@field_list) {
12292         $f =~ s/^\s*//; $f =~ s/\s*$//;
12293         if ($f =~ /^(.{3})(\w*)(?:\[([^]]*)\])?$/) {
12294             my $field = $1;
12295             $field =~ s/\s+//;
12296             my $sf = $2;
12297             $sf =~ s/\s+//;
12298             my $match = $3;
12299             $match =~ s/^\s*//; $match =~ s/\s*$//;
12300             $fields{$field} = { sf => [ split('', $sf) ] };
12301             if ($match) {
12302                 my ($msf,$mre) = split('~', $match);
12303                 if (length($msf) > 0 and length($mre) > 0) {
12304                     $msf =~ s/^\s*//; $msf =~ s/\s*$//;
12305                     $mre =~ s/^\s*//; $mre =~ s/\s*$//;
12306                     $fields{$field}{match} = { sf => $msf, re => qr/$mre/ };
12307                 }
12308             }
12309         }
12310     }
12311
12312     for my $f ( keys %fields) {
12313         if ( @{$fields{$f}{sf}} ) {
12314             for my $from_field ($source_r->field( $f )) {
12315                 for my $to_field ($target_r->field( $f )) {
12316                     if (exists($fields{$f}{match})) {
12317                         next unless (grep { $_ =~ $fields{$f}{match}{re} } $to_field->subfield($fields{$f}{match}{sf}));
12318                     }
12319                     my @new_sf = map { ($_ => $from_field->subfield($_)) } @{$fields{$f}{sf}};
12320                     $to_field->add_subfields( @new_sf );
12321                 }
12322             }
12323         } else {
12324             my @new_fields = map { $_->clone } $source_r->field( $f );
12325             $target_r->insert_fields_ordered( @new_fields );
12326         }
12327     }
12328
12329     $target_xml = $target_r->as_xml_record;
12330     $target_xml =~ s/^<\?.+?\?>$//mo;
12331     $target_xml =~ s/\n//sgo;
12332     $target_xml =~ s/>\s+</></sgo;
12333
12334     return $target_xml;
12335
12336 $_$ LANGUAGE PLPERLU;
12337
12338 CREATE OR REPLACE FUNCTION vandelay.strip_field ( xml TEXT, field TEXT ) RETURNS TEXT AS $_$
12339
12340     use MARC::Record;
12341     use MARC::File::XML (BinaryEncoding => 'UTF-8');
12342     use strict;
12343
12344     my $xml = shift;
12345     my $r = MARC::Record->new_from_xml( $xml );
12346
12347     return $xml unless ($r);
12348
12349     my $field_spec = shift;
12350     my @field_list = split(',', $field_spec);
12351
12352     my %fields;
12353     for my $f (@field_list) {
12354         $f =~ s/^\s*//; $f =~ s/\s*$//;
12355         if ($f =~ /^(.{3})(\w*)(?:\[([^]]*)\])?$/) {
12356             my $field = $1;
12357             $field =~ s/\s+//;
12358             my $sf = $2;
12359             $sf =~ s/\s+//;
12360             my $match = $3;
12361             $match =~ s/^\s*//; $match =~ s/\s*$//;
12362             $fields{$field} = { sf => [ split('', $sf) ] };
12363             if ($match) {
12364                 my ($msf,$mre) = split('~', $match);
12365                 if (length($msf) > 0 and length($mre) > 0) {
12366                     $msf =~ s/^\s*//; $msf =~ s/\s*$//;
12367                     $mre =~ s/^\s*//; $mre =~ s/\s*$//;
12368                     $fields{$field}{match} = { sf => $msf, re => qr/$mre/ };
12369                 }
12370             }
12371         }
12372     }
12373
12374     for my $f ( keys %fields) {
12375         for my $to_field ($r->field( $f )) {
12376             if (exists($fields{$f}{match})) {
12377                 next unless (grep { $_ =~ $fields{$f}{match}{re} } $to_field->subfield($fields{$f}{match}{sf}));
12378             }
12379
12380             if ( @{$fields{$f}{sf}} ) {
12381                 $to_field->delete_subfield(code => $fields{$f}{sf});
12382             } else {
12383                 $r->delete_field( $to_field );
12384             }
12385         }
12386     }
12387
12388     $xml = $r->as_xml_record;
12389     $xml =~ s/^<\?.+?\?>$//mo;
12390     $xml =~ s/\n//sgo;
12391     $xml =~ s/>\s+</></sgo;
12392
12393     return $xml;
12394
12395 $_$ LANGUAGE PLPERLU;
12396
12397 CREATE OR REPLACE FUNCTION vandelay.replace_field ( target_xml TEXT, source_xml TEXT, field TEXT ) RETURNS TEXT AS $_$
12398     SELECT vandelay.add_field( vandelay.strip_field( $1, $3), $2, $3 );
12399 $_$ LANGUAGE SQL;
12400
12401 CREATE OR REPLACE FUNCTION vandelay.preserve_field ( incumbent_xml TEXT, incoming_xml TEXT, field TEXT ) RETURNS TEXT AS $_$
12402     SELECT vandelay.add_field( vandelay.strip_field( $2, $3), $1, $3 );
12403 $_$ LANGUAGE SQL;
12404
12405 CREATE VIEW action.unfulfilled_hold_max_loop AS
12406         SELECT  hold,
12407                 max(count) AS max
12408         FROM    action.unfulfilled_hold_loops
12409         GROUP BY 1;
12410
12411 ALTER TABLE acq.lineitem_attr
12412         DROP CONSTRAINT lineitem_attr_lineitem_fkey;
12413
12414 ALTER TABLE acq.lineitem_attr
12415         ADD FOREIGN KEY (lineitem) REFERENCES acq.lineitem( id )
12416                 ON DELETE CASCADE
12417                 DEFERRABLE INITIALLY DEFERRED;
12418
12419 ALTER TABLE acq.po_note
12420         ADD COLUMN vendor_public BOOLEAN NOT NULL DEFAULT FALSE;
12421
12422 CREATE TABLE vandelay.merge_profile (
12423     id              BIGSERIAL   PRIMARY KEY,
12424     owner           INT         NOT NULL REFERENCES actor.org_unit (id) ON UPDATE CASCADE ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED,
12425     name            TEXT        NOT NULL,
12426     add_spec        TEXT,
12427     replace_spec    TEXT,
12428     strip_spec      TEXT,
12429     preserve_spec   TEXT,
12430     CONSTRAINT vand_merge_prof_owner_name_idx UNIQUE (owner,name),
12431     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))
12432 );
12433
12434 CREATE OR REPLACE FUNCTION vandelay.match_bib_record ( ) RETURNS TRIGGER AS $func$
12435 DECLARE
12436     attr        RECORD;
12437     attr_def    RECORD;
12438     eg_rec      RECORD;
12439     id_value    TEXT;
12440     exact_id    BIGINT;
12441 BEGIN
12442
12443     DELETE FROM vandelay.bib_match WHERE queued_record = NEW.id;
12444
12445     SELECT * INTO attr_def FROM vandelay.bib_attr_definition WHERE xpath = '//*[@tag="901"]/*[@code="c"]' ORDER BY id LIMIT 1;
12446
12447     IF attr_def IS NOT NULL AND attr_def.id IS NOT NULL THEN
12448         id_value := extract_marc_field('vandelay.queued_bib_record', NEW.id, attr_def.xpath, attr_def.remove);
12449
12450         IF id_value IS NOT NULL AND id_value <> '' AND id_value ~ $r$^\d+$$r$ THEN
12451             SELECT id INTO exact_id FROM biblio.record_entry WHERE id = id_value::BIGINT AND NOT deleted;
12452             SELECT * INTO attr FROM vandelay.queued_bib_record_attr WHERE record = NEW.id and field = attr_def.id LIMIT 1;
12453             IF exact_id IS NOT NULL THEN
12454                 INSERT INTO vandelay.bib_match (field_type, matched_attr, queued_record, eg_record) VALUES ('id', attr.id, NEW.id, exact_id);
12455             END IF;
12456         END IF;
12457     END IF;
12458
12459     IF exact_id IS NULL THEN
12460         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
12461
12462             -- All numbers? check for an id match
12463             IF (attr.attr_value ~ $r$^\d+$$r$) THEN
12464                 FOR eg_rec IN SELECT * FROM biblio.record_entry WHERE id = attr.attr_value::BIGINT AND deleted IS FALSE LOOP
12465                     INSERT INTO vandelay.bib_match (field_type, matched_attr, queued_record, eg_record) VALUES ('id', attr.id, NEW.id, eg_rec.id);
12466                 END LOOP;
12467             END IF;
12468
12469             -- Looks like an ISBN? check for an isbn match
12470             IF (attr.attr_value ~* $r$^[0-9x]+$$r$ AND character_length(attr.attr_value) IN (10,13)) THEN
12471                 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
12472                     PERFORM id FROM biblio.record_entry WHERE id = eg_rec.record AND deleted IS FALSE;
12473                     IF FOUND THEN
12474                         INSERT INTO vandelay.bib_match (field_type, matched_attr, queued_record, eg_record) VALUES ('isbn', attr.id, NEW.id, eg_rec.record);
12475                     END IF;
12476                 END LOOP;
12477
12478                 -- subcheck for isbn-as-tcn
12479                 FOR eg_rec IN SELECT * FROM biblio.record_entry WHERE tcn_value = 'i' || attr.attr_value AND deleted IS FALSE LOOP
12480                     INSERT INTO vandelay.bib_match (field_type, matched_attr, queued_record, eg_record) VALUES ('tcn_value', attr.id, NEW.id, eg_rec.id);
12481                 END LOOP;
12482             END IF;
12483
12484             -- check for an OCLC tcn_value match
12485             IF (attr.attr_value ~ $r$^o\d+$$r$) THEN
12486                 FOR eg_rec IN SELECT * FROM biblio.record_entry WHERE tcn_value = regexp_replace(attr.attr_value,'^o','ocm') AND deleted IS FALSE LOOP
12487                     INSERT INTO vandelay.bib_match (field_type, matched_attr, queued_record, eg_record) VALUES ('tcn_value', attr.id, NEW.id, eg_rec.id);
12488                 END LOOP;
12489             END IF;
12490
12491             -- check for a direct tcn_value match
12492             FOR eg_rec IN SELECT * FROM biblio.record_entry WHERE tcn_value = attr.attr_value AND deleted IS FALSE LOOP
12493                 INSERT INTO vandelay.bib_match (field_type, matched_attr, queued_record, eg_record) VALUES ('tcn_value', attr.id, NEW.id, eg_rec.id);
12494             END LOOP;
12495
12496             -- check for a direct item barcode match
12497             FOR eg_rec IN
12498                     SELECT  DISTINCT b.*
12499                       FROM  biblio.record_entry b
12500                             JOIN asset.call_number cn ON (cn.record = b.id)
12501                             JOIN asset.copy cp ON (cp.call_number = cn.id)
12502                       WHERE cp.barcode = attr.attr_value AND cp.deleted IS FALSE
12503             LOOP
12504                 INSERT INTO vandelay.bib_match (field_type, matched_attr, queued_record, eg_record) VALUES ('id', attr.id, NEW.id, eg_rec.id);
12505             END LOOP;
12506
12507         END LOOP;
12508     END IF;
12509
12510     RETURN NULL;
12511 END;
12512 $func$ LANGUAGE PLPGSQL;
12513
12514 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 $_$
12515     SELECT vandelay.replace_field( vandelay.add_field( vandelay.strip_field( $1, $5) , $2, $3 ), $2, $4);
12516 $_$ LANGUAGE SQL;
12517
12518 CREATE TYPE vandelay.compile_profile AS (add_rule TEXT, replace_rule TEXT, preserve_rule TEXT, strip_rule TEXT);
12519 CREATE OR REPLACE FUNCTION vandelay.compile_profile ( incoming_xml TEXT ) RETURNS vandelay.compile_profile AS $_$
12520 DECLARE
12521     output              vandelay.compile_profile%ROWTYPE;
12522     profile             vandelay.merge_profile%ROWTYPE;
12523     profile_tmpl        TEXT;
12524     profile_tmpl_owner  TEXT;
12525     add_rule            TEXT := '';
12526     strip_rule          TEXT := '';
12527     replace_rule        TEXT := '';
12528     preserve_rule       TEXT := '';
12529
12530 BEGIN
12531
12532     profile_tmpl := (oils_xpath('//*[@tag="905"]/*[@code="t"]/text()',incoming_xml))[1];
12533     profile_tmpl_owner := (oils_xpath('//*[@tag="905"]/*[@code="o"]/text()',incoming_xml))[1];
12534
12535     IF profile_tmpl IS NOT NULL AND profile_tmpl <> '' AND profile_tmpl_owner IS NOT NULL AND profile_tmpl_owner <> '' THEN
12536         SELECT  p.* INTO profile
12537           FROM  vandelay.merge_profile p
12538                 JOIN actor.org_unit u ON (u.id = p.owner)
12539           WHERE p.name = profile_tmpl
12540                 AND u.shortname = profile_tmpl_owner;
12541
12542         IF profile.id IS NOT NULL THEN
12543             add_rule := COALESCE(profile.add_spec,'');
12544             strip_rule := COALESCE(profile.strip_spec,'');
12545             replace_rule := COALESCE(profile.replace_spec,'');
12546             preserve_rule := COALESCE(profile.preserve_spec,'');
12547         END IF;
12548     END IF;
12549
12550     add_rule := add_rule || ',' || COALESCE(ARRAY_TO_STRING(oils_xpath('//*[@tag="905"]/*[@code="a"]/text()',incoming_xml),''),'');
12551     strip_rule := strip_rule || ',' || COALESCE(ARRAY_TO_STRING(oils_xpath('//*[@tag="905"]/*[@code="d"]/text()',incoming_xml),''),'');
12552     replace_rule := replace_rule || ',' || COALESCE(ARRAY_TO_STRING(oils_xpath('//*[@tag="905"]/*[@code="r"]/text()',incoming_xml),''),'');
12553     preserve_rule := preserve_rule || ',' || COALESCE(ARRAY_TO_STRING(oils_xpath('//*[@tag="905"]/*[@code="p"]/text()',incoming_xml),''),'');
12554
12555     output.add_rule := BTRIM(add_rule,',');
12556     output.replace_rule := BTRIM(replace_rule,',');
12557     output.strip_rule := BTRIM(strip_rule,',');
12558     output.preserve_rule := BTRIM(preserve_rule,',');
12559
12560     RETURN output;
12561 END;
12562 $_$ LANGUAGE PLPGSQL;
12563
12564 -- Template-based marc munging functions
12565 CREATE OR REPLACE FUNCTION vandelay.template_overlay_bib_record ( v_marc TEXT, eg_id BIGINT, merge_profile_id INT ) RETURNS BOOL AS $$
12566 DECLARE
12567     merge_profile   vandelay.merge_profile%ROWTYPE;
12568     dyn_profile     vandelay.compile_profile%ROWTYPE;
12569     editor_string   TEXT;
12570     editor_id       INT;
12571     source_marc     TEXT;
12572     target_marc     TEXT;
12573     eg_marc         TEXT;
12574     replace_rule    TEXT;
12575     match_count     INT;
12576 BEGIN
12577
12578     SELECT  b.marc INTO eg_marc
12579       FROM  biblio.record_entry b
12580       WHERE b.id = eg_id
12581       LIMIT 1;
12582
12583     IF eg_marc IS NULL OR v_marc IS NULL THEN
12584         -- RAISE NOTICE 'no marc for template or bib record';
12585         RETURN FALSE;
12586     END IF;
12587
12588     dyn_profile := vandelay.compile_profile( v_marc );
12589
12590     IF merge_profile_id IS NOT NULL THEN
12591         SELECT * INTO merge_profile FROM vandelay.merge_profile WHERE id = merge_profile_id;
12592         IF FOUND THEN
12593             dyn_profile.add_rule := BTRIM( dyn_profile.add_rule || ',' || COALESCE(merge_profile.add_spec,''), ',');
12594             dyn_profile.strip_rule := BTRIM( dyn_profile.strip_rule || ',' || COALESCE(merge_profile.strip_spec,''), ',');
12595             dyn_profile.replace_rule := BTRIM( dyn_profile.replace_rule || ',' || COALESCE(merge_profile.replace_spec,''), ',');
12596             dyn_profile.preserve_rule := BTRIM( dyn_profile.preserve_rule || ',' || COALESCE(merge_profile.preserve_spec,''), ',');
12597         END IF;
12598     END IF;
12599
12600     IF dyn_profile.replace_rule <> '' AND dyn_profile.preserve_rule <> '' THEN
12601         -- RAISE NOTICE 'both replace [%] and preserve [%] specified', dyn_profile.replace_rule, dyn_profile.preserve_rule;
12602         RETURN FALSE;
12603     END IF;
12604
12605     IF dyn_profile.replace_rule <> '' THEN
12606         source_marc = v_marc;
12607         target_marc = eg_marc;
12608         replace_rule = dyn_profile.replace_rule;
12609     ELSE
12610         source_marc = eg_marc;
12611         target_marc = v_marc;
12612         replace_rule = dyn_profile.preserve_rule;
12613     END IF;
12614
12615     UPDATE  biblio.record_entry
12616       SET   marc = vandelay.merge_record_xml( target_marc, source_marc, dyn_profile.add_rule, replace_rule, dyn_profile.strip_rule )
12617       WHERE id = eg_id;
12618
12619     IF NOT FOUND THEN
12620         -- RAISE NOTICE 'update of biblio.record_entry failed';
12621         RETURN FALSE;
12622     END IF;
12623
12624     RETURN TRUE;
12625
12626 END;
12627 $$ LANGUAGE PLPGSQL;
12628
12629 CREATE OR REPLACE FUNCTION vandelay.template_overlay_bib_record ( v_marc TEXT, eg_id BIGINT) RETURNS BOOL AS $$
12630     SELECT vandelay.template_overlay_bib_record( $1, $2, NULL);
12631 $$ LANGUAGE SQL;
12632
12633 CREATE OR REPLACE FUNCTION vandelay.overlay_bib_record ( import_id BIGINT, eg_id BIGINT, merge_profile_id INT ) RETURNS BOOL AS $$
12634 DECLARE
12635     merge_profile   vandelay.merge_profile%ROWTYPE;
12636     dyn_profile     vandelay.compile_profile%ROWTYPE;
12637     editor_string   TEXT;
12638     editor_id       INT;
12639     source_marc     TEXT;
12640     target_marc     TEXT;
12641     eg_marc         TEXT;
12642     v_marc          TEXT;
12643     replace_rule    TEXT;
12644     match_count     INT;
12645 BEGIN
12646
12647     SELECT  q.marc INTO v_marc
12648       FROM  vandelay.queued_record q
12649             JOIN vandelay.bib_match m ON (m.queued_record = q.id AND q.id = import_id)
12650       LIMIT 1;
12651
12652     IF v_marc IS NULL THEN
12653         -- RAISE NOTICE 'no marc for vandelay or bib record';
12654         RETURN FALSE;
12655     END IF;
12656
12657     IF vandelay.template_overlay_bib_record( v_marc, eg_id, merge_profile_id) THEN
12658         UPDATE  vandelay.queued_bib_record
12659           SET   imported_as = eg_id,
12660                 import_time = NOW()
12661           WHERE id = import_id;
12662
12663         editor_string := (oils_xpath('//*[@tag="905"]/*[@code="u"]/text()',v_marc))[1];
12664
12665         IF editor_string IS NOT NULL AND editor_string <> '' THEN
12666             SELECT usr INTO editor_id FROM actor.card WHERE barcode = editor_string;
12667
12668             IF editor_id IS NULL THEN
12669                 SELECT id INTO editor_id FROM actor.usr WHERE usrname = editor_string;
12670             END IF;
12671
12672             IF editor_id IS NOT NULL THEN
12673                 UPDATE biblio.record_entry SET editor = editor_id WHERE id = eg_id;
12674             END IF;
12675         END IF;
12676
12677         RETURN TRUE;
12678     END IF;
12679
12680     -- RAISE NOTICE 'update of biblio.record_entry failed';
12681
12682     RETURN FALSE;
12683
12684 END;
12685 $$ LANGUAGE PLPGSQL;
12686
12687 CREATE OR REPLACE FUNCTION vandelay.auto_overlay_bib_record ( import_id BIGINT, merge_profile_id INT ) RETURNS BOOL AS $$
12688 DECLARE
12689     eg_id           BIGINT;
12690     match_count     INT;
12691     match_attr      vandelay.bib_attr_definition%ROWTYPE;
12692 BEGIN
12693
12694     PERFORM * FROM vandelay.queued_bib_record WHERE import_time IS NOT NULL AND id = import_id;
12695
12696     IF FOUND THEN
12697         -- RAISE NOTICE 'already imported, cannot auto-overlay'
12698         RETURN FALSE;
12699     END IF;
12700
12701     SELECT COUNT(*) INTO match_count FROM vandelay.bib_match WHERE queued_record = import_id;
12702
12703     IF match_count <> 1 THEN
12704         -- RAISE NOTICE 'not an exact match';
12705         RETURN FALSE;
12706     END IF;
12707
12708     SELECT  d.* INTO match_attr
12709       FROM  vandelay.bib_attr_definition d
12710             JOIN vandelay.queued_bib_record_attr a ON (a.field = d.id)
12711             JOIN vandelay.bib_match m ON (m.matched_attr = a.id)
12712       WHERE m.queued_record = import_id;
12713
12714     IF NOT (match_attr.xpath ~ '@tag="901"' AND match_attr.xpath ~ '@code="c"') THEN
12715         -- RAISE NOTICE 'not a 901c match: %', match_attr.xpath;
12716         RETURN FALSE;
12717     END IF;
12718
12719     SELECT  m.eg_record INTO eg_id
12720       FROM  vandelay.bib_match m
12721       WHERE m.queued_record = import_id
12722       LIMIT 1;
12723
12724     IF eg_id IS NULL THEN
12725         RETURN FALSE;
12726     END IF;
12727
12728     RETURN vandelay.overlay_bib_record( import_id, eg_id, merge_profile_id );
12729 END;
12730 $$ LANGUAGE PLPGSQL;
12731
12732 CREATE OR REPLACE FUNCTION vandelay.auto_overlay_bib_queue ( queue_id BIGINT, merge_profile_id INT ) RETURNS SETOF BIGINT AS $$
12733 DECLARE
12734     queued_record   vandelay.queued_bib_record%ROWTYPE;
12735 BEGIN
12736
12737     FOR queued_record IN SELECT * FROM vandelay.queued_bib_record WHERE queue = queue_id AND import_time IS NULL LOOP
12738
12739         IF vandelay.auto_overlay_bib_record( queued_record.id, merge_profile_id ) THEN
12740             RETURN NEXT queued_record.id;
12741         END IF;
12742
12743     END LOOP;
12744
12745     RETURN;
12746
12747 END;
12748 $$ LANGUAGE PLPGSQL;
12749
12750 CREATE OR REPLACE FUNCTION vandelay.auto_overlay_bib_queue ( queue_id BIGINT ) RETURNS SETOF BIGINT AS $$
12751     SELECT * FROM vandelay.auto_overlay_bib_queue( $1, NULL );
12752 $$ LANGUAGE SQL;
12753
12754 CREATE OR REPLACE FUNCTION vandelay.overlay_authority_record ( import_id BIGINT, eg_id BIGINT, merge_profile_id INT ) RETURNS BOOL AS $$
12755 DECLARE
12756     merge_profile   vandelay.merge_profile%ROWTYPE;
12757     dyn_profile     vandelay.compile_profile%ROWTYPE;
12758     source_marc     TEXT;
12759     target_marc     TEXT;
12760     eg_marc         TEXT;
12761     v_marc          TEXT;
12762     replace_rule    TEXT;
12763     match_count     INT;
12764 BEGIN
12765
12766     SELECT  b.marc INTO eg_marc
12767       FROM  authority.record_entry b
12768             JOIN vandelay.authority_match m ON (m.eg_record = b.id AND m.queued_record = import_id)
12769       LIMIT 1;
12770
12771     SELECT  q.marc INTO v_marc
12772       FROM  vandelay.queued_record q
12773             JOIN vandelay.authority_match m ON (m.queued_record = q.id AND q.id = import_id)
12774       LIMIT 1;
12775
12776     IF eg_marc IS NULL OR v_marc IS NULL THEN
12777         -- RAISE NOTICE 'no marc for vandelay or authority record';
12778         RETURN FALSE;
12779     END IF;
12780
12781     dyn_profile := vandelay.compile_profile( v_marc );
12782
12783     IF merge_profile_id IS NOT NULL THEN
12784         SELECT * INTO merge_profile FROM vandelay.merge_profile WHERE id = merge_profile_id;
12785         IF FOUND THEN
12786             dyn_profile.add_rule := BTRIM( dyn_profile.add_rule || ',' || COALESCE(merge_profile.add_spec,''), ',');
12787             dyn_profile.strip_rule := BTRIM( dyn_profile.strip_rule || ',' || COALESCE(merge_profile.strip_spec,''), ',');
12788             dyn_profile.replace_rule := BTRIM( dyn_profile.replace_rule || ',' || COALESCE(merge_profile.replace_spec,''), ',');
12789             dyn_profile.preserve_rule := BTRIM( dyn_profile.preserve_rule || ',' || COALESCE(merge_profile.preserve_spec,''), ',');
12790         END IF;
12791     END IF;
12792
12793     IF dyn_profile.replace_rule <> '' AND dyn_profile.preserve_rule <> '' THEN
12794         -- RAISE NOTICE 'both replace [%] and preserve [%] specified', dyn_profile.replace_rule, dyn_profile.preserve_rule;
12795         RETURN FALSE;
12796     END IF;
12797
12798     IF dyn_profile.replace_rule <> '' THEN
12799         source_marc = v_marc;
12800         target_marc = eg_marc;
12801         replace_rule = dyn_profile.replace_rule;
12802     ELSE
12803         source_marc = eg_marc;
12804         target_marc = v_marc;
12805         replace_rule = dyn_profile.preserve_rule;
12806     END IF;
12807
12808     UPDATE  authority.record_entry
12809       SET   marc = vandelay.merge_record_xml( target_marc, source_marc, dyn_profile.add_rule, replace_rule, dyn_profile.strip_rule )
12810       WHERE id = eg_id;
12811
12812     IF FOUND THEN
12813         UPDATE  vandelay.queued_authority_record
12814           SET   imported_as = eg_id,
12815                 import_time = NOW()
12816           WHERE id = import_id;
12817         RETURN TRUE;
12818     END IF;
12819
12820     -- RAISE NOTICE 'update of authority.record_entry failed';
12821
12822     RETURN FALSE;
12823
12824 END;
12825 $$ LANGUAGE PLPGSQL;
12826
12827 CREATE OR REPLACE FUNCTION vandelay.auto_overlay_authority_record ( import_id BIGINT, merge_profile_id INT ) RETURNS BOOL AS $$
12828 DECLARE
12829     eg_id           BIGINT;
12830     match_count     INT;
12831 BEGIN
12832     SELECT COUNT(*) INTO match_count FROM vandelay.authority_match WHERE queued_record = import_id;
12833
12834     IF match_count <> 1 THEN
12835         -- RAISE NOTICE 'not an exact match';
12836         RETURN FALSE;
12837     END IF;
12838
12839     SELECT  m.eg_record INTO eg_id
12840       FROM  vandelay.authority_match m
12841       WHERE m.queued_record = import_id
12842       LIMIT 1;
12843
12844     IF eg_id IS NULL THEN
12845         RETURN FALSE;
12846     END IF;
12847
12848     RETURN vandelay.overlay_authority_record( import_id, eg_id, merge_profile_id );
12849 END;
12850 $$ LANGUAGE PLPGSQL;
12851
12852 CREATE OR REPLACE FUNCTION vandelay.auto_overlay_authority_queue ( queue_id BIGINT, merge_profile_id INT ) RETURNS SETOF BIGINT AS $$
12853 DECLARE
12854     queued_record   vandelay.queued_authority_record%ROWTYPE;
12855 BEGIN
12856
12857     FOR queued_record IN SELECT * FROM vandelay.queued_authority_record WHERE queue = queue_id AND import_time IS NULL LOOP
12858
12859         IF vandelay.auto_overlay_authority_record( queued_record.id, merge_profile_id ) THEN
12860             RETURN NEXT queued_record.id;
12861         END IF;
12862
12863     END LOOP;
12864
12865     RETURN;
12866
12867 END;
12868 $$ LANGUAGE PLPGSQL;
12869
12870 CREATE OR REPLACE FUNCTION vandelay.auto_overlay_authority_queue ( queue_id BIGINT ) RETURNS SETOF BIGINT AS $$
12871     SELECT * FROM vandelay.auto_overlay_authority_queue( $1, NULL );
12872 $$ LANGUAGE SQL;
12873
12874 CREATE TYPE vandelay.tcn_data AS (tcn TEXT, tcn_source TEXT, used BOOL);
12875 CREATE OR REPLACE FUNCTION vandelay.find_bib_tcn_data ( xml TEXT ) RETURNS SETOF vandelay.tcn_data AS $_$
12876 DECLARE
12877     eg_tcn          TEXT;
12878     eg_tcn_source   TEXT;
12879     output          vandelay.tcn_data%ROWTYPE;
12880 BEGIN
12881
12882     -- 001/003
12883     eg_tcn := BTRIM((oils_xpath('//*[@tag="001"]/text()',xml))[1]);
12884     IF eg_tcn IS NOT NULL AND eg_tcn <> '' THEN
12885
12886         eg_tcn_source := BTRIM((oils_xpath('//*[@tag="003"]/text()',xml))[1]);
12887         IF eg_tcn_source IS NULL OR eg_tcn_source = '' THEN
12888             eg_tcn_source := 'System Local';
12889         END IF;
12890
12891         PERFORM id FROM biblio.record_entry WHERE tcn_value = eg_tcn  AND NOT deleted;
12892
12893         IF NOT FOUND THEN
12894             output.used := FALSE;
12895         ELSE
12896             output.used := TRUE;
12897         END IF;
12898
12899         output.tcn := eg_tcn;
12900         output.tcn_source := eg_tcn_source;
12901         RETURN NEXT output;
12902
12903     END IF;
12904
12905     -- 901 ab
12906     eg_tcn := BTRIM((oils_xpath('//*[@tag="901"]/*[@code="a"]/text()',xml))[1]);
12907     IF eg_tcn IS NOT NULL AND eg_tcn <> '' THEN
12908
12909         eg_tcn_source := BTRIM((oils_xpath('//*[@tag="901"]/*[@code="b"]/text()',xml))[1]);
12910         IF eg_tcn_source IS NULL OR eg_tcn_source = '' THEN
12911             eg_tcn_source := 'System Local';
12912         END IF;
12913
12914         PERFORM id FROM biblio.record_entry WHERE tcn_value = eg_tcn  AND NOT deleted;
12915
12916         IF NOT FOUND THEN
12917             output.used := FALSE;
12918         ELSE
12919             output.used := TRUE;
12920         END IF;
12921
12922         output.tcn := eg_tcn;
12923         output.tcn_source := eg_tcn_source;
12924         RETURN NEXT output;
12925
12926     END IF;
12927
12928     -- 039 ab
12929     eg_tcn := BTRIM((oils_xpath('//*[@tag="039"]/*[@code="a"]/text()',xml))[1]);
12930     IF eg_tcn IS NOT NULL AND eg_tcn <> '' THEN
12931
12932         eg_tcn_source := BTRIM((oils_xpath('//*[@tag="039"]/*[@code="b"]/text()',xml))[1]);
12933         IF eg_tcn_source IS NULL OR eg_tcn_source = '' THEN
12934             eg_tcn_source := 'System Local';
12935         END IF;
12936
12937         PERFORM id FROM biblio.record_entry WHERE tcn_value = eg_tcn  AND NOT deleted;
12938
12939         IF NOT FOUND THEN
12940             output.used := FALSE;
12941         ELSE
12942             output.used := TRUE;
12943         END IF;
12944
12945         output.tcn := eg_tcn;
12946         output.tcn_source := eg_tcn_source;
12947         RETURN NEXT output;
12948
12949     END IF;
12950
12951     -- 020 a
12952     eg_tcn := REGEXP_REPLACE((oils_xpath('//*[@tag="020"]/*[@code="a"]/text()',xml))[1], $re$^(\w+).*?$$re$, $re$\1$re$);
12953     IF eg_tcn IS NOT NULL AND eg_tcn <> '' THEN
12954
12955         eg_tcn_source := 'ISBN';
12956
12957         PERFORM id FROM biblio.record_entry WHERE tcn_value = eg_tcn  AND NOT deleted;
12958
12959         IF NOT FOUND THEN
12960             output.used := FALSE;
12961         ELSE
12962             output.used := TRUE;
12963         END IF;
12964
12965         output.tcn := eg_tcn;
12966         output.tcn_source := eg_tcn_source;
12967         RETURN NEXT output;
12968
12969     END IF;
12970
12971     -- 022 a
12972     eg_tcn := REGEXP_REPLACE((oils_xpath('//*[@tag="022"]/*[@code="a"]/text()',xml))[1], $re$^(\w+).*?$$re$, $re$\1$re$);
12973     IF eg_tcn IS NOT NULL AND eg_tcn <> '' THEN
12974
12975         eg_tcn_source := 'ISSN';
12976
12977         PERFORM id FROM biblio.record_entry WHERE tcn_value = eg_tcn  AND NOT deleted;
12978
12979         IF NOT FOUND THEN
12980             output.used := FALSE;
12981         ELSE
12982             output.used := TRUE;
12983         END IF;
12984
12985         output.tcn := eg_tcn;
12986         output.tcn_source := eg_tcn_source;
12987         RETURN NEXT output;
12988
12989     END IF;
12990
12991     -- 010 a
12992     eg_tcn := REGEXP_REPLACE((oils_xpath('//*[@tag="010"]/*[@code="a"]/text()',xml))[1], $re$^(\w+).*?$$re$, $re$\1$re$);
12993     IF eg_tcn IS NOT NULL AND eg_tcn <> '' THEN
12994
12995         eg_tcn_source := 'LCCN';
12996
12997         PERFORM id FROM biblio.record_entry WHERE tcn_value = eg_tcn  AND NOT deleted;
12998
12999         IF NOT FOUND THEN
13000             output.used := FALSE;
13001         ELSE
13002             output.used := TRUE;
13003         END IF;
13004
13005         output.tcn := eg_tcn;
13006         output.tcn_source := eg_tcn_source;
13007         RETURN NEXT output;
13008
13009     END IF;
13010
13011     -- 035 a
13012     eg_tcn := REGEXP_REPLACE((oils_xpath('//*[@tag="035"]/*[@code="a"]/text()',xml))[1], $re$^.*?(\w+)$$re$, $re$\1$re$);
13013     IF eg_tcn IS NOT NULL AND eg_tcn <> '' THEN
13014
13015         eg_tcn_source := 'System Legacy';
13016
13017         PERFORM id FROM biblio.record_entry WHERE tcn_value = eg_tcn  AND NOT deleted;
13018
13019         IF NOT FOUND THEN
13020             output.used := FALSE;
13021         ELSE
13022             output.used := TRUE;
13023         END IF;
13024
13025         output.tcn := eg_tcn;
13026         output.tcn_source := eg_tcn_source;
13027         RETURN NEXT output;
13028
13029     END IF;
13030
13031     RETURN;
13032 END;
13033 $_$ LANGUAGE PLPGSQL;
13034
13035 CREATE INDEX claim_lid_idx ON acq.claim( lineitem_detail );
13036
13037 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);
13038
13039 UPDATE biblio.record_entry SET marc = '<record xmlns="http://www.loc.gov/MARC21/slim"/>' WHERE id = -1;
13040
13041 CREATE INDEX metabib_title_field_entry_value_idx ON metabib.title_field_entry (SUBSTRING(value,1,1024)) WHERE index_vector = ''::TSVECTOR;
13042 CREATE INDEX metabib_author_field_entry_value_idx ON metabib.author_field_entry (SUBSTRING(value,1,1024)) WHERE index_vector = ''::TSVECTOR;
13043 CREATE INDEX metabib_subject_field_entry_value_idx ON metabib.subject_field_entry (SUBSTRING(value,1,1024)) WHERE index_vector = ''::TSVECTOR;
13044 CREATE INDEX metabib_keyword_field_entry_value_idx ON metabib.keyword_field_entry (SUBSTRING(value,1,1024)) WHERE index_vector = ''::TSVECTOR;
13045 CREATE INDEX metabib_series_field_entry_value_idx ON metabib.series_field_entry (SUBSTRING(value,1,1024)) WHERE index_vector = ''::TSVECTOR;
13046
13047 CREATE INDEX metabib_author_field_entry_source_idx ON metabib.author_field_entry (source);
13048 CREATE INDEX metabib_keyword_field_entry_source_idx ON metabib.keyword_field_entry (source);
13049 CREATE INDEX metabib_title_field_entry_source_idx ON metabib.title_field_entry (source);
13050 CREATE INDEX metabib_series_field_entry_source_idx ON metabib.series_field_entry (source);
13051
13052 CREATE TABLE acq.claim_policy_action (
13053         id              SERIAL       PRIMARY KEY,
13054         claim_policy    INT          NOT NULL REFERENCES acq.claim_policy
13055                                  ON DELETE CASCADE
13056                                      DEFERRABLE INITIALLY DEFERRED,
13057         action_interval INTERVAL     NOT NULL,
13058         action          INT          NOT NULL REFERENCES acq.claim_event_type
13059                                      DEFERRABLE INITIALLY DEFERRED,
13060         CONSTRAINT action_sequence UNIQUE (claim_policy, action_interval)
13061 );
13062
13063 CREATE OR REPLACE FUNCTION public.ingest_acq_marc ( ) RETURNS TRIGGER AS $function$
13064 DECLARE
13065     value       TEXT;
13066     atype       TEXT;
13067     prov        INT;
13068     pos         INT;
13069     adef        RECORD;
13070     xpath_string    TEXT;
13071 BEGIN
13072     FOR adef IN SELECT *,tableoid FROM acq.lineitem_attr_definition LOOP
13073  
13074         SELECT relname::TEXT INTO atype FROM pg_class WHERE oid = adef.tableoid;
13075  
13076         IF (atype NOT IN ('lineitem_usr_attr_definition','lineitem_local_attr_definition')) THEN
13077             IF (atype = 'lineitem_provider_attr_definition') THEN
13078                 SELECT provider INTO prov FROM acq.lineitem_provider_attr_definition WHERE id = adef.id;
13079                 CONTINUE WHEN NEW.provider IS NULL OR prov <> NEW.provider;
13080             END IF;
13081  
13082             IF (atype = 'lineitem_provider_attr_definition') THEN
13083                 SELECT xpath INTO xpath_string FROM acq.lineitem_provider_attr_definition WHERE id = adef.id;
13084             ELSIF (atype = 'lineitem_marc_attr_definition') THEN
13085                 SELECT xpath INTO xpath_string FROM acq.lineitem_marc_attr_definition WHERE id = adef.id;
13086             ELSIF (atype = 'lineitem_generated_attr_definition') THEN
13087                 SELECT xpath INTO xpath_string FROM acq.lineitem_generated_attr_definition WHERE id = adef.id;
13088             END IF;
13089  
13090             xpath_string := REGEXP_REPLACE(xpath_string,$re$//?text\(\)$$re$,'');
13091  
13092             pos := 1;
13093  
13094             LOOP
13095                 SELECT extract_acq_marc_field(id, xpath_string || '[' || pos || ']', adef.remove) INTO value FROM acq.lineitem WHERE id = NEW.id;
13096  
13097                 IF (value IS NOT NULL AND value <> '') THEN
13098                     INSERT INTO acq.lineitem_attr (lineitem, definition, attr_type, attr_name, attr_value)
13099                         VALUES (NEW.id, adef.id, atype, adef.code, value);
13100                 ELSE
13101                     EXIT;
13102                 END IF;
13103  
13104                 pos := pos + 1;
13105             END LOOP;
13106  
13107         END IF;
13108  
13109     END LOOP;
13110  
13111     RETURN NULL;
13112 END;
13113 $function$ LANGUAGE PLPGSQL;
13114
13115 UPDATE config.metabib_field SET label = name;
13116 ALTER TABLE config.metabib_field ALTER COLUMN label SET NOT NULL;
13117
13118 ALTER TABLE config.metabib_field ADD CONSTRAINT field_class_fkey FOREIGN KEY (field_class) REFERENCES config.metabib_class (name);
13119
13120 ALTER TABLE config.metabib_field DROP CONSTRAINT metabib_field_field_class_check;
13121
13122 ALTER TABLE config.metabib_field ADD CONSTRAINT metabib_field_format_fkey FOREIGN KEY (format) REFERENCES config.xml_transform (name);
13123
13124 CREATE TABLE config.metabib_search_alias (
13125     alias       TEXT    PRIMARY KEY,
13126     field_class TEXT    NOT NULL REFERENCES config.metabib_class (name),
13127     field       INT     REFERENCES config.metabib_field (id)
13128 );
13129
13130 INSERT INTO config.metabib_search_alias (alias,field_class) VALUES ('kw','keyword');
13131 INSERT INTO config.metabib_search_alias (alias,field_class) VALUES ('eg.keyword','keyword');
13132 INSERT INTO config.metabib_search_alias (alias,field_class) VALUES ('dc.publisher','keyword');
13133 INSERT INTO config.metabib_search_alias (alias,field_class) VALUES ('dc.identifier','keyword');
13134 INSERT INTO config.metabib_search_alias (alias,field_class) VALUES ('bib.subjecttitle','keyword');
13135 INSERT INTO config.metabib_search_alias (alias,field_class) VALUES ('bib.genre','keyword');
13136 INSERT INTO config.metabib_search_alias (alias,field_class) VALUES ('bib.edition','keyword');
13137 INSERT INTO config.metabib_search_alias (alias,field_class) VALUES ('srw.serverchoice','keyword');
13138
13139 INSERT INTO config.metabib_search_alias (alias,field_class) VALUES ('au','author');
13140 INSERT INTO config.metabib_search_alias (alias,field_class) VALUES ('name','author');
13141 INSERT INTO config.metabib_search_alias (alias,field_class) VALUES ('creator','author');
13142 INSERT INTO config.metabib_search_alias (alias,field_class) VALUES ('eg.author','author');
13143 INSERT INTO config.metabib_search_alias (alias,field_class) VALUES ('eg.name','author');
13144 INSERT INTO config.metabib_search_alias (alias,field_class) VALUES ('dc.creator','author');
13145 INSERT INTO config.metabib_search_alias (alias,field_class) VALUES ('dc.contributor','author');
13146 INSERT INTO config.metabib_search_alias (alias,field_class) VALUES ('bib.name','author');
13147 INSERT INTO config.metabib_search_alias (alias,field_class,field) VALUES ('bib.namepersonal','author',8);
13148 INSERT INTO config.metabib_search_alias (alias,field_class,field) VALUES ('bib.namepersonalfamily','author',8);
13149 INSERT INTO config.metabib_search_alias (alias,field_class,field) VALUES ('bib.namepersonalgiven','author',8);
13150 INSERT INTO config.metabib_search_alias (alias,field_class,field) VALUES ('bib.namecorporate','author',7);
13151 INSERT INTO config.metabib_search_alias (alias,field_class,field) VALUES ('bib.nameconference','author',9);
13152
13153 INSERT INTO config.metabib_search_alias (alias,field_class) VALUES ('ti','title');
13154 INSERT INTO config.metabib_search_alias (alias,field_class) VALUES ('eg.title','title');
13155 INSERT INTO config.metabib_search_alias (alias,field_class) VALUES ('dc.title','title');
13156 INSERT INTO config.metabib_search_alias (alias,field_class,field) VALUES ('bib.titleabbreviated','title',2);
13157 INSERT INTO config.metabib_search_alias (alias,field_class,field) VALUES ('bib.titleuniform','title',5);
13158 INSERT INTO config.metabib_search_alias (alias,field_class,field) VALUES ('bib.titletranslated','title',3);
13159 INSERT INTO config.metabib_search_alias (alias,field_class,field) VALUES ('bib.titlealternative','title',4);
13160 INSERT INTO config.metabib_search_alias (alias,field_class,field) VALUES ('bib.title','title',2);
13161
13162 INSERT INTO config.metabib_search_alias (alias,field_class) VALUES ('su','subject');
13163 INSERT INTO config.metabib_search_alias (alias,field_class) VALUES ('eg.subject','subject');
13164 INSERT INTO config.metabib_search_alias (alias,field_class) VALUES ('dc.subject','subject');
13165 INSERT INTO config.metabib_search_alias (alias,field_class,field) VALUES ('bib.subjectplace','subject',11);
13166 INSERT INTO config.metabib_search_alias (alias,field_class,field) VALUES ('bib.subjectname','subject',12);
13167 INSERT INTO config.metabib_search_alias (alias,field_class,field) VALUES ('bib.subjectoccupation','subject',16);
13168
13169 INSERT INTO config.metabib_search_alias (alias,field_class) VALUES ('se','series');
13170 INSERT INTO config.metabib_search_alias (alias,field_class) VALUES ('eg.series','series');
13171 INSERT INTO config.metabib_search_alias (alias,field_class,field) VALUES ('bib.titleseries','series',1);
13172
13173 UPDATE config.metabib_field SET facet_field=TRUE WHERE id = 1;
13174 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;
13175 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;
13176 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;
13177 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;
13178
13179 UPDATE config.metabib_field SET facet_field=TRUE WHERE id = 11;
13180 UPDATE config.metabib_field SET facet_field=TRUE , facet_xpath=$$*[local-name()='namePart']$$ WHERE id = 12;
13181 UPDATE config.metabib_field SET facet_field=TRUE WHERE id = 13;
13182 UPDATE config.metabib_field SET facet_field=TRUE WHERE id = 14;
13183
13184 CREATE INDEX metabib_rec_descriptor_item_type_idx ON metabib.rec_descriptor (item_type);
13185 CREATE INDEX metabib_rec_descriptor_item_form_idx ON metabib.rec_descriptor (item_form);
13186 CREATE INDEX metabib_rec_descriptor_bib_level_idx ON metabib.rec_descriptor (bib_level);
13187 CREATE INDEX metabib_rec_descriptor_control_type_idx ON metabib.rec_descriptor (control_type);
13188 CREATE INDEX metabib_rec_descriptor_char_encoding_idx ON metabib.rec_descriptor (char_encoding);
13189 CREATE INDEX metabib_rec_descriptor_enc_level_idx ON metabib.rec_descriptor (enc_level);
13190 CREATE INDEX metabib_rec_descriptor_audience_idx ON metabib.rec_descriptor (audience);
13191 CREATE INDEX metabib_rec_descriptor_lit_form_idx ON metabib.rec_descriptor (lit_form);
13192 CREATE INDEX metabib_rec_descriptor_cat_form_idx ON metabib.rec_descriptor (cat_form);
13193 CREATE INDEX metabib_rec_descriptor_pub_status_idx ON metabib.rec_descriptor (pub_status);
13194 CREATE INDEX metabib_rec_descriptor_item_lang_idx ON metabib.rec_descriptor (item_lang);
13195 CREATE INDEX metabib_rec_descriptor_vr_format_idx ON metabib.rec_descriptor (vr_format);
13196 CREATE INDEX metabib_rec_descriptor_date1_idx ON metabib.rec_descriptor (date1);
13197 CREATE INDEX metabib_rec_descriptor_dates_idx ON metabib.rec_descriptor (date1,date2);
13198
13199 CREATE TABLE asset.opac_visible_copies (
13200   id        BIGINT primary key, -- copy id
13201   record    BIGINT,
13202   circ_lib  INTEGER
13203 );
13204 COMMENT ON TABLE asset.opac_visible_copies IS $$
13205 Materialized view of copies that are visible in the OPAC, used by
13206 search.query_parser_fts() to speed up OPAC visibility checks on large
13207 databases.  Contents are maintained by a set of triggers.
13208 $$;
13209 CREATE INDEX opac_visible_copies_idx1 on asset.opac_visible_copies (record, circ_lib);
13210
13211 CREATE OR REPLACE FUNCTION search.query_parser_fts (
13212
13213     param_search_ou INT,
13214     param_depth     INT,
13215     param_query     TEXT,
13216     param_statuses  INT[],
13217     param_locations INT[],
13218     param_offset    INT,
13219     param_check     INT,
13220     param_limit     INT,
13221     metarecord      BOOL,
13222     staff           BOOL
13223  
13224 ) RETURNS SETOF search.search_result AS $func$
13225 DECLARE
13226
13227     current_res         search.search_result%ROWTYPE;
13228     search_org_list     INT[];
13229
13230     check_limit         INT;
13231     core_limit          INT;
13232     core_offset         INT;
13233     tmp_int             INT;
13234
13235     core_result         RECORD;
13236     core_cursor         REFCURSOR;
13237     core_rel_query      TEXT;
13238
13239     total_count         INT := 0;
13240     check_count         INT := 0;
13241     deleted_count       INT := 0;
13242     visible_count       INT := 0;
13243     excluded_count      INT := 0;
13244
13245 BEGIN
13246
13247     check_limit := COALESCE( param_check, 1000 );
13248     core_limit  := COALESCE( param_limit, 25000 );
13249     core_offset := COALESCE( param_offset, 0 );
13250
13251     -- core_skip_chk := COALESCE( param_skip_chk, 1 );
13252
13253     IF param_search_ou > 0 THEN
13254         IF param_depth IS NOT NULL THEN
13255             SELECT array_accum(distinct id) INTO search_org_list FROM actor.org_unit_descendants( param_search_ou, param_depth );
13256         ELSE
13257             SELECT array_accum(distinct id) INTO search_org_list FROM actor.org_unit_descendants( param_search_ou );
13258         END IF;
13259     ELSIF param_search_ou < 0 THEN
13260         SELECT array_accum(distinct org_unit) INTO search_org_list FROM actor.org_lasso_map WHERE lasso = -param_search_ou;
13261     ELSIF param_search_ou = 0 THEN
13262         -- reserved for user lassos (ou_buckets/type='lasso') with ID passed in depth ... hack? sure.
13263     END IF;
13264
13265     OPEN core_cursor FOR EXECUTE param_query;
13266
13267     LOOP
13268
13269         FETCH core_cursor INTO core_result;
13270         EXIT WHEN NOT FOUND;
13271         EXIT WHEN total_count >= core_limit;
13272
13273         total_count := total_count + 1;
13274
13275         CONTINUE WHEN total_count NOT BETWEEN  core_offset + 1 AND check_limit + core_offset;
13276
13277         check_count := check_count + 1;
13278
13279         PERFORM 1 FROM biblio.record_entry b WHERE NOT b.deleted AND b.id IN ( SELECT * FROM search.explode_array( core_result.records ) );
13280         IF NOT FOUND THEN
13281             -- RAISE NOTICE ' % were all deleted ... ', core_result.records;
13282             deleted_count := deleted_count + 1;
13283             CONTINUE;
13284         END IF;
13285
13286         PERFORM 1
13287           FROM  biblio.record_entry b
13288                 JOIN config.bib_source s ON (b.source = s.id)
13289           WHERE s.transcendant
13290                 AND b.id IN ( SELECT * FROM search.explode_array( core_result.records ) );
13291
13292         IF FOUND THEN
13293             -- RAISE NOTICE ' % were all transcendant ... ', core_result.records;
13294             visible_count := visible_count + 1;
13295
13296             current_res.id = core_result.id;
13297             current_res.rel = core_result.rel;
13298
13299             tmp_int := 1;
13300             IF metarecord THEN
13301                 SELECT COUNT(DISTINCT s.source) INTO tmp_int FROM metabib.metarecord_source_map s WHERE s.metarecord = core_result.id;
13302             END IF;
13303
13304             IF tmp_int = 1 THEN
13305                 current_res.record = core_result.records[1];
13306             ELSE
13307                 current_res.record = NULL;
13308             END IF;
13309
13310             RETURN NEXT current_res;
13311
13312             CONTINUE;
13313         END IF;
13314
13315         PERFORM 1
13316           FROM  asset.call_number cn
13317                 JOIN asset.uri_call_number_map map ON (map.call_number = cn.id)
13318                 JOIN asset.uri uri ON (map.uri = uri.id)
13319           WHERE NOT cn.deleted
13320                 AND cn.label = '##URI##'
13321                 AND uri.active
13322                 AND ( param_locations IS NULL OR array_upper(param_locations, 1) IS NULL )
13323                 AND cn.record IN ( SELECT * FROM search.explode_array( core_result.records ) )
13324                 AND cn.owning_lib IN ( SELECT * FROM search.explode_array( search_org_list ) )
13325           LIMIT 1;
13326
13327         IF FOUND THEN
13328             -- RAISE NOTICE ' % have at least one URI ... ', core_result.records;
13329             visible_count := visible_count + 1;
13330
13331             current_res.id = core_result.id;
13332             current_res.rel = core_result.rel;
13333
13334             tmp_int := 1;
13335             IF metarecord THEN
13336                 SELECT COUNT(DISTINCT s.source) INTO tmp_int FROM metabib.metarecord_source_map s WHERE s.metarecord = core_result.id;
13337             END IF;
13338
13339             IF tmp_int = 1 THEN
13340                 current_res.record = core_result.records[1];
13341             ELSE
13342                 current_res.record = NULL;
13343             END IF;
13344
13345             RETURN NEXT current_res;
13346
13347             CONTINUE;
13348         END IF;
13349
13350         IF param_statuses IS NOT NULL AND array_upper(param_statuses, 1) > 0 THEN
13351
13352             PERFORM 1
13353               FROM  asset.call_number cn
13354                     JOIN asset.copy cp ON (cp.call_number = cn.id)
13355               WHERE NOT cn.deleted
13356                     AND NOT cp.deleted
13357                     AND cp.status IN ( SELECT * FROM search.explode_array( param_statuses ) )
13358                     AND cn.record IN ( SELECT * FROM search.explode_array( core_result.records ) )
13359                     AND cp.circ_lib IN ( SELECT * FROM search.explode_array( search_org_list ) )
13360               LIMIT 1;
13361
13362             IF NOT FOUND THEN
13363                 -- RAISE NOTICE ' % were all status-excluded ... ', core_result.records;
13364                 excluded_count := excluded_count + 1;
13365                 CONTINUE;
13366             END IF;
13367
13368         END IF;
13369
13370         IF param_locations IS NOT NULL AND array_upper(param_locations, 1) > 0 THEN
13371
13372             PERFORM 1
13373               FROM  asset.call_number cn
13374                     JOIN asset.copy cp ON (cp.call_number = cn.id)
13375               WHERE NOT cn.deleted
13376                     AND NOT cp.deleted
13377                     AND cp.location IN ( SELECT * FROM search.explode_array( param_locations ) )
13378                     AND cn.record IN ( SELECT * FROM search.explode_array( core_result.records ) )
13379                     AND cp.circ_lib IN ( SELECT * FROM search.explode_array( search_org_list ) )
13380               LIMIT 1;
13381
13382             IF NOT FOUND THEN
13383                 -- RAISE NOTICE ' % were all copy_location-excluded ... ', core_result.records;
13384                 excluded_count := excluded_count + 1;
13385                 CONTINUE;
13386             END IF;
13387
13388         END IF;
13389
13390         IF staff IS NULL OR NOT staff THEN
13391
13392             PERFORM 1
13393               FROM  asset.opac_visible_copies
13394               WHERE circ_lib IN ( SELECT * FROM search.explode_array( search_org_list ) )
13395                     AND record IN ( SELECT * FROM search.explode_array( core_result.records ) )
13396               LIMIT 1;
13397
13398             IF NOT FOUND THEN
13399                 -- RAISE NOTICE ' % were all visibility-excluded ... ', core_result.records;
13400                 excluded_count := excluded_count + 1;
13401                 CONTINUE;
13402             END IF;
13403
13404         ELSE
13405
13406             PERFORM 1
13407               FROM  asset.call_number cn
13408                     JOIN asset.copy cp ON (cp.call_number = cn.id)
13409                     JOIN actor.org_unit a ON (cp.circ_lib = a.id)
13410               WHERE NOT cn.deleted
13411                     AND NOT cp.deleted
13412                     AND cp.circ_lib IN ( SELECT * FROM search.explode_array( search_org_list ) )
13413                     AND cn.record IN ( SELECT * FROM search.explode_array( core_result.records ) )
13414               LIMIT 1;
13415
13416             IF NOT FOUND THEN
13417
13418                 PERFORM 1
13419                   FROM  asset.call_number cn
13420                   WHERE cn.record IN ( SELECT * FROM search.explode_array( core_result.records ) )
13421                   LIMIT 1;
13422
13423                 IF FOUND THEN
13424                     -- RAISE NOTICE ' % were all visibility-excluded ... ', core_result.records;
13425                     excluded_count := excluded_count + 1;
13426                     CONTINUE;
13427                 END IF;
13428
13429             END IF;
13430
13431         END IF;
13432
13433         visible_count := visible_count + 1;
13434
13435         current_res.id = core_result.id;
13436         current_res.rel = core_result.rel;
13437
13438         tmp_int := 1;
13439         IF metarecord THEN
13440             SELECT COUNT(DISTINCT s.source) INTO tmp_int FROM metabib.metarecord_source_map s WHERE s.metarecord = core_result.id;
13441         END IF;
13442
13443         IF tmp_int = 1 THEN
13444             current_res.record = core_result.records[1];
13445         ELSE
13446             current_res.record = NULL;
13447         END IF;
13448
13449         RETURN NEXT current_res;
13450
13451         IF visible_count % 1000 = 0 THEN
13452             -- RAISE NOTICE ' % visible so far ... ', visible_count;
13453         END IF;
13454
13455     END LOOP;
13456
13457     current_res.id = NULL;
13458     current_res.rel = NULL;
13459     current_res.record = NULL;
13460     current_res.total = total_count;
13461     current_res.checked = check_count;
13462     current_res.deleted = deleted_count;
13463     current_res.visible = visible_count;
13464     current_res.excluded = excluded_count;
13465
13466     CLOSE core_cursor;
13467
13468     RETURN NEXT current_res;
13469
13470 END;
13471 $func$ LANGUAGE PLPGSQL;
13472
13473 ALTER TABLE biblio.record_entry ADD COLUMN owner INT REFERENCES actor.org_unit (id);
13474 ALTER TABLE biblio.record_entry ADD COLUMN share_depth INT;
13475
13476 ALTER TABLE auditor.biblio_record_entry_history ADD COLUMN owner INT;
13477 ALTER TABLE auditor.biblio_record_entry_history ADD COLUMN share_depth INT;
13478
13479 CREATE OR REPLACE FUNCTION public.first_word ( TEXT ) RETURNS TEXT AS $$
13480         SELECT COALESCE(SUBSTRING( $1 FROM $_$^\S+$_$), '');
13481 $$ LANGUAGE SQL STRICT IMMUTABLE;
13482
13483 CREATE OR REPLACE FUNCTION public.normalize_space( TEXT ) RETURNS TEXT AS $$
13484     SELECT regexp_replace(regexp_replace(regexp_replace($1, E'\\n', ' ', 'g'), E'(?:^\\s+)|(\\s+$)', '', 'g'), E'\\s+', ' ', 'g');
13485 $$ LANGUAGE SQL STRICT IMMUTABLE;
13486
13487 CREATE OR REPLACE FUNCTION public.lowercase( TEXT ) RETURNS TEXT AS $$
13488     return lc(shift);
13489 $$ LANGUAGE PLPERLU STRICT IMMUTABLE;
13490
13491 CREATE OR REPLACE FUNCTION public.uppercase( TEXT ) RETURNS TEXT AS $$
13492     return uc(shift);
13493 $$ LANGUAGE PLPERLU STRICT IMMUTABLE;
13494
13495 CREATE OR REPLACE FUNCTION public.remove_diacritics( TEXT ) RETURNS TEXT AS $$
13496     use Unicode::Normalize;
13497
13498     my $x = NFD(shift);
13499     $x =~ s/\pM+//go;
13500     return $x;
13501
13502 $$ LANGUAGE PLPERLU STRICT IMMUTABLE;
13503
13504 CREATE OR REPLACE FUNCTION public.entityize( TEXT ) RETURNS TEXT AS $$
13505     use Unicode::Normalize;
13506
13507     my $x = NFC(shift);
13508     $x =~ s/([\x{0080}-\x{fffd}])/sprintf('&#x%X;',ord($1))/sgoe;
13509     return $x;
13510
13511 $$ LANGUAGE PLPERLU STRICT IMMUTABLE;
13512
13513 CREATE OR REPLACE FUNCTION actor.org_unit_ancestor_setting( setting_name TEXT, org_id INT ) RETURNS SETOF actor.org_unit_setting AS $$
13514 DECLARE
13515     setting RECORD;
13516     cur_org INT;
13517 BEGIN
13518     cur_org := org_id;
13519     LOOP
13520         SELECT INTO setting * FROM actor.org_unit_setting WHERE org_unit = cur_org AND name = setting_name;
13521         IF FOUND THEN
13522             RETURN NEXT setting;
13523         END IF;
13524         SELECT INTO cur_org parent_ou FROM actor.org_unit WHERE id = cur_org;
13525         EXIT WHEN cur_org IS NULL;
13526     END LOOP;
13527     RETURN;
13528 END;
13529 $$ LANGUAGE plpgsql STABLE;
13530
13531 CREATE OR REPLACE FUNCTION acq.extract_holding_attr_table (lineitem int, tag text) RETURNS SETOF acq.flat_lineitem_holding_subfield AS $$
13532 DECLARE
13533     counter INT;
13534     lida    acq.flat_lineitem_holding_subfield%ROWTYPE;
13535 BEGIN
13536
13537     SELECT  COUNT(*) INTO counter
13538       FROM  oils_xpath_table(
13539                 'id',
13540                 'marc',
13541                 'acq.lineitem',
13542                 '//*[@tag="' || tag || '"]',
13543                 'id=' || lineitem
13544             ) as t(i int,c text);
13545
13546     FOR i IN 1 .. counter LOOP
13547         FOR lida IN
13548             SELECT  *
13549               FROM  (   SELECT  id,i,t,v
13550                           FROM  oils_xpath_table(
13551                                     'id',
13552                                     'marc',
13553                                     'acq.lineitem',
13554                                     '//*[@tag="' || tag || '"][position()=' || i || ']/*/@code|' ||
13555                                         '//*[@tag="' || tag || '"][position()=' || i || ']/*[@code]',
13556                                     'id=' || lineitem
13557                                 ) as t(id int,t text,v text)
13558                     )x
13559         LOOP
13560             RETURN NEXT lida;
13561         END LOOP;
13562     END LOOP;
13563
13564     RETURN;
13565 END;
13566 $$ LANGUAGE PLPGSQL;
13567
13568 CREATE OR REPLACE FUNCTION oils_i18n_xlate ( keytable TEXT, keyclass TEXT, keycol TEXT, identcol TEXT, keyvalue TEXT, raw_locale TEXT ) RETURNS TEXT AS $func$
13569 DECLARE
13570     locale      TEXT := REGEXP_REPLACE( REGEXP_REPLACE( raw_locale, E'[;, ].+$', '' ), E'_', '-', 'g' );
13571     language    TEXT := REGEXP_REPLACE( locale, E'-.+$', '' );
13572     result      config.i18n_core%ROWTYPE;
13573     fallback    TEXT;
13574     keyfield    TEXT := keyclass || '.' || keycol;
13575 BEGIN
13576
13577     -- Try the full locale
13578     SELECT  * INTO result
13579       FROM  config.i18n_core
13580       WHERE fq_field = keyfield
13581             AND identity_value = keyvalue
13582             AND translation = locale;
13583
13584     -- Try just the language
13585     IF NOT FOUND THEN
13586         SELECT  * INTO result
13587           FROM  config.i18n_core
13588           WHERE fq_field = keyfield
13589                 AND identity_value = keyvalue
13590                 AND translation = language;
13591     END IF;
13592
13593     -- Fall back to the string we passed in in the first place
13594     IF NOT FOUND THEN
13595     EXECUTE
13596             'SELECT ' ||
13597                 keycol ||
13598             ' FROM ' || keytable ||
13599             ' WHERE ' || identcol || ' = ' || quote_literal(keyvalue)
13600                 INTO fallback;
13601         RETURN fallback;
13602     END IF;
13603
13604     RETURN result.string;
13605 END;
13606 $func$ LANGUAGE PLPGSQL STABLE;
13607
13608 SELECT auditor.create_auditor ( 'acq', 'invoice' );
13609
13610 SELECT auditor.create_auditor ( 'acq', 'invoice_item' );
13611
13612 SELECT auditor.create_auditor ( 'acq', 'invoice_entry' );
13613
13614 INSERT INTO acq.cancel_reason ( id, org_unit, label, description, keep_debits ) VALUES (
13615     3, 1, 'delivered_but_lost',
13616     oils_i18n_gettext( 2, 'Delivered but not received; presumed lost', 'acqcr', 'label' ), TRUE );
13617
13618 CREATE TABLE config.global_flag (
13619     label   TEXT    NOT NULL
13620 ) INHERITS (config.internal_flag);
13621 ALTER TABLE config.global_flag ADD PRIMARY KEY (name);
13622
13623 INSERT INTO config.global_flag (name, label) -- defaults to enabled=FALSE
13624     VALUES (
13625         'cat.bib.use_id_for_tcn',
13626         oils_i18n_gettext(
13627             'cat.bib.use_id_for_tcn',
13628             'Cat: Use Internal ID for TCN Value',
13629             'cgf', 
13630             'label'
13631         )
13632     );
13633
13634 -- resolves performance issue noted by EG Indiana
13635
13636 CREATE INDEX scecm_owning_copy_idx ON asset.stat_cat_entry_copy_map(owning_copy);
13637
13638 INSERT INTO config.metabib_class ( name, label ) VALUES ( 'identifier', oils_i18n_gettext('identifier', 'Identifier', 'cmc', 'name') );
13639
13640 INSERT INTO config.metabib_field ( id, field_class, name, label, format, xpath, facet_field ) VALUES
13641     (17, 'identifier', 'accession', oils_i18n_gettext(17, 'Accession Number', 'cmf', 'label'), 'marcxml', $$//marcxml:datafield[tag="001"]/text()$$, TRUE );
13642 INSERT INTO config.metabib_field ( id, field_class, name, label, format, xpath, facet_field ) VALUES
13643     (18, 'identifier', 'isbn', oils_i18n_gettext(18, 'ISBN', 'cmf', 'label'), 'marcxml', $$//marcxml:datafield[tag="020"]/marcxml:subfield[code="a" or code="z"]/text()$$, TRUE );
13644 INSERT INTO config.metabib_field ( id, field_class, name, label, format, xpath, facet_field ) VALUES
13645     (19, 'identifier', 'issn', oils_i18n_gettext(19, 'ISSN', 'cmf', 'label'), 'marcxml', $$//marcxml:datafield[tag="022"]/marcxml:subfield[code="a" or code="z"]/text()$$, TRUE );
13646 INSERT INTO config.metabib_field ( id, field_class, name, label, format, xpath, facet_field ) VALUES
13647     (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 );
13648 INSERT INTO config.metabib_field ( id, field_class, name, label, format, xpath, facet_field ) VALUES
13649     (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 );
13650 INSERT INTO config.metabib_field ( id, field_class, name, label, format, xpath, facet_field ) VALUES
13651     (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 );
13652 INSERT INTO config.metabib_field ( id, field_class, name, label, format, xpath, facet_field ) VALUES
13653     (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 );
13654 INSERT INTO config.metabib_field ( id, field_class, name, label, format, xpath, facet_field ) VALUES
13655     (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 );
13656 INSERT INTO config.metabib_field ( id, field_class, name, label, format, xpath, facet_field ) VALUES
13657     (25, 'identifier', 'bibcn', oils_i18n_gettext(25, 'Local Free-Text Call Number', 'cmf', 'label'), 'marcxml', $$//marcxml:datafield[tag="099"]//text()$$, TRUE );
13658
13659 SELECT SETVAL('config.metabib_field_id_seq'::TEXT, (SELECT MAX(id) FROM config.metabib_field), TRUE);
13660  
13661
13662 DELETE FROM config.metabib_search_alias WHERE alias = 'dc.identifier';
13663
13664 INSERT INTO config.metabib_search_alias (alias,field_class) VALUES ('id','identifier');
13665 INSERT INTO config.metabib_search_alias (alias,field_class) VALUES ('dc.identifier','identifier');
13666 INSERT INTO config.metabib_search_alias (alias,field_class,field) VALUES ('eg.isbn','identifier', 18);
13667 INSERT INTO config.metabib_search_alias (alias,field_class,field) VALUES ('eg.issn','identifier', 19);
13668 INSERT INTO config.metabib_search_alias (alias,field_class,field) VALUES ('eg.upc','identifier', 20);
13669 INSERT INTO config.metabib_search_alias (alias,field_class,field) VALUES ('eg.callnumber','identifier', 25);
13670
13671 CREATE TABLE metabib.identifier_field_entry (
13672         id              BIGSERIAL       PRIMARY KEY,
13673         source          BIGINT          NOT NULL,
13674         field           INT             NOT NULL,
13675         value           TEXT            NOT NULL,
13676         index_vector    tsvector        NOT NULL
13677 );
13678 CREATE TRIGGER metabib_identifier_field_entry_fti_trigger
13679         BEFORE UPDATE OR INSERT ON metabib.identifier_field_entry
13680         FOR EACH ROW EXECUTE PROCEDURE oils_tsearch2('keyword');
13681
13682 CREATE INDEX metabib_identifier_field_entry_index_vector_idx ON metabib.identifier_field_entry USING GIST (index_vector);
13683 CREATE INDEX metabib_identifier_field_entry_value_idx ON metabib.identifier_field_entry
13684     (SUBSTRING(value,1,1024)) WHERE index_vector = ''::TSVECTOR;
13685 CREATE INDEX metabib_identifier_field_entry_source_idx ON metabib.identifier_field_entry (source);
13686
13687 ALTER TABLE metabib.identifier_field_entry ADD CONSTRAINT metabib_identifier_field_entry_source_pkey
13688     FOREIGN KEY (source) REFERENCES biblio.record_entry (id) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED;
13689 ALTER TABLE metabib.identifier_field_entry ADD CONSTRAINT metabib_identifier_field_entry_field_pkey
13690     FOREIGN KEY (field) REFERENCES config.metabib_field (id) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED;
13691
13692 CREATE OR REPLACE FUNCTION public.translate_isbn1013( TEXT ) RETURNS TEXT AS $func$
13693     use Business::ISBN;
13694     use strict;
13695     use warnings;
13696
13697     # For each ISBN found in a single string containing a set of ISBNs:
13698     #   * Normalize an incoming ISBN to have the correct checksum and no hyphens
13699     #   * Convert an incoming ISBN10 or ISBN13 to its counterpart and return
13700
13701     my $input = shift;
13702     my $output = '';
13703
13704     foreach my $word (split(/\s/, $input)) {
13705         my $isbn = Business::ISBN->new($word);
13706
13707         # First check the checksum; if it is not valid, fix it and add the original
13708         # bad-checksum ISBN to the output
13709         if ($isbn && $isbn->is_valid_checksum() == Business::ISBN::BAD_CHECKSUM) {
13710             $output .= $isbn->isbn() . " ";
13711             $isbn->fix_checksum();
13712         }
13713
13714         # If we now have a valid ISBN, convert it to its counterpart ISBN10/ISBN13
13715         # and add the normalized original ISBN to the output
13716         if ($isbn && $isbn->is_valid()) {
13717             my $isbn_xlated = ($isbn->type eq "ISBN13") ? $isbn->as_isbn10 : $isbn->as_isbn13;
13718             $output .= $isbn->isbn . " ";
13719
13720             # If we successfully converted the ISBN to its counterpart, add the
13721             # converted ISBN to the output as well
13722             $output .= ($isbn_xlated->isbn . " ") if ($isbn_xlated);
13723         }
13724     }
13725     return $output if $output;
13726
13727     # If there were no valid ISBNs, just return the raw input
13728     return $input;
13729 $func$ LANGUAGE PLPERLU;
13730
13731 COMMENT ON FUNCTION public.translate_isbn1013(TEXT) IS $$
13732 /*
13733  * Copyright (C) 2010 Merrimack Valley Library Consortium
13734  * Jason Stephenson <jstephenson@mvlc.org>
13735  * Copyright (C) 2010 Laurentian University
13736  * Dan Scott <dscott@laurentian.ca>
13737  *
13738  * The translate_isbn1013 function takes an input ISBN and returns the
13739  * following in a single space-delimited string if the input ISBN is valid:
13740  *   - The normalized input ISBN (hyphens stripped)
13741  *   - The normalized input ISBN with a fixed checksum if the checksum was bad
13742  *   - The ISBN converted to its ISBN10 or ISBN13 counterpart, if possible
13743  */
13744 $$;
13745
13746 UPDATE config.metabib_field SET facet_field = FALSE WHERE id BETWEEN 17 AND 25;
13747 UPDATE config.metabib_field SET xpath = REPLACE(xpath,'marcxml','marc') WHERE id BETWEEN 17 AND 25;
13748 UPDATE config.metabib_field SET xpath = REPLACE(xpath,'tag','@tag') WHERE id BETWEEN 17 AND 25;
13749 UPDATE config.metabib_field SET xpath = REPLACE(xpath,'code','@code') WHERE id BETWEEN 17 AND 25;
13750 UPDATE config.metabib_field SET xpath = REPLACE(xpath,'"',E'\'') WHERE id BETWEEN 17 AND 25;
13751 UPDATE config.metabib_field SET xpath = REPLACE(xpath,'/text()','') WHERE id BETWEEN 17 AND 24;
13752
13753 INSERT INTO config.index_normalizer (name, description, func, param_count) VALUES (
13754         'ISBN 10/13 conversion',
13755         'Translate ISBN10 to ISBN13, and vice versa, for indexing purposes.',
13756         'translate_isbn1013',
13757         0
13758 );
13759
13760 INSERT INTO config.index_normalizer (name, description, func, param_count) VALUES (
13761         'Replace',
13762         'Replace all occurances of first parameter in the string with the second parameter.',
13763         'replace',
13764         2
13765 );
13766
13767 INSERT INTO config.metabib_field_index_norm_map (field,norm,pos)
13768     SELECT  m.id, i.id, 1
13769       FROM  config.metabib_field m,
13770             config.index_normalizer i
13771       WHERE i.func IN ('first_word')
13772             AND m.id IN (18);
13773
13774 INSERT INTO config.metabib_field_index_norm_map (field,norm,pos)
13775     SELECT  m.id, i.id, 2
13776       FROM  config.metabib_field m,
13777             config.index_normalizer i
13778       WHERE i.func IN ('translate_isbn1013')
13779             AND m.id IN (18);
13780
13781 INSERT INTO config.metabib_field_index_norm_map (field,norm,params)
13782     SELECT  m.id, i.id, $$['-','']$$
13783       FROM  config.metabib_field m,
13784             config.index_normalizer i
13785       WHERE i.func IN ('replace')
13786             AND m.id IN (19);
13787
13788 INSERT INTO config.metabib_field_index_norm_map (field,norm,params)
13789     SELECT  m.id, i.id, $$[' ','']$$
13790       FROM  config.metabib_field m,
13791             config.index_normalizer i
13792       WHERE i.func IN ('replace')
13793             AND m.id IN (19);
13794
13795 DELETE FROM config.metabib_field_index_norm_map WHERE norm IN (1,2) and field > 16;
13796
13797 UPDATE  config.metabib_field_index_norm_map
13798   SET   params = REPLACE(params,E'\'','"')
13799   WHERE params IS NOT NULL AND params <> '';
13800
13801 DROP TRIGGER IF EXISTS metabib_identifier_field_entry_fti_trigger ON metabib.identifier_field_entry;
13802
13803 CREATE TEXT SEARCH CONFIGURATION identifier ( COPY = title );
13804
13805 ALTER TABLE config.circ_modifier
13806         ADD COLUMN avg_wait_time INTERVAL;
13807
13808 --CREATE TABLE actor.usr_password_reset (
13809 --  id SERIAL PRIMARY KEY,
13810 --  uuid TEXT NOT NULL, 
13811 --  usr BIGINT NOT NULL REFERENCES actor.usr(id) DEFERRABLE INITIALLY DEFERRED, 
13812 --  request_time TIMESTAMP NOT NULL DEFAULT NOW(), 
13813 --  has_been_reset BOOL NOT NULL DEFAULT false
13814 --);
13815 --COMMENT ON TABLE actor.usr_password_reset IS $$
13816 --/*
13817 -- * Copyright (C) 2010 Laurentian University
13818 -- * Dan Scott <dscott@laurentian.ca>
13819 -- *
13820 -- * Self-serve password reset requests
13821 -- *
13822 -- * ****
13823 -- *
13824 -- * This program is free software; you can redistribute it and/or
13825 -- * modify it under the terms of the GNU General Public License
13826 -- * as published by the Free Software Foundation; either version 2
13827 -- * of the License, or (at your option) any later version.
13828 -- *
13829 -- * This program is distributed in the hope that it will be useful,
13830 -- * but WITHOUT ANY WARRANTY; without even the implied warranty of
13831 -- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13832 -- * GNU General Public License for more details.
13833 -- */
13834 --$$;
13835 --CREATE UNIQUE INDEX actor_usr_password_reset_uuid_idx ON actor.usr_password_reset (uuid);
13836 --CREATE INDEX actor_usr_password_reset_usr_idx ON actor.usr_password_reset (usr);
13837 --CREATE INDEX actor_usr_password_reset_request_time_idx ON actor.usr_password_reset (request_time);
13838 --CREATE INDEX actor_usr_password_reset_has_been_reset_idx ON actor.usr_password_reset (has_been_reset);
13839
13840 -- Use the identifier search class tsconfig
13841 DROP TRIGGER IF EXISTS metabib_identifier_field_entry_fti_trigger ON metabib.identifier_field_entry;
13842 CREATE TRIGGER metabib_identifier_field_entry_fti_trigger
13843     BEFORE INSERT OR UPDATE ON metabib.identifier_field_entry
13844     FOR EACH ROW
13845     EXECUTE PROCEDURE public.oils_tsearch2('identifier');
13846
13847 INSERT INTO config.global_flag (name,label,enabled)
13848     VALUES ('history.circ.retention_age',oils_i18n_gettext('history.circ.retention_age', 'Historical Circulation Retention Age', 'cgf', 'label'), TRUE);
13849 INSERT INTO config.global_flag (name,label,enabled)
13850     VALUES ('history.circ.retention_count',oils_i18n_gettext('history.circ.retention_count', 'Historical Circulations per Copy', 'cgf', 'label'), TRUE);
13851
13852 -- turn a JSON scalar into an SQL TEXT value
13853 CREATE OR REPLACE FUNCTION oils_json_to_text( TEXT ) RETURNS TEXT AS $f$
13854     use JSON::XS;                    
13855     my $json = shift();
13856     my $txt;
13857     eval { $txt = JSON::XS->new->allow_nonref->decode( $json ) };   
13858     return undef if ($@);
13859     return $txt
13860 $f$ LANGUAGE PLPERLU;
13861
13862 -- Return the list of circ chain heads in xact_start order that the user has chosen to "retain"
13863 CREATE OR REPLACE FUNCTION action.usr_visible_circs (usr_id INT) RETURNS SETOF action.circulation AS $func$
13864 DECLARE
13865     c               action.circulation%ROWTYPE;
13866     view_age        INTERVAL;
13867     usr_view_age    actor.usr_setting%ROWTYPE;
13868     usr_view_start  actor.usr_setting%ROWTYPE;
13869 BEGIN
13870     SELECT * INTO usr_view_age FROM actor.usr_setting WHERE usr = usr_id AND name = 'history.circ.retention_age';
13871     SELECT * INTO usr_view_start FROM actor.usr_setting WHERE usr = usr_id AND name = 'history.circ.retention_start';
13872
13873     IF usr_view_age.value IS NOT NULL AND usr_view_start.value IS NOT NULL THEN
13874         -- User opted in and supplied a retention age
13875         IF oils_json_to_text(usr_view_age.value)::INTERVAL > AGE(NOW(), oils_json_to_text(usr_view_start.value)::TIMESTAMPTZ) THEN
13876             view_age := AGE(NOW(), oils_json_to_text(usr_view_start.value)::TIMESTAMPTZ);
13877         ELSE
13878             view_age := oils_json_to_text(usr_view_age.value)::INTERVAL;
13879         END IF;
13880     ELSIF usr_view_start.value IS NOT NULL THEN
13881         -- User opted in
13882         view_age := AGE(NOW(), oils_json_to_text(usr_view_start.value)::TIMESTAMPTZ);
13883     ELSE
13884         -- User did not opt in
13885         RETURN;
13886     END IF;
13887
13888     FOR c IN
13889         SELECT  *
13890           FROM  action.circulation
13891           WHERE usr = usr_id
13892                 AND parent_circ IS NULL
13893                 AND xact_start > NOW() - view_age
13894           ORDER BY xact_start
13895     LOOP
13896         RETURN NEXT c;
13897     END LOOP;
13898
13899     RETURN;
13900 END;
13901 $func$ LANGUAGE PLPGSQL;
13902
13903 CREATE OR REPLACE FUNCTION action.purge_circulations () RETURNS INT AS $func$
13904 DECLARE
13905     usr_keep_age    actor.usr_setting%ROWTYPE;
13906     usr_keep_start  actor.usr_setting%ROWTYPE;
13907     org_keep_age    INTERVAL;
13908     org_keep_count  INT;
13909
13910     keep_age        INTERVAL;
13911
13912     target_acp      RECORD;
13913     circ_chain_head action.circulation%ROWTYPE;
13914     circ_chain_tail action.circulation%ROWTYPE;
13915
13916     purge_position  INT;
13917     count_purged    INT;
13918 BEGIN
13919
13920     count_purged := 0;
13921
13922     SELECT value::INTERVAL INTO org_keep_age FROM config.global_flag WHERE name = 'history.circ.retention_age' AND enabled;
13923
13924     SELECT value::INT INTO org_keep_count FROM config.global_flag WHERE name = 'history.circ.retention_count' AND enabled;
13925     IF org_keep_count IS NULL THEN
13926         RETURN count_purged; -- Gimme a count to keep, or I keep them all, forever
13927     END IF;
13928
13929     -- First, find copies with more than keep_count non-renewal circs
13930     FOR target_acp IN
13931         SELECT  target_copy,
13932                 COUNT(*) AS total_real_circs
13933           FROM  action.circulation
13934           WHERE parent_circ IS NULL
13935                 AND xact_finish IS NOT NULL
13936           GROUP BY target_copy
13937           HAVING COUNT(*) > org_keep_count
13938     LOOP
13939         purge_position := 0;
13940         -- And, for those, select circs that are finished and older than keep_age
13941         FOR circ_chain_head IN
13942             SELECT  *
13943               FROM  action.circulation
13944               WHERE target_copy = target_acp.target_copy
13945                     AND parent_circ IS NULL
13946               ORDER BY xact_start
13947         LOOP
13948
13949             -- Stop once we've purged enough circs to hit org_keep_count
13950             EXIT WHEN target_acp.total_real_circs - purge_position <= org_keep_count;
13951
13952             SELECT * INTO circ_chain_tail FROM action.circ_chain(circ_chain_head.id) ORDER BY xact_start DESC LIMIT 1;
13953             EXIT WHEN circ_chain_tail.xact_finish IS NULL;
13954
13955             -- Now get the user setings, if any, to block purging if the user wants to keep more circs
13956             usr_keep_age.value := NULL;
13957             SELECT * INTO usr_keep_age FROM actor.usr_setting WHERE usr = circ_chain_head.usr AND name = 'history.circ.retention_age';
13958
13959             usr_keep_start.value := NULL;
13960             SELECT * INTO usr_keep_start FROM actor.usr_setting WHERE usr = circ_chain_head.usr AND name = 'history.circ.retention_start_date';
13961
13962             IF usr_keep_age.value IS NOT NULL AND usr_keep_start.value IS NOT NULL THEN
13963                 IF oils_json_to_string(usr_keep_age.value)::INTERVAL > AGE(NOW(), oils_json_to_string(usr_keep_start.value)::TIMESTAMPTZ) THEN
13964                     keep_age := AGE(NOW(), oils_json_to_string(usr_keep_start.value)::TIMESTAMPTZ);
13965                 ELSE
13966                     keep_age := oils_json_to_string(usr_keep_age.value)::INTERVAL;
13967                 END IF;
13968             ELSIF usr_keep_start.value IS NOT NULL THEN
13969                 keep_age := AGE(NOW(), oils_json_to_string(usr_keep_start.value)::TIMESTAMPTZ);
13970             ELSE
13971                 keep_age := COALESCE( org_keep_age::INTERVAL, '2000 years'::INTEVAL );
13972             END IF;
13973
13974             EXIT WHEN AGE(NOW(), circ_chain_tail.xact_finish) < keep_age;
13975
13976             -- We've passed the purging tests, purge the circ chain starting at the end
13977             DELETE FROM action.circulation WHERE id = circ_chain_tail.id;
13978             WHILE circ_chain_tail.parent_circ IS NOT NULL LOOP
13979                 SELECT * INTO circ_chain_tail FROM action.circulation WHERE id = circ_chain_tail.parent_circ;
13980                 DELETE FROM action.circulation WHERE id = circ_chain_tail.id;
13981             END LOOP;
13982
13983             count_purged := count_purged + 1;
13984             purge_position := purge_position + 1;
13985
13986         END LOOP;
13987     END LOOP;
13988 END;
13989 $func$ LANGUAGE PLPGSQL;
13990
13991 CREATE OR REPLACE FUNCTION action.usr_visible_holds (usr_id INT) RETURNS SETOF action.hold_request AS $func$
13992 DECLARE
13993     h               action.hold_request%ROWTYPE;
13994     view_age        INTERVAL;
13995     view_count      INT;
13996     usr_view_count  actor.usr_setting%ROWTYPE;
13997     usr_view_age    actor.usr_setting%ROWTYPE;
13998     usr_view_start  actor.usr_setting%ROWTYPE;
13999 BEGIN
14000     SELECT * INTO usr_view_count FROM actor.usr_setting WHERE usr = usr_id AND name = 'history.hold.retention_count';
14001     SELECT * INTO usr_view_age FROM actor.usr_setting WHERE usr = usr_id AND name = 'history.hold.retention_age';
14002     SELECT * INTO usr_view_start FROM actor.usr_setting WHERE usr = usr_id AND name = 'history.hold.retention_start';
14003
14004     FOR h IN
14005         SELECT  *
14006           FROM  action.hold_request
14007           WHERE usr = usr_id
14008                 AND fulfillment_time IS NULL
14009                 AND cancel_time IS NULL
14010           ORDER BY request_time DESC
14011     LOOP
14012         RETURN NEXT h;
14013     END LOOP;
14014
14015     IF usr_view_start.value IS NULL THEN
14016         RETURN;
14017     END IF;
14018
14019     IF usr_view_age.value IS NOT NULL THEN
14020         -- User opted in and supplied a retention age
14021         IF oils_json_to_string(usr_view_age.value)::INTERVAL > AGE(NOW(), oils_json_to_string(usr_view_start.value)::TIMESTAMPTZ) THEN
14022             view_age := AGE(NOW(), oils_json_to_string(usr_view_start.value)::TIMESTAMPTZ);
14023         ELSE
14024             view_age := oils_json_to_string(usr_view_age.value)::INTERVAL;
14025         END IF;
14026     ELSE
14027         -- User opted in
14028         view_age := AGE(NOW(), oils_json_to_string(usr_view_start.value)::TIMESTAMPTZ);
14029     END IF;
14030
14031     IF usr_view_count.value IS NOT NULL THEN
14032         view_count := oils_json_to_text(usr_view_count.value)::INT;
14033     ELSE
14034         view_count := 1000;
14035     END IF;
14036
14037     -- show some fulfilled/canceled holds
14038     FOR h IN
14039         SELECT  *
14040           FROM  action.hold_request
14041           WHERE usr = usr_id
14042                 AND ( fulfillment_time IS NOT NULL OR cancel_time IS NOT NULL )
14043                 AND request_time > NOW() - view_age
14044           ORDER BY request_time DESC
14045           LIMIT view_count
14046     LOOP
14047         RETURN NEXT h;
14048     END LOOP;
14049
14050     RETURN;
14051 END;
14052 $func$ LANGUAGE PLPGSQL;
14053
14054 DROP TABLE IF EXISTS serial.bib_summary CASCADE;
14055
14056 DROP TABLE IF EXISTS serial.index_summary CASCADE;
14057
14058 DROP TABLE IF EXISTS serial.sup_summary CASCADE;
14059
14060 DROP TABLE IF EXISTS serial.issuance CASCADE;
14061
14062 DROP TABLE IF EXISTS serial.binding_unit CASCADE;
14063
14064 DROP TABLE IF EXISTS serial.subscription CASCADE;
14065
14066 CREATE TABLE asset.copy_template (
14067         id             SERIAL   PRIMARY KEY,
14068         owning_lib     INT      NOT NULL
14069                                 REFERENCES actor.org_unit (id)
14070                                 DEFERRABLE INITIALLY DEFERRED,
14071         creator        BIGINT   NOT NULL
14072                                 REFERENCES actor.usr (id)
14073                                 DEFERRABLE INITIALLY DEFERRED,
14074         editor         BIGINT   NOT NULL
14075                                 REFERENCES actor.usr (id)
14076                                 DEFERRABLE INITIALLY DEFERRED,
14077         create_date    TIMESTAMP WITH TIME ZONE    DEFAULT NOW(),
14078         edit_date      TIMESTAMP WITH TIME ZONE    DEFAULT NOW(),
14079         name           TEXT     NOT NULL,
14080         -- columns above this point are attributes of the template itself
14081         -- columns after this point are attributes of the copy this template modifies/creates
14082         circ_lib       INT      REFERENCES actor.org_unit (id)
14083                                 DEFERRABLE INITIALLY DEFERRED,
14084         status         INT      REFERENCES config.copy_status (id)
14085                                 DEFERRABLE INITIALLY DEFERRED,
14086         location       INT      REFERENCES asset.copy_location (id)
14087                                 DEFERRABLE INITIALLY DEFERRED,
14088         loan_duration  INT      CONSTRAINT valid_loan_duration CHECK (
14089                                     loan_duration IS NULL OR loan_duration IN (1,2,3)),
14090         fine_level     INT      CONSTRAINT valid_fine_level CHECK (
14091                                     fine_level IS NULL OR loan_duration IN (1,2,3)),
14092         age_protect    INT,
14093         circulate      BOOL,
14094         deposit        BOOL,
14095         ref            BOOL,
14096         holdable       BOOL,
14097         deposit_amount NUMERIC(6,2),
14098         price          NUMERIC(8,2),
14099         circ_modifier  TEXT,
14100         circ_as_type   TEXT,
14101         alert_message  TEXT,
14102         opac_visible   BOOL,
14103         floating       BOOL,
14104         mint_condition BOOL
14105 );
14106
14107 CREATE TABLE serial.subscription (
14108         id                     SERIAL       PRIMARY KEY,
14109         start_date             TIMESTAMP WITH TIME ZONE     NOT NULL,
14110         end_date               TIMESTAMP WITH TIME ZONE,    -- interpret NULL as current subscription
14111         record_entry           BIGINT       REFERENCES serial.record_entry (id)
14112                                             DEFERRABLE INITIALLY DEFERRED,
14113         expected_date_offset   INTERVAL,
14114         owning_lib             INT          NOT NULL DEFAULT 1
14115                                             REFERENCES actor.org_unit (id)
14116                                             ON DELETE SET NULL
14117                                             DEFERRABLE INITIALLY DEFERRED
14118         -- acquisitions/business-side tables link to here
14119 );
14120
14121 --at least one distribution per org_unit holding issues
14122 CREATE TABLE serial.distribution (
14123         id                    SERIAL  PRIMARY KEY,
14124         subscription          INT     NOT NULL
14125                                       REFERENCES serial.subscription (id)
14126                                                                   ON DELETE CASCADE
14127                                                                   DEFERRABLE INITIALLY DEFERRED,
14128         holding_lib           INT     NOT NULL
14129                                       REFERENCES actor.org_unit (id)
14130                                                                   DEFERRABLE INITIALLY DEFERRED,
14131         label                 TEXT    NOT NULL,
14132         receive_call_number   BIGINT  REFERENCES asset.call_number (id)
14133                                       DEFERRABLE INITIALLY DEFERRED,
14134         receive_unit_template INT     REFERENCES asset.copy_template (id)
14135                                       DEFERRABLE INITIALLY DEFERRED,
14136         bind_call_number      BIGINT  REFERENCES asset.call_number (id)
14137                                       DEFERRABLE INITIALLY DEFERRED,
14138         bind_unit_template    INT     REFERENCES asset.copy_template (id)
14139                                       DEFERRABLE INITIALLY DEFERRED,
14140         unit_label_prefix     TEXT,
14141         unit_label_suffix     TEXT,
14142         record_entry          INT     REFERENCES serial.record_entry (id)
14143                                       ON DELETE SET NULL
14144                                       DEFERRABLE INITIALLY DEFERRED
14145 );
14146
14147 CREATE UNIQUE INDEX one_dist_per_sre_idx ON serial.distribution (record_entry);
14148
14149 CREATE TABLE serial.stream (
14150         id              SERIAL  PRIMARY KEY,
14151         distribution    INT     NOT NULL
14152                                 REFERENCES serial.distribution (id)
14153                                 ON DELETE CASCADE
14154                                 DEFERRABLE INITIALLY DEFERRED,
14155         routing_label   TEXT
14156 );
14157
14158 CREATE UNIQUE INDEX label_once_per_dist
14159         ON serial.stream (distribution, routing_label)
14160         WHERE routing_label IS NOT NULL;
14161
14162 CREATE TABLE serial.routing_list_user (
14163         id             SERIAL       PRIMARY KEY,
14164         stream         INT          NOT NULL
14165                                     REFERENCES serial.stream
14166                                     ON DELETE CASCADE
14167                                     DEFERRABLE INITIALLY DEFERRED,
14168         pos            INT          NOT NULL DEFAULT 1,
14169         reader         INT          REFERENCES actor.usr
14170                                     ON DELETE CASCADE
14171                                     DEFERRABLE INITIALLY DEFERRED,
14172         department     TEXT,
14173         note           TEXT,
14174         CONSTRAINT one_pos_per_routing_list UNIQUE ( stream, pos ),
14175         CONSTRAINT reader_or_dept CHECK
14176         (
14177             -- Recipient is a person or a department, but not both
14178                 (reader IS NOT NULL AND department IS NULL) OR
14179                 (reader IS NULL AND department IS NOT NULL)
14180         )
14181 );
14182
14183 CREATE TABLE serial.caption_and_pattern (
14184         id           SERIAL       PRIMARY KEY,
14185         type         TEXT         NOT NULL
14186                                   CONSTRAINT cap_type CHECK ( type in
14187                                   ( 'basic', 'supplement', 'index' )),
14188         create_date  TIMESTAMPTZ  NOT NULL DEFAULT now(),
14189         active       BOOL         NOT NULL DEFAULT FALSE,
14190         pattern_code TEXT         NOT NULL,       -- must contain JSON
14191         enum_1       TEXT,
14192         enum_2       TEXT,
14193         enum_3       TEXT,
14194         enum_4       TEXT,
14195         enum_5       TEXT,
14196         enum_6       TEXT,
14197         chron_1      TEXT,
14198         chron_2      TEXT,
14199         chron_3      TEXT,
14200         chron_4      TEXT,
14201         chron_5      TEXT,
14202         subscription INT          NOT NULL REFERENCES serial.subscription (id)
14203                                   ON DELETE CASCADE
14204                                   DEFERRABLE INITIALLY DEFERRED
14205 );
14206
14207 CREATE TABLE serial.issuance (
14208         id              SERIAL    PRIMARY KEY,
14209         creator         INT       NOT NULL
14210                                   REFERENCES actor.usr (id)
14211                                                           DEFERRABLE INITIALLY DEFERRED,
14212         editor          INT       NOT NULL
14213                                   REFERENCES actor.usr (id)
14214                                   DEFERRABLE INITIALLY DEFERRED,
14215         create_date     TIMESTAMP WITH TIME ZONE        NOT NULL DEFAULT now(),
14216         edit_date       TIMESTAMP WITH TIME ZONE        NOT NULL DEFAULT now(),
14217         subscription    INT       NOT NULL
14218                                   REFERENCES serial.subscription (id)
14219                                   ON DELETE CASCADE
14220                                   DEFERRABLE INITIALLY DEFERRED,
14221         label           TEXT,
14222         date_published  TIMESTAMP WITH TIME ZONE,
14223         holding_code    TEXT,
14224         holding_type    TEXT      CONSTRAINT valid_holding_type CHECK
14225                                   (
14226                                       holding_type IS NULL
14227                                       OR holding_type IN ('basic','supplement','index')
14228                                   ),
14229         holding_link_id INT,
14230         caption_and_pattern  INT  REFERENCES serial.caption_and_pattern (id)
14231                               DEFERRABLE INITIALLY DEFERRED
14232         -- TODO: add columns for separate enumeration/chronology values
14233 );
14234
14235 CREATE TABLE serial.unit (
14236         label           TEXT,
14237         label_sort_key  TEXT,
14238         contents        TEXT    NOT NULL
14239 ) INHERITS (asset.copy);
14240
14241 ALTER TABLE serial.unit ADD PRIMARY KEY (id);
14242
14243 ALTER TABLE serial.unit ADD CONSTRAINT serial_unit_call_number_fkey FOREIGN KEY (call_number) REFERENCES asset.call_number (id) DEFERRABLE INITIALLY DEFERRED;
14244
14245 ALTER TABLE serial.unit ADD CONSTRAINT serial_unit_creator_fkey FOREIGN KEY (creator) REFERENCES actor.usr (id) ON DELETE SET NULL DEFERRABLE INITIALLY DEFERRED;
14246
14247 ALTER TABLE serial.unit ADD CONSTRAINT serial_unit_editor_fkey FOREIGN KEY (editor) REFERENCES actor.usr (id) ON DELETE SET NULL DEFERRABLE INITIALLY DEFERRED;
14248
14249 CREATE TABLE serial.item (
14250         id              SERIAL  PRIMARY KEY,
14251         creator         INT     NOT NULL
14252                                 REFERENCES actor.usr (id)
14253                                 DEFERRABLE INITIALLY DEFERRED,
14254         editor          INT     NOT NULL
14255                                 REFERENCES actor.usr (id)
14256                                 DEFERRABLE INITIALLY DEFERRED,
14257         create_date     TIMESTAMP WITH TIME ZONE        NOT NULL DEFAULT now(),
14258         edit_date       TIMESTAMP WITH TIME ZONE        NOT NULL DEFAULT now(),
14259         issuance        INT     NOT NULL
14260                                 REFERENCES serial.issuance (id)
14261                                 ON DELETE CASCADE
14262                                 DEFERRABLE INITIALLY DEFERRED,
14263         stream          INT     NOT NULL
14264                                 REFERENCES serial.stream (id)
14265                                 ON DELETE CASCADE
14266                                 DEFERRABLE INITIALLY DEFERRED,
14267         unit            INT     REFERENCES serial.unit (id)
14268                                 ON DELETE SET NULL
14269                                 DEFERRABLE INITIALLY DEFERRED,
14270         uri             INT     REFERENCES asset.uri (id)
14271                                 ON DELETE SET NULL
14272                                 DEFERRABLE INITIALLY DEFERRED,
14273         date_expected   TIMESTAMP WITH TIME ZONE,
14274         date_received   TIMESTAMP WITH TIME ZONE,
14275         status          TEXT    CONSTRAINT value_status_check CHECK (
14276                                status IN ( 'Bindery', 'Bound', 'Claimed', 'Discarded',
14277                                'Expected', 'Not Held', 'Not Published', 'Received'))
14278                             DEFAULT 'Expected',
14279         shadowed        BOOL    NOT NULL DEFAULT FALSE
14280 );
14281
14282 CREATE TABLE serial.item_note (
14283         id          SERIAL  PRIMARY KEY,
14284         item        INT     NOT NULL
14285                             REFERENCES serial.item (id)
14286                             ON DELETE CASCADE
14287                             DEFERRABLE INITIALLY DEFERRED,
14288         creator     INT     NOT NULL
14289                             REFERENCES actor.usr (id)
14290                             DEFERRABLE INITIALLY DEFERRED,
14291         create_date TIMESTAMP WITH TIME ZONE    DEFAULT NOW(),
14292         pub         BOOL    NOT NULL    DEFAULT FALSE,
14293         title       TEXT    NOT NULL,
14294         value       TEXT    NOT NULL
14295 );
14296
14297 CREATE TABLE serial.basic_summary (
14298         id                  SERIAL  PRIMARY KEY,
14299         distribution        INT     NOT NULL
14300                                     REFERENCES serial.distribution (id)
14301                                     ON DELETE CASCADE
14302                                     DEFERRABLE INITIALLY DEFERRED,
14303         generated_coverage  TEXT    NOT NULL,
14304         textual_holdings    TEXT
14305 );
14306
14307 CREATE TABLE serial.supplement_summary (
14308         id                  SERIAL  PRIMARY KEY,
14309         distribution        INT     NOT NULL
14310                                     REFERENCES serial.distribution (id)
14311                                     ON DELETE CASCADE
14312                                     DEFERRABLE INITIALLY DEFERRED,
14313         generated_coverage  TEXT    NOT NULL,
14314         textual_holdings    TEXT
14315 );
14316
14317 CREATE TABLE serial.index_summary (
14318         id                  SERIAL  PRIMARY KEY,
14319         distribution        INT     NOT NULL
14320                                     REFERENCES serial.distribution (id)
14321                                     ON DELETE CASCADE
14322                                     DEFERRABLE INITIALLY DEFERRED,
14323         generated_coverage  TEXT    NOT NULL,
14324         textual_holdings    TEXT
14325 );
14326
14327 -- 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.
14328
14329 DROP INDEX IF EXISTS authority.authority_record_unique_tcn;
14330 CREATE UNIQUE INDEX authority_record_unique_tcn ON authority.record_entry (arn_source,arn_value) WHERE deleted = FALSE OR deleted IS FALSE;
14331
14332 DROP INDEX IF EXISTS asset.asset_call_number_label_once_per_lib;
14333 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;
14334
14335 DROP INDEX IF EXISTS biblio.biblio_record_unique_tcn;
14336 CREATE UNIQUE INDEX biblio_record_unique_tcn ON biblio.record_entry (tcn_value) WHERE deleted = FALSE OR deleted IS FALSE;
14337
14338 CREATE OR REPLACE FUNCTION config.interval_to_seconds( interval_val INTERVAL )
14339 RETURNS INTEGER AS $$
14340 BEGIN
14341         RETURN EXTRACT( EPOCH FROM interval_val );
14342 END;
14343 $$ LANGUAGE plpgsql;
14344
14345 CREATE OR REPLACE FUNCTION config.interval_to_seconds( interval_string TEXT )
14346 RETURNS INTEGER AS $$
14347 BEGIN
14348         RETURN config.interval_to_seconds( interval_string::INTERVAL );
14349 END;
14350 $$ LANGUAGE plpgsql;
14351
14352 INSERT INTO container.biblio_record_entry_bucket_type( code, label ) VALUES (
14353     'temp',
14354     oils_i18n_gettext(
14355         'temp',
14356         'Temporary bucket which gets deleted after use.',
14357         'cbrebt',
14358         'label'
14359     )
14360 );
14361
14362 -- 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.
14363
14364 CREATE OR REPLACE FUNCTION biblio.check_marcxml_well_formed () RETURNS TRIGGER AS $func$
14365 BEGIN
14366
14367     IF xml_is_well_formed(NEW.marc) THEN
14368         RETURN NEW;
14369     ELSE
14370         RAISE EXCEPTION 'Attempted to % MARCXML that is not well formed', TG_OP;
14371     END IF;
14372     
14373 END;
14374 $func$ LANGUAGE PLPGSQL;
14375
14376 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();
14377
14378 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();
14379
14380 ALTER TABLE serial.record_entry
14381         ALTER COLUMN marc DROP NOT NULL;
14382
14383 insert INTO CONFIG.xml_transform(name, namespace_uri, prefix, xslt)
14384 VALUES ('marc21expand880', 'http://www.loc.gov/MARC21/slim', 'marc', $$<?xml version="1.0" encoding="UTF-8"?>
14385 <xsl:stylesheet
14386     xmlns:xsl="http://www.w3.org/1999/XSL/Transform" 
14387     xmlns:marc="http://www.loc.gov/MARC21/slim"
14388     version="1.0">
14389 <!--
14390 Copyright (C) 2010  Equinox Software, Inc.
14391 Galen Charlton <gmc@esilibrary.cOM.
14392
14393 This program is free software; you can redistribute it and/or
14394 modify it under the terms of the GNU General Public License
14395 as published by the Free Software Foundation; either version 2
14396 of the License, or (at your option) any later version.
14397
14398 This program is distributed in the hope that it will be useful,
14399 but WITHOUT ANY WARRANTY; without even the implied warranty of
14400 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14401 GNU General Public License for more details.
14402
14403 marc21_expand_880.xsl - stylesheet used during indexing to
14404                         map alternative graphical representations
14405                         of MARC fields stored in 880 fields
14406                         to the corresponding tag name and value.
14407
14408 For example, if a MARC record for a Chinese book has
14409
14410 245.00 $6 880-01 $a Ba shi san nian duan pian xiao shuo xuan
14411 880.00 $6 245-01/$1 $a八十三年短篇小說選
14412
14413 this stylesheet will transform it to the equivalent of
14414
14415 245.00 $6 880-01 $a Ba shi san nian duan pian xiao shuo xuan
14416 245.00 $6 245-01/$1 $a八十三年短篇小說選
14417
14418 -->
14419     <xsl:output encoding="UTF-8" indent="yes" method="xml"/>
14420
14421     <xsl:template match="@*|node()">
14422         <xsl:copy>
14423             <xsl:apply-templates select="@*|node()"/>
14424         </xsl:copy>
14425     </xsl:template>
14426
14427     <xsl:template match="//marc:datafield[@tag='880']">
14428         <xsl:if test="./marc:subfield[@code='6'] and string-length(./marc:subfield[@code='6']) &gt;= 6">
14429             <marc:datafield>
14430                 <xsl:attribute name="tag">
14431                     <xsl:value-of select="substring(./marc:subfield[@code='6'], 1, 3)" />
14432                 </xsl:attribute>
14433                 <xsl:attribute name="ind1">
14434                     <xsl:value-of select="@ind1" />
14435                 </xsl:attribute>
14436                 <xsl:attribute name="ind2">
14437                     <xsl:value-of select="@ind2" />
14438                 </xsl:attribute>
14439                 <xsl:apply-templates />
14440             </marc:datafield>
14441         </xsl:if>
14442     </xsl:template>
14443     
14444 </xsl:stylesheet>$$);
14445
14446 -- Splitting the ingest trigger up into little bits
14447
14448 CREATE TEMPORARY TABLE eg_0301_check_if_has_contents (
14449     flag INTEGER PRIMARY KEY
14450 ) ON COMMIT DROP;
14451 INSERT INTO eg_0301_check_if_has_contents VALUES (1);
14452
14453 -- cause failure if either of the tables we want to drop have rows
14454 INSERT INTO eg_0301_check_if_has_contents SELECT 1 FROM asset.copy_transparency LIMIT 1;
14455 INSERT INTO eg_0301_check_if_has_contents SELECT 1 FROM asset.copy_transparency_map LIMIT 1;
14456
14457 DROP TABLE IF EXISTS asset.copy_transparency_map;
14458 DROP TABLE IF EXISTS asset.copy_transparency;
14459
14460 UPDATE config.metabib_field SET facet_xpath = '//' || facet_xpath WHERE facet_xpath IS NOT NULL;
14461
14462 -- We won't necessarily use all of these, but they are here for completeness.
14463 -- Source is the EDI spec 1229 codelist, eg: http://www.stylusstudio.com/edifact/D04B/1229.htm
14464 -- Values are the EDI code value + 1000
14465
14466 INSERT INTO acq.cancel_reason (keep_debits, id, org_unit, label, description) VALUES 
14467 ('t',(  1+1000), 1, 'Added',     'The information is to be or has been added.'),
14468 ('f',(  2+1000), 1, 'Deleted',   'The information is to be or has been deleted.'),
14469 ('t',(  3+1000), 1, 'Changed',   'The information is to be or has been changed.'),
14470 ('t',(  4+1000), 1, 'No action',                  'This line item is not affected by the actual message.'),
14471 ('t',(  5+1000), 1, 'Accepted without amendment', 'This line item is entirely accepted by the seller.'),
14472 ('t',(  6+1000), 1, 'Accepted with amendment',    'This line item is accepted but amended by the seller.'),
14473 ('f',(  7+1000), 1, 'Not accepted',               'This line item is not accepted by the seller.'),
14474 ('t',(  8+1000), 1, 'Schedule only', 'Code specifying that the message is a schedule only.'),
14475 ('t',(  9+1000), 1, 'Amendments',    'Code specifying that amendments are requested/notified.'),
14476 ('f',( 10+1000), 1, 'Not found',   'This line item is not found in the referenced message.'),
14477 ('t',( 11+1000), 1, 'Not amended', 'This line is not amended by the buyer.'),
14478 ('t',( 12+1000), 1, 'Line item numbers changed', 'Code specifying that the line item numbers have changed.'),
14479 ('t',( 13+1000), 1, 'Buyer has deducted amount', 'Buyer has deducted amount from payment.'),
14480 ('t',( 14+1000), 1, 'Buyer claims against invoice', 'Buyer has a claim against an outstanding invoice.'),
14481 ('t',( 15+1000), 1, 'Charge back by seller', 'Factor has been requested to charge back the outstanding item.'),
14482 ('t',( 16+1000), 1, 'Seller will issue credit note', 'Seller agrees to issue a credit note.'),
14483 ('t',( 17+1000), 1, 'Terms changed for new terms', 'New settlement terms have been agreed.'),
14484 ('t',( 18+1000), 1, 'Abide outcome of negotiations', 'Factor agrees to abide by the outcome of negotiations between seller and buyer.'),
14485 ('t',( 19+1000), 1, 'Seller rejects dispute', 'Seller does not accept validity of dispute.'),
14486 ('t',( 20+1000), 1, 'Settlement', 'The reported situation is settled.'),
14487 ('t',( 21+1000), 1, 'No delivery', 'Code indicating that no delivery will be required.'),
14488 ('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).'),
14489 ('t',( 23+1000), 1, 'Proposed amendment', 'A code used to indicate an amendment suggested by the sender.'),
14490 ('t',( 24+1000), 1, 'Accepted with amendment, no confirmation required', 'Accepted with changes which require no confirmation.'),
14491 ('t',( 25+1000), 1, 'Equipment provisionally repaired', 'The equipment or component has been provisionally repaired.'),
14492 ('t',( 26+1000), 1, 'Included', 'Code indicating that the entity is included.'),
14493 ('t',( 27+1000), 1, 'Verified documents for coverage', 'Upon receipt and verification of documents we shall cover you when due as per your instructions.'),
14494 ('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.'),
14495 ('t',( 29+1000), 1, 'Authenticated advice for coverage',      'On receipt of your authenticated advice we shall cover you when due as per your instructions.'),
14496 ('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.'),
14497 ('t',( 31+1000), 1, 'Authenticated advice for credit',        'On receipt of your authenticated advice we shall credit your account with us when due.'),
14498 ('t',( 32+1000), 1, 'Credit advice requested for direct debit',           'A credit advice is requested for the direct debit.'),
14499 ('t',( 33+1000), 1, 'Credit advice and acknowledgement for direct debit', 'A credit advice and acknowledgement are requested for the direct debit.'),
14500 ('t',( 34+1000), 1, 'Inquiry',     'Request for information.'),
14501 ('t',( 35+1000), 1, 'Checked',     'Checked.'),
14502 ('t',( 36+1000), 1, 'Not checked', 'Not checked.'),
14503 ('f',( 37+1000), 1, 'Cancelled',   'Discontinued.'),
14504 ('t',( 38+1000), 1, 'Replaced',    'Provide a replacement.'),
14505 ('t',( 39+1000), 1, 'New',         'Not existing before.'),
14506 ('t',( 40+1000), 1, 'Agreed',      'Consent.'),
14507 ('t',( 41+1000), 1, 'Proposed',    'Put forward for consideration.'),
14508 ('t',( 42+1000), 1, 'Already delivered', 'Delivery has taken place.'),
14509 ('t',( 43+1000), 1, 'Additional subordinate structures will follow', 'Additional subordinate structures will follow the current hierarchy level.'),
14510 ('t',( 44+1000), 1, 'Additional subordinate structures will not follow', 'No additional subordinate structures will follow the current hierarchy level.'),
14511 ('t',( 45+1000), 1, 'Result opposed',         'A notification that the result is opposed.'),
14512 ('t',( 46+1000), 1, 'Auction held',           'A notification that an auction was held.'),
14513 ('t',( 47+1000), 1, 'Legal action pursued',   'A notification that legal action has been pursued.'),
14514 ('t',( 48+1000), 1, 'Meeting held',           'A notification that a meeting was held.'),
14515 ('t',( 49+1000), 1, 'Result set aside',       'A notification that the result has been set aside.'),
14516 ('t',( 50+1000), 1, 'Result disputed',        'A notification that the result has been disputed.'),
14517 ('t',( 51+1000), 1, 'Countersued',            'A notification that a countersuit has been filed.'),
14518 ('t',( 52+1000), 1, 'Pending',                'A notification that an action is awaiting settlement.'),
14519 ('f',( 53+1000), 1, 'Court action dismissed', 'A notification that a court action will no longer be heard.'),
14520 ('t',( 54+1000), 1, 'Referred item, accepted', 'The item being referred to has been accepted.'),
14521 ('f',( 55+1000), 1, 'Referred item, rejected', 'The item being referred to has been rejected.'),
14522 ('t',( 56+1000), 1, 'Debit advice statement line',  'Notification that the statement line is a debit advice.'),
14523 ('t',( 57+1000), 1, 'Credit advice statement line', 'Notification that the statement line is a credit advice.'),
14524 ('t',( 58+1000), 1, 'Grouped credit advices',       'Notification that the credit advices are grouped.'),
14525 ('t',( 59+1000), 1, 'Grouped debit advices',        'Notification that the debit advices are grouped.'),
14526 ('t',( 60+1000), 1, 'Registered', 'The name is registered.'),
14527 ('f',( 61+1000), 1, 'Payment denied', 'The payment has been denied.'),
14528 ('t',( 62+1000), 1, 'Approved as amended', 'Approved with modifications.'),
14529 ('t',( 63+1000), 1, 'Approved as submitted', 'The request has been approved as submitted.'),
14530 ('f',( 64+1000), 1, 'Cancelled, no activity', 'Cancelled due to the lack of activity.'),
14531 ('t',( 65+1000), 1, 'Under investigation', 'Investigation is being done.'),
14532 ('t',( 66+1000), 1, 'Initial claim received', 'Notification that the initial claim was received.'),
14533 ('f',( 67+1000), 1, 'Not in process', 'Not in process.'),
14534 ('f',( 68+1000), 1, 'Rejected, duplicate', 'Rejected because it is a duplicate.'),
14535 ('f',( 69+1000), 1, 'Rejected, resubmit with corrections', 'Rejected but may be resubmitted when corrected.'),
14536 ('t',( 70+1000), 1, 'Pending, incomplete', 'Pending because of incomplete information.'),
14537 ('t',( 71+1000), 1, 'Under field office investigation', 'Investigation by the field is being done.'),
14538 ('t',( 72+1000), 1, 'Pending, awaiting additional material', 'Pending awaiting receipt of additional material.'),
14539 ('t',( 73+1000), 1, 'Pending, awaiting review', 'Pending while awaiting review.'),
14540 ('t',( 74+1000), 1, 'Reopened', 'Opened again.'),
14541 ('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).'),
14542 ('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).'),
14543 ('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).'),
14544 ('t',( 78+1000), 1, 'Previous payment decision reversed', 'A previous payment decision has been reversed.'),
14545 ('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).'),
14546 ('t',( 80+1000), 1, 'Transferred to correct insurance carrier', 'The request has been transferred to the correct insurance carrier for processing.'),
14547 ('t',( 81+1000), 1, 'Not paid, predetermination pricing only', 'Payment has not been made and the enclosed response is predetermination pricing only.'),
14548 ('t',( 82+1000), 1, 'Documentation claim', 'The claim is for documentation purposes only, no payment required.'),
14549 ('t',( 83+1000), 1, 'Reviewed', 'Assessed.'),
14550 ('f',( 84+1000), 1, 'Repriced', 'This price was changed.'),
14551 ('t',( 85+1000), 1, 'Audited', 'An official examination has occurred.'),
14552 ('t',( 86+1000), 1, 'Conditionally paid', 'Payment has been conditionally made.'),
14553 ('t',( 87+1000), 1, 'On appeal', 'Reconsideration of the decision has been applied for.'),
14554 ('t',( 88+1000), 1, 'Closed', 'Shut.'),
14555 ('t',( 89+1000), 1, 'Reaudited', 'A subsequent official examination has occurred.'),
14556 ('t',( 90+1000), 1, 'Reissued', 'Issued again.'),
14557 ('t',( 91+1000), 1, 'Closed after reopening', 'Reopened and then closed.'),
14558 ('t',( 92+1000), 1, 'Redetermined', 'Determined again or differently.'),
14559 ('t',( 93+1000), 1, 'Processed as primary',   'Processed as the first.'),
14560 ('t',( 94+1000), 1, 'Processed as secondary', 'Processed as the second.'),
14561 ('t',( 95+1000), 1, 'Processed as tertiary',  'Processed as the third.'),
14562 ('t',( 96+1000), 1, 'Correction of error', 'A correction to information previously communicated which contained an error.'),
14563 ('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.'),
14564 ('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.'),
14565 ('t',( 99+1000), 1, 'Interim response', 'The response is an interim one.'),
14566 ('t',(100+1000), 1, 'Final response',   'The response is an final one.'),
14567 ('t',(101+1000), 1, 'Debit advice requested', 'A debit advice is requested for the transaction.'),
14568 ('t',(102+1000), 1, 'Transaction not impacted', 'Advice that the transaction is not impacted.'),
14569 ('t',(103+1000), 1, 'Patient to be notified',                    'The action to take is to notify the patient.'),
14570 ('t',(104+1000), 1, 'Healthcare provider to be notified',        'The action to take is to notify the healthcare provider.'),
14571 ('t',(105+1000), 1, 'Usual general practitioner to be notified', 'The action to take is to notify the usual general practitioner.'),
14572 ('t',(106+1000), 1, 'Advice without details', 'An advice without details is requested or notified.'),
14573 ('t',(107+1000), 1, 'Advice with details', 'An advice with details is requested or notified.'),
14574 ('t',(108+1000), 1, 'Amendment requested', 'An amendment is requested.'),
14575 ('t',(109+1000), 1, 'For information', 'Included for information only.'),
14576 ('f',(110+1000), 1, 'Withdraw', 'A code indicating discontinuance or retraction.'),
14577 ('t',(111+1000), 1, 'Delivery date change', 'The action / notiification is a change of the delivery date.'),
14578 ('f',(112+1000), 1, 'Quantity change',      'The action / notification is a change of quantity.'),
14579 ('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.'),
14580 ('t',(114+1000), 1, 'Resale',           'The identified items have been sold by the distributor to the end customer.'),
14581 ('t',(115+1000), 1, 'Prior addition', 'This existing line item becomes available at an earlier date.');
14582
14583 INSERT INTO config.metabib_field ( id, field_class, name, label, format, xpath, facet_field, search_field ) VALUES
14584     (26, 'identifier', 'arcn', oils_i18n_gettext(26, 'Authority record control number', 'cmf', 'label'), 'marcxml', $$//marc:subfield[@code='0']$$, TRUE, FALSE );
14585  
14586 SELECT SETVAL('config.metabib_field_id_seq'::TEXT, (SELECT MAX(id) FROM config.metabib_field), TRUE);
14587  
14588 INSERT INTO config.index_normalizer (name, description, func, param_count) VALUES (
14589         'Remove Parenthesized Substring',
14590         'Remove any parenthesized substrings from the extracted text, such as the agency code preceding authority record control numbers in subfield 0.',
14591         'remove_paren_substring',
14592         0
14593 );
14594
14595 INSERT INTO config.index_normalizer (name, description, func, param_count) VALUES (
14596         'Trim Surrounding Space',
14597         'Trim leading and trailing spaces from extracted text.',
14598         'btrim',
14599         0
14600 );
14601
14602 INSERT INTO config.metabib_field_index_norm_map (field,norm,pos)
14603     SELECT  m.id,
14604             i.id,
14605             -2
14606       FROM  config.metabib_field m,
14607             config.index_normalizer i
14608       WHERE i.func IN ('remove_paren_substring')
14609             AND m.id IN (26);
14610
14611 INSERT INTO config.metabib_field_index_norm_map (field,norm,pos)
14612     SELECT  m.id,
14613             i.id,
14614             -1
14615       FROM  config.metabib_field m,
14616             config.index_normalizer i
14617       WHERE i.func IN ('btrim')
14618             AND m.id IN (26);
14619
14620 -- Function that takes, and returns, marcxml and compiles an embedded ruleset for you, and they applys it
14621 CREATE OR REPLACE FUNCTION vandelay.merge_record_xml ( target_marc TEXT, template_marc TEXT ) RETURNS TEXT AS $$
14622 DECLARE
14623     dyn_profile     vandelay.compile_profile%ROWTYPE;
14624     replace_rule    TEXT;
14625     tmp_marc        TEXT;
14626     trgt_marc        TEXT;
14627     tmpl_marc        TEXT;
14628     match_count     INT;
14629 BEGIN
14630
14631     IF target_marc IS NULL OR template_marc IS NULL THEN
14632         -- RAISE NOTICE 'no marc for target or template record';
14633         RETURN NULL;
14634     END IF;
14635
14636     dyn_profile := vandelay.compile_profile( template_marc );
14637
14638     IF dyn_profile.replace_rule <> '' AND dyn_profile.preserve_rule <> '' THEN
14639         -- RAISE NOTICE 'both replace [%] and preserve [%] specified', dyn_profile.replace_rule, dyn_profile.preserve_rule;
14640         RETURN NULL;
14641     END IF;
14642
14643     IF dyn_profile.replace_rule <> '' THEN
14644         trgt_marc = target_marc;
14645         tmpl_marc = template_marc;
14646         replace_rule = dyn_profile.replace_rule;
14647     ELSE
14648         tmp_marc = target_marc;
14649         trgt_marc = template_marc;
14650         tmpl_marc = tmp_marc;
14651         replace_rule = dyn_profile.preserve_rule;
14652     END IF;
14653
14654     RETURN vandelay.merge_record_xml( trgt_marc, tmpl_marc, dyn_profile.add_rule, replace_rule, dyn_profile.strip_rule );
14655
14656 END;
14657 $$ LANGUAGE PLPGSQL;
14658
14659 -- Function to generate an ephemeral overlay template from an authority record
14660 CREATE OR REPLACE FUNCTION authority.generate_overlay_template ( TEXT, BIGINT ) RETURNS TEXT AS $func$
14661
14662     use MARC::Record;
14663     use MARC::File::XML (BinaryEncoding => 'UTF-8');
14664
14665     my $xml = shift;
14666     my $r = MARC::Record->new_from_xml( $xml );
14667
14668     return undef unless ($r);
14669
14670     my $id = shift() || $r->subfield( '901' => 'c' );
14671     $id =~ s/^\s*(?:\([^)]+\))?\s*(.+)\s*?$/$1/;
14672     return undef unless ($id); # We need an ID!
14673
14674     my $tmpl = MARC::Record->new();
14675
14676     my @rule_fields;
14677     for my $field ( $r->field( '1..' ) ) { # Get main entry fields from the authority record
14678
14679         my $tag = $field->tag;
14680         my $i1 = $field->indicator(1);
14681         my $i2 = $field->indicator(2);
14682         my $sf = join '', map { $_->[0] } $field->subfields;
14683         my @data = map { @$_ } $field->subfields;
14684
14685         my @replace_them;
14686
14687         # Map the authority field to bib fields it can control.
14688         if ($tag >= 100 and $tag <= 111) {       # names
14689             @replace_them = map { $tag + $_ } (0, 300, 500, 600, 700);
14690         } elsif ($tag eq '130') {                # uniform title
14691             @replace_them = qw/130 240 440 730 830/;
14692         } elsif ($tag >= 150 and $tag <= 155) {  # subjects
14693             @replace_them = ($tag + 500);
14694         } elsif ($tag >= 180 and $tag <= 185) {  # floating subdivisions
14695             @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/;
14696         } else {
14697             next;
14698         }
14699
14700         # Dummy up the bib-side data
14701         $tmpl->append_fields(
14702             map {
14703                 MARC::Field->new( $_, $i1, $i2, @data )
14704             } @replace_them
14705         );
14706
14707         # Construct some 'replace' rules
14708         push @rule_fields, map { $_ . $sf . '[0~\)' .$id . '$]' } @replace_them;
14709     }
14710
14711     # Insert the replace rules into the template
14712     $tmpl->append_fields(
14713         MARC::Field->new( '905' => ' ' => ' ' => 'r' => join(',', @rule_fields ) )
14714     );
14715
14716     $xml = $tmpl->as_xml_record;
14717     $xml =~ s/^<\?.+?\?>$//mo;
14718     $xml =~ s/\n//sgo;
14719     $xml =~ s/>\s+</></sgo;
14720
14721     return $xml;
14722
14723 $func$ LANGUAGE PLPERLU;
14724
14725 CREATE OR REPLACE FUNCTION authority.generate_overlay_template ( BIGINT ) RETURNS TEXT AS $func$
14726     SELECT authority.generate_overlay_template( marc, id ) FROM authority.record_entry WHERE id = $1;
14727 $func$ LANGUAGE SQL;
14728
14729 CREATE OR REPLACE FUNCTION authority.generate_overlay_template ( TEXT ) RETURNS TEXT AS $func$
14730     SELECT authority.generate_overlay_template( $1, NULL );
14731 $func$ LANGUAGE SQL;
14732
14733 DELETE FROM config.metabib_field_index_norm_map WHERE field = 26;
14734 DELETE FROM config.metabib_field WHERE id = 26;
14735
14736 -- Making this a global_flag (UI accessible) instead of an internal_flag
14737 INSERT INTO config.global_flag (name, label) -- defaults to enabled=FALSE
14738     VALUES (
14739         'ingest.disable_authority_linking',
14740         oils_i18n_gettext(
14741             'ingest.disable_authority_linking',
14742             'Authority Automation: Disable bib-authority link tracking',
14743             'cgf', 
14744             'label'
14745         )
14746     );
14747 UPDATE config.global_flag SET enabled = (SELECT enabled FROM ONLY config.internal_flag WHERE name = 'ingest.disable_authority_linking');
14748 DELETE FROM config.internal_flag WHERE name = 'ingest.disable_authority_linking';
14749
14750 INSERT INTO config.global_flag (name, label) -- defaults to enabled=FALSE
14751     VALUES (
14752         'ingest.disable_authority_auto_update',
14753         oils_i18n_gettext(
14754             'ingest.disable_authority_auto_update',
14755             'Authority Automation: Disable automatic authority updating (requires link tracking)',
14756             'cgf', 
14757             'label'
14758         )
14759     );
14760
14761 -- Enable automated ingest of authority records; just insert the row into
14762 -- authority.record_entry and authority.full_rec will automatically be populated
14763
14764 CREATE OR REPLACE FUNCTION authority.propagate_changes (aid BIGINT, bid BIGINT) RETURNS BIGINT AS $func$
14765     UPDATE  biblio.record_entry
14766       SET   marc = vandelay.merge_record_xml( marc, authority.generate_overlay_template( $1 ) )
14767       WHERE id = $2;
14768     SELECT $1;
14769 $func$ LANGUAGE SQL;
14770
14771 CREATE OR REPLACE FUNCTION authority.propagate_changes (aid BIGINT) RETURNS SETOF BIGINT AS $func$
14772     SELECT authority.propagate_changes( authority, bib ) FROM authority.bib_linking WHERE authority = $1;
14773 $func$ LANGUAGE SQL;
14774
14775 -- authority.rec_descriptor appears to be unused currently
14776 CREATE OR REPLACE FUNCTION authority.reingest_authority_rec_descriptor( auth_id BIGINT ) RETURNS VOID AS $func$
14777 BEGIN
14778     DELETE FROM authority.rec_descriptor WHERE record = auth_id;
14779 --    INSERT INTO authority.rec_descriptor (record, record_status, char_encoding)
14780 --        SELECT  auth_id, ;
14781
14782     RETURN;
14783 END;
14784 $func$ LANGUAGE PLPGSQL;
14785
14786 CREATE OR REPLACE FUNCTION authority.reingest_authority_full_rec( auth_id BIGINT ) RETURNS VOID AS $func$
14787 BEGIN
14788     DELETE FROM authority.full_rec WHERE record = auth_id;
14789     INSERT INTO authority.full_rec (record, tag, ind1, ind2, subfield, value)
14790         SELECT record, tag, ind1, ind2, subfield, value FROM authority.flatten_marc( auth_id );
14791
14792     RETURN;
14793 END;
14794 $func$ LANGUAGE PLPGSQL;
14795
14796 -- AFTER UPDATE OR INSERT trigger for authority.record_entry
14797 CREATE OR REPLACE FUNCTION authority.indexing_ingest_or_delete () RETURNS TRIGGER AS $func$
14798 BEGIN
14799
14800     IF NEW.deleted IS TRUE THEN -- If this authority is deleted
14801         DELETE FROM authority.bib_linking WHERE authority = NEW.id; -- Avoid updating fields in bibs that are no longer visible
14802           -- Should remove matching $0 from controlled fields at the same time?
14803         RETURN NEW; -- and we're done
14804     END IF;
14805
14806     IF TG_OP = 'UPDATE' THEN -- re-ingest?
14807         PERFORM * FROM config.internal_flag WHERE name = 'ingest.reingest.force_on_same_marc' AND enabled;
14808
14809         IF NOT FOUND AND OLD.marc = NEW.marc THEN -- don't do anything if the MARC didn't change
14810             RETURN NEW;
14811         END IF;
14812     END IF;
14813
14814     -- Flatten and insert the afr data
14815     PERFORM * FROM config.internal_flag WHERE name = 'ingest.disable_authority_full_rec' AND enabled;
14816     IF NOT FOUND THEN
14817         PERFORM authority.reingest_authority_full_rec(NEW.id);
14818 -- authority.rec_descriptor is not currently used
14819 --        PERFORM * FROM config.internal_flag WHERE name = 'ingest.disable_authority_rec_descriptor' AND enabled;
14820 --        IF NOT FOUND THEN
14821 --            PERFORM authority.reingest_authority_rec_descriptor(NEW.id);
14822 --        END IF;
14823     END IF;
14824
14825     RETURN NEW;
14826 END;
14827 $func$ LANGUAGE PLPGSQL;
14828
14829 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 ();
14830
14831 -- Some records manage to get XML namespace declarations into each element,
14832 -- like <datafield xmlns:marc="http://www.loc.gov/MARC21/slim"
14833 -- This broke the old maintain_901(), so we'll make the regex more robust
14834
14835 CREATE OR REPLACE FUNCTION maintain_901 () RETURNS TRIGGER AS $func$
14836 BEGIN
14837     -- Remove any existing 901 fields before we insert the authoritative one
14838     NEW.marc := REGEXP_REPLACE(NEW.marc, E'<datafield\s*[^<>]*?\s*tag="901".+?</datafield>', '', 'g');
14839     IF TG_TABLE_SCHEMA = 'biblio' THEN
14840         NEW.marc := REGEXP_REPLACE(
14841             NEW.marc,
14842             E'(</(?:[^:]*?:)?record>)',
14843             E'<datafield tag="901" ind1=" " ind2=" ">' ||
14844                 '<subfield code="a">' || NEW.tcn_value || E'</subfield>' ||
14845                 '<subfield code="b">' || NEW.tcn_source || E'</subfield>' ||
14846                 '<subfield code="c">' || NEW.id || E'</subfield>' ||
14847                 '<subfield code="t">' || TG_TABLE_SCHEMA || E'</subfield>' ||
14848                 CASE WHEN NEW.owner IS NOT NULL THEN '<subfield code="o">' || NEW.owner || E'</subfield>' ELSE '' END ||
14849                 CASE WHEN NEW.share_depth IS NOT NULL THEN '<subfield code="d">' || NEW.share_depth || E'</subfield>' ELSE '' END ||
14850              E'</datafield>\\1'
14851         );
14852     ELSIF TG_TABLE_SCHEMA = 'authority' THEN
14853         NEW.marc := REGEXP_REPLACE(
14854             NEW.marc,
14855             E'(</(?:[^:]*?:)?record>)',
14856             E'<datafield tag="901" ind1=" " ind2=" ">' ||
14857                 '<subfield code="a">' || NEW.arn_value || E'</subfield>' ||
14858                 '<subfield code="b">' || NEW.arn_source || E'</subfield>' ||
14859                 '<subfield code="c">' || NEW.id || E'</subfield>' ||
14860                 '<subfield code="t">' || TG_TABLE_SCHEMA || E'</subfield>' ||
14861              E'</datafield>\\1'
14862         );
14863     ELSIF TG_TABLE_SCHEMA = 'serial' THEN
14864         NEW.marc := REGEXP_REPLACE(
14865             NEW.marc,
14866             E'(</(?:[^:]*?:)?record>)',
14867             E'<datafield tag="901" ind1=" " ind2=" ">' ||
14868                 '<subfield code="c">' || NEW.id || E'</subfield>' ||
14869                 '<subfield code="t">' || TG_TABLE_SCHEMA || E'</subfield>' ||
14870                 '<subfield code="o">' || NEW.owning_lib || E'</subfield>' ||
14871                 CASE WHEN NEW.record IS NOT NULL THEN '<subfield code="r">' || NEW.record || E'</subfield>' ELSE '' END ||
14872              E'</datafield>\\1'
14873         );
14874     ELSE
14875         NEW.marc := REGEXP_REPLACE(
14876             NEW.marc,
14877             E'(</(?:[^:]*?:)?record>)',
14878             E'<datafield tag="901" ind1=" " ind2=" ">' ||
14879                 '<subfield code="c">' || NEW.id || E'</subfield>' ||
14880                 '<subfield code="t">' || TG_TABLE_SCHEMA || E'</subfield>' ||
14881              E'</datafield>\\1'
14882         );
14883     END IF;
14884
14885     RETURN NEW;
14886 END;
14887 $func$ LANGUAGE PLPGSQL;
14888
14889 CREATE TRIGGER b_maintain_901 BEFORE INSERT OR UPDATE ON biblio.record_entry FOR EACH ROW EXECUTE PROCEDURE maintain_901();
14890 CREATE TRIGGER b_maintain_901 BEFORE INSERT OR UPDATE ON authority.record_entry FOR EACH ROW EXECUTE PROCEDURE maintain_901();
14891 CREATE TRIGGER b_maintain_901 BEFORE INSERT OR UPDATE ON serial.record_entry FOR EACH ROW EXECUTE PROCEDURE maintain_901();
14892  
14893 -- In booking, elbow room defines:
14894 --  a) how far in the future you must make a reservation on a given item if
14895 --      that item will have to transit somewhere to fulfill the reservation.
14896 --  b) how soon a reservation must be starting for the reserved item to
14897 --      be op-captured by the checkin interface.
14898
14899 -- We don't want to clobber any default_elbow room at any level:
14900
14901 CREATE OR REPLACE FUNCTION pg_temp.default_elbow() RETURNS INTEGER AS $$
14902 DECLARE
14903     existing    actor.org_unit_setting%ROWTYPE;
14904 BEGIN
14905     SELECT INTO existing id FROM actor.org_unit_setting WHERE name = 'circ.booking_reservation.default_elbow_room';
14906     IF NOT FOUND THEN
14907         INSERT INTO actor.org_unit_setting (org_unit, name, value) VALUES (
14908             (SELECT id FROM actor.org_unit WHERE parent_ou IS NULL),
14909             'circ.booking_reservation.default_elbow_room',
14910             '"1 day"'
14911         );
14912         RETURN 1;
14913     END IF;
14914     RETURN 0;
14915 END;
14916 $$ LANGUAGE plpgsql;
14917
14918 SELECT pg_temp.default_elbow();
14919
14920 DROP FUNCTION IF EXISTS action.usr_visible_circ_copies( INTEGER );
14921
14922 -- returns the distinct set of target copy IDs from a user's visible circulation history
14923 CREATE OR REPLACE FUNCTION action.usr_visible_circ_copies( INTEGER ) RETURNS SETOF BIGINT AS $$
14924     SELECT DISTINCT(target_copy) FROM action.usr_visible_circs($1)
14925 $$ LANGUAGE SQL;
14926
14927 ALTER TABLE action.in_house_use DROP CONSTRAINT in_house_use_item_fkey;
14928 ALTER TABLE action.transit_copy DROP CONSTRAINT transit_copy_target_copy_fkey;
14929 ALTER TABLE action.hold_transit_copy DROP CONSTRAINT ahtc_tc_fkey;
14930
14931 ALTER TABLE asset.stat_cat_entry_copy_map DROP CONSTRAINT a_sc_oc_fkey;
14932
14933 ALTER TABLE authority.record_entry ADD COLUMN owner INT;
14934 ALTER TABLE serial.record_entry ADD COLUMN owner INT;
14935
14936 INSERT INTO config.global_flag (name, label) -- defaults to enabled=FALSE
14937     VALUES (
14938         'cat.maintain_control_numbers',
14939         oils_i18n_gettext(
14940             'cat.maintain_control_numbers',
14941             'Cat: Maintain 001/003/035 according to the MARC21 specification',
14942             'cgf', 
14943             'label'
14944         )
14945     );
14946
14947 CREATE OR REPLACE FUNCTION maintain_control_numbers() RETURNS TRIGGER AS $func$
14948 use strict;
14949 use MARC::Record;
14950 use MARC::File::XML (BinaryEncoding => 'UTF-8');
14951 use Encode;
14952 use Unicode::Normalize;
14953
14954 my $record = MARC::Record->new_from_xml($_TD->{new}{marc});
14955 my $schema = $_TD->{table_schema};
14956 my $rec_id = $_TD->{new}{id};
14957
14958 # Short-circuit if maintaining control numbers per MARC21 spec is not enabled
14959 my $enable = spi_exec_query("SELECT enabled FROM config.global_flag WHERE name = 'cat.maintain_control_numbers'");
14960 if (!($enable->{processed}) or $enable->{rows}[0]->{enabled} eq 'f') {
14961     return;
14962 }
14963
14964 # Get the control number identifier from an OU setting based on $_TD->{new}{owner}
14965 my $ou_cni = 'EVRGRN';
14966
14967 my $owner;
14968 if ($schema eq 'serial') {
14969     $owner = $_TD->{new}{owning_lib};
14970 } else {
14971     # are.owner and bre.owner can be null, so fall back to the consortial setting
14972     $owner = $_TD->{new}{owner} || 1;
14973 }
14974
14975 my $ous_rv = spi_exec_query("SELECT value FROM actor.org_unit_ancestor_setting('cat.marc_control_number_identifier', $owner)");
14976 if ($ous_rv->{processed}) {
14977     $ou_cni = $ous_rv->{rows}[0]->{value};
14978     $ou_cni =~ s/"//g; # Stupid VIM syntax highlighting"
14979 } else {
14980     # Fall back to the shortname of the OU if there was no OU setting
14981     $ous_rv = spi_exec_query("SELECT shortname FROM actor.org_unit WHERE id = $owner");
14982     if ($ous_rv->{processed}) {
14983         $ou_cni = $ous_rv->{rows}[0]->{shortname};
14984     }
14985 }
14986
14987 my ($create, $munge) = (0, 0);
14988 my ($orig_001, $orig_003) = ('', '');
14989
14990 # Incoming MARC records may have multiple 001s or 003s, despite the spec
14991 my @control_ids = $record->field('003');
14992 my @scns = $record->field('035');
14993
14994 foreach my $id_field ('001', '003') {
14995     my $spec_value;
14996     my @controls = $record->field($id_field);
14997
14998     if ($id_field eq '001') {
14999         $spec_value = $rec_id;
15000     } else {
15001         $spec_value = $ou_cni;
15002     }
15003
15004     # Create the 001/003 if none exist
15005     if (scalar(@controls) == 0) {
15006         $record->insert_fields_ordered(MARC::Field->new($id_field, $spec_value));
15007         $create = 1;
15008     } elsif (scalar(@controls) > 1) {
15009         # Do we already have the right 001/003 value in the existing set?
15010         unless (grep $_->data() eq $spec_value, @controls) {
15011             $munge = 1;
15012         }
15013
15014         # Delete the other fields, as with more than 1 001/003 we do not know which 003/001 to match
15015         foreach my $control (@controls) {
15016             unless ($control->data() eq $spec_value) {
15017                 $record->delete_field($control);
15018             }
15019         }
15020     } else {
15021         # Only one field; check to see if we need to munge it
15022         unless (grep $_->data() eq $spec_value, @controls) {
15023             $munge = 1;
15024         }
15025     }
15026 }
15027
15028 # Now, if we need to munge the 001, we will first push the existing 001/003 into the 035
15029 if ($munge) {
15030     my $scn = "(" . $record->field('003')->data() . ")" . $record->field('001')->data();
15031
15032     # Do not create duplicate 035 fields
15033     unless (grep $_->subfield('a') eq $scn, @scns) {
15034         $record->insert_fields_ordered(MARC::Field->new('035', '', '', 'a' => $scn));
15035     }
15036 }
15037
15038 # Set the 001/003 and update the MARC
15039 if ($create or $munge) {
15040     $record->field('001')->data($rec_id);
15041     $record->field('003')->data($ou_cni);
15042
15043     my $xml = $record->as_xml_record();
15044     $xml =~ s/\n//sgo;
15045     $xml =~ s/^<\?xml.+\?\s*>//go;
15046     $xml =~ s/>\s+</></go;
15047     $xml =~ s/\p{Cc}//go;
15048
15049     # Embed a version of OpenILS::Application::AppUtils->entityize()
15050     # to avoid having to set PERL5LIB for PostgreSQL as well
15051
15052     # If we are going to convert non-ASCII characters to XML entities,
15053     # we had better be dealing with a UTF8 string to begin with
15054     $xml = decode_utf8($xml);
15055
15056     $xml = NFC($xml);
15057
15058     # Convert raw ampersands to entities
15059     $xml =~ s/&(?!\S+;)/&amp;/gso;
15060
15061     # Convert Unicode characters to entities
15062     $xml =~ s/([\x{0080}-\x{fffd}])/sprintf('&#x%X;',ord($1))/sgoe;
15063
15064     $xml =~ s/[\x00-\x1f]//go;
15065     $_TD->{new}{marc} = $xml;
15066
15067     return "MODIFY";
15068 }
15069
15070 return;
15071 $func$ LANGUAGE PLPERLU;
15072
15073 CREATE TRIGGER c_maintain_control_numbers BEFORE INSERT OR UPDATE ON authority.record_entry FOR EACH ROW EXECUTE PROCEDURE maintain_control_numbers();
15074 CREATE TRIGGER c_maintain_control_numbers BEFORE INSERT OR UPDATE ON biblio.record_entry FOR EACH ROW EXECUTE PROCEDURE maintain_control_numbers();
15075 CREATE TRIGGER c_maintain_control_numbers BEFORE INSERT OR UPDATE ON serial.record_entry FOR EACH ROW EXECUTE PROCEDURE maintain_control_numbers();
15076
15077 INSERT INTO metabib.facet_entry (source, field, value)
15078     SELECT source, field, value FROM (
15079         SELECT * FROM metabib.author_field_entry
15080             UNION ALL
15081         SELECT * FROM metabib.keyword_field_entry
15082             UNION ALL
15083         SELECT * FROM metabib.identifier_field_entry
15084             UNION ALL
15085         SELECT * FROM metabib.title_field_entry
15086             UNION ALL
15087         SELECT * FROM metabib.subject_field_entry
15088             UNION ALL
15089         SELECT * FROM metabib.series_field_entry
15090         )x
15091     WHERE x.index_vector = '';
15092         
15093 DELETE FROM metabib.author_field_entry WHERE index_vector = '';
15094 DELETE FROM metabib.keyword_field_entry WHERE index_vector = '';
15095 DELETE FROM metabib.identifier_field_entry WHERE index_vector = '';
15096 DELETE FROM metabib.title_field_entry WHERE index_vector = '';
15097 DELETE FROM metabib.subject_field_entry WHERE index_vector = '';
15098 DELETE FROM metabib.series_field_entry WHERE index_vector = '';
15099
15100 CREATE INDEX metabib_facet_entry_field_idx ON metabib.facet_entry (field);
15101 CREATE INDEX metabib_facet_entry_value_idx ON metabib.facet_entry (SUBSTRING(value,1,1024));
15102 CREATE INDEX metabib_facet_entry_source_idx ON metabib.facet_entry (source);
15103
15104 -- copy OPAC visibility materialized view
15105 CREATE OR REPLACE FUNCTION asset.refresh_opac_visible_copies_mat_view () RETURNS VOID AS $$
15106
15107     TRUNCATE TABLE asset.opac_visible_copies;
15108
15109     INSERT INTO asset.opac_visible_copies (id, circ_lib, record)
15110     SELECT  cp.id, cp.circ_lib, cn.record
15111     FROM  asset.copy cp
15112         JOIN asset.call_number cn ON (cn.id = cp.call_number)
15113         JOIN actor.org_unit a ON (cp.circ_lib = a.id)
15114         JOIN asset.copy_location cl ON (cp.location = cl.id)
15115         JOIN config.copy_status cs ON (cp.status = cs.id)
15116         JOIN biblio.record_entry b ON (cn.record = b.id)
15117     WHERE NOT cp.deleted
15118         AND NOT cn.deleted
15119         AND NOT b.deleted
15120         AND cs.opac_visible
15121         AND cl.opac_visible
15122         AND cp.opac_visible
15123         AND a.opac_visible;
15124
15125 $$ LANGUAGE SQL;
15126 COMMENT ON FUNCTION asset.refresh_opac_visible_copies_mat_view() IS $$
15127 Rebuild the copy OPAC visibility cache.  Useful during migrations.
15128 $$;
15129
15130 -- and actually populate the table
15131 SELECT asset.refresh_opac_visible_copies_mat_view();
15132
15133 CREATE OR REPLACE FUNCTION asset.cache_copy_visibility () RETURNS TRIGGER as $func$
15134 DECLARE
15135     add_query       TEXT;
15136     remove_query    TEXT;
15137     do_add          BOOLEAN := false;
15138     do_remove       BOOLEAN := false;
15139 BEGIN
15140     add_query := $$
15141             INSERT INTO asset.opac_visible_copies (id, circ_lib, record)
15142                 SELECT  cp.id, cp.circ_lib, cn.record
15143                   FROM  asset.copy cp
15144                         JOIN asset.call_number cn ON (cn.id = cp.call_number)
15145                         JOIN actor.org_unit a ON (cp.circ_lib = a.id)
15146                         JOIN asset.copy_location cl ON (cp.location = cl.id)
15147                         JOIN config.copy_status cs ON (cp.status = cs.id)
15148                         JOIN biblio.record_entry b ON (cn.record = b.id)
15149                   WHERE NOT cp.deleted
15150                         AND NOT cn.deleted
15151                         AND NOT b.deleted
15152                         AND cs.opac_visible
15153                         AND cl.opac_visible
15154                         AND cp.opac_visible
15155                         AND a.opac_visible
15156     $$;
15157  
15158     remove_query := $$ DELETE FROM asset.opac_visible_copies WHERE id IN ( SELECT id FROM asset.copy WHERE $$;
15159
15160     IF TG_OP = 'INSERT' THEN
15161
15162         IF TG_TABLE_NAME IN ('copy', 'unit') THEN
15163             add_query := add_query || 'AND cp.id = ' || NEW.id || ';';
15164             EXECUTE add_query;
15165         END IF;
15166
15167         RETURN NEW;
15168
15169     END IF;
15170
15171     -- handle items first, since with circulation activity
15172     -- their statuses change frequently
15173     IF TG_TABLE_NAME IN ('copy', 'unit') THEN
15174
15175         IF OLD.location    <> NEW.location OR
15176            OLD.call_number <> NEW.call_number OR
15177            OLD.status      <> NEW.status OR
15178            OLD.circ_lib    <> NEW.circ_lib THEN
15179             -- any of these could change visibility, but
15180             -- we'll save some queries and not try to calculate
15181             -- the change directly
15182             do_remove := true;
15183             do_add := true;
15184         ELSE
15185
15186             IF OLD.deleted <> NEW.deleted THEN
15187                 IF NEW.deleted THEN
15188                     do_remove := true;
15189                 ELSE
15190                     do_add := true;
15191                 END IF;
15192             END IF;
15193
15194             IF OLD.opac_visible <> NEW.opac_visible THEN
15195                 IF OLD.opac_visible THEN
15196                     do_remove := true;
15197                 ELSIF NOT do_remove THEN -- handle edge case where deleted item
15198                                         -- is also marked opac_visible
15199                     do_add := true;
15200                 END IF;
15201             END IF;
15202
15203         END IF;
15204
15205         IF do_remove THEN
15206             DELETE FROM asset.opac_visible_copies WHERE id = NEW.id;
15207         END IF;
15208         IF do_add THEN
15209             add_query := add_query || 'AND cp.id = ' || NEW.id || ';';
15210             EXECUTE add_query;
15211         END IF;
15212
15213         RETURN NEW;
15214
15215     END IF;
15216
15217     IF TG_TABLE_NAME IN ('call_number', 'record_entry') THEN -- these have a 'deleted' column
15218  
15219         IF OLD.deleted AND NEW.deleted THEN -- do nothing
15220
15221             RETURN NEW;
15222  
15223         ELSIF NEW.deleted THEN -- remove rows
15224  
15225             IF TG_TABLE_NAME = 'call_number' THEN
15226                 DELETE FROM asset.opac_visible_copies WHERE id IN (SELECT id FROM asset.copy WHERE call_number = NEW.id);
15227             ELSIF TG_TABLE_NAME = 'record_entry' THEN
15228                 DELETE FROM asset.opac_visible_copies WHERE record = NEW.id;
15229             END IF;
15230  
15231             RETURN NEW;
15232  
15233         ELSIF OLD.deleted THEN -- add rows
15234  
15235             IF TG_TABLE_NAME IN ('copy','unit') THEN
15236                 add_query := add_query || 'AND cp.id = ' || NEW.id || ';';
15237             ELSIF TG_TABLE_NAME = 'call_number' THEN
15238                 add_query := add_query || 'AND cp.call_number = ' || NEW.id || ';';
15239             ELSIF TG_TABLE_NAME = 'record_entry' THEN
15240                 add_query := add_query || 'AND cn.record = ' || NEW.id || ';';
15241             END IF;
15242  
15243             EXECUTE add_query;
15244             RETURN NEW;
15245  
15246         END IF;
15247  
15248     END IF;
15249
15250     IF TG_TABLE_NAME = 'call_number' THEN
15251
15252         IF OLD.record <> NEW.record THEN
15253             -- call number is linked to different bib
15254             remove_query := remove_query || 'call_number = ' || NEW.id || ');';
15255             EXECUTE remove_query;
15256             add_query := add_query || 'AND cp.call_number = ' || NEW.id || ';';
15257             EXECUTE add_query;
15258         END IF;
15259
15260         RETURN NEW;
15261
15262     END IF;
15263
15264     IF TG_TABLE_NAME IN ('record_entry') THEN
15265         RETURN NEW; -- don't have 'opac_visible'
15266     END IF;
15267
15268     -- actor.org_unit, asset.copy_location, asset.copy_status
15269     IF NEW.opac_visible = OLD.opac_visible THEN -- do nothing
15270
15271         RETURN NEW;
15272
15273     ELSIF NEW.opac_visible THEN -- add rows
15274
15275         IF TG_TABLE_NAME = 'org_unit' THEN
15276             add_query := add_query || 'AND cp.circ_lib = ' || NEW.id || ';';
15277         ELSIF TG_TABLE_NAME = 'copy_location' THEN
15278             add_query := add_query || 'AND cp.location = ' || NEW.id || ';';
15279         ELSIF TG_TABLE_NAME = 'copy_status' THEN
15280             add_query := add_query || 'AND cp.status = ' || NEW.id || ';';
15281         END IF;
15282  
15283         EXECUTE add_query;
15284  
15285     ELSE -- delete rows
15286
15287         IF TG_TABLE_NAME = 'org_unit' THEN
15288             remove_query := 'DELETE FROM asset.opac_visible_copies WHERE circ_lib = ' || NEW.id || ';';
15289         ELSIF TG_TABLE_NAME = 'copy_location' THEN
15290             remove_query := remove_query || 'location = ' || NEW.id || ');';
15291         ELSIF TG_TABLE_NAME = 'copy_status' THEN
15292             remove_query := remove_query || 'status = ' || NEW.id || ');';
15293         END IF;
15294  
15295         EXECUTE remove_query;
15296  
15297     END IF;
15298  
15299     RETURN NEW;
15300 END;
15301 $func$ LANGUAGE PLPGSQL;
15302 COMMENT ON FUNCTION asset.cache_copy_visibility() IS $$
15303 Trigger function to update the copy OPAC visiblity cache.
15304 $$;
15305 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();
15306 CREATE TRIGGER a_opac_vis_mat_view_tgr AFTER INSERT OR UPDATE ON asset.copy FOR EACH ROW EXECUTE PROCEDURE asset.cache_copy_visibility();
15307 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();
15308 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();
15309 CREATE TRIGGER a_opac_vis_mat_view_tgr AFTER INSERT OR UPDATE ON serial.unit FOR EACH ROW EXECUTE PROCEDURE asset.cache_copy_visibility();
15310 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();
15311 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();
15312
15313 -- must create this rule explicitly; it is not inherited from asset.copy
15314 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;
15315
15316 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);
15317
15318 CREATE OR REPLACE FUNCTION authority.merge_records ( target_record BIGINT, source_record BIGINT ) RETURNS INT AS $func$
15319 DECLARE
15320     moved_objects INT := 0;
15321     bib_id        INT := 0;
15322     bib_rec       biblio.record_entry%ROWTYPE;
15323     auth_link     authority.bib_linking%ROWTYPE;
15324 BEGIN
15325
15326     -- 1. Make source_record MARC a copy of the target_record to get auto-sync in linked bib records
15327     UPDATE authority.record_entry
15328       SET marc = (
15329         SELECT marc
15330           FROM authority.record_entry
15331           WHERE id = target_record
15332       )
15333       WHERE id = source_record;
15334
15335     -- 2. Update all bib records with the ID from target_record in their $0
15336     FOR bib_rec IN SELECT bre.* FROM biblio.record_entry bre 
15337       INNER JOIN authority.bib_linking abl ON abl.bib = bre.id
15338       WHERE abl.authority = target_record LOOP
15339
15340         UPDATE biblio.record_entry
15341           SET marc = REGEXP_REPLACE(marc, 
15342             E'(<subfield\\s+code="0"\\s*>[^<]*?\\))' || source_record || '<',
15343             E'\\1' || target_record || '<', 'g')
15344           WHERE id = bib_rec.id;
15345
15346           moved_objects := moved_objects + 1;
15347     END LOOP;
15348
15349     -- 3. "Delete" source_record
15350     DELETE FROM authority.record_entry
15351       WHERE id = source_record;
15352
15353     RETURN moved_objects;
15354 END;
15355 $func$ LANGUAGE plpgsql;
15356
15357 -- serial.record_entry already had an owner column spelled "owning_lib"
15358 -- Adjust the table and affected functions accordingly
15359
15360 ALTER TABLE serial.record_entry DROP COLUMN owner;
15361
15362 CREATE TABLE actor.usr_saved_search (
15363     id              SERIAL          PRIMARY KEY,
15364         owner           INT             NOT NULL REFERENCES actor.usr (id)
15365                                         ON DELETE CASCADE
15366                                         DEFERRABLE INITIALLY DEFERRED,
15367         name            TEXT            NOT NULL,
15368         create_date     TIMESTAMPTZ     NOT NULL DEFAULT now(),
15369         query_text      TEXT            NOT NULL,
15370         query_type      TEXT            NOT NULL
15371                                         CONSTRAINT valid_query_text CHECK (
15372                                         query_type IN ( 'URL' )) DEFAULT 'URL',
15373                                         -- we may add other types someday
15374         target          TEXT            NOT NULL
15375                                         CONSTRAINT valid_target CHECK (
15376                                         target IN ( 'record', 'metarecord', 'callnumber' )),
15377         CONSTRAINT name_once_per_user UNIQUE (owner, name)
15378 );
15379
15380 -- Apply Dan Wells' changes to the serial schema, from the
15381 -- seials-integration branch
15382
15383 CREATE TABLE serial.subscription_note (
15384         id           SERIAL PRIMARY KEY,
15385         subscription INT    NOT NULL
15386                             REFERENCES serial.subscription (id)
15387                             ON DELETE CASCADE
15388                             DEFERRABLE INITIALLY DEFERRED,
15389         creator      INT    NOT NULL
15390                             REFERENCES actor.usr (id)
15391                             DEFERRABLE INITIALLY DEFERRED,
15392         create_date  TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
15393         pub          BOOL   NOT NULL DEFAULT FALSE,
15394         title        TEXT   NOT NULL,
15395         value        TEXT   NOT NULL
15396 );
15397
15398 CREATE TABLE serial.distribution_note (
15399         id           SERIAL PRIMARY KEY,
15400         distribution INT    NOT NULL
15401                             REFERENCES serial.distribution (id)
15402                             ON DELETE CASCADE
15403                             DEFERRABLE INITIALLY DEFERRED,
15404         creator      INT    NOT NULL
15405                             REFERENCES actor.usr (id)
15406                             DEFERRABLE INITIALLY DEFERRED,
15407         create_date  TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
15408         pub          BOOL   NOT NULL DEFAULT FALSE,
15409         title        TEXT   NOT NULL,
15410         value        TEXT   NOT NULL
15411 );
15412
15413 ------- Begin surgery on serial.unit
15414
15415 ALTER TABLE serial.unit
15416         DROP COLUMN label;
15417
15418 ALTER TABLE serial.unit
15419         RENAME COLUMN label_sort_key TO sort_key;
15420
15421 ALTER TABLE serial.unit
15422         RENAME COLUMN contents TO detailed_contents;
15423
15424 ALTER TABLE serial.unit
15425         ADD COLUMN summary_contents TEXT;
15426
15427 UPDATE serial.unit
15428 SET summary_contents = detailed_contents;
15429
15430 ALTER TABLE serial.unit
15431         ALTER column summary_contents SET NOT NULL;
15432
15433 ------- End surgery on serial.unit
15434
15435 -- 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' );
15436
15437 -- Now rebuild the constraints dropped via cascade.
15438 -- ALTER TABLE acq.provider    ADD CONSTRAINT provider_edi_default_fkey FOREIGN KEY (edi_default) REFERENCES acq.edi_account (id) DEFERRABLE INITIALLY DEFERRED;
15439 DROP INDEX IF EXISTS money.money_mat_summary_id_idx;
15440 ALTER TABLE money.materialized_billable_xact_summary ADD PRIMARY KEY (id);
15441
15442 -- ALTER TABLE staging.billing_address_stage ADD PRIMARY KEY (row_id);
15443
15444 DELETE FROM config.metabib_field_index_norm_map
15445     WHERE norm IN (
15446         SELECT id 
15447             FROM config.index_normalizer
15448             WHERE func IN ('first_word', 'naco_normalize', 'split_date_range')
15449     )
15450     AND field = 18
15451 ;
15452
15453 -- We won't necessarily use all of these, but they are here for completeness.
15454 -- Source is the EDI spec 6063 codelist, eg: http://www.stylusstudio.com/edifact/D04B/6063.htm
15455 -- Values are the EDI code value + 1200
15456
15457 INSERT INTO acq.cancel_reason (org_unit, keep_debits, id, label, description) VALUES 
15458 (1, 't', 1201, 'Discrete quantity', 'Individually separated and distinct quantity.'),
15459 (1, 't', 1202, 'Charge', 'Quantity relevant for charge.'),
15460 (1, 't', 1203, 'Cumulative quantity', 'Quantity accumulated.'),
15461 (1, 't', 1204, 'Interest for overdrawn account', 'Interest for overdrawing the account.'),
15462 (1, 't', 1205, 'Active ingredient dose per unit', 'The dosage of active ingredient per unit.'),
15463 (1, 't', 1206, 'Auditor', 'The number of entities that audit accounts.'),
15464 (1, 't', 1207, 'Branch locations, leased', 'The number of branch locations being leased by an entity.'),
15465 (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.'),
15466 (1, 't', 1209, 'Branch locations, owned', 'The number of branch locations owned by an entity.'),
15467 (1, 't', 1210, 'Judgements registered', 'The number of judgements registered against an entity.'),
15468 (1, 't', 1211, 'Split quantity', 'Part of the whole quantity.'),
15469 (1, 't', 1212, 'Despatch quantity', 'Quantity despatched by the seller.'),
15470 (1, 't', 1213, 'Liens registered', 'The number of liens registered against an entity.'),
15471 (1, 't', 1214, 'Livestock', 'The number of animals kept for use or profit.'),
15472 (1, 't', 1215, 'Insufficient funds returned cheques', 'The number of cheques returned due to insufficient funds.'),
15473 (1, 't', 1216, 'Stolen cheques', 'The number of stolen cheques.'),
15474 (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.'),
15475 (1, 't', 1218, 'Previous quantity', 'Quantity previously referenced.'),
15476 (1, 't', 1219, 'Paid-in security shares', 'The number of security shares issued and for which full payment has been made.'),
15477 (1, 't', 1220, 'Unusable quantity', 'Quantity not usable.'),
15478 (1, 't', 1221, 'Ordered quantity', '[6024] The quantity which has been ordered.'),
15479 (1, 't', 1222, 'Quantity at 100%', 'Equivalent quantity at 100% purity.'),
15480 (1, 't', 1223, 'Active ingredient', 'Quantity at 100% active agent content.'),
15481 (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.'),
15482 (1, 't', 1225, 'Retail sales', 'Quantity of retail point of sale activity.'),
15483 (1, 't', 1226, 'Promotion quantity', 'A quantity associated with a promotional event.'),
15484 (1, 't', 1227, 'On hold for shipment', 'Article received which cannot be shipped in its present form.'),
15485 (1, 't', 1228, 'Military sales quantity', 'Quantity of goods or services sold to a military organization.'),
15486 (1, 't', 1229, 'On premises sales',  'Sale of product in restaurants or bars.'),
15487 (1, 't', 1230, 'Off premises sales', 'Sale of product directly to a store.'),
15488 (1, 't', 1231, 'Estimated annual volume', 'Volume estimated for a year.'),
15489 (1, 't', 1232, 'Minimum delivery batch', 'Minimum quantity of goods delivered at one time.'),
15490 (1, 't', 1233, 'Maximum delivery batch', 'Maximum quantity of goods delivered at one time.'),
15491 (1, 't', 1234, 'Pipes', 'The number of tubes used to convey a substance.'),
15492 (1, 't', 1235, 'Price break from', 'The minimum quantity of a quantity range for a specified (unit) price.'),
15493 (1, 't', 1236, 'Price break to', 'Maximum quantity to which the price break applies.'),
15494 (1, 't', 1237, 'Poultry', 'The number of domestic fowl.'),
15495 (1, 't', 1238, 'Secured charges registered', 'The number of secured charges registered against an entity.'),
15496 (1, 't', 1239, 'Total properties owned', 'The total number of properties owned by an entity.'),
15497 (1, 't', 1240, 'Normal delivery', 'Quantity normally delivered by the seller.'),
15498 (1, 't', 1241, 'Sales quantity not included in the replenishment', 'calculation Sales which will not be included in the calculation of replenishment requirements.'),
15499 (1, 't', 1242, 'Maximum supply quantity, supplier endorsed', 'Maximum supply quantity endorsed by a supplier.'),
15500 (1, 't', 1243, 'Buyer', 'The number of buyers.'),
15501 (1, 't', 1244, 'Debenture bond', 'The number of fixed-interest bonds of an entity backed by general credit rather than specified assets.'),
15502 (1, 't', 1245, 'Debentures filed against directors', 'The number of notices of indebtedness filed against an entity''s directors.'),
15503 (1, 't', 1246, 'Pieces delivered', 'Number of pieces actually received at the final destination.'),
15504 (1, 't', 1247, 'Invoiced quantity', 'The quantity as per invoice.'),
15505 (1, 't', 1248, 'Received quantity', 'The quantity which has been received.'),
15506 (1, 't', 1249, 'Chargeable distance', '[6110] The distance between two points for which a specific tariff applies.'),
15507 (1, 't', 1250, 'Disposition undetermined quantity', 'Product quantity that has not yet had its disposition determined.'),
15508 (1, 't', 1251, 'Inventory category transfer', 'Inventory that has been moved from one inventory category to another.'),
15509 (1, 't', 1252, 'Quantity per pack', 'Quantity for each pack.'),
15510 (1, 't', 1253, 'Minimum order quantity', 'Minimum quantity of goods for an order.'),
15511 (1, 't', 1254, 'Maximum order quantity', 'Maximum quantity of goods for an order.'),
15512 (1, 't', 1255, 'Total sales', 'The summation of total quantity sales.'),
15513 (1, 't', 1256, 'Wholesaler to wholesaler sales', 'Sale of product to other wholesalers by a wholesaler.'),
15514 (1, 't', 1257, 'In transit quantity', 'A quantity that is en route.'),
15515 (1, 't', 1258, 'Quantity withdrawn', 'Quantity withdrawn from a location.'),
15516 (1, 't', 1259, 'Numbers of consumer units in the traded unit', 'Number of units for consumer sales in a unit for trading.'),
15517 (1, 't', 1260, 'Current inventory quantity available for shipment', 'Current inventory quantity available for shipment.'),
15518 (1, 't', 1261, 'Return quantity', 'Quantity of goods returned.'),
15519 (1, 't', 1262, 'Sorted quantity', 'The quantity that is sorted.'),
15520 (1, 'f', 1263, 'Sorted quantity rejected', 'The sorted quantity that is rejected.'),
15521 (1, 't', 1264, 'Scrap quantity', 'Remainder of the total quantity after split deliveries.'),
15522 (1, 'f', 1265, 'Destroyed quantity', 'Quantity of goods destroyed.'),
15523 (1, 't', 1266, 'Committed quantity', 'Quantity a party is committed to.'),
15524 (1, 't', 1267, 'Estimated reading quantity', 'The value that is estimated to be the reading of a measuring device (e.g. meter).'),
15525 (1, 't', 1268, 'End quantity', 'The quantity recorded at the end of an agreement or period.'),
15526 (1, 't', 1269, 'Start quantity', 'The quantity recorded at the start of an agreement or period.'),
15527 (1, 't', 1270, 'Cumulative quantity received', 'Cumulative quantity of all deliveries of this article received by the buyer.'),
15528 (1, 't', 1271, 'Cumulative quantity ordered', 'Cumulative quantity of all deliveries, outstanding and scheduled orders.'),
15529 (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.'),
15530 (1, 't', 1273, 'Outstanding quantity', 'Difference between quantity ordered and quantity received.'),
15531 (1, 't', 1274, 'Latest cumulative quantity', 'Cumulative quantity after complete delivery of all scheduled quantities of the product.'),
15532 (1, 't', 1275, 'Previous highest cumulative quantity', 'Cumulative quantity after complete delivery of all scheduled quantities of the product from a prior schedule period.'),
15533 (1, 't', 1276, 'Adjusted corrector reading', 'A corrector reading after it has been adjusted.'),
15534 (1, 't', 1277, 'Work days', 'Number of work days, e.g. per respective period.'),
15535 (1, 't', 1278, 'Cumulative quantity scheduled', 'Adding the quantity actually scheduled to previous cumulative quantity.'),
15536 (1, 't', 1279, 'Previous cumulative quantity', 'Cumulative quantity prior the actual order.'),
15537 (1, 't', 1280, 'Unadjusted corrector reading', 'A corrector reading before it has been adjusted.'),
15538 (1, 't', 1281, 'Extra unplanned delivery', 'Non scheduled additional quantity.'),
15539 (1, 't', 1282, 'Quantity requirement for sample inspection', 'Required quantity for sample inspection.'),
15540 (1, 't', 1283, 'Backorder quantity', 'The quantity of goods that is on back-order.'),
15541 (1, 't', 1284, 'Urgent delivery quantity', 'Quantity for urgent delivery.'),
15542 (1, 'f', 1285, 'Previous order quantity to be cancelled', 'Quantity ordered previously to be cancelled.'),
15543 (1, 't', 1286, 'Normal reading quantity', 'The value recorded or read from a measuring device (e.g. meter) in the normal conditions.'),
15544 (1, 't', 1287, 'Customer reading quantity', 'The value recorded or read from a measuring device (e.g. meter) by the customer.'),
15545 (1, 't', 1288, 'Information reading quantity', 'The value recorded or read from a measuring device (e.g. meter) for information purposes.'),
15546 (1, 't', 1289, 'Quality control held', 'Quantity of goods held pending completion of a quality control assessment.'),
15547 (1, 't', 1290, 'As is quantity', 'Quantity as it is in the existing circumstances.'),
15548 (1, 't', 1291, 'Open quantity', 'Quantity remaining after partial delivery.'),
15549 (1, 't', 1292, 'Final delivery quantity', 'Quantity of final delivery to a respective order.'),
15550 (1, 't', 1293, 'Subsequent delivery quantity', 'Quantity delivered to a respective order after it''s final delivery.'),
15551 (1, 't', 1294, 'Substitutional quantity', 'Quantity delivered replacing previous deliveries.'),
15552 (1, 't', 1295, 'Redelivery after post processing', 'Quantity redelivered after post processing.'),
15553 (1, 'f', 1296, 'Quality control failed', 'Quantity of goods which have failed quality control.'),
15554 (1, 't', 1297, 'Minimum inventory', 'Minimum stock quantity on which replenishment is based.'),
15555 (1, 't', 1298, 'Maximum inventory', 'Maximum stock quantity on which replenishment is based.'),
15556 (1, 't', 1299, 'Estimated quantity', 'Quantity estimated.'),
15557 (1, 't', 1300, 'Chargeable weight', 'The weight on which charges are based.'),
15558 (1, 't', 1301, 'Chargeable gross weight', 'The gross weight on which charges are based.'),
15559 (1, 't', 1302, 'Chargeable tare weight', 'The tare weight on which charges are based.'),
15560 (1, 't', 1303, 'Chargeable number of axles', 'The number of axles on which charges are based.'),
15561 (1, 't', 1304, 'Chargeable number of containers', 'The number of containers on which charges are based.'),
15562 (1, 't', 1305, 'Chargeable number of rail wagons', 'The number of rail wagons on which charges are based.'),
15563 (1, 't', 1306, 'Chargeable number of packages', 'The number of packages on which charges are based.'),
15564 (1, 't', 1307, 'Chargeable number of units', 'The number of units on which charges are based.'),
15565 (1, 't', 1308, 'Chargeable period', 'The period of time on which charges are based.'),
15566 (1, 't', 1309, 'Chargeable volume', 'The volume on which charges are based.'),
15567 (1, 't', 1310, 'Chargeable cubic measurements', 'The cubic measurements on which charges are based.'),
15568 (1, 't', 1311, 'Chargeable surface', 'The surface area on which charges are based.'),
15569 (1, 't', 1312, 'Chargeable length', 'The length on which charges are based.'),
15570 (1, 't', 1313, 'Quantity to be delivered', 'The quantity to be delivered.'),
15571 (1, 't', 1314, 'Number of passengers', 'Total number of passengers on the conveyance.'),
15572 (1, 't', 1315, 'Number of crew', 'Total number of crew members on the conveyance.'),
15573 (1, 't', 1316, 'Number of transport documents', 'Total number of air waybills, bills of lading, etc. being reported for a specific conveyance.'),
15574 (1, 't', 1317, 'Quantity landed', 'Quantity of goods actually arrived.'),
15575 (1, 't', 1318, 'Quantity manifested', 'Quantity of goods contracted for delivery by the carrier.'),
15576 (1, 't', 1319, 'Short shipped', 'Indication that part of the consignment was not shipped.'),
15577 (1, 't', 1320, 'Split shipment', 'Indication that the consignment has been split into two or more shipments.'),
15578 (1, 't', 1321, 'Over shipped', 'The quantity of goods shipped that exceeds the quantity contracted.'),
15579 (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.'),
15580 (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.'),
15581 (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.'),
15582 (1, 'f', 1325, 'Pilferage goods', 'Quantity of goods stolen during transport.'),
15583 (1, 'f', 1326, 'Lost goods', 'Quantity of goods that disappeared in transport.'),
15584 (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.'),
15585 (1, 't', 1328, 'Quantity loaded', 'Quantity of goods loaded onto a means of transport.'),
15586 (1, 't', 1329, 'Units per unit price', 'Number of units per unit price.'),
15587 (1, 't', 1330, 'Allowance', 'Quantity relevant for allowance.'),
15588 (1, 't', 1331, 'Delivery quantity', 'Quantity required by buyer to be delivered.'),
15589 (1, 't', 1332, 'Cumulative quantity, preceding period, planned', 'Cumulative quantity originally planned for the preceding period.'),
15590 (1, 't', 1333, 'Cumulative quantity, preceding period, reached', 'Cumulative quantity reached in the preceding period.'),
15591 (1, 't', 1334, 'Cumulative quantity, actual planned',            'Cumulative quantity planned for now.'),
15592 (1, 't', 1335, 'Period quantity, planned', 'Quantity planned for this period.'),
15593 (1, 't', 1336, 'Period quantity, reached', 'Quantity reached during this period.'),
15594 (1, 't', 1337, 'Cumulative quantity, preceding period, estimated', 'Estimated cumulative quantity reached in the preceding period.'),
15595 (1, 't', 1338, 'Cumulative quantity, actual estimated',            'Estimated cumulative quantity reached now.'),
15596 (1, 't', 1339, 'Cumulative quantity, preceding period, measured', 'Surveyed cumulative quantity reached in the preceding period.'),
15597 (1, 't', 1340, 'Cumulative quantity, actual measured', 'Surveyed cumulative quantity reached now.'),
15598 (1, 't', 1341, 'Period quantity, measured',            'Surveyed quantity reached during this period.'),
15599 (1, 't', 1342, 'Total quantity, planned', 'Total quantity planned.'),
15600 (1, 't', 1343, 'Quantity, remaining', 'Quantity remaining.'),
15601 (1, 't', 1344, 'Tolerance', 'Plus or minus tolerance expressed as a monetary amount.'),
15602 (1, 't', 1345, 'Actual stock',          'The stock on hand, undamaged, and available for despatch, sale or use.'),
15603 (1, 't', 1346, 'Model or target stock', 'The stock quantity required or planned to have on hand, undamaged and available for use.'),
15604 (1, 't', 1347, 'Direct shipment quantity', 'Quantity to be shipped directly to a customer from a manufacturing site.'),
15605 (1, 't', 1348, 'Amortization total quantity',     'Indication of final quantity for amortization.'),
15606 (1, 't', 1349, 'Amortization order quantity',     'Indication of actual share of the order quantity for amortization.'),
15607 (1, 't', 1350, 'Amortization cumulated quantity', 'Indication of actual cumulated quantity of previous and actual amortization order quantity.'),
15608 (1, 't', 1351, 'Quantity advised',  'Quantity advised by supplier or shipper, in contrast to quantity actually received.'),
15609 (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.'),
15610 (1, 't', 1353, 'Statistical sales quantity', 'Quantity of goods sold in a specified period.'),
15611 (1, 't', 1354, 'Sales quantity planned',     'Quantity of goods required to meet future demands. - Market intelligence quantity.'),
15612 (1, 't', 1355, 'Replenishment quantity',     'Quantity required to maintain the requisite on-hand stock of goods.'),
15613 (1, 't', 1356, 'Inventory movement quantity', 'To specify the quantity of an inventory movement.'),
15614 (1, 't', 1357, 'Opening stock balance quantity', 'To specify the quantity of an opening stock balance.'),
15615 (1, 't', 1358, 'Closing stock balance quantity', 'To specify the quantity of a closing stock balance.'),
15616 (1, 't', 1359, 'Number of stops', 'Number of times a means of transport stops before arriving at destination.'),
15617 (1, 't', 1360, 'Minimum production batch', 'The quantity specified is the minimum output from a single production run.'),
15618 (1, 't', 1361, 'Dimensional sample quantity', 'The quantity defined is a sample for the purpose of validating dimensions.'),
15619 (1, 't', 1362, 'Functional sample quantity', 'The quantity defined is a sample for the purpose of validating function and performance.'),
15620 (1, 't', 1363, 'Pre-production quantity', 'Quantity of the referenced item required prior to full production.'),
15621 (1, 't', 1364, 'Delivery batch', 'Quantity of the referenced item which constitutes a standard batch for deliver purposes.'),
15622 (1, 't', 1365, 'Delivery batch multiple', 'The multiples in which delivery batches can be supplied.'),
15623 (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.'),
15624 (1, 't', 1367, 'Total delivery quantity',  'The total quantity required by the buyer to be delivered.'),
15625 (1, 't', 1368, 'Single delivery quantity', 'The quantity required by the buyer to be delivered in a single shipment.'),
15626 (1, 't', 1369, 'Supplied quantity',  'Quantity of the referenced item actually shipped.'),
15627 (1, 't', 1370, 'Allocated quantity', 'Quantity of the referenced item allocated from available stock for delivery.'),
15628 (1, 't', 1371, 'Maximum stackability', 'The number of pallets/handling units which can be safely stacked one on top of another.'),
15629 (1, 't', 1372, 'Amortisation quantity', 'The quantity of the referenced item which has a cost for tooling amortisation included in the item price.'),
15630 (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.'),
15631 (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.'),
15632 (1, 't', 1375, 'Number of moulds', 'The number of pressing moulds contained within a single piece of the referenced tooling.'),
15633 (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.'),
15634 (1, 't', 1377, 'Periodic capacity of tooling', 'Maximum production output of the referenced tool over a period of time.'),
15635 (1, 't', 1378, 'Lifetime capacity of tooling', 'Maximum production output of the referenced tool over its productive lifetime.'),
15636 (1, 't', 1379, 'Number of deliveries per despatch period', 'The number of deliveries normally expected to be despatched within each despatch period.'),
15637 (1, 't', 1380, 'Provided quantity', 'The quantity of a referenced component supplied by the buyer for manufacturing of an ordered item.'),
15638 (1, 't', 1381, 'Maximum production batch', 'The quantity specified is the maximum output from a single production run.'),
15639 (1, 'f', 1382, 'Cancelled quantity', 'Quantity of the referenced item which has previously been ordered and is now cancelled.'),
15640 (1, 't', 1383, 'No delivery requirement in this instruction', 'This delivery instruction does not contain any delivery requirements.'),
15641 (1, 't', 1384, 'Quantity of material in ordered time', 'Quantity of the referenced material within the ordered time.'),
15642 (1, 'f', 1385, 'Rejected quantity', 'The quantity of received goods rejected for quantity reasons.'),
15643 (1, 't', 1386, 'Cumulative quantity scheduled up to accumulation start date', 'The cumulative quantity scheduled up to the accumulation start date.'),
15644 (1, 't', 1387, 'Quantity scheduled', 'The quantity scheduled for delivery.'),
15645 (1, 't', 1388, 'Number of identical handling units', 'Number of identical handling units in terms of type and contents.'),
15646 (1, 't', 1389, 'Number of packages in handling unit', 'The number of packages contained in one handling unit.'),
15647 (1, 't', 1390, 'Despatch note quantity', 'The item quantity specified on the despatch note.'),
15648 (1, 't', 1391, 'Adjustment to inventory quantity', 'An adjustment to inventory quantity.'),
15649 (1, 't', 1392, 'Free goods quantity',    'Quantity of goods which are free of charge.'),
15650 (1, 't', 1393, 'Free quantity included', 'Quantity included to which no charge is applicable.'),
15651 (1, 't', 1394, 'Received and accepted',  'Quantity which has been received and accepted at a given location.'),
15652 (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.'),
15653 (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.'),
15654 (1, 't', 1397, 'Reordering level', 'Quantity at which an order may be triggered to replenish.'),
15655 (1, 't', 1399, 'Inventory withdrawal quantity', 'Quantity which has been withdrawn from inventory since the last inventory report.'),
15656 (1, 't', 1400, 'Free quantity not included', 'Free quantity not included in ordered quantity.'),
15657 (1, 't', 1401, 'Recommended overhaul and repair quantity', 'To indicate the recommended quantity of an article required to support overhaul and repair activities.'),
15658 (1, 't', 1402, 'Quantity per next higher assembly', 'To indicate the quantity required for the next higher assembly.'),
15659 (1, 't', 1403, 'Quantity per unit of issue', 'Provides the standard quantity of an article in which one unit can be issued.'),
15660 (1, 't', 1404, 'Cumulative scrap quantity',  'Provides the cumulative quantity of an item which has been identified as scrapped.'),
15661 (1, 't', 1405, 'Publication turn size', 'The quantity of magazines or newspapers grouped together with the spine facing alternate directions in a bundle.'),
15662 (1, 't', 1406, 'Recommended maintenance quantity', 'Recommended quantity of an article which is required to meet an agreed level of maintenance.'),
15663 (1, 't', 1407, 'Labour hours', 'Number of labour hours.'),
15664 (1, 't', 1408, 'Quantity requirement for maintenance and repair of', 'equipment Quantity of the material needed to maintain and repair equipment.'),
15665 (1, 't', 1409, 'Additional replenishment demand quantity', 'Incremental needs over and above normal replenishment calculations, but not intended to permanently change the model parameters.'),
15666 (1, 't', 1410, 'Returned by consumer quantity', 'Quantity returned by a consumer.'),
15667 (1, 't', 1411, 'Replenishment override quantity', 'Quantity to override the normal replenishment model calculations, but not intended to permanently change the model parameters.'),
15668 (1, 't', 1412, 'Quantity sold, net', 'Net quantity sold which includes returns of saleable inventory and other adjustments.'),
15669 (1, 't', 1413, 'Transferred out quantity',   'Quantity which was transferred out of this location.'),
15670 (1, 't', 1414, 'Transferred in quantity',    'Quantity which was transferred into this location.'),
15671 (1, 't', 1415, 'Unsaleable quantity',        'Quantity of inventory received which cannot be sold in its present condition.'),
15672 (1, 't', 1416, 'Consumer reserved quantity', 'Quantity reserved for consumer delivery or pickup and not yet withdrawn from inventory.'),
15673 (1, 't', 1417, 'Out of inventory quantity',  'Quantity of inventory which was requested but was not available.'),
15674 (1, 't', 1418, 'Quantity returned, defective or damaged', 'Quantity returned in a damaged or defective condition.'),
15675 (1, 't', 1419, 'Taxable quantity',           'Quantity subject to taxation.'),
15676 (1, 't', 1420, 'Meter reading', 'The numeric value of measure units counted by a meter.'),
15677 (1, 't', 1421, 'Maximum requestable quantity', 'The maximum quantity which may be requested.'),
15678 (1, 't', 1422, 'Minimum requestable quantity', 'The minimum quantity which may be requested.'),
15679 (1, 't', 1423, 'Daily average quantity', 'The quantity for a defined period divided by the number of days of the period.'),
15680 (1, 't', 1424, 'Budgeted hours',     'The number of budgeted hours.'),
15681 (1, 't', 1425, 'Actual hours',       'The number of actual hours.'),
15682 (1, 't', 1426, 'Earned value hours', 'The number of earned value hours.'),
15683 (1, 't', 1427, 'Estimated hours',    'The number of estimated hours.'),
15684 (1, 't', 1428, 'Level resource task quantity', 'Quantity of a resource that is level for the duration of the task.'),
15685 (1, 't', 1429, 'Available resource task quantity', 'Quantity of a resource available to complete a task.'),
15686 (1, 't', 1430, 'Work time units',   'Quantity of work units of time.'),
15687 (1, 't', 1431, 'Daily work shifts', 'Quantity of work shifts per day.'),
15688 (1, 't', 1432, 'Work time units per shift', 'Work units of time per work shift.'),
15689 (1, 't', 1433, 'Work calendar units',       'Work calendar units of time.'),
15690 (1, 't', 1434, 'Elapsed duration',   'Quantity representing the elapsed duration.'),
15691 (1, 't', 1435, 'Remaining duration', 'Quantity representing the remaining duration.'),
15692 (1, 't', 1436, 'Original duration',  'Quantity representing the original duration.'),
15693 (1, 't', 1437, 'Current duration',   'Quantity representing the current duration.'),
15694 (1, 't', 1438, 'Total float time',   'Quantity representing the total float time.'),
15695 (1, 't', 1439, 'Free float time',    'Quantity representing the free float time.'),
15696 (1, 't', 1440, 'Lag time',           'Quantity representing lag time.'),
15697 (1, 't', 1441, 'Lead time',          'Quantity representing lead time.'),
15698 (1, 't', 1442, 'Number of months', 'The number of months.'),
15699 (1, 't', 1443, 'Reserved quantity customer direct delivery sales', 'Quantity of products reserved for sales delivered direct to the customer.'),
15700 (1, 't', 1444, 'Reserved quantity retail sales', 'Quantity of products reserved for retail sales.'),
15701 (1, 't', 1445, 'Consolidated discount inventory', 'A quantity of inventory supplied at consolidated discount terms.'),
15702 (1, 't', 1446, 'Returns replacement quantity',    'A quantity of goods issued as a replacement for a returned quantity.'),
15703 (1, 't', 1447, 'Additional promotion sales forecast quantity', 'A forecast of additional quantity which will be sold during a period of promotional activity.'),
15704 (1, 't', 1448, 'Reserved quantity', 'Quantity reserved for specific purposes.'),
15705 (1, 't', 1449, 'Quantity displayed not available for sale', 'Quantity displayed within a retail outlet but not available for sale.'),
15706 (1, 't', 1450, 'Inventory discrepancy', 'The difference recorded between theoretical and physical inventory.'),
15707 (1, 't', 1451, 'Incremental order quantity', 'The incremental quantity by which ordering is carried out.'),
15708 (1, 't', 1452, 'Quantity requiring manipulation before despatch', 'A quantity of goods which needs manipulation before despatch.'),
15709 (1, 't', 1453, 'Quantity in quarantine',              'A quantity of goods which are held in a restricted area for quarantine purposes.'),
15710 (1, 't', 1454, 'Quantity withheld by owner of goods', 'A quantity of goods which has been withheld by the owner of the goods.'),
15711 (1, 't', 1455, 'Quantity not available for despatch', 'A quantity of goods not available for despatch.'),
15712 (1, 't', 1456, 'Quantity awaiting delivery', 'Quantity of goods which are awaiting delivery.'),
15713 (1, 't', 1457, 'Quantity in physical inventory',      'A quantity of goods held in physical inventory.'),
15714 (1, 't', 1458, 'Quantity held by logistic service provider', 'Quantity of goods under the control of a logistic service provider.'),
15715 (1, 't', 1459, 'Optimal quantity', 'The optimal quantity for a given purpose.'),
15716 (1, 't', 1460, 'Delivery quantity balance', 'The difference between the scheduled quantity and the quantity delivered to the consignee at a given date.'),
15717 (1, 't', 1461, 'Cumulative quantity shipped', 'Cumulative quantity of all shipments.'),
15718 (1, 't', 1462, 'Quantity suspended', 'The quantity of something which is suspended.'),
15719 (1, 't', 1463, 'Control quantity', 'The quantity designated for control purposes.'),
15720 (1, 't', 1464, 'Equipment quantity', 'A count of a quantity of equipment.'),
15721 (1, 't', 1465, 'Factor', 'Number by which the measured unit has to be multiplied to calculate the units used.'),
15722 (1, 't', 1466, 'Unsold quantity held by wholesaler', 'Unsold quantity held by the wholesaler.'),
15723 (1, 't', 1467, 'Quantity held by delivery vehicle', 'Quantity of goods held by the delivery vehicle.'),
15724 (1, 't', 1468, 'Quantity held by retail outlet', 'Quantity held by the retail outlet.'),
15725 (1, 'f', 1469, 'Rejected return quantity', 'A quantity for return which has been rejected.'),
15726 (1, 't', 1470, 'Accounts', 'The number of accounts.'),
15727 (1, 't', 1471, 'Accounts placed for collection', 'The number of accounts placed for collection.'),
15728 (1, 't', 1472, 'Activity codes', 'The number of activity codes.'),
15729 (1, 't', 1473, 'Agents', 'The number of agents.'),
15730 (1, 't', 1474, 'Airline attendants', 'The number of airline attendants.'),
15731 (1, 't', 1475, 'Authorised shares',  'The number of shares authorised for issue.'),
15732 (1, 't', 1476, 'Employee average',   'The average number of employees.'),
15733 (1, 't', 1477, 'Branch locations',   'The number of branch locations.'),
15734 (1, 't', 1478, 'Capital changes',    'The number of capital changes made.'),
15735 (1, 't', 1479, 'Clerks', 'The number of clerks.'),
15736 (1, 't', 1480, 'Companies in same activity', 'The number of companies doing business in the same activity category.'),
15737 (1, 't', 1481, 'Companies included in consolidated financial statement', 'The number of companies included in a consolidated financial statement.'),
15738 (1, 't', 1482, 'Cooperative shares', 'The number of cooperative shares.'),
15739 (1, 't', 1483, 'Creditors',   'The number of creditors.'),
15740 (1, 't', 1484, 'Departments', 'The number of departments.'),
15741 (1, 't', 1485, 'Design employees', 'The number of employees involved in the design process.'),
15742 (1, 't', 1486, 'Physicians', 'The number of medical doctors.'),
15743 (1, 't', 1487, 'Domestic affiliated companies', 'The number of affiliated companies located within the country.'),
15744 (1, 't', 1488, 'Drivers', 'The number of drivers.'),
15745 (1, 't', 1489, 'Employed at location',     'The number of employees at the specified location.'),
15746 (1, 't', 1490, 'Employed by this company', 'The number of employees at the specified company.'),
15747 (1, 't', 1491, 'Total employees',    'The total number of employees.'),
15748 (1, 't', 1492, 'Employees shared',   'The number of employees shared among entities.'),
15749 (1, 't', 1493, 'Engineers',          'The number of engineers.'),
15750 (1, 't', 1494, 'Estimated accounts', 'The estimated number of accounts.'),
15751 (1, 't', 1495, 'Estimated employees at location', 'The estimated number of employees at the specified location.'),
15752 (1, 't', 1496, 'Estimated total employees',       'The total estimated number of employees.'),
15753 (1, 't', 1497, 'Executives', 'The number of executives.'),
15754 (1, 't', 1498, 'Agricultural workers',   'The number of agricultural workers.'),
15755 (1, 't', 1499, 'Financial institutions', 'The number of financial institutions.'),
15756 (1, 't', 1500, 'Floors occupied', 'The number of floors occupied.'),
15757 (1, 't', 1501, 'Foreign related entities', 'The number of related entities located outside the country.'),
15758 (1, 't', 1502, 'Group employees',    'The number of employees within the group.'),
15759 (1, 't', 1503, 'Indirect employees', 'The number of employees not associated with direct production.'),
15760 (1, 't', 1504, 'Installers',    'The number of employees involved with the installation process.'),
15761 (1, 't', 1505, 'Invoices',      'The number of invoices.'),
15762 (1, 't', 1506, 'Issued shares', 'The number of shares actually issued.'),
15763 (1, 't', 1507, 'Labourers',     'The number of labourers.'),
15764 (1, 't', 1508, 'Manufactured units', 'The number of units manufactured.'),
15765 (1, 't', 1509, 'Maximum number of employees', 'The maximum number of people employed.'),
15766 (1, 't', 1510, 'Maximum number of employees at location', 'The maximum number of people employed at a location.'),
15767 (1, 't', 1511, 'Members in group', 'The number of members within a group.'),
15768 (1, 't', 1512, 'Minimum number of employees at location', 'The minimum number of people employed at a location.'),
15769 (1, 't', 1513, 'Minimum number of employees', 'The minimum number of people employed.'),
15770 (1, 't', 1514, 'Non-union employees', 'The number of employees not belonging to a labour union.'),
15771 (1, 't', 1515, 'Floors', 'The number of floors in a building.'),
15772 (1, 't', 1516, 'Nurses', 'The number of nurses.'),
15773 (1, 't', 1517, 'Office workers', 'The number of workers in an office.'),
15774 (1, 't', 1518, 'Other employees', 'The number of employees otherwise categorised.'),
15775 (1, 't', 1519, 'Part time employees', 'The number of employees working on a part time basis.'),
15776 (1, 't', 1520, 'Accounts payable average overdue days', 'The average number of days accounts payable are overdue.'),
15777 (1, 't', 1521, 'Pilots', 'The number of pilots.'),
15778 (1, 't', 1522, 'Plant workers', 'The number of workers within a plant.'),
15779 (1, 't', 1523, 'Previous number of accounts', 'The number of accounts which preceded the current count.'),
15780 (1, 't', 1524, 'Previous number of branch locations', 'The number of branch locations which preceded the current count.'),
15781 (1, 't', 1525, 'Principals included as employees', 'The number of principals which are included in the count of employees.'),
15782 (1, 't', 1526, 'Protested bills', 'The number of bills which are protested.'),
15783 (1, 't', 1527, 'Registered brands distributed', 'The number of registered brands which are being distributed.'),
15784 (1, 't', 1528, 'Registered brands manufactured', 'The number of registered brands which are being manufactured.'),
15785 (1, 't', 1529, 'Related business entities', 'The number of related business entities.'),
15786 (1, 't', 1530, 'Relatives employed', 'The number of relatives which are counted as employees.'),
15787 (1, 't', 1531, 'Rooms',        'The number of rooms.'),
15788 (1, 't', 1532, 'Salespersons', 'The number of salespersons.'),
15789 (1, 't', 1533, 'Seats',        'The number of seats.'),
15790 (1, 't', 1534, 'Shareholders', 'The number of shareholders.'),
15791 (1, 't', 1535, 'Shares of common stock', 'The number of shares of common stock.'),
15792 (1, 't', 1536, 'Shares of preferred stock', 'The number of shares of preferred stock.'),
15793 (1, 't', 1537, 'Silent partners', 'The number of silent partners.'),
15794 (1, 't', 1538, 'Subcontractors',  'The number of subcontractors.'),
15795 (1, 't', 1539, 'Subsidiaries',    'The number of subsidiaries.'),
15796 (1, 't', 1540, 'Law suits',       'The number of law suits.'),
15797 (1, 't', 1541, 'Suppliers',       'The number of suppliers.'),
15798 (1, 't', 1542, 'Teachers',        'The number of teachers.'),
15799 (1, 't', 1543, 'Technicians',     'The number of technicians.'),
15800 (1, 't', 1544, 'Trainees',        'The number of trainees.'),
15801 (1, 't', 1545, 'Union employees', 'The number of employees who are members of a labour union.'),
15802 (1, 't', 1546, 'Number of units', 'The quantity of units.'),
15803 (1, 't', 1547, 'Warehouse employees', 'The number of employees who work in a warehouse setting.'),
15804 (1, 't', 1548, 'Shareholders holding remainder of shares', 'Number of shareholders owning the remainder of shares.'),
15805 (1, 't', 1549, 'Payment orders filed', 'Number of payment orders filed.'),
15806 (1, 't', 1550, 'Uncovered cheques', 'Number of uncovered cheques.'),
15807 (1, 't', 1551, 'Auctions', 'Number of auctions.'),
15808 (1, 't', 1552, 'Units produced', 'The number of units produced.'),
15809 (1, 't', 1553, 'Added employees', 'Number of employees that were added to the workforce.'),
15810 (1, 't', 1554, 'Number of added locations', 'Number of locations that were added.'),
15811 (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.'),
15812 (1, 't', 1556, 'Number of closed locations', 'Number of locations that were closed.'),
15813 (1, 't', 1557, 'Counter clerks', 'The number of clerks that work behind a flat-topped fitment.'),
15814 (1, 't', 1558, 'Payment experiences in the last 3 months', 'The number of payment experiences received for an entity over the last 3 months.'),
15815 (1, 't', 1559, 'Payment experiences in the last 12 months', 'The number of payment experiences received for an entity over the last 12 months.'),
15816 (1, 't', 1560, 'Total number of subsidiaries not included in the financial', 'statement The total number of subsidiaries not included in the financial statement.'),
15817 (1, 't', 1561, 'Paid-in common shares', 'The number of paid-in common shares.'),
15818 (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.'),
15819 (1, 't', 1563, 'Total number of foreign subsidiaries included in financial statement', 'The total number of foreign subsidiaries included in the financial statement.'),
15820 (1, 't', 1564, 'Total number of domestic subsidiaries included in financial statement', 'The total number of domestic subsidiaries included in the financial statement.'),
15821 (1, 't', 1565, 'Total transactions', 'The total number of transactions.'),
15822 (1, 't', 1566, 'Paid-in preferred shares', 'The number of paid-in preferred shares.'),
15823 (1, 't', 1567, 'Employees', 'Code specifying the quantity of persons working for a company, whose services are used for pay.'),
15824 (1, 't', 1568, 'Active ingredient dose per unit, dispensed', 'The dosage of active ingredient per dispensed unit.'),
15825 (1, 't', 1569, 'Budget', 'Budget quantity.'),
15826 (1, 't', 1570, 'Budget, cumulative to date', 'Budget quantity, cumulative to date.'),
15827 (1, 't', 1571, 'Actual units', 'The number of actual units.'),
15828 (1, 't', 1572, 'Actual units, cumulative to date', 'The number of cumulative to date actual units.'),
15829 (1, 't', 1573, 'Earned value', 'Earned value quantity.'),
15830 (1, 't', 1574, 'Earned value, cumulative to date', 'Earned value quantity accumulated to date.'),
15831 (1, 't', 1575, 'At completion quantity, estimated', 'The estimated quantity when a project is complete.'),
15832 (1, 't', 1576, 'To complete quantity, estimated', 'The estimated quantity required to complete a project.'),
15833 (1, 't', 1577, 'Adjusted units', 'The number of adjusted units.'),
15834 (1, 't', 1578, 'Number of limited partnership shares', 'Number of shares held in a limited partnership.'),
15835 (1, 't', 1579, 'National business failure incidences', 'Number of firms in a country that discontinued with a loss to creditors.'),
15836 (1, 't', 1580, 'Industry business failure incidences', 'Number of firms in a specific industry that discontinued with a loss to creditors.'),
15837 (1, 't', 1581, 'Business class failure incidences', 'Number of firms in a specific class that discontinued with a loss to creditors.'),
15838 (1, 't', 1582, 'Mechanics', 'Number of mechanics.'),
15839 (1, 't', 1583, 'Messengers', 'Number of messengers.'),
15840 (1, 't', 1584, 'Primary managers', 'Number of primary managers.'),
15841 (1, 't', 1585, 'Secretaries', 'Number of secretaries.'),
15842 (1, 't', 1586, 'Detrimental legal filings', 'Number of detrimental legal filings.'),
15843 (1, 't', 1587, 'Branch office locations, estimated', 'Estimated number of branch office locations.'),
15844 (1, 't', 1588, 'Previous number of employees', 'The number of employees for a previous period.'),
15845 (1, 't', 1589, 'Asset seizers', 'Number of entities that seize assets of another entity.'),
15846 (1, 't', 1590, 'Out-turned quantity', 'The quantity discharged.'),
15847 (1, 't', 1591, 'Material on-board quantity, prior to loading', 'The material in vessel tanks, void spaces, and pipelines prior to loading.'),
15848 (1, 't', 1592, 'Supplier estimated previous meter reading', 'Previous meter reading estimated by the supplier.'),
15849 (1, 't', 1593, 'Supplier estimated latest meter reading',   'Latest meter reading estimated by the supplier.'),
15850 (1, 't', 1594, 'Customer estimated previous meter reading', 'Previous meter reading estimated by the customer.'),
15851 (1, 't', 1595, 'Customer estimated latest meter reading',   'Latest meter reading estimated by the customer.'),
15852 (1, 't', 1596, 'Supplier previous meter reading',           'Previous meter reading done by the supplier.'),
15853 (1, 't', 1597, 'Supplier latest meter reading',             'Latest meter reading recorded by the supplier.'),
15854 (1, 't', 1598, 'Maximum number of purchase orders allowed', 'Maximum number of purchase orders that are allowed.'),
15855 (1, 't', 1599, 'File size before compression', 'The size of a file before compression.'),
15856 (1, 't', 1600, 'File size after compression', 'The size of a file after compression.'),
15857 (1, 't', 1601, 'Securities shares', 'Number of shares of securities.'),
15858 (1, 't', 1602, 'Patients',         'Number of patients.'),
15859 (1, 't', 1603, 'Completed projects', 'Number of completed projects.'),
15860 (1, 't', 1604, 'Promoters',        'Number of entities who finance or organize an event or a production.'),
15861 (1, 't', 1605, 'Administrators',   'Number of administrators.'),
15862 (1, 't', 1606, 'Supervisors',      'Number of supervisors.'),
15863 (1, 't', 1607, 'Professionals',    'Number of professionals.'),
15864 (1, 't', 1608, 'Debt collectors',  'Number of debt collectors.'),
15865 (1, 't', 1609, 'Inspectors',       'Number of individuals who perform inspections.'),
15866 (1, 't', 1610, 'Operators',        'Number of operators.'),
15867 (1, 't', 1611, 'Trainers',         'Number of trainers.'),
15868 (1, 't', 1612, 'Active accounts',  'Number of accounts in a current or active status.'),
15869 (1, 't', 1613, 'Trademarks used',  'Number of trademarks used.'),
15870 (1, 't', 1614, 'Machines',         'Number of machines.'),
15871 (1, 't', 1615, 'Fuel pumps',       'Number of fuel pumps.'),
15872 (1, 't', 1616, 'Tables available', 'Number of tables available for use.'),
15873 (1, 't', 1617, 'Directors',        'Number of directors.'),
15874 (1, 't', 1618, 'Freelance debt collectors', 'Number of debt collectors who work on a freelance basis.'),
15875 (1, 't', 1619, 'Freelance salespersons',    'Number of salespersons who work on a freelance basis.'),
15876 (1, 't', 1620, 'Travelling employees',      'Number of travelling employees.'),
15877 (1, 't', 1621, 'Foremen', 'Number of workers with limited supervisory responsibilities.'),
15878 (1, 't', 1622, 'Production workers', 'Number of employees engaged in production.'),
15879 (1, 't', 1623, 'Employees not including owners', 'Number of employees excluding business owners.'),
15880 (1, 't', 1624, 'Beds', 'Number of beds.'),
15881 (1, 't', 1625, 'Resting quantity', 'A quantity of product that is at rest before it can be used.'),
15882 (1, 't', 1626, 'Production requirements', 'Quantity needed to meet production requirements.'),
15883 (1, 't', 1627, 'Corrected quantity', 'The quantity has been corrected.'),
15884 (1, 't', 1628, 'Operating divisions', 'Number of divisions operating.'),
15885 (1, 't', 1629, 'Quantitative incentive scheme base', 'Quantity constituting the base for the quantitative incentive scheme.'),
15886 (1, 't', 1630, 'Petitions filed', 'Number of petitions that have been filed.'),
15887 (1, 't', 1631, 'Bankruptcy petitions filed', 'Number of bankruptcy petitions that have been filed.'),
15888 (1, 't', 1632, 'Projects in process', 'Number of projects in process.'),
15889 (1, 't', 1633, 'Changes in capital structure', 'Number of modifications made to the capital structure of an entity.'),
15890 (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.'),
15891 (1, 't', 1635, 'Number of failed businesses of directors', 'The number of failed businesses with which the directors have been associated.'),
15892 (1, 't', 1636, 'Professor', 'The number of professors.'),
15893 (1, 't', 1637, 'Seller',    'The number of sellers.'),
15894 (1, 't', 1638, 'Skilled worker', 'The number of skilled workers.'),
15895 (1, 't', 1639, 'Trademark represented', 'The number of trademarks represented.'),
15896 (1, 't', 1640, 'Number of quantitative incentive scheme units', 'Number of units allocated to a quantitative incentive scheme.'),
15897 (1, 't', 1641, 'Quantity in manufacturing process', 'Quantity currently in the manufacturing process.'),
15898 (1, 't', 1642, 'Number of units in the width of a layer', 'Number of units which make up the width of a layer.'),
15899 (1, 't', 1643, 'Number of units in the depth of a layer', 'Number of units which make up the depth of a layer.'),
15900 (1, 't', 1644, 'Return to warehouse', 'A quantity of products sent back to the warehouse.'),
15901 (1, 't', 1645, 'Return to the manufacturer', 'A quantity of products sent back from the manufacturer.'),
15902 (1, 't', 1646, 'Delta quantity', 'An increment or decrement to a quantity.'),
15903 (1, 't', 1647, 'Quantity moved between outlets', 'A quantity of products moved between outlets.'),
15904 (1, 't', 1648, 'Pre-paid invoice annual consumption, estimated', 'The estimated annual consumption used for a prepayment invoice.'),
15905 (1, 't', 1649, 'Total quoted quantity', 'The sum of quoted quantities.'),
15906 (1, 't', 1650, 'Requests pertaining to entity in last 12 months', 'Number of requests received in last 12 months pertaining to the entity.'),
15907 (1, 't', 1651, 'Total inquiry matches', 'Number of instances which correspond with the inquiry.'),
15908 (1, 't', 1652, 'En route to warehouse quantity',   'A quantity of products that is en route to a warehouse.'),
15909 (1, 't', 1653, 'En route from warehouse quantity', 'A quantity of products that is en route from a warehouse.'),
15910 (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.'),
15911 (1, 't', 1655, 'Not yet ordered quantity', 'The quantity which has not yet been ordered.'),
15912 (1, 't', 1656, 'Net reserve power', 'The reserve power available for the net.'),
15913 (1, 't', 1657, 'Maximum number of units per shelf', 'Maximum number of units of a product that can be placed on a shelf.'),
15914 (1, 't', 1658, 'Stowaway', 'Number of stowaway(s) on a conveyance.'),
15915 (1, 't', 1659, 'Tug', 'The number of tugboat(s).'),
15916 (1, 't', 1660, 'Maximum quantity capability of the package', 'Maximum quantity of a product that can be contained in a package.'),
15917 (1, 't', 1661, 'Calculated', 'The calculated quantity.'),
15918 (1, 't', 1662, 'Monthly volume, estimated', 'Volume estimated for a month.'),
15919 (1, 't', 1663, 'Total number of persons', 'Quantity representing the total number of persons.'),
15920 (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.'),
15921 (1, 't', 1665, 'Deducted tariff quantity',   'Quantity deducted from tariff quantity to reckon duty/tax/fee assessment bases.'),
15922 (1, 't', 1666, 'Advised but not arrived',    'Goods are advised by the consignor or supplier, but have not yet arrived at the destination.'),
15923 (1, 't', 1667, 'Received but not available', 'Goods have been received in the arrival area but are not yet available.'),
15924 (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.'),
15925 (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.'),
15926 (1, 't', 1670, 'Chargeable number of trailers', 'The number of trailers on which charges are based.'),
15927 (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.'),
15928 (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.'),
15929 (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.'),
15930 (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.'),
15931 (1, 't', 1675, 'Agreed maximum buying quantity', 'The agreed maximum quantity of the trade item that may be purchased.'),
15932 (1, 't', 1676, 'Agreed minimum buying quantity', 'The agreed minimum quantity of the trade item that may be purchased.'),
15933 (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.'),
15934 (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.'),
15935 (1, 't', 1679, 'Marine Diesel Oil bunkers, loaded',                  'Number of Marine Diesel Oil (MDO) bunkers taken on in the port.'),
15936 (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.'),
15937 (1, 't', 1681, 'Intermediate Fuel Oil bunkers, loaded',              'Number of Intermediate Fuel Oil (IFO) bunkers taken on in the port.'),
15938 (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.'),
15939 (1, 't', 1683, 'Bunker C bunkers, loaded', 'Number of Bunker C, or Number 6 fuel oil bunkers, taken on in the port.'),
15940 (1, 't', 1684, 'Number of individual units within the smallest packaging', 'unit Total number of individual units contained within the smallest unit of packaging.'),
15941 (1, 't', 1685, 'Percentage of constituent element', 'The part of a product or material that is composed of the constituent element, as a percentage.'),
15942 (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).'),
15943 (1, 't', 1687, 'Regulated commodity count', 'The number of regulated items.'),
15944 (1, 't', 1688, 'Number of passengers, embarking', 'The number of passengers going aboard a conveyance.'),
15945 (1, 't', 1689, 'Number of passengers, disembarking', 'The number of passengers disembarking the conveyance.'),
15946 (1, 't', 1690, 'Constituent element or component quantity', 'The specific quantity of the identified constituent element.')
15947 ;
15948 -- ZZZ, 'Mutually defined', 'As agreed by the trading partners.'),
15949
15950 CREATE TABLE acq.serial_claim (
15951     id     SERIAL           PRIMARY KEY,
15952     type   INT              NOT NULL REFERENCES acq.claim_type
15953                                      DEFERRABLE INITIALLY DEFERRED,
15954     item    BIGINT          NOT NULL REFERENCES serial.item
15955                                      DEFERRABLE INITIALLY DEFERRED
15956 );
15957
15958 CREATE INDEX serial_claim_lid_idx ON acq.serial_claim( item );
15959
15960 CREATE TABLE acq.serial_claim_event (
15961     id             BIGSERIAL        PRIMARY KEY,
15962     type           INT              NOT NULL REFERENCES acq.claim_event_type
15963                                              DEFERRABLE INITIALLY DEFERRED,
15964     claim          SERIAL           NOT NULL REFERENCES acq.serial_claim
15965                                              DEFERRABLE INITIALLY DEFERRED,
15966     event_date     TIMESTAMPTZ      NOT NULL DEFAULT now(),
15967     creator        INT              NOT NULL REFERENCES actor.usr
15968                                              DEFERRABLE INITIALLY DEFERRED,
15969     note           TEXT
15970 );
15971
15972 CREATE INDEX serial_claim_event_claim_date_idx ON acq.serial_claim_event( claim, event_date );
15973
15974 ALTER TABLE asset.stat_cat ADD COLUMN required BOOL NOT NULL DEFAULT FALSE;
15975
15976 -- now what about the auditor.*_lifecycle views??
15977
15978 INSERT INTO config.metabib_field ( id, field_class, name, label, format, xpath ) VALUES
15979     (26, 'identifier', 'tcn', oils_i18n_gettext(26, 'Title Control Number', 'cmf', 'label'), 'marcxml', $$//marc:datafield[@tag='901']/marc:subfield[@code='a']$$ );
15980 INSERT INTO config.metabib_field ( id, field_class, name, label, format, xpath ) VALUES
15981     (27, 'identifier', 'bibid', oils_i18n_gettext(27, 'Internal ID', 'cmf', 'label'), 'marcxml', $$//marc:datafield[@tag='901']/marc:subfield[@code='c']$$ );
15982 INSERT INTO config.metabib_search_alias (alias,field_class,field) VALUES ('eg.tcn','identifier', 26);
15983 INSERT INTO config.metabib_search_alias (alias,field_class,field) VALUES ('eg.bibid','identifier', 27);
15984
15985 CREATE TABLE asset.call_number_class (
15986     id             bigserial     PRIMARY KEY,
15987     name           TEXT          NOT NULL,
15988     normalizer     TEXT          NOT NULL DEFAULT 'asset.normalize_generic',
15989     field          TEXT          NOT NULL DEFAULT '050ab,055ab,060ab,070ab,080ab,082ab,086ab,088ab,090,092,096,098,099'
15990 );
15991
15992 COMMENT ON TABLE asset.call_number_class IS $$
15993 Defines the call number normalization database functions in the "normalizer"
15994 column and the tag/subfield combinations to use to lookup the call number in
15995 the "field" column for a given classification scheme. Tag/subfield combinations
15996 are delimited by commas.
15997 $$;
15998
15999 INSERT INTO asset.call_number_class (name, normalizer) VALUES 
16000     ('Generic', 'asset.label_normalizer_generic'),
16001     ('Dewey (DDC)', 'asset.label_normalizer_dewey'),
16002     ('Library of Congress (LC)', 'asset.label_normalizer_lc')
16003 ;
16004
16005 -- Generic fields
16006 UPDATE asset.call_number_class
16007     SET field = '050ab,055ab,060ab,070ab,080ab,082ab,086ab,088ab,090,092,096,098,099'
16008     WHERE id = 1
16009 ;
16010
16011 -- Dewey fields
16012 UPDATE asset.call_number_class
16013     SET field = '080ab,082ab'
16014     WHERE id = 2
16015 ;
16016
16017 -- LC fields
16018 UPDATE asset.call_number_class
16019     SET field = '050ab,055ab'
16020     WHERE id = 3
16021 ;
16022  
16023 ALTER TABLE auditor.asset_call_number_history ADD COLUMN label_class BIGINT;
16024 ALTER TABLE auditor.asset_call_number_history ADD COLUMN label_sortkey TEXT;
16025 ALTER TABLE asset.call_number ADD COLUMN label_class BIGINT DEFAULT 1 NOT NULL REFERENCES asset.call_number_class(id) DEFERRABLE INITIALLY DEFERRED;
16026 ALTER TABLE asset.call_number ADD COLUMN label_sortkey TEXT;
16027 CREATE INDEX asset_call_number_label_sortkey ON asset.call_number(label_sortkey);
16028
16029 CREATE OR REPLACE FUNCTION asset.label_normalizer() RETURNS TRIGGER AS $func$
16030 DECLARE
16031     sortkey        TEXT := '';
16032 BEGIN
16033     sortkey := NEW.label_sortkey;
16034
16035     EXECUTE 'SELECT ' || acnc.normalizer || '(' || 
16036        quote_literal( NEW.label ) || ')'
16037        FROM asset.call_number_class acnc
16038        WHERE acnc.id = NEW.label_class
16039        INTO sortkey;
16040
16041     NEW.label_sortkey = sortkey;
16042
16043     RETURN NEW;
16044 END;
16045 $func$ LANGUAGE PLPGSQL;
16046
16047 CREATE OR REPLACE FUNCTION asset.label_normalizer_generic(TEXT) RETURNS TEXT AS $func$
16048     # Created after looking at the Koha C4::ClassSortRoutine::Generic module,
16049     # thus could probably be considered a derived work, although nothing was
16050     # directly copied - but to err on the safe side of providing attribution:
16051     # Copyright (C) 2007 LibLime
16052     # Licensed under the GPL v2 or later
16053
16054     use strict;
16055     use warnings;
16056
16057     # Converts the callnumber to uppercase
16058     # Strips spaces from start and end of the call number
16059     # Converts anything other than letters, digits, and periods into underscores
16060     # Collapses multiple underscores into a single underscore
16061     my $callnum = uc(shift);
16062     $callnum =~ s/^\s//g;
16063     $callnum =~ s/\s$//g;
16064     $callnum =~ s/[^A-Z0-9_.]/_/g;
16065     $callnum =~ s/_{2,}/_/g;
16066
16067     return $callnum;
16068 $func$ LANGUAGE PLPERLU;
16069
16070 CREATE OR REPLACE FUNCTION asset.label_normalizer_dewey(TEXT) RETURNS TEXT AS $func$
16071     # Derived from the Koha C4::ClassSortRoutine::Dewey module
16072     # Copyright (C) 2007 LibLime
16073     # Licensed under the GPL v2 or later
16074
16075     use strict;
16076     use warnings;
16077
16078     my $init = uc(shift);
16079     $init =~ s/^\s+//;
16080     $init =~ s/\s+$//;
16081     $init =~ s!/!!g;
16082     $init =~ s/^([\p{IsAlpha}]+)/$1 /;
16083     my @tokens = split /\.|\s+/, $init;
16084     my $digit_group_count = 0;
16085     for (my $i = 0; $i <= $#tokens; $i++) {
16086         if ($tokens[$i] =~ /^\d+$/) {
16087             $digit_group_count++;
16088             if (2 == $digit_group_count) {
16089                 $tokens[$i] = sprintf("%-15.15s", $tokens[$i]);
16090                 $tokens[$i] =~ tr/ /0/;
16091             }
16092         }
16093     }
16094     my $key = join("_", @tokens);
16095     $key =~ s/[^\p{IsAlnum}_]//g;
16096
16097     return $key;
16098
16099 $func$ LANGUAGE PLPERLU;
16100
16101 CREATE OR REPLACE FUNCTION asset.label_normalizer_lc(TEXT) RETURNS TEXT AS $func$
16102     use strict;
16103     use warnings;
16104
16105     # Library::CallNumber::LC is currently hosted at http://code.google.com/p/library-callnumber-lc/
16106     # The author hopes to upload it to CPAN some day, which would make our lives easier
16107     use Library::CallNumber::LC;
16108
16109     my $callnum = Library::CallNumber::LC->new(shift);
16110     return $callnum->normalize();
16111
16112 $func$ LANGUAGE PLPERLU;
16113
16114 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$
16115 DECLARE
16116     ans RECORD;
16117     trans INT;
16118 BEGIN
16119     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;
16120
16121     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
16122         RETURN QUERY
16123         SELECT  ans.depth,
16124                 ans.id,
16125                 COUNT( av.id ),
16126                 SUM( CASE WHEN cp.status IN (0,7,12) THEN 1 ELSE 0 END ),
16127                 COUNT( av.id ),
16128                 trans
16129           FROM
16130                 actor.org_unit_descendants(ans.id) d
16131                 JOIN asset.opac_visible_copies av ON (av.record = record AND av.circ_lib = d.id)
16132                 JOIN asset.copy cp ON (cp.id = av.id)
16133           GROUP BY 1,2,6;
16134
16135         IF NOT FOUND THEN
16136             RETURN QUERY SELECT ans.depth, ans.id, 0::BIGINT, 0::BIGINT, 0::BIGINT, trans;
16137         END IF;
16138
16139     END LOOP;
16140
16141     RETURN;
16142 END;
16143 $f$ LANGUAGE PLPGSQL;
16144
16145 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$
16146 DECLARE
16147     ans RECORD;
16148     trans INT;
16149 BEGIN
16150     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;
16151
16152     FOR ans IN SELECT u.org_unit AS id FROM actor.org_lasso_map AS u WHERE lasso = i_lasso LOOP
16153         RETURN QUERY
16154         SELECT  -1,
16155                 ans.id,
16156                 COUNT( av.id ),
16157                 SUM( CASE WHEN cp.status IN (0,7,12) THEN 1 ELSE 0 END ),
16158                 COUNT( av.id ),
16159                 trans
16160           FROM
16161                 actor.org_unit_descendants(ans.id) d
16162                 JOIN asset.opac_visible_copies av ON (av.record = record AND av.circ_lib = d.id)
16163                 JOIN asset.copy cp ON (cp.id = av.id)
16164           GROUP BY 1,2,6;
16165
16166         IF NOT FOUND THEN
16167             RETURN QUERY SELECT ans.depth, ans.id, 0::BIGINT, 0::BIGINT, 0::BIGINT, trans;
16168         END IF;
16169
16170     END LOOP;
16171
16172     RETURN;
16173 END;
16174 $f$ LANGUAGE PLPGSQL;
16175
16176 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$
16177 DECLARE
16178     ans RECORD;
16179     trans INT;
16180 BEGIN
16181     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;
16182
16183     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
16184         RETURN QUERY
16185         SELECT  ans.depth,
16186                 ans.id,
16187                 COUNT( cp.id ),
16188                 SUM( CASE WHEN cp.status IN (0,7,12) THEN 1 ELSE 0 END ),
16189                 COUNT( cp.id ),
16190                 trans
16191           FROM
16192                 actor.org_unit_descendants(ans.id) d
16193                 JOIN asset.copy cp ON (cp.circ_lib = d.id)
16194                 JOIN asset.call_number cn ON (cn.record = record AND cn.id = cp.call_number)
16195           GROUP BY 1,2,6;
16196
16197         IF NOT FOUND THEN
16198             RETURN QUERY SELECT ans.depth, ans.id, 0::BIGINT, 0::BIGINT, 0::BIGINT, trans;
16199         END IF;
16200
16201     END LOOP;
16202
16203     RETURN;
16204 END;
16205 $f$ LANGUAGE PLPGSQL;
16206
16207 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$
16208 DECLARE
16209     ans RECORD;
16210     trans INT;
16211 BEGIN
16212     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;
16213
16214     FOR ans IN SELECT u.org_unit AS id FROM actor.org_lasso_map AS u WHERE lasso = i_lasso LOOP
16215         RETURN QUERY
16216         SELECT  -1,
16217                 ans.id,
16218                 COUNT( cp.id ),
16219                 SUM( CASE WHEN cp.status IN (0,7,12) THEN 1 ELSE 0 END ),
16220                 COUNT( cp.id ),
16221                 trans
16222           FROM
16223                 actor.org_unit_descendants(ans.id) d
16224                 JOIN asset.copy cp ON (cp.circ_lib = d.id)
16225                 JOIN asset.call_number cn ON (cn.record = record AND cn.id = cp.call_number)
16226           GROUP BY 1,2,6;
16227
16228         IF NOT FOUND THEN
16229             RETURN QUERY SELECT ans.depth, ans.id, 0::BIGINT, 0::BIGINT, 0::BIGINT, trans;
16230         END IF;
16231
16232     END LOOP;
16233
16234     RETURN;
16235 END;
16236 $f$ LANGUAGE PLPGSQL;
16237
16238 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$
16239 BEGIN
16240     IF staff IS TRUE THEN
16241         IF place > 0 THEN
16242             RETURN QUERY SELECT * FROM asset.staff_ou_record_copy_count( place, record );
16243         ELSE
16244             RETURN QUERY SELECT * FROM asset.staff_lasso_record_copy_count( -place, record );
16245         END IF;
16246     ELSE
16247         IF place > 0 THEN
16248             RETURN QUERY SELECT * FROM asset.opac_ou_record_copy_count( place, record );
16249         ELSE
16250             RETURN QUERY SELECT * FROM asset.opac_lasso_record_copy_count( -place, record );
16251         END IF;
16252     END IF;
16253
16254     RETURN;
16255 END;
16256 $f$ LANGUAGE PLPGSQL;
16257
16258 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$
16259 DECLARE
16260     ans RECORD;
16261     trans INT;
16262 BEGIN
16263     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;
16264
16265     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
16266         RETURN QUERY
16267         SELECT  ans.depth,
16268                 ans.id,
16269                 COUNT( av.id ),
16270                 SUM( CASE WHEN cp.status IN (0,7,12) THEN 1 ELSE 0 END ),
16271                 COUNT( av.id ),
16272                 trans
16273           FROM
16274                 actor.org_unit_descendants(ans.id) d
16275                 JOIN asset.opac_visible_copies av ON (av.record = record AND av.circ_lib = d.id)
16276                 JOIN asset.copy cp ON (cp.id = av.id)
16277                 JOIN metabib.metarecord_source_map m ON (m.source = av.record)
16278           GROUP BY 1,2,6;
16279
16280         IF NOT FOUND THEN
16281             RETURN QUERY SELECT ans.depth, ans.id, 0::BIGINT, 0::BIGINT, 0::BIGINT, trans;
16282         END IF;
16283
16284     END LOOP;
16285
16286     RETURN;
16287 END;
16288 $f$ LANGUAGE PLPGSQL;
16289
16290 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$
16291 DECLARE
16292     ans RECORD;
16293     trans INT;
16294 BEGIN
16295     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;
16296
16297     FOR ans IN SELECT u.org_unit AS id FROM actor.org_lasso_map AS u WHERE lasso = i_lasso LOOP
16298         RETURN QUERY
16299         SELECT  -1,
16300                 ans.id,
16301                 COUNT( av.id ),
16302                 SUM( CASE WHEN cp.status IN (0,7,12) THEN 1 ELSE 0 END ),
16303                 COUNT( av.id ),
16304                 trans
16305           FROM
16306                 actor.org_unit_descendants(ans.id) d
16307                 JOIN asset.opac_visible_copies av ON (av.record = record AND av.circ_lib = d.id)
16308                 JOIN asset.copy cp ON (cp.id = av.id)
16309                 JOIN metabib.metarecord_source_map m ON (m.source = av.record)
16310           GROUP BY 1,2,6;
16311
16312         IF NOT FOUND THEN
16313             RETURN QUERY SELECT ans.depth, ans.id, 0::BIGINT, 0::BIGINT, 0::BIGINT, trans;
16314         END IF;
16315
16316     END LOOP;
16317
16318     RETURN;
16319 END;
16320 $f$ LANGUAGE PLPGSQL;
16321
16322 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$
16323 DECLARE
16324     ans RECORD;
16325     trans INT;
16326 BEGIN
16327     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;
16328
16329     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
16330         RETURN QUERY
16331         SELECT  ans.depth,
16332                 ans.id,
16333                 COUNT( cp.id ),
16334                 SUM( CASE WHEN cp.status IN (0,7,12) THEN 1 ELSE 0 END ),
16335                 COUNT( cp.id ),
16336                 trans
16337           FROM
16338                 actor.org_unit_descendants(ans.id) d
16339                 JOIN asset.copy cp ON (cp.circ_lib = d.id)
16340                 JOIN asset.call_number cn ON (cn.record = record AND cn.id = cp.call_number)
16341                 JOIN metabib.metarecord_source_map m ON (m.source = cn.record)
16342           GROUP BY 1,2,6;
16343
16344         IF NOT FOUND THEN
16345             RETURN QUERY SELECT ans.depth, ans.id, 0::BIGINT, 0::BIGINT, 0::BIGINT, trans;
16346         END IF;
16347
16348     END LOOP;
16349
16350     RETURN;
16351 END;
16352 $f$ LANGUAGE PLPGSQL;
16353
16354 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$
16355 DECLARE
16356     ans RECORD;
16357     trans INT;
16358 BEGIN
16359     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;
16360
16361     FOR ans IN SELECT u.org_unit AS id FROM actor.org_lasso_map AS u WHERE lasso = i_lasso LOOP
16362         RETURN QUERY
16363         SELECT  -1,
16364                 ans.id,
16365                 COUNT( cp.id ),
16366                 SUM( CASE WHEN cp.status IN (0,7,12) THEN 1 ELSE 0 END ),
16367                 COUNT( cp.id ),
16368                 trans
16369           FROM
16370                 actor.org_unit_descendants(ans.id) d
16371                 JOIN asset.copy cp ON (cp.circ_lib = d.id)
16372                 JOIN asset.call_number cn ON (cn.record = record AND cn.id = cp.call_number)
16373                 JOIN metabib.metarecord_source_map m ON (m.source = cn.record)
16374           GROUP BY 1,2,6;
16375
16376         IF NOT FOUND THEN
16377             RETURN QUERY SELECT ans.depth, ans.id, 0::BIGINT, 0::BIGINT, 0::BIGINT, trans;
16378         END IF;
16379
16380     END LOOP;
16381
16382     RETURN;
16383 END;
16384 $f$ LANGUAGE PLPGSQL;
16385
16386 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$
16387 BEGIN
16388     IF staff IS TRUE THEN
16389         IF place > 0 THEN
16390             RETURN QUERY SELECT * FROM asset.staff_ou_metarecord_copy_count( place, record );
16391         ELSE
16392             RETURN QUERY SELECT * FROM asset.staff_lasso_metarecord_copy_count( -place, record );
16393         END IF;
16394     ELSE
16395         IF place > 0 THEN
16396             RETURN QUERY SELECT * FROM asset.opac_ou_metarecord_copy_count( place, record );
16397         ELSE
16398             RETURN QUERY SELECT * FROM asset.opac_lasso_metarecord_copy_count( -place, record );
16399         END IF;
16400     END IF;
16401
16402     RETURN;
16403 END;
16404 $f$ LANGUAGE PLPGSQL;
16405
16406 -- No transaction is required
16407
16408 -- Triggers on the vandelay.queued_*_record tables delete entries from
16409 -- the associated vandelay.queued_*_record_attr tables based on the record's
16410 -- ID; create an index on that column to avoid sequential scans for each
16411 -- queued record that is deleted
16412 CREATE INDEX queued_bib_record_attr_record_idx ON vandelay.queued_bib_record_attr (record);
16413 CREATE INDEX queued_authority_record_attr_record_idx ON vandelay.queued_authority_record_attr (record);
16414
16415 -- Avoid sequential scans for queue retrieval operations by providing an
16416 -- index on the queue column
16417 CREATE INDEX queued_bib_record_queue_idx ON vandelay.queued_bib_record (queue);
16418 CREATE INDEX queued_authority_record_queue_idx ON vandelay.queued_authority_record (queue);
16419
16420 CREATE INDEX actor_card_barcode_lower_idx ON actor.card (lower(barcode));
16421
16422 -- Start picking up call number label prefixes and suffixes
16423 -- from asset.copy_location
16424 ALTER TABLE asset.copy_location ADD COLUMN label_prefix TEXT;
16425 ALTER TABLE asset.copy_location ADD COLUMN label_suffix TEXT;
16426
16427 COMMIT;
16428
16429 -- Outside of the commit: various things that may legitimately fail.
16430
16431 ALTER TABLE auditor.action_hold_request_history ADD COLUMN cut_in_line BOOL;
16432
16433 ALTER TABLE auditor.action_hold_request_history
16434 ADD COLUMN mint_condition boolean NOT NULL DEFAULT TRUE;
16435
16436 ALTER TABLE auditor.action_hold_request_history
16437 ADD COLUMN shelf_expire_time TIMESTAMPTZ;
16438
16439 ALTER TABLE reporter.report RENAME COLUMN recurance TO recurrence;
16440
16441 -- Recreate the temporarily dropped view, having altered the action.all_circulation view:
16442
16443 CREATE OR REPLACE VIEW extend_reporter.full_circ_count AS
16444  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
16445    FROM asset."copy" cp
16446    LEFT JOIN extend_reporter.legacy_circ_count c USING (id)
16447    LEFT JOIN "action".circulation circ ON circ.target_copy = cp.id
16448    LEFT JOIN "action".aged_circulation acirc ON acirc.target_copy = cp.id
16449   GROUP BY cp.id;
16450
16451 -- Let's not break existing reports
16452 UPDATE reporter.template SET data = REGEXP_REPLACE(data, E'^(.*)recuring(.*)$', E'\\1recurring\\2') WHERE data LIKE '%recuring%';
16453 UPDATE reporter.template SET data = REGEXP_REPLACE(data, E'^(.*)recurance(.*)$', E'\\1recurrence\\2') WHERE data LIKE '%recurance%';
16454
16455 -- Need to recreate this view with DISTINCT calls to ARRAY_ACCUM, thus avoiding duplicated ISBN and ISSN values
16456 CREATE OR REPLACE VIEW reporter.old_super_simple_record AS
16457 SELECT  r.id,
16458     r.fingerprint,
16459     r.quality,
16460     r.tcn_source,
16461     r.tcn_value,
16462     FIRST(title.value) AS title,
16463     FIRST(author.value) AS author,
16464     ARRAY_TO_STRING(ARRAY_ACCUM( DISTINCT publisher.value), ', ') AS publisher,
16465     ARRAY_TO_STRING(ARRAY_ACCUM( DISTINCT SUBSTRING(pubdate.value FROM $$\d+$$) ), ', ') AS pubdate,
16466     ARRAY_ACCUM( DISTINCT SUBSTRING(isbn.value FROM $$^\S+$$) ) AS isbn,
16467     ARRAY_ACCUM( DISTINCT SUBSTRING(issn.value FROM $$^\S+$$) ) AS issn
16468   FROM  biblio.record_entry r
16469     LEFT JOIN metabib.full_rec title ON (r.id = title.record AND title.tag = '245' AND title.subfield = 'a')
16470     LEFT JOIN metabib.full_rec author ON (r.id = author.record AND author.tag IN ('100','110','111') AND author.subfield = 'a')
16471     LEFT JOIN metabib.full_rec publisher ON (r.id = publisher.record AND publisher.tag = '260' AND publisher.subfield = 'b')
16472     LEFT JOIN metabib.full_rec pubdate ON (r.id = pubdate.record AND pubdate.tag = '260' AND pubdate.subfield = 'c')
16473     LEFT JOIN metabib.full_rec isbn ON (r.id = isbn.record AND isbn.tag IN ('024', '020') AND isbn.subfield IN ('a','z'))
16474     LEFT JOIN metabib.full_rec issn ON (r.id = issn.record AND issn.tag = '022' AND issn.subfield = 'a')
16475   GROUP BY 1,2,3,4,5;
16476
16477 -- Correct the ISSN array definition for reporter.simple_record
16478
16479 CREATE OR REPLACE VIEW reporter.simple_record AS
16480 SELECT  r.id,
16481         s.metarecord,
16482         r.fingerprint,
16483         r.quality,
16484         r.tcn_source,
16485         r.tcn_value,
16486         title.value AS title,
16487         uniform_title.value AS uniform_title,
16488         author.value AS author,
16489         publisher.value AS publisher,
16490         SUBSTRING(pubdate.value FROM $$\d+$$) AS pubdate,
16491         series_title.value AS series_title,
16492         series_statement.value AS series_statement,
16493         summary.value AS summary,
16494         ARRAY_ACCUM( SUBSTRING(isbn.value FROM $$^\S+$$) ) AS isbn,
16495         ARRAY_ACCUM( REGEXP_REPLACE(issn.value, E'^\\S*(\\d{4})[-\\s](\\d{3,4}x?)', E'\\1 \\2') ) AS issn,
16496         ARRAY((SELECT DISTINCT value FROM metabib.full_rec WHERE tag = '650' AND subfield = 'a' AND record = r.id)) AS topic_subject,
16497         ARRAY((SELECT DISTINCT value FROM metabib.full_rec WHERE tag = '651' AND subfield = 'a' AND record = r.id)) AS geographic_subject,
16498         ARRAY((SELECT DISTINCT value FROM metabib.full_rec WHERE tag = '655' AND subfield = 'a' AND record = r.id)) AS genre,
16499         ARRAY((SELECT DISTINCT value FROM metabib.full_rec WHERE tag = '600' AND subfield = 'a' AND record = r.id)) AS name_subject,
16500         ARRAY((SELECT DISTINCT value FROM metabib.full_rec WHERE tag = '610' AND subfield = 'a' AND record = r.id)) AS corporate_subject,
16501         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
16502   FROM  biblio.record_entry r
16503         JOIN metabib.metarecord_source_map s ON (s.source = r.id)
16504         LEFT JOIN metabib.full_rec uniform_title ON (r.id = uniform_title.record AND uniform_title.tag = '240' AND uniform_title.subfield = 'a')
16505         LEFT JOIN metabib.full_rec title ON (r.id = title.record AND title.tag = '245' AND title.subfield = 'a')
16506         LEFT JOIN metabib.full_rec author ON (r.id = author.record AND author.tag = '100' AND author.subfield = 'a')
16507         LEFT JOIN metabib.full_rec publisher ON (r.id = publisher.record AND publisher.tag = '260' AND publisher.subfield = 'b')
16508         LEFT JOIN metabib.full_rec pubdate ON (r.id = pubdate.record AND pubdate.tag = '260' AND pubdate.subfield = 'c')
16509         LEFT JOIN metabib.full_rec isbn ON (r.id = isbn.record AND isbn.tag IN ('024', '020') AND isbn.subfield IN ('a','z'))
16510         LEFT JOIN metabib.full_rec issn ON (r.id = issn.record AND issn.tag = '022' AND issn.subfield = 'a')
16511         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')
16512         LEFT JOIN metabib.full_rec series_statement ON (r.id = series_statement.record AND series_statement.tag = '490' AND series_statement.subfield = 'a')
16513         LEFT JOIN metabib.full_rec summary ON (r.id = summary.record AND summary.tag = '520' AND summary.subfield = 'a')
16514   GROUP BY 1,2,3,4,5,6,7,8,9,10,11,12,13,14;
16515
16516 CREATE OR REPLACE FUNCTION reporter.disable_materialized_simple_record_trigger () RETURNS VOID AS $$
16517     DROP TRIGGER IF EXISTS zzz_update_materialized_simple_record_tgr ON metabib.real_full_rec;
16518 $$ LANGUAGE SQL;
16519
16520 CREATE OR REPLACE FUNCTION reporter.simple_rec_trigger () RETURNS TRIGGER AS $func$
16521 BEGIN
16522     IF TG_OP = 'DELETE' THEN
16523         PERFORM reporter.simple_rec_delete(NEW.id);
16524     ELSE
16525         PERFORM reporter.simple_rec_update(NEW.id);
16526     END IF;
16527
16528     RETURN NEW;
16529 END;
16530 $func$ LANGUAGE PLPGSQL;
16531
16532 CREATE TRIGGER bbb_simple_rec_trigger AFTER INSERT OR UPDATE ON biblio.record_entry FOR EACH ROW EXECUTE PROCEDURE reporter.simple_rec_trigger ();
16533
16534 ALTER TABLE extend_reporter.legacy_circ_count DROP CONSTRAINT legacy_circ_count_id_fkey;