Catch up to trunk phase 2 (the rest) -- Merged revisions 17889,17891-17892,17894...
authordbwells <dbwells@dcc99617-32d9-48b4-a31d-7c20da2025e4>
Mon, 18 Oct 2010 14:05:06 +0000 (14:05 +0000)
committerdbwells <dbwells@dcc99617-32d9-48b4-a31d-7c20da2025e4>
Mon, 18 Oct 2010 14:05:06 +0000 (14:05 +0000)
svn://svn.open-ils.org/ILS/trunk

........
  r17889 | scottmk | 2010-09-22 11:59:06 -0400 (Wed, 22 Sep 2010) | 8 lines

  For action.circulation and action.aged_circulation: add indexes
  on the target_copy column.

  This change was made in upgrade script # 0017, but was apparently
  never applied to the base install script.

  M    Open-ILS/src/sql/Pg/090.schema.action.sql
........
  r17891 | scottmk | 2010-09-22 15:16:26 -0400 (Wed, 22 Sep 2010) | 7 lines

  Adding a trigger.  Upgrade # 0364 created the trigger function but not
  the trigger itself.  However the base install script 040.schema.asset.sql
  creates both the function and the trigger.

  M    Open-ILS/src/sql/Pg/002.schema.config.sql
  A    Open-ILS/src/sql/Pg/upgrade/0414.schema.call-number-upd-ins-trigger.sql
........
  r17892 | phasefx | 2010-09-22 15:31:07 -0400 (Wed, 22 Sep 2010) | 1 line

  Holdings Maintenance used to make too many assumptions about what a decent org hierarchy looked like, and had these rules to suppress some of the fetch-render-happy behavior that existed when clicking on org units.  We can remove them now, and this allows volumes to render when owned by the top of the org tree
........
  r17894 | phasefx | 2010-09-22 15:37:43 -0400 (Wed, 22 Sep 2010) | 2 lines

  fixes a bug in Item Status -> Alternate View when the item being viewed has no circ modifier
........
  r17896 | phasefx | 2010-09-22 16:01:55 -0400 (Wed, 22 Sep 2010) | 1 line

  fix from tsbere to handle case where the print strategy is set to custom/external but the actual command is missing (perhaps due to xulrunner upgrades?)
........
  r17897 | phasefx | 2010-09-22 16:01:59 -0400 (Wed, 22 Sep 2010) | 1 line

  set label printer context for label printing
........
  r17899 | senator | 2010-09-22 20:14:03 -0400 (Wed, 22 Sep 2010) | 3 lines

  Hopefully fix a holds list sorting issue that only manifested when printing.
........
  r17901 | senator | 2010-09-22 20:34:22 -0400 (Wed, 22 Sep 2010) | 2 lines

  Another IDL chunking fix for web/templates-based interfaces
........
  r17903 | miker | 2010-09-22 22:12:02 -0400 (Wed, 22 Sep 2010) | 1 line

  only flesh up to, but not including, the leaf for the group_field
........
  r17905 | miker | 2010-09-22 22:49:36 -0400 (Wed, 22 Sep 2010) | 1 line

  Use a transaction to avoid talking to a replicated db when building A/T data structures
........
  r17906 | dbs | 2010-09-22 23:09:19 -0400 (Wed, 22 Sep 2010) | 7 lines

  Remove most UI annoyances from authority management interface

    * term input field now gets the focus automatically
    * pressing enter in most places submits a new search
    * removed the onBlur event as that required users to click a second
      time to open the action menu
........
  r17908 | dbs | 2010-09-22 23:18:14 -0400 (Wed, 22 Sep 2010) | 6 lines

  Browse through 20 authority records at a time in management interface

  The default browse list is set to 9 elements, but we have enough vertical
  space to make use of more. Perhaps we should check the font size and
  viewport height and then adjust accordingly, but that would be Hard.
........
  r17910 | dbs | 2010-09-22 23:45:16 -0400 (Wed, 22 Sep 2010) | 10 lines

  Remove most annoying UI "feature" of the new authority browse list interface

  When I added the new authority browse list interface, I used dojo.xhrGet()
  to retrieve records from the authority browse backend - but because I didn't
  specify sync:true, when you first right-clicked on a subfield, the function
  would return immediately and default to showing the context menu. You would
  then need to click two more times to show the authority list.

  Now you get it on your first right-click, as it should be.
........
  r17912 | miker | 2010-09-23 00:33:39 -0400 (Thu, 23 Sep 2010) | 1 line

  Have the CStoreEditor grow a DESTROY
........
  r17913 | erickson | 2010-09-23 01:31:03 -0400 (Thu, 23 Sep 2010) | 1 line

  event firing util code needs to run in a xact
........
  r17914 | erickson | 2010-09-23 03:17:00 -0400 (Thu, 23 Sep 2010) | 1 line

  fetch last updated event before comitting to stay in the xact
........
  r17915 | scottmk | 2010-09-23 09:45:06 -0400 (Thu, 23 Sep 2010) | 8 lines

  Dropping and recreating a foreign key constraint for config.metabib_field,
  in order to change its name.  WHen this foreign key was first introduced,
  the upgrade script gave it one name and the base install script gave it
  a different name.  Here we bring the names into sync.

  M    Open-ILS/src/sql/Pg/002.schema.config.sql
  A    Open-ILS/src/sql/Pg/upgrade/0415.schema.rename-field-class-fkey.sql
........
  r17917 | scottmk | 2010-09-23 10:31:32 -0400 (Thu, 23 Sep 2010) | 5 lines

  Rename a couple of indexes (recuring -> recurring)

  M    Open-ILS/src/sql/Pg/002.schema.config.sql
  A    Open-ILS/src/sql/Pg/upgrade/0416.schema.rename-recuring-idx.sql
........
  r17918 | scottmk | 2010-09-23 11:22:29 -0400 (Thu, 23 Sep 2010) | 12 lines

  Drop the never-used column item_count from acq.lineitem.
  Drop it also from the associated history table, and rebuild
  the function that maintains it.  Finally, rebuild the
  associated lifecycle view.

  Apply to trunk only; this column never existed in 2.0.
  The column has already been removed from the base installation
  script.

  M    Open-ILS/src/sql/Pg/002.schema.config.sql
  A    Open-ILS/src/sql/Pg/upgrade/0417.schema.acq.drop-lineitem-item-count.sql
........
  r17922 | miker | 2010-09-23 11:38:46 -0400 (Thu, 23 Sep 2010) | 11 lines

  Forward-port of a patch from Steve Callendar, via James Fournie, via launchpad:

  When the patron.password.use_phone is set, new patrons are created with their password set to the last 4 digits of their phone number, HOWEVER, when a patron's password is reset, it does not work properly. Although the little underlined summary shows the proper 4 digits, the password box displays 9-ish digits, and is not the last 4 digits of the password.

  The attached patch was created by Steve Callender and is confirmed working on 1.6.0

  ORIGINAL CAVEAT: This patch will not work on 2.0 as that has a new user editor, but it would presumably be worthwhile to verify this functionality works in that editor as well.

  ED NOTE: Because it is possible to use the old editor -- it still exists -- this patch should be applied.  It does what it advertises to do, which is fix the old editor to follow the "use phone number" OU setting.
........
  r17927 | phasefx | 2010-09-23 12:35:20 -0400 (Thu, 23 Sep 2010) | 2 lines

  disable holds Detail View button until the asynchronously loading details UI is ready
........
  r17929 | scottmk | 2010-09-23 14:58:36 -0400 (Thu, 23 Sep 2010) | 5 lines

  esolving various discrepancies between a freshly built 2.0
  database and an upgraded one.

  M    Open-ILS/src/sql/Pg/1.6.1-2.0-upgrade-db.sql
........
  r17931 | erickson | 2010-09-23 16:47:47 -0400 (Thu, 23 Sep 2010) | 1 line

  repaired search call for user_setting. cstoreeditor uses the fieldmapper name, sos/usr/user/
........
  r17933 | senator | 2010-09-23 16:59:13 -0400 (Thu, 23 Sep 2010) | 2 lines

  IE hates the dangling comma (sometimes).
........
  r17935 | erickson | 2010-09-23 19:01:06 -0400 (Thu, 23 Sep 2010) | 1 line

  I can has authoritative for selfcheck receipts
........
  r17937 | miker | 2010-09-23 19:38:10 -0400 (Thu, 23 Sep 2010) | 1 line

  Stopping the leak
........
  r17938 | miker | 2010-09-23 19:47:49 -0400 (Thu, 23 Sep 2010) | 1 line

  reverting previous. sorry, folks
........
  r17939 | miker | 2010-09-23 19:49:26 -0400 (Thu, 23 Sep 2010) | 1 line

  Stopping the leak (let us try this again)
........
  r17941 | senator | 2010-09-23 20:15:31 -0400 (Thu, 23 Sep 2010) | 2 lines

  Re-commit miker's PermaCrud changes, just a tad more nicer ;-)
........
  r17944 | scottmk | 2010-09-23 20:56:21 -0400 (Thu, 23 Sep 2010) | 10 lines

  Apply some fixes that were earlier applied to the base installation script
  090.schema.action.sql, but not in an upgrade script.

  Also: correct a typo (INTEVAL -> INTERVAL).  Hence this upgrade needs to go
  to v2.0 as well as to trunk.

  M    Open-ILS/src/sql/Pg/090.schema.action.sql
  M    Open-ILS/src/sql/Pg/002.schema.config.sql
  A    Open-ILS/src/sql/Pg/upgrade/0418.function.action.fix-purge-circ.sql
........
  r17945 | miker | 2010-09-23 21:46:41 -0400 (Thu, 23 Sep 2010) | 1 line

  correct the comment
........
  r17946 | miker | 2010-09-23 21:47:06 -0400 (Thu, 23 Sep 2010) | 1 line

  use _session_request in RO calls
........
  r17948 | phasefx | 2010-09-23 22:36:55 -0400 (Thu, 23 Sep 2010) | 2 lines

  We're starting to get events where ilsevent == empty string now, not just null or a number.
........
  r17950 | senator | 2010-09-24 00:45:42 -0400 (Fri, 24 Sep 2010) | 3 lines

  Be more tolerant of long-running A/T event handling for holds pull list printing
........
  r17952 | phasefx | 2010-09-24 01:48:39 -0400 (Fri, 24 Sep 2010) | 2 lines

  this call returns an array; fixes surprise stat cats in patron display
........
  r17954 | gmc | 2010-09-24 12:37:23 -0400 (Fri, 24 Sep 2010) | 9 lines

  improve hold targetting

  * all potential capturable copies are now checked (up to the first
    one that is permitted for the request), instead of a small random
    subset of them
  * don't do redundant permission checks

Signed-off-by: Galen Charlton <gmc@esilibrary.com>
........
  r17956 | miker | 2010-09-24 13:45:49 -0400 (Fri, 24 Sep 2010) | 1 line

  excise the no-potentials check, should only effect frozen holds
........
  r17957 | miker | 2010-09-24 13:56:31 -0400 (Fri, 24 Sep 2010) | 1 line

  put the frozen (and also empty issuance or last-copy-removed) check back in as a conditional select if we found nothing mapped
........
  r17958 | phasefx | 2010-09-24 14:04:59 -0400 (Fri, 24 Sep 2010) | 2 lines

  don't styled the juvenile indicator in the patron display based on age calculations by default, just go by the juvenile flag on the user.  local CSS can restore the behavior if desired
........
  r17963 | miker | 2010-09-24 14:08:05 -0400 (Fri, 24 Sep 2010) | 1 line

  cut-n-paste-o ... removing bad "my"
........
  r17965 | phasefx | 2010-09-24 14:23:10 -0400 (Fri, 24 Sep 2010) | 2 lines

  spawn item attribute editor instead of item notes UI during process missing pieces workflow
........
  r17972 | erickson | 2010-09-24 15:30:10 -0400 (Fri, 24 Sep 2010) | 1 line

  removed extra {}'s from where clause
........
  r17974 | senator | 2010-09-24 16:11:12 -0400 (Fri, 24 Sep 2010) | 2 lines

  Fix an autogrid create dialog of the kind that gets big, unusable and jumpy
........
  r17976 | miker | 2010-09-24 16:29:43 -0400 (Fri, 24 Sep 2010) | 1 line

  Remove confusing older targeter version; move status trimming to earlier in the process
........
  r17978 | erickson | 2010-09-24 18:23:58 -0400 (Fri, 24 Sep 2010) | 1 line

  clean up the fire_object_events cstore xact
........
  r17980 | erickson | 2010-09-24 19:04:45 -0400 (Fri, 24 Sep 2010) | 1 line

  more transaction cleanups
........
  r17982 | senator | 2010-09-24 20:56:24 -0400 (Fri, 24 Sep 2010) | 2 lines

  Maybe not abandon poor little cstore so much.
........
  r17984 | miker | 2010-09-24 20:58:35 -0400 (Fri, 24 Sep 2010) | 1 line

  pedantic protection of cstore backends -- always use die_event when in xact mode, and rollback otherwise
........
  r17985 | senator | 2010-09-24 21:13:31 -0400 (Fri, 24 Sep 2010) | 2 lines

  paranoia
........
  r17987 | erickson | 2010-09-24 23:27:20 -0400 (Fri, 24 Sep 2010) | 1 line

  events don't have granularity's.  removing
........
  r17989 | senator | 2010-09-24 23:45:21 -0400 (Fri, 24 Sep 2010) | 2 lines

  How did this get lost? PermaCrud authoritative might work now?
........
  r17991 | erickson | 2010-09-24 23:46:03 -0400 (Fri, 24 Sep 2010) | 1 line

  no need to start a xact in the holds pull list.  really long lists can results in sending a rollback on a timed-out cstore handle, resulting in errors
........
  r17992 | senator | 2010-09-24 23:54:09 -0400 (Fri, 24 Sep 2010) | 2 lines

  fix misplaced paranoia
........
  r17994 | erickson | 2010-09-25 00:09:57 -0400 (Sat, 25 Sep 2010) | 1 line

  only need to wrap event retrieve in xact for latest copy.  otherwise, run the risk of xact-ed cstore timing out
........
  r17995 | gmc | 2010-09-25 22:42:50 -0400 (Sat, 25 Sep 2010) | 6 lines

  don't leak cstores if CStoreEditor rollback fails

  Patch by Mike Rylander.

Signed-off-by: Galen Charlton <gmc@esilibrary.com>
........
  r17997 | gmc | 2010-09-25 22:49:39 -0400 (Sat, 25 Sep 2010) | 10 lines

  more selfcheck receipt transaction hackery

  Instead of having authoritative versions of
  open-ils.circ.fire_*_trigger_events, wrap just the
  target retrieval itself in a transaction.  Avoids as
  yet unexplained rollback failure that occurs if
  processing a selfcheck receipt with more than a few items on it.

Signed-off-by: Galen Charlton <gmc@esilibrary.com>
........
  r17999 | phasefx | 2010-09-25 23:17:05 -0400 (Sat, 25 Sep 2010) | 2 lines

  make address_type required in the patron editor, otherwise a blank value sends a null to the db where it silently fails
........
  r18001 | phasefx | 2010-09-26 02:34:22 -0400 (Sun, 26 Sep 2010) | 2 lines

  Show cover art in Z39.50 client.  I'm slow, so it only hit me this year that we can show added content for material not actually in the catalog yet
........
  r18002 | phasefx | 2010-09-26 03:05:58 -0400 (Sun, 26 Sep 2010) | 2 lines

  make the splitters in the z39.50 interface sticky
........
  r18003 | phasefx | 2010-09-26 04:06:33 -0400 (Sun, 26 Sep 2010) | 2 lines

  make all the splitters sticky with oils_persist
........
  r18004 | phasefx | 2010-09-26 05:23:43 -0400 (Sun, 26 Sep 2010) | 2 lines

  Call persist_helper() in most interfaces.  Give it the ability to handle resizing windows and set some windows up so that their height, width, and maximized state persist
........
  r18005 | phasefx | 2010-09-26 06:02:38 -0400 (Sun, 26 Sep 2010) | 2 lines

  make the MARC editor optional with Z39.50
........
  r18006 | phasefx | 2010-09-26 06:29:36 -0400 (Sun, 26 Sep 2010) | 2 lines

  unmark record for overlay after it is overlaid
........
  r18007 | miker | 2010-09-26 11:31:41 -0400 (Sun, 26 Sep 2010) | 1 line

  allow more complex facet ordering
........
  r18009 | miker | 2010-09-26 11:53:21 -0400 (Sun, 26 Sep 2010) | 1 line

  Only show facetOrder limited facets that have values
........
  r18011 | gmc | 2010-09-26 12:26:21 -0400 (Sun, 26 Sep 2010) | 4 lines

  added missing columns to CDBI table definitions

Signed-off-by: Galen Charlton <gmc@esilibrary.com>
........
  r18013 | dbs | 2010-09-26 14:34:47 -0400 (Sun, 26 Sep 2010) | 2 lines

  Remove a debugging statement that slipped in
........
  r18015 | erickson | 2010-09-26 19:20:10 -0400 (Sun, 26 Sep 2010) | 1 line

  push copy location order update into ML method.  return updated orders from method to prevent replication issues.  update UI JS to match
........
  r18016 | erickson | 2010-09-26 19:20:11 -0400 (Sun, 26 Sep 2010) | 1 line

  no need to warn when emails may just be disabled
........
  r18017 | erickson | 2010-09-26 19:20:12 -0400 (Sun, 26 Sep 2010) | 1 line

  honor SIP return date as the circ backdate
........
  r18021 | gmc | 2010-09-26 21:43:12 -0400 (Sun, 26 Sep 2010) | 4 lines

  avoid multiple clicks of selfcheck logout link

Signed-off-by: Galen Charlton <gmc@esilibrary.com>
........
  r18023 | scottmk | 2010-09-27 08:45:57 -0400 (Mon, 27 Sep 2010) | 5 lines

  Resolve differences between stored procedures in a freshly installed
  2.0 database and those in an upgraded one.

  M    Open-ILS/src/sql/Pg/1.6.1-2.0-upgrade-db.sql
........
  r18025 | miker | 2010-09-27 11:23:10 -0400 (Mon, 27 Sep 2010) | 1 line

  Provide for limiting the number of classOrder elements to a specific number. Skips those without facet values.
........
  r18027 | miker | 2010-09-27 12:15:14 -0400 (Mon, 27 Sep 2010) | 1 line

  Adjust hold-to-record view to cover I, F and R hold types
........
  r18029 | phasefx | 2010-09-27 12:29:19 -0400 (Mon, 27 Sep 2010) | 2 lines

  since we're enforcing the requiredness of address type, let's give it a default value (since empty strings don't work with these widgets)
........
  r18034 | miker | 2010-09-27 14:26:29 -0400 (Mon, 27 Sep 2010) | 1 line

  use transaction when gathering records from a newly created queue; adjust rollback usage in import loop
........
  r18037 | gmc | 2010-09-27 14:50:56 -0400 (Mon, 27 Sep 2010) | 4 lines

  fix default path to selfcheck sounds

Signed-off-by: Galen Charlton <gmc@esilibrary.com>
........
  r18039 | miker | 2010-09-27 15:11:21 -0400 (Mon, 27 Sep 2010) | 1 line

  make pubdate sorting on search faster by pre-munging date1 and date2 into an acceptable sortkey
........
  r18041 | miker | 2010-09-27 15:16:57 -0400 (Mon, 27 Sep 2010) | 1 line

  Correct the subquery for A/T opt_in_setting
........
  r18043 | miker | 2010-09-27 15:24:13 -0400 (Mon, 27 Sep 2010) | 1 line

  missed in the previous commit, sorry folks
........
  r18045 | gmc | 2010-09-27 15:27:04 -0400 (Mon, 27 Sep 2010) | 7 lines

  fix glitch in hold target OU weighting

  We want to weight by the copy's circulation library, not the
  pickup library.

Signed-off-by: Galen Charlton <gmc@esilibrary.com>
........
  r18047 | gmc | 2010-09-27 15:48:33 -0400 (Mon, 27 Sep 2010) | 4 lines

  fix another hold targeting glitch

Signed-off-by: Galen Charlton <gmc@esilibrary.com>
........
  r18049 | gmc | 2010-09-27 16:14:49 -0400 (Mon, 27 Sep 2010) | 7 lines

  adjustments to pubdate sorting patch

  * handle upgrade for date1/date2 containing the empty string
  * syntax error fix

Signed-off-by: Galen Charlton <gmc@esilibrary.com>
........
  r18051 | senator | 2010-09-27 16:54:56 -0400 (Mon, 27 Sep 2010) | 2 lines

  Special overriden widgets need forcible population when used in edit dialogs
........
  r18054 | scottmk | 2010-09-27 17:31:17 -0400 (Mon, 27 Sep 2010) | 12 lines

  1. Turn some ints into bigints.

  2. Rename the uniqueness constraint for booking.resource_type.

  M    Open-ILS/src/sql/Pg/090.schema.action.sql
  M    Open-ILS/src/sql/Pg/200.schema.acq.sql
  M    Open-ILS/src/sql/Pg/012.schema.vandelay.sql
  M    Open-ILS/src/sql/Pg/095.schema.booking.sql
  M    Open-ILS/src/sql/Pg/002.schema.config.sql
  M    Open-ILS/src/sql/Pg/070.schema.container.sql
  A    Open-ILS/src/sql/Pg/upgrade/0421.schema.embiggen-ints.sql
........
  r18055 | atz | 2010-09-27 21:22:40 -0400 (Mon, 27 Sep 2010) | 3 lines

  For edi_translator on RHEL

  Note: RubyGems 1.3.4 is not available anymore
........
  r18056 | atz | 2010-09-27 21:22:41 -0400 (Mon, 27 Sep 2010) | 1 line

  Fix POD for RemoteAccount
........
  r18057 | atz | 2010-09-27 21:24:20 -0400 (Mon, 27 Sep 2010) | 1 line

  Minor cleanup
........
  r18058 | atz | 2010-09-27 21:26:07 -0400 (Mon, 27 Sep 2010) | 1 line

  typo in POD
........
  r18062 | dbs | 2010-09-27 23:08:48 -0400 (Mon, 27 Sep 2010) | 5 lines

  Use quoted attribute values to make Firefox / XULRunner happier about dojo queries

  The unquoted values in BibTemplate and the OPAC detail template were generating
  mucho noise in the JavaScript console; this hushes it up nicely.
........
  r18068 | dbs | 2010-09-27 23:32:28 -0400 (Mon, 27 Sep 2010) | 11 lines

  Add a placeholder server/skin/custom.js to prevent one error in JS console

  custom.js enables you to override the settings in the stock constants.js
  to skin the behaviour of your staff client at your installation, without
  being subject to grief at upgrade time.

  Not having a custom.js file at all was, however, causing errors to be generated
  in the JS console as XULRunner tried hard to parse the 404 message as
  JavaScript. I suppose one could have changed the 404 message to valid
  JavaScript... naw, that would be evil.
........
  r18072 | scottmk | 2010-09-28 01:13:32 -0400 (Tue, 28 Sep 2010) | 6 lines

  Turn an int into a bigint in acq.acq_lineitem_history, following up on
  a similar change to acq.lineitem.

  M    Open-ILS/src/sql/Pg/002.schema.config.sql
  A    Open-ILS/src/sql/Pg/upgrade/0422.schema.acq.lineitem-history-bigint.sql
........
  r18073 | scottmk | 2010-09-28 01:17:03 -0400 (Tue, 28 Sep 2010) | 4 lines

  Incorporate several recent upgrade scripts, through # 0422.

  M    Open-ILS/src/sql/Pg/1.6.1-2.0-upgrade-db.sql
........
  r18075 | dbs | 2010-09-28 02:18:07 -0400 (Tue, 28 Sep 2010) | 6 lines

  Silence SQL warnings from O:A:Storage:Publisher:metabib

  PostgreSQL wants strings containing regular expressions to be prefixed
  with E; this patch considerably reduces the noise in open-ils.storage_unix.log
  by complying with PostgreSQL's wishes.
........
  r18076 | dbs | 2010-09-28 02:43:41 -0400 (Tue, 28 Sep 2010) | 13 lines

  Make authority validation rules match authority ingest rules for better matches

  This resolves a problem in O:A:Storage:Publisher:authority:validate_tag() where
  the full NACO normalization rules that are applied to the subfields of the
  authority records when they are ingested into authority.full_rec are not similarly
  applied to the incoming subfields of the bib field that is being validated;
  only diacritic characters in the bib field subfields were being normalized.

  Now we apply naco_normalize() to the search terms so that they will match
  the ingested form of the authority record.

  Addresses https://bugs.launchpad.net/evergreen/+bug/649556
........
  r18077 | miker | 2010-09-28 12:35:24 -0400 (Tue, 28 Sep 2010) | 1 line

  add support for the null() xpath function, which works in pgxml (AKA xml2) but not in the builtin XPATH function for 8.3+
........
  r18079 | dbs | 2010-09-28 12:40:02 -0400 (Tue, 28 Sep 2010) | 7 lines

  Change memcached default location to 127.0.0.1 from localhost

  Debian Squeeze currently doesn't fare well with memcached servers pointing to
  localhost and needs an explicit 127.0.0.1, whereas Ubuntu Lucid is happy with
  either. Changing the default here means one less possible gotcha in the out
  of the box install & configure experience for Debian Squeeze folk.
........
  r18081 | phasefx | 2010-09-28 12:46:34 -0400 (Tue, 28 Sep 2010) | 1 line

  this breaks (hides) the buttonbar in windows.  Sorry James, miker
........
  r18083 | erickson | 2010-09-28 14:12:12 -0400 (Tue, 28 Sep 2010) | 1 line

  repaired logic bug in lineitem worksheet template
........
  r18084 | erickson | 2010-09-28 14:12:13 -0400 (Tue, 28 Sep 2010) | 5 lines

  ability to override checkin events. minor fixes.

  repaired bug that caused checkin to sometimes fail when a hold
  was captured for a user that had at least 1 inactive card;
  made checkin override events configurable; minor cleanup
........
  r18085 | erickson | 2010-09-28 14:12:14 -0400 (Tue, 28 Sep 2010) | 1 line

  for troubleshooting, log the size of the template output to be stored
........
  r18089 | senator | 2010-09-28 16:26:09 -0400 (Tue, 28 Sep 2010) | 5 lines

  A/T: Send an early response back to the client when running all pending events
  The client may wish to know when the relativelty fast process of creating the
  events is over, and the relatively slow process of validating them and running
  their reactors/cleanup/etc is about to begin.
........
  r18091 | miker | 2010-09-28 16:45:17 -0400 (Tue, 28 Sep 2010) | 1 line

  move the early-out response so we avoid breaking the caller
........
  r18093 | phasefx | 2010-09-28 17:12:11 -0400 (Tue, 28 Sep 2010) | 2 lines

  Delete all cookies on logoff.  There's dojo code that looks for stale session cookies and has the xul client prompt for a new session.  When you logoff, the session is destroyed, but the stale cookies were being left behind (and not overwritten on login? I don't understand that part).  But this fixes it
........
  r18095 | miker | 2010-09-28 19:12:58 -0400 (Tue, 28 Sep 2010) | 1 line

  Use transactions everywhere in Vandelay
........
  r18097 | miker | 2010-09-29 10:45:50 -0400 (Wed, 29 Sep 2010) | 1 line

  Process item imports during record import, not as a secondary call (never should have worked, but for transaction timing)
........
  r18099 | miker | 2010-09-29 11:05:53 -0400 (Wed, 29 Sep 2010) | 1 line

  support per-grunlarity parallelizing via flag and lock file
........
  r18101 | gmc | 2010-09-29 13:48:52 -0400 (Wed, 29 Sep 2010) | 10 lines

  reporter: don't try to write Excel formulas

  Any cell value that starts with = is now always written
  as a text cell in spreadsheet output.  Avoids a possible
  exploit as well as errors like this:

  Couldn't parse formula: = at /openils/bin/clark-kent.pl line 429

Signed-off-by: Galen Charlton <gmc@esilibrary.com>
........
  r18103 | gmc | 2010-09-29 14:07:43 -0400 (Wed, 29 Sep 2010) | 4 lines

  fix FM type of reporter.report.data

Signed-off-by: Galen Charlton <gmc@esilibrary.com>
........
  r18105 | miker | 2010-09-29 16:16:41 -0400 (Wed, 29 Sep 2010) | 1 line

  Give BibTemplate the ability to inspect and optionally parse XML, instead of requiring a DOM node
........
  r18106 | phasefx | 2010-09-29 16:49:05 -0400 (Wed, 29 Sep 2010) | 2 lines

  Fix overzealous prompting for auth credentials, and some debug lines
........
  r18109 | erickson | 2010-09-30 16:49:14 -0400 (Thu, 30 Sep 2010) | 1 line

  fire the hold_request.cancel.staff when hold is cancelled by staff
........
  r18111 | atz | 2010-09-30 19:06:39 -0400 (Thu, 30 Sep 2010) | 6 lines

  Silence warning

  Warnings was:
  Use of uninitialized value in subroutine entry at /openils/lib/perl5/OpenILS/SIP/Patron.pm line 230.

Signed-off-by: Joe Atzberger <atz@esilibrary.com>
........
  r18112 | dbs | 2010-10-01 01:16:41 -0400 (Fri, 01 Oct 2010) | 6 lines

  Add serial.record_entry to CDBI definitions

  Better late than never; never added the serial.record_entry
  CDBI definitions in the 1.6 series, but as that table can
  still be active in 2.0 we might as well get it in place.
........
  r18113 | dbs | 2010-10-01 01:24:38 -0400 (Fri, 01 Oct 2010) | 14 lines

  Teach marc_export script how to export MFHD serial records

  Passing the --mfhd flag will export any non-deleted MFHD records in
  serial.record_entry associated with each bib ID. So, for a hypothetical set of
  bib IDs 1, 2, 3, where 2 has no associated MFHD records and 3 has 2 MFHD records
  associated with it, the output will be structured as follows:

  Bib MARC for bib ID 1
  MFHD MARC for bib ID 1
  Bib MARC for bib ID 2
  Bib MARC for bib ID 3
  MFHD MARC for bib ID 3
  MFHD MARC for bib ID 2
........
  r18116 | phasefx | 2010-10-01 08:19:11 -0400 (Fri, 01 Oct 2010) | 2 lines

  tweak remoteauth.cgi to offer usrname and barcode params in addition to user.  user param now looks for the opac.barcode_regex org unit setting to determine whether the value is a usrname or barcode.  change double-quotes to single-quotes if we're not doing string interpolation.  change apache instructions for configuration
........
  r18121 | phasefx | 2010-10-01 14:07:00 -0400 (Fri, 01 Oct 2010) | 2 lines

  use an opac login here, which doesn't require the STAFF_LOGIN permission by default
........
  r18123 | miker | 2010-10-01 15:39:51 -0400 (Fri, 01 Oct 2010) | 1 line

  Implement a process-local cache for event target fleshing -- particularly helpful with large event groups
........
  r18124 | senator | 2010-10-01 15:45:27 -0400 (Fri, 01 Oct 2010) | 5 lines

  Offer yet another pull list printing pathway

  If you have pull lists long enough to make A/T groan, perhaps this will work
  better for you.
........
  r18126 | dbs | 2010-10-01 16:07:59 -0400 (Fri, 01 Oct 2010) | 11 lines

  Set due times for durations measured in days to 23:59:59 after inserts OR updates

  The existing trigger acted only on the initial insert of a circulation
  transaction for duration intervals perfectly divisible by 24 hours.
  If updates to those due dates were subsequently issued, then the due
  time would revert to 00:00:00 - which could cause surprising overdue
  fines to be generated on the due date, rather than after the due date.

  This commit makes the trigger take effect on both INSERT and UPDATE
  to the action.circulation table.
........
  r18128 | dbs | 2010-10-01 16:13:10 -0400 (Fri, 01 Oct 2010) | 2 lines

  Bring the 1.6.1-2.0 upgrade script up to date for the push_due_date_tgr
........
  r18130 | senator | 2010-10-01 17:48:41 -0400 (Fri, 01 Oct 2010) | 4 lines

  Fix an apparent bug in a case where OpenILS::WWW::Proxy means to send a
  redirect, and also avoid the issue altogether in the new holds pull list
  printing interface just added earlier today.
........
  r18132 | miker | 2010-10-02 01:47:02 -0400 (Sat, 02 Oct 2010) | 7 lines

  Massive search core-query speed improvement.

   * Only compile the tsquery once
   * Use direct ids instead of going back to the db udring a queyr
   * Change a remaining CASE to COALESCE/NULLIF
........
  r18133 | miker | 2010-10-02 02:04:05 -0400 (Sat, 02 Oct 2010) | 1 line

  putting back the NUMERIC cast, it is needed
........
  r18136 | miker | 2010-10-02 11:47:28 -0400 (Sat, 02 Oct 2010) | 1 line

  configurable chunking of the holds stream to avoid the xulrunner "I forgot the chunked stream" problem
........
  r18138 | scottmk | 2010-10-03 10:17:37 -0400 (Sun, 03 Oct 2010) | 4 lines

  Incorporate upgrade scripts 0423 and 0424

  M    Open-ILS/src/sql/Pg/1.6.1-2.0-upgrade-db.sql
........
  r18140 | erickson | 2010-10-03 11:06:45 -0400 (Sun, 03 Oct 2010) | 1 line

  moved the cache clear to later in the firing to pick up data; fixed some typos/thinkos
........
  r18141 | miker | 2010-10-03 20:40:44 -0400 (Sun, 03 Oct 2010) | 1 line

  Allow caller to ignore selected facet classes; ignore identifier class by default
........
  r18146 | scottmk | 2010-10-04 09:43:13 -0400 (Mon, 04 Oct 2010) | 8 lines

  Add hold_priority column to permission.grp_tree table.

  M    Open-ILS/src/perlmods/OpenILS/Application/Storage/CDBI/permission.pm
  M    Open-ILS/src/sql/Pg/002.schema.config.sql
  A    Open-ILS/src/sql/Pg/upgrade/0425.schema.perm-grp-tree-hold-priority.sql
  M    Open-ILS/src/sql/Pg/006.schema.permissions.sql
  M    Open-ILS/examples/fm_IDL.xml
........
  r18148 | senator | 2010-10-04 10:01:28 -0400 (Mon, 04 Oct 2010) | 2 lines

  Fix a slight bug in 18136, and the alt pull list printing interface works again.
........
  r18150 | senator | 2010-10-04 11:40:21 -0400 (Mon, 04 Oct 2010) | 2 lines

  Add a control for the hold_priority field in the permission group UI
........
  r18151 | miker | 2010-10-04 12:15:41 -0400 (Mon, 04 Oct 2010) | 1 line

  add support for hold_priority sorting in open-ils.storage.action.hold_request.nearest_hold
........
  r18153 | erickson | 2010-10-04 12:48:01 -0400 (Mon, 04 Oct 2010) | 1 line

  fixed typo in vandelay merge profile permission check
........
  r18158 | senator | 2010-10-04 14:57:20 -0400 (Mon, 04 Oct 2010) | 2 lines

  Add "Patron Alias" as an available column in pull list, holds shelf interfaces
........
  r18159 | dbs | 2010-10-04 15:05:59 -0400 (Mon, 04 Oct 2010) | 2 lines

  French translation of password reset form and prompts at /opac/password/fr-CA/
........
  r18163 | senator | 2010-10-04 17:28:17 -0400 (Mon, 04 Oct 2010) | 3 lines

  Add some missing ACQ perms. Other perms may be missing, but I /know/ these
  are needed.
........
  r18165 | erickson | 2010-10-05 10:15:40 -0400 (Tue, 05 Oct 2010) | 1 line

  avoid running activated hook when in dry run mode
........
  r18167 | gmc | 2010-10-05 10:44:21 -0400 (Tue, 05 Oct 2010) | 4 lines

  make it easier to grep for bib search durations from the Pg log

Signed-off-by: Galen Charlton <gmc@esilibrary.com>
........
  r18169 | phasefx | 2010-10-05 11:16:42 -0400 (Tue, 05 Oct 2010) | 8 lines

  more classname hooks for local CSS to latch on to if needed

  For example, you may have a server/skin/global_custom.css containing:

      .hide_patron_credit { display: none }
      .hide_patron_work{ display: none }
      .hide_patron_goods { display: none }
........
  r18171 | atz | 2010-10-05 12:14:46 -0400 (Tue, 05 Oct 2010) | 1 line

  Add title to PO view page.
........
  r18172 | atz | 2010-10-05 12:16:59 -0400 (Tue, 05 Oct 2010) | 1 line

  EDI job POD
........
  r18175 | senator | 2010-10-05 12:37:57 -0400 (Tue, 05 Oct 2010) | 3 lines

  This is a silly commit, as this template should be made configurable down the
  road, maybe by OU setting or something. Anyway, make the list more legible.
........
  r18177 | atz | 2010-10-05 13:08:56 -0400 (Tue, 05 Oct 2010) | 1 line

  The whole point of test mode, NOT actually committing actions.
........
  r18179 | gmc | 2010-10-05 14:54:05 -0400 (Tue, 05 Oct 2010) | 8 lines

  hold matrix selection: treat root OU as just another OU

  Treat the root OU as just another OU for the purpose of
  calculating OU proximity adjustments when selecting a
  matchpoint.

Signed-off-by: Galen Charlton <gmc@esilibrary.com>
........
  r18180 | phasefx | 2010-10-05 15:33:08 -0400 (Tue, 05 Oct 2010) | 2 lines

  be quiet with errors retrieving remote column settings
........
  r18182 | phasefx | 2010-10-05 16:12:58 -0400 (Tue, 05 Oct 2010) | 1 line

  patch from tsbere to use _blank for new windows instead of generated identifiers
........
  r18183 | phasefx | 2010-10-05 16:13:01 -0400 (Tue, 05 Oct 2010) | 2 lines

  patch from tsbere to improve tab behavior and to give command-line options for controlling tabs
  See https://bugs.launchpad.net/evergreen/+bug/625056
........
  r18184 | phasefx | 2010-10-05 16:13:03 -0400 (Tue, 05 Oct 2010) | 1 line

  bug fix for close tab regression
........
  r18185 | phasefx | 2010-10-05 16:13:06 -0400 (Tue, 05 Oct 2010) | 1 line

  some I18N and tweak call to getIntPref.  Is the 2nd parameter version an undocumented way of providing a default if the pref isn't found?
........
  r18187 | miker | 2010-10-05 22:31:55 -0400 (Tue, 05 Oct 2010) | 1 line

  Add a retargetting specific version of the hold-permit function which skips user tests
........
  r18188 | miker | 2010-10-05 22:35:17 -0400 (Tue, 05 Oct 2010) | 1 line

  usr_grp is entirely unused currently; hide it
........
  r18191 | miker | 2010-10-05 23:07:30 -0400 (Tue, 05 Oct 2010) | 1 line

  Add a switch to turn on strict OU matching for all OUs required by a hold matchpoint
........
  r18192 | scottmk | 2010-10-06 09:27:26 -0400 (Wed, 06 Oct 2010) | 5 lines

  Fixed the second and third function definitions, which wouldn't compile.
  An SQL function cannot reference its parameters by name, but only by number.

  M    Open-ILS/src/sql/Pg/upgrade/0428.schema.hold_retarget.sql
........
  r18193 | erickson | 2010-10-06 09:28:09 -0400 (Wed, 06 Oct 2010) | 1 line

  first level of groups hash can be array or hash, leading to breakage;  for now, keep it simple and return continue-status to keep the caller alive
........
  r18195 | miker | 2010-10-06 09:43:52 -0400 (Wed, 06 Oct 2010) | 1 line

  Make retargetting check less lax by including appropriate patron-side test
........
  r18196 | miker | 2010-10-06 09:45:00 -0400 (Wed, 06 Oct 2010) | 1 line

  Start out exact-ou-match rules with an "always win" weight
........
  r18198 | miker | 2010-10-06 09:53:18 -0400 (Wed, 06 Oct 2010) | 1 line

  Thinko fix for field name. The script would not commit, so please simply reapply 0430.
........
  r18199 | erickson | 2010-10-06 09:54:26 -0400 (Wed, 06 Oct 2010) | 1 line

  suppress usr_grp field in hold matrix matchpoint;  see also 18188
........
  r18202 | scottmk | 2010-10-06 10:32:01 -0400 (Wed, 06 Oct 2010) | 5 lines

  Further propagating a syntax correction; SQL functions cannot reference
  their parameters by name.

  M    Open-ILS/src/sql/Pg/110.hold_matrix.sql
........
  r18203 | phasefx | 2010-10-06 10:59:43 -0400 (Wed, 06 Oct 2010) | 2 lines

  hold/transit slips may want to use stat cats as well
........
  r18205 | erickson | 2010-10-06 11:01:38 -0400 (Wed, 06 Oct 2010) | 1 line

  avoid retreiving/searching a linked object, when there is no object to retrieve (i.e. ident value is null) and silence various warnings by skipping this scenario.  mild variable refactor for easy reading for feeble eyes.
........
  r18207 | scottmk | 2010-10-06 11:55:14 -0400 (Wed, 06 Oct 2010) | 14 lines

  Two changes to config schema:

  1. Add new column date_ceiling to rule_circ_duration table.

  2. New table hard_due_date.

  For trunk, v1.6, v1.6.2 (eventually), and v2.1 (eventually).

  NOT for v1.6.1 or v2.0.

  M    Open-ILS/src/sql/Pg/002.schema.config.sql
  A    Open-ILS/src/sql/Pg/upgrade/0432.schema.config_hard_due_date.sql
  M    Open-ILS/examples/fm_IDL.xml
........
  r18210 | scottmk | 2010-10-06 14:19:43 -0400 (Wed, 06 Oct 2010) | 4 lines

  Incorporate upgrades 0427 and 0428 into the consolidated upgrade script.

  M    Open-ILS/src/sql/Pg/1.6.1-2.0-upgrade-db.sql
........
  r18211 | miker | 2010-10-06 16:07:39 -0400 (Wed, 06 Oct 2010) | 1 line

  db upgrade script dislikes holes -- spackle this one
........
  r18213 | erickson | 2010-10-06 16:44:46 -0400 (Wed, 06 Oct 2010) | 1 line

  added support for honoring the due date ceiling from the duration rule.  AKA, end-of-semester due dates
........
  r18215 | senator | 2010-10-06 17:01:42 -0400 (Wed, 06 Oct 2010) | 14 lines

  New way to printing shelf-expired holds

  This just takes the newest template for printing hold pull lists and
  grafts this new functionality onto it.  It should perhaps be adjusted to also
  be able to print things on the holds shelf that /aren't/ shelf-expired.

  For now you get to this under Admin -> For Developers -> Local Administration

  This also corrects a bug because of which a "print pull list (alternate
  strategy)" button appeared where it shouldn't.

  This also removes the booking links from Admin -> For Developers -> Local
  Administration, as there are regular staff client menu entries for those now.
........
  r18217 | atz | 2010-10-06 18:12:36 -0400 (Wed, 06 Oct 2010) | 1 line

  Warn but continue on skipped upgrade revs.
........
  r18219 | erickson | 2010-10-07 10:56:43 -0400 (Thu, 07 Oct 2010) | 6 lines

  Parallel action/trigger collection and reaction

  QA'ed patch from miker to support parallel a/t event collection and
  reaction.  Max parallel procs is controlled by two new opensrf.xml
  trigger app_settings.  Sample config included, settings disabled by
  default.
........
  r18220 | phasefx | 2010-10-07 11:14:50 -0400 (Thu, 07 Oct 2010) | 2 lines

  destination_shelf macro for hold and hold/transit slips.  Will contain either HOLD SHELF, PUBLIC HOLD SHELF, or PRIVATE HOLD SHELF (these are localizable).  The latter two depend on the org unit setting 'circ.holds.behind_desk_pickup_supported' being in effect, and also considers a user setting for the holds user at the time of printing.
........
  r18222 | senator | 2010-10-07 13:18:58 -0400 (Thu, 07 Oct 2010) | 2 lines

  Add some reasonable default sorting to this expired holds list
........
  r18224 | atz | 2010-10-07 14:48:45 -0400 (Thu, 07 Oct 2010) | 6 lines

  EDI template update for ORDERS

  This template produces JSON for the edi translator to convert into
  actual EDI lines.  It now handles vendor-specific requirements for
  account and sub-account identification, and also transmits notes
  of the vendor-public variety as FTX segments in the lineitem.
........
  r18226 | senator | 2010-10-07 15:10:58 -0400 (Thu, 07 Oct 2010) | 3 lines

  Just a minor thing on this shelf expired print interface,
  probably more changes coming.
........
  r18228 | gmc | 2010-10-07 17:39:05 -0400 (Thu, 07 Oct 2010) | 4 lines

  choose the hold permit test variant correctly

Signed-off-by: Galen Charlton <gmc@esilibrary.com>
........
  r18230 | senator | 2010-10-07 18:37:45 -0400 (Thu, 07 Oct 2010) | 8 lines

  Expired holds shelf printer needs to be a holds shelf *clearer* and printer

  This needs cleaned up and stuff, and made into something cooler.
  Basically just does what XUL interfaces under the Circ menu can already do,
  but streamlined to tolerate really big datasets.

  Much of this code originates from berick and miker.
........
  r18231 | senator | 2010-10-07 18:46:24 -0400 (Thu, 07 Oct 2010) | 2 lines

  progress to ProgressDialog means total, not incremental
........
  r18233 | gmc | 2010-10-07 21:34:08 -0400 (Thu, 07 Oct 2010) | 8 lines

  do not apply superpage limit inside bib search joins

  Oddly, empirical evidence suggests that this might actually
  be faster, and if this pans out, search results will definitely
  be more complete.

Signed-off-by: Galen Charlton <gmc@esilibrary.com>
........
  r18239 | dbs | 2010-10-07 23:30:00 -0400 (Thu, 07 Oct 2010) | 7 lines

  Avoid scary SSL / HTTPS errors in Apache configuration

  When port 443 is the last listener port, Apache generates lots
  of "unknown protocol speaking not SSL to HTTPS port!?" errors in
  the logs - which are scary, but harmless. Putting port 80 last
  avoids those errors entirely, per http://wiki.apache.org/httpd/InternalDummyConnection
........
  r18240 | scottmk | 2010-10-08 10:16:19 -0400 (Fri, 08 Oct 2010) | 19 lines

  Tidied up buildSELECT() a bit:

  1. Sprinkled the const qualifier here and there.

  2. Moved some variable declarations to get them closer to the point of
  first use, and to limit their scope.

  3. Renamed some variables to better reflect their meaning.

  4. Split a couple of variables into multiple variables, instead of using
  them for multiple unrelated purposes.

  5. Plugged a memory leak in the case of an error return.

  6. Added comments, including a Doxygen-style comment at the top of the
  function.

  M    Open-ILS/src/c-apps/oils_sql.c
........
  r18244 | miker | 2010-10-08 13:06:52 -0400 (Fri, 08 Oct 2010) | 1 line

  add a method to overlay a special bib container full of records (or bucket + template)
........
  r18245 | phasefx | 2010-10-08 13:29:20 -0400 (Fri, 08 Oct 2010) | 2 lines

  moving users in and out of groups produced dialogs that expected vertical patron summaries.  this fixes that
........
  r18247 | phasefx | 2010-10-08 13:38:24 -0400 (Fri, 08 Oct 2010) | 2 lines

  fix logic error where we were just testing for the presence of a user setting instead of its value
........
  r18249 | senator | 2010-10-08 14:02:08 -0400 (Fri, 08 Oct 2010) | 2 lines

  Be a little more tolerant of aberrant data when clearing the holds shelf
........
  r18251 | phasefx | 2010-10-08 16:58:37 -0400 (Fri, 08 Oct 2010) | 1 line

  typo
........
  r18253 | erickson | 2010-10-08 17:10:43 -0400 (Fri, 08 Oct 2010) | 1 line

  mild fixes for a/t interface admin interface.  sort by def name instead of hook, which probably makes more sense to a human.  hide the opt-in and max-delay columns to free up some badly needed horizontal space.  use percentage width for name column, which acts a lot like 'auto', but allows the user to manually resize
........
  r18255 | miker | 2010-10-08 22:32:49 -0400 (Fri, 08 Oct 2010) | 1 line

  mod_perl handler to allow batch update bib records from an ephemeral template
........
  r18256 | erickson | 2010-10-11 10:01:45 -0400 (Mon, 11 Oct 2010) | 1 line

  wait to run-pending if a specific granularity used
........
  r18258 | scottmk | 2010-10-11 10:25:05 -0400 (Mon, 11 Oct 2010) | 12 lines

  Pull out into a separate function: the code in SELECT() that builds a
  comma-separated list of ORDER BY expressions from a JSON_ARRAY.

  Invoke that function, not only from SELECT(), but also from the
  buildSELECT() function.

  As a result, the select methods will be able to use the same array
  syntax as json_query for ORDER BY clauses, as an alternative to the
  existing hash syntax.

  M    Open-ILS/src/c-apps/oils_sql.c
........
  r18259 | atz | 2010-10-11 11:21:12 -0400 (Mon, 11 Oct 2010) | 1 line

  Clean up method registration sigs/descs
........
  r18260 | atz | 2010-10-11 12:06:23 -0400 (Mon, 11 Oct 2010) | 1 line

  Method registration cleanup
........
  r18261 | miker | 2010-10-11 12:06:52 -0400 (Mon, 11 Oct 2010) | 1 line

  supply a dummy leader, as required by the Perl MARC modules
........
  r18262 | atz | 2010-10-11 12:07:09 -0400 (Mon, 11 Oct 2010) | 1 line

  Tighter verbose format for skipped POs
........
  r18264 | miker | 2010-10-11 12:07:47 -0400 (Mon, 11 Oct 2010) | 1 line

  Working template-based batch bib updater!
........
  r18269 | phasefx | 2010-10-11 16:39:31 -0400 (Mon, 11 Oct 2010) | 2 lines

  initialize these lists just once to prevent display glitch
........
  r18271 | miker | 2010-10-11 16:41:37 -0400 (Mon, 11 Oct 2010) | 1 line

  dogfooding cleanup
........
  r18272 | miker | 2010-10-11 16:42:46 -0400 (Mon, 11 Oct 2010) | 1 line

  make the source selection less confusing, and improve some wording in the template section (more to come)
........
  r18274 | phasefx | 2010-10-11 17:42:48 -0400 (Mon, 11 Oct 2010) | 2 lines

  avoid race condition with post-save patron editor refresh and replicated databases
........
  r18277 | scottmk | 2010-10-12 09:40:14 -0400 (Tue, 12 Oct 2010) | 4 lines

  Incorporate upgrade # 0433 into the consolidated upgrade script.

  M    Open-ILS/src/sql/Pg/1.6.1-2.0-upgrade-db.sql
........
  r18278 | miker | 2010-10-12 11:19:54 -0400 (Tue, 12 Oct 2010) | 1 line

  skip unknown order_by entries instead of erroring
........
  r18279 | miker | 2010-10-12 11:39:47 -0400 (Tue, 12 Oct 2010) | 1 line

  rearrangement for variable existance
........
  r18280 | miker | 2010-10-12 11:40:45 -0400 (Tue, 12 Oct 2010) | 1 line

  and ... use a function name that exists
........
  r18281 | miker | 2010-10-12 12:02:31 -0400 (Tue, 12 Oct 2010) | 1 line

  now that we are working again, hush the warnings (osrfLogInternal to the rescue)
........
  r18282 | atz | 2010-10-12 13:01:52 -0400 (Tue, 12 Oct 2010) | 1 line

  Be sure to copy new JS files from OpenSRF on a FULL update/install
........
  r18285 | miker | 2010-10-12 15:23:22 -0400 (Tue, 12 Oct 2010) | 1 line

  improve labeling and documentation
........
  r18286 | phasefx | 2010-10-12 15:34:35 -0400 (Tue, 12 Oct 2010) | 1 line

  add option for hiding entire embedded browser toolbar
........
  r18287 | phasefx | 2010-10-12 15:34:39 -0400 (Tue, 12 Oct 2010) | 1 line

  allow util.deck to be instantiated with either a xul deck object or a xul deck id
........
  r18288 | phasefx | 2010-10-12 15:34:50 -0400 (Tue, 12 Oct 2010) | 1 line

  vertical bib summary (mainly for use in the Merge Record interface).  I tried using just bib_brief.xul with document.loadOverlay to choose between bib_brief_overlay_vertical.xul and bib_brief_overlay.xul based on a param, but was running into too much pain going that route.
........
  r18289 | phasefx | 2010-10-12 15:35:42 -0400 (Tue, 12 Oct 2010) | 2 lines

  new record merge UI.  needs r18287, r18288.  I'll backport them all together
........
  r18290 | miker | 2010-10-12 15:55:58 -0400 (Tue, 12 Oct 2010) | 1 line

  more terminology cleanup and inline documentation
........
  r18291 | miker | 2010-10-12 16:31:31 -0400 (Tue, 12 Oct 2010) | 1 line

  Get rid of commas, apparently confusing; make action button label more generic / less "developery"
........
  r18293 | miker | 2010-10-12 17:31:33 -0400 (Tue, 12 Oct 2010) | 1 line

  when we have no target field, add the entire source field, even when we have a subfield designation
........
  r18294 | phasefx | 2010-10-12 17:32:21 -0400 (Tue, 12 Oct 2010) | 1 line

  entry points for miker_'s batch marc editor
........
  r18298 | miker | 2010-10-12 20:27:46 -0400 (Tue, 12 Oct 2010) | 1 line

  deduplicate bibs going into the merge queue
........
  r18299 | miker | 2010-10-12 20:28:43 -0400 (Tue, 12 Oct 2010) | 1 line

  arg, missing semicolon
........
  r18300 | miker | 2010-10-12 20:29:38 -0400 (Tue, 12 Oct 2010) | 1 line

  double-arg, backwards logic
........
  r18303 | miker | 2010-10-12 22:33:59 -0400 (Tue, 12 Oct 2010) | 1 line

  thinko supporting multiple rules of the same type in in-line merge rulesets
........
  r18310 | phasefx | 2010-10-13 12:03:25 -0400 (Wed, 13 Oct 2010) | 2 lines

  missing semicolon
........
  r18311 | erickson | 2010-10-13 12:13:23 -0400 (Wed, 13 Oct 2010) | 1 line

  when the target for an event is no longer around, immediately invalidate the event and prevent the event from bubbling up for further processing
........
  r18312 | miker | 2010-10-13 12:17:14 -0400 (Wed, 13 Oct 2010) | 1 line

  Use just one transaction, and inside an rstore editor no less, for fleshing env paths
........
  r18313 | phasefx | 2010-10-13 12:30:38 -0400 (Wed, 13 Oct 2010) | 1 line

  overzealous trimming of cat.properties
........
  r18321 | scottmk | 2010-10-13 14:53:56 -0400 (Wed, 13 Oct 2010) | 22 lines

  Changes to the treatment of ORDER BY:

  1. For json_query: when ORDER BY is expressed as an object keyed on class
  (instead of an array of field specifications), and the class is not in
  scope, error out instead of silently ignoring the class.

  The other changes affect only methods other than json_query:

  2. When the ORDER BY list is provided as a raw text string: block any
  string containing a semicolon, in order to block simple SQL injections.
  For now we make no exceptions for quoted semicolons, which are not
  likely ever to appear an an ORDER BY clause.

  3. Keep virtual fields out of the ORDER BY clause.  For now we silently
  ignore them, as we ignore non-existent fields.  In both cases we should
  perhaps error out.

  4. Don't require that a class referenced in the ORDER BY clause also be
  referenced in the SELECT clause.  Just make sure it's in scope.

  M    Open-ILS/src/c-apps/oils_sql.c
........
  r18322 | erickson | 2010-10-13 16:16:57 -0400 (Wed, 13 Oct 2010) | 1 line

  clean up duplicate system-controlled penalties during penalty calculation; perform penalty trigger event creation after the standalone commit occurs.  todo, handle non-standalone post-commit penalty trigger event creation
........
  r18323 | erickson | 2010-10-13 16:16:58 -0400 (Wed, 13 Oct 2010) | 1 line

  default to standard cstore instead of rstore for env building editor
........
  r18326 | phasefx | 2010-10-14 11:29:29 -0400 (Thu, 14 Oct 2010) | 2 lines

  change the Swap Editor button to a persisted checkbox
........
  r18329 | miker | 2010-10-14 14:13:20 -0400 (Thu, 14 Oct 2010) | 1 line

  correct top-half ordering by label_sortkey
........
  r18331 | miker | 2010-10-14 14:35:27 -0400 (Thu, 14 Oct 2010) | 1 line

  patch from Steve Callendar to avoid resetting the passwd every time a phone number changes
........
  r18333 | miker | 2010-10-14 15:18:11 -0400 (Thu, 14 Oct 2010) | 1 line

  cast label_sortkey to bytea in order to get ascii-betical sorting in any locale, even C. stupid glibc ...
........
  r18336 | miker | 2010-10-14 15:33:41 -0400 (Thu, 14 Oct 2010) | 1 line

  backward compat indexing for label instead of label_sortkey
........
  r18342 | erickson | 2010-10-14 15:57:42 -0400 (Thu, 14 Oct 2010) | 1 line

  repaired upgrade version
........
  r18343 | miker | 2010-10-14 16:04:05 -0400 (Thu, 14 Oct 2010) | 1 line

  "as" not "to" ... you pointed that out the first time, miker, what is your deal?
........
  r18349 | miker | 2010-10-14 16:17:00 -0400 (Thu, 14 Oct 2010) | 1 line

  force granularity-only when any granularity is specified
........
  r18351 | erickson | 2010-10-14 18:17:21 -0400 (Thu, 14 Oct 2010) | 1 line

  If an item is captured for a hold, but not in transit (i.e. on holds shelf), set the destination_location equal to the pickup library (i.e where it's supposedly on the shelf).  This is useful for autmated sorting so that the item will return to the branch whose shelf where it belongs.
........
  r18353 | gmc | 2010-10-15 09:08:52 -0400 (Fri, 15 Oct 2010) | 7 lines

  tweak expanding search field aliases

  Avoids a glitch that can occur if a search field alias
  exists that happens to have the same name as a search field.

Signed-off-by: Galen Charlton <gmc@esilibrary.com>
........
  r18364 | miker | 2010-10-16 11:32:32 -0400 (Sat, 16 Oct 2010) | 1 line

  use a function to wrap up escaping of solidus and casting to bytea, propogate to indexing and where/order_by
........
  r18365 | miker | 2010-10-16 11:35:39 -0400 (Sat, 16 Oct 2010) | 1 line

  need to update this index as well
........
  r18366 | miker | 2010-10-16 11:38:21 -0400 (Sat, 16 Oct 2010) | 1 line

  go ahead and use the new index if we need to
........

git-svn-id: svn://svn.open-ils.org/ILS/branches/serials-integration@18372 dcc99617-32d9-48b4-a31d-7c20da2025e4

228 files changed:
Open-ILS/examples/apache/eg.conf
Open-ILS/examples/apache/eg_vhost.conf
Open-ILS/examples/fm_IDL.xml
Open-ILS/examples/oils_sip.xml.example
Open-ILS/examples/opensrf.xml.example
Open-ILS/examples/remoteauth.cgi
Open-ILS/src/c-apps/oils_sql.c
Open-ILS/src/edi_translator/install.RHEL.sh [new file with mode: 0755]
Open-ILS/src/perlmods/OpenILS/Application/Acq/Lineitem.pm
Open-ILS/src/perlmods/OpenILS/Application/Acq/Order.pm
Open-ILS/src/perlmods/OpenILS/Application/Actor.pm
Open-ILS/src/perlmods/OpenILS/Application/AppUtils.pm
Open-ILS/src/perlmods/OpenILS/Application/Booking.pm
Open-ILS/src/perlmods/OpenILS/Application/Cat.pm
Open-ILS/src/perlmods/OpenILS/Application/Circ.pm
Open-ILS/src/perlmods/OpenILS/Application/Circ/Circulate.pm
Open-ILS/src/perlmods/OpenILS/Application/Circ/CopyLocations.pm
Open-ILS/src/perlmods/OpenILS/Application/Circ/Holds.pm
Open-ILS/src/perlmods/OpenILS/Application/Search/Biblio.pm
Open-ILS/src/perlmods/OpenILS/Application/Storage/CDBI/asset.pm
Open-ILS/src/perlmods/OpenILS/Application/Storage/CDBI/permission.pm
Open-ILS/src/perlmods/OpenILS/Application/Storage/CDBI/serial.pm
Open-ILS/src/perlmods/OpenILS/Application/Storage/Driver/Pg/QueryParser.pm
Open-ILS/src/perlmods/OpenILS/Application/Storage/Publisher/action.pm
Open-ILS/src/perlmods/OpenILS/Application/Storage/Publisher/asset.pm
Open-ILS/src/perlmods/OpenILS/Application/Storage/Publisher/authority.pm
Open-ILS/src/perlmods/OpenILS/Application/Storage/Publisher/metabib.pm
Open-ILS/src/perlmods/OpenILS/Application/Storage/QueryParser.pm
Open-ILS/src/perlmods/OpenILS/Application/SuperCat.pm
Open-ILS/src/perlmods/OpenILS/Application/Trigger.pm
Open-ILS/src/perlmods/OpenILS/Application/Trigger/Event.pm
Open-ILS/src/perlmods/OpenILS/Application/Trigger/EventGroup.pm
Open-ILS/src/perlmods/OpenILS/Application/Trigger/Reactor.pm
Open-ILS/src/perlmods/OpenILS/Application/Vandelay.pm
Open-ILS/src/perlmods/OpenILS/SIP.pm
Open-ILS/src/perlmods/OpenILS/SIP/Item.pm
Open-ILS/src/perlmods/OpenILS/SIP/Patron.pm
Open-ILS/src/perlmods/OpenILS/SIP/Transaction/Checkin.pm
Open-ILS/src/perlmods/OpenILS/Utils/CStoreEditor.pm
Open-ILS/src/perlmods/OpenILS/Utils/Penalty.pm
Open-ILS/src/perlmods/OpenILS/Utils/PermitHold.pm
Open-ILS/src/perlmods/OpenILS/Utils/RemoteAccount.pm
Open-ILS/src/perlmods/OpenILS/WWW/Proxy.pm
Open-ILS/src/perlmods/OpenILS/WWW/TemplateBatchBibUpdate.pm [new file with mode: 0644]
Open-ILS/src/reporter/clark-kent.pl
Open-ILS/src/sql/Pg/002.functions.config.sql
Open-ILS/src/sql/Pg/002.schema.config.sql
Open-ILS/src/sql/Pg/006.schema.permissions.sql
Open-ILS/src/sql/Pg/012.schema.vandelay.sql
Open-ILS/src/sql/Pg/030.schema.metabib.sql
Open-ILS/src/sql/Pg/040.schema.asset.sql
Open-ILS/src/sql/Pg/070.schema.container.sql
Open-ILS/src/sql/Pg/090.schema.action.sql
Open-ILS/src/sql/Pg/095.schema.booking.sql
Open-ILS/src/sql/Pg/1.6.1-2.0-upgrade-db.sql
Open-ILS/src/sql/Pg/110.hold_matrix.sql
Open-ILS/src/sql/Pg/200.schema.acq.sql
Open-ILS/src/sql/Pg/950.data.seed-values.sql
Open-ILS/src/sql/Pg/reporter-schema.sql
Open-ILS/src/sql/Pg/upgrade/0414.schema.call-number-upd-ins-trigger.sql [new file with mode: 0644]
Open-ILS/src/sql/Pg/upgrade/0415.schema.rename-field-class-fkey.sql [new file with mode: 0644]
Open-ILS/src/sql/Pg/upgrade/0416.schema.rename-recuring-idx.sql [new file with mode: 0644]
Open-ILS/src/sql/Pg/upgrade/0417.schema.acq.drop-lineitem-item-count.sql [new file with mode: 0644]
Open-ILS/src/sql/Pg/upgrade/0418.function.action.fix-purge-circ.sql [new file with mode: 0644]
Open-ILS/src/sql/Pg/upgrade/0419.schema.hold_record_view.sql [new file with mode: 0644]
Open-ILS/src/sql/Pg/upgrade/0420.schema.premunge_dates.sql [new file with mode: 0644]
Open-ILS/src/sql/Pg/upgrade/0421.schema.embiggen-ints.sql [new file with mode: 0644]
Open-ILS/src/sql/Pg/upgrade/0422.schema.acq.lineitem-history-bigint.sql [new file with mode: 0644]
Open-ILS/src/sql/Pg/upgrade/0423.schema.support-null-function-in-xpath_table.sql [new file with mode: 0644]
Open-ILS/src/sql/Pg/upgrade/0424.schema.circ_due_date_trigger.sql [new file with mode: 0644]
Open-ILS/src/sql/Pg/upgrade/0425.schema.perm-grp-tree-hold-priority.sql [new file with mode: 0644]
Open-ILS/src/sql/Pg/upgrade/0426.data.perm-list.misc-acq.sql [new file with mode: 0644]
Open-ILS/src/sql/Pg/upgrade/0427.schema.hold_matrix_root_ou.sql [new file with mode: 0644]
Open-ILS/src/sql/Pg/upgrade/0428.schema.hold_retarget.sql [new file with mode: 0644]
Open-ILS/src/sql/Pg/upgrade/0429.noop-because-miker-skipped-it.sql [new file with mode: 0644]
Open-ILS/src/sql/Pg/upgrade/0430.schema.strict_ou_test.sql [new file with mode: 0644]
Open-ILS/src/sql/Pg/upgrade/0431.schema.hold_retarget.sql [new file with mode: 0644]
Open-ILS/src/sql/Pg/upgrade/0432.schema.config_hard_due_date.sql [new file with mode: 0644]
Open-ILS/src/sql/Pg/upgrade/0433.edi_orders_template.sql [new file with mode: 0644]
Open-ILS/src/sql/Pg/upgrade/0434.data.merge_template_container_type.sql [new file with mode: 0644]
Open-ILS/src/sql/Pg/upgrade/0435.schema.template-add-field.sql [new file with mode: 0644]
Open-ILS/src/sql/Pg/upgrade/0436.schema.multiple-rules.sql [new file with mode: 0644]
Open-ILS/src/sql/Pg/upgrade/0437.schema.bytea-index-label_sortkey.sql [new file with mode: 0644]
Open-ILS/src/sql/Pg/upgrade/0438.schema.bytea-index-label.sql [new file with mode: 0644]
Open-ILS/src/sql/Pg/upgrade/0439.schema.function-bytea-index-label.sql [new file with mode: 0644]
Open-ILS/src/support-scripts/action_trigger_runner.pl
Open-ILS/src/support-scripts/edi_fetcher.pl
Open-ILS/src/support-scripts/edi_pusher.pl
Open-ILS/src/support-scripts/marc_export
Open-ILS/src/templates/password-reset/strings.fr-CA [new file with mode: 0644]
Open-ILS/web/conify/global/permission/grp_tree.html
Open-ILS/web/js/dojo/MARC/Field.js
Open-ILS/web/js/dojo/MARC/Record.js
Open-ILS/web/js/dojo/openils/BibTemplate.js
Open-ILS/web/js/dojo/openils/PermaCrud.js
Open-ILS/web/js/dojo/openils/XUL.js
Open-ILS/web/js/dojo/openils/actor/nls/register.js
Open-ILS/web/js/dojo/openils/widget/FacetSidebar.js
Open-ILS/web/js/dojo/openils/widget/ProgressDialog.js
Open-ILS/web/js/ui/base.js
Open-ILS/web/js/ui/default/actor/user/register.js
Open-ILS/web/js/ui/default/cat/authority/list.js
Open-ILS/web/js/ui/default/circ/selfcheck/selfcheck.js
Open-ILS/web/js/ui/default/conify/global/action_trigger/event_definition.js
Open-ILS/web/js/ui/default/conify/global/asset/copy_location_order.js
Open-ILS/web/js/ui/default/vandelay/vandelay.js
Open-ILS/web/opac/common/js/opac_utils.js
Open-ILS/web/opac/extras/circ/alt_holds_print.html [new file with mode: 0644]
Open-ILS/web/opac/extras/circ/alt_holds_print.js [new file with mode: 0644]
Open-ILS/web/opac/locale/en-US/conify.dtd
Open-ILS/web/opac/locale/en-US/lang.dtd
Open-ILS/web/opac/skin/default/xml/rdetail/rdetail_summary.xml
Open-ILS/web/templates/base.tt2
Open-ILS/web/templates/default/acq/po/view.tt2
Open-ILS/web/templates/default/actor/user/register_table.tt2
Open-ILS/web/templates/default/circ/selfcheck/audio_config.tt2
Open-ILS/web/templates/default/conify/global/action_trigger/event_definition.tt2
Open-ILS/web/templates/default/conify/global/config/hold_matrix_matchpoint.tt2
Open-ILS/web/templates/default/vandelay/inc/item_attrs.tt2
Open-ILS/xul/staff_client/chrome/content/OpenILS/data.js
Open-ILS/xul/staff_client/chrome/content/OpenILS/global_util.js
Open-ILS/xul/staff_client/chrome/content/auth/session.js
Open-ILS/xul/staff_client/chrome/content/cat/opac.js
Open-ILS/xul/staff_client/chrome/content/circ/offline.xul
Open-ILS/xul/staff_client/chrome/content/main/constants.js
Open-ILS/xul/staff_client/chrome/content/main/main.js
Open-ILS/xul/staff_client/chrome/content/main/main.xul
Open-ILS/xul/staff_client/chrome/content/main/menu.js
Open-ILS/xul/staff_client/chrome/content/main/menu_frame.xul
Open-ILS/xul/staff_client/chrome/content/main/menu_frame_menus.xul
Open-ILS/xul/staff_client/chrome/content/main/menu_frame_overlay.xul
Open-ILS/xul/staff_client/chrome/content/util/deck.js
Open-ILS/xul/staff_client/chrome/content/util/list.js
Open-ILS/xul/staff_client/chrome/content/util/network.js
Open-ILS/xul/staff_client/chrome/content/util/print.js
Open-ILS/xul/staff_client/chrome/content/util/rbrowser.xul
Open-ILS/xul/staff_client/chrome/content/util/widgets.js
Open-ILS/xul/staff_client/chrome/content/util/window.js
Open-ILS/xul/staff_client/chrome/locale/en-US/offline.properties
Open-ILS/xul/staff_client/components/clh.js [new file with mode: 0644]
Open-ILS/xul/staff_client/external/dojo_template.xul
Open-ILS/xul/staff_client/external/template.xul
Open-ILS/xul/staff_client/server/admin/do_not_auto_attempt_print_setting.xul
Open-ILS/xul/staff_client/server/admin/font_settings.xul
Open-ILS/xul/staff_client/server/admin/index.xhtml
Open-ILS/xul/staff_client/server/admin/offline_manage_xacts.xul
Open-ILS/xul/staff_client/server/admin/transit_list.xul
Open-ILS/xul/staff_client/server/admin/work_log.xul
Open-ILS/xul/staff_client/server/cat/bib_brief.xul
Open-ILS/xul/staff_client/server/cat/bib_brief_overlay_vertical.xul [new file with mode: 0644]
Open-ILS/xul/staff_client/server/cat/bib_brief_vertical.xul [new file with mode: 0644]
Open-ILS/xul/staff_client/server/cat/bibs_abreast.js [new file with mode: 0644]
Open-ILS/xul/staff_client/server/cat/bibs_abreast.xul [new file with mode: 0644]
Open-ILS/xul/staff_client/server/cat/copy_browser.js
Open-ILS/xul/staff_client/server/cat/copy_browser.xul
Open-ILS/xul/staff_client/server/cat/copy_buckets_overlay.xul
Open-ILS/xul/staff_client/server/cat/copy_editor.xul
Open-ILS/xul/staff_client/server/cat/copy_notes.xul
Open-ILS/xul/staff_client/server/cat/marc_new.xul
Open-ILS/xul/staff_client/server/cat/marc_view.xul
Open-ILS/xul/staff_client/server/cat/marcedit.js
Open-ILS/xul/staff_client/server/cat/marcedit.xul
Open-ILS/xul/staff_client/server/cat/record_buckets.js
Open-ILS/xul/staff_client/server/cat/record_buckets_overlay.xul
Open-ILS/xul/staff_client/server/cat/spine_labels.js
Open-ILS/xul/staff_client/server/cat/spine_labels.xul
Open-ILS/xul/staff_client/server/cat/util.js
Open-ILS/xul/staff_client/server/cat/volume_buckets.xul
Open-ILS/xul/staff_client/server/cat/volume_copy_creator.xul
Open-ILS/xul/staff_client/server/cat/z3950.js
Open-ILS/xul/staff_client/server/cat/z3950.xul
Open-ILS/xul/staff_client/server/circ/alternate_copy_summary.js
Open-ILS/xul/staff_client/server/circ/alternate_copy_summary.xul
Open-ILS/xul/staff_client/server/circ/circ_brief.xul
Open-ILS/xul/staff_client/server/circ/circ_summary.xul
Open-ILS/xul/staff_client/server/circ/copy_details.xul
Open-ILS/xul/staff_client/server/circ/missing_pieces.xul
Open-ILS/xul/staff_client/server/circ/pre_cat_fields.xul
Open-ILS/xul/staff_client/server/circ/print_list_template_editor.xul
Open-ILS/xul/staff_client/server/circ/util.js
Open-ILS/xul/staff_client/server/locale/en-US/cat.properties
Open-ILS/xul/staff_client/server/locale/en-US/circ.properties
Open-ILS/xul/staff_client/server/main/data.xul
Open-ILS/xul/staff_client/server/main/simple_auth.xul
Open-ILS/xul/staff_client/server/main/verify_credentials.xul
Open-ILS/xul/staff_client/server/patron/barcode_entry.xul
Open-ILS/xul/staff_client/server/patron/bill2.xul
Open-ILS/xul/staff_client/server/patron/bill_cc_info.xul
Open-ILS/xul/staff_client/server/patron/bill_check_info.xul
Open-ILS/xul/staff_client/server/patron/bill_details.xul
Open-ILS/xul/staff_client/server/patron/bill_wizard.xul
Open-ILS/xul/staff_client/server/patron/display_horiz_overlay.xul
Open-ILS/xul/staff_client/server/patron/display_overlay.xul
Open-ILS/xul/staff_client/server/patron/edit_standing_penalty.xul
Open-ILS/xul/staff_client/server/patron/hold_cancel.xul
Open-ILS/xul/staff_client/server/patron/hold_details.js
Open-ILS/xul/staff_client/server/patron/hold_details.xul
Open-ILS/xul/staff_client/server/patron/holds.js
Open-ILS/xul/staff_client/server/patron/holds_overlay.xul
Open-ILS/xul/staff_client/server/patron/info_group.js
Open-ILS/xul/staff_client/server/patron/info_group.xul
Open-ILS/xul/staff_client/server/patron/info_notes.xul
Open-ILS/xul/staff_client/server/patron/info_stat_cats.xul
Open-ILS/xul/staff_client/server/patron/info_surveys.xul
Open-ILS/xul/staff_client/server/patron/items.xul
Open-ILS/xul/staff_client/server/patron/items_overlay.xul
Open-ILS/xul/staff_client/server/patron/new_standing_penalty.xul
Open-ILS/xul/staff_client/server/patron/search_form.xul
Open-ILS/xul/staff_client/server/patron/search_form_horiz.xul
Open-ILS/xul/staff_client/server/patron/search_result.xul
Open-ILS/xul/staff_client/server/patron/standing_penalties.xul
Open-ILS/xul/staff_client/server/patron/summary.xul
Open-ILS/xul/staff_client/server/patron/ue.js
Open-ILS/xul/staff_client/server/patron/ue_config.js
Open-ILS/xul/staff_client/server/patron/user_buckets.xul
Open-ILS/xul/staff_client/server/serial/manage_items.js
Open-ILS/xul/staff_client/server/serial/manage_items.xul
Open-ILS/xul/staff_client/server/serial/manage_subs.xul
Open-ILS/xul/staff_client/server/serial/notes.xul
Open-ILS/xul/staff_client/server/serial/sdist_editor.xul
Open-ILS/xul/staff_client/server/serial/serctrl_main.xul
Open-ILS/xul/staff_client/server/serial/siss_editor.xul
Open-ILS/xul/staff_client/server/serial/sitem_editor.xul
Open-ILS/xul/staff_client/server/serial/ssub_editor.xul
Open-ILS/xul/staff_client/server/skin/custom.js [new file with mode: 0644]
Open-ILS/xul/staff_client/server/skin/patron_display.css
build/tools/update.sh
build/tools/update_db.sh

index 77cd6d0..f44ae92 100644 (file)
@@ -99,26 +99,6 @@ ExpiresByType application/xhtml+xml A64800
 ExpiresByType application/x-javascript A64800
 ExpiresByType text/css A3000
 
-
-
-
-# ----------------------------------------------------------------------------------
-# Set up our main virtual host
-# ----------------------------------------------------------------------------------
-NameVirtualHost *:80
-<VirtualHost *:80>
-       ServerName localhost:80
-       ServerAlias 127.0.0.1:80
-       DocumentRoot /openils/var/web/
-       DirectoryIndex index.xml index.html index.xhtml
-    # - absorb the shared virtual host settings
-    Include eg_vhost.conf
-</VirtualHost>
-
-
-
-
-
 # ----------------------------------------------------------------------------------
 # Set up our SSL virtual host
 # ----------------------------------------------------------------------------------
@@ -147,4 +127,18 @@ NameVirtualHost *:443
 
 </VirtualHost>
 
+# ----------------------------------------------------------------------------------
+# Set up our main virtual host
+# Port 80 comes after 443 to avoid "unknown protocol speaking not SSL to HTTPS port!?" 
+# errors, per http://wiki.apache.org/httpd/InternalDummyConnection
+# ----------------------------------------------------------------------------------
+NameVirtualHost *:80
+<VirtualHost *:80>
+       ServerName localhost:80
+       ServerAlias 127.0.0.1:80
+       DocumentRoot /openils/var/web/
+       DirectoryIndex index.xml index.html index.xhtml
+    # - absorb the shared virtual host settings
+    Include eg_vhost.conf
+</VirtualHost>
 
index 1f93a21..4a2266f 100644 (file)
@@ -395,6 +395,28 @@ RewriteRule - - [E=locale:en-US] [L]
     allow from all
 </Location>
 
+<Location /opac/extras/merge_template>
+    SetHandler perl-script
+    PerlSetVar OILSProxyTitle "Batch Update Login"
+    PerlSetVar OILSProxyDescription "Please log in to update records in batch"
+    PerlSetVar OILSProxyPermissions "STAFF_LOGIN"
+    PerlHandler OpenILS::WWW::Proxy OpenILS::WWW::TemplateBatchBibUpdate
+    PerlSendHeader On
+    Options +ExecCGI
+    allow from all
+</Location>
+
+<Location /opac/extras/circ>
+    SetHandler perl-script
+    PerlSetVar OILSProxyTitle "Circ Extras Login"
+    PerlSetVar OILSProxyDescription "Please log in with an authorized staff account to export records"
+    PerlSetVar OILSProxyPermissions "STAFF_LOGIN"
+    PerlHandler OpenILS::WWW::Proxy
+    Options +ExecCGI
+    PerlSendHeader On
+    allow from all
+</Location>
+
 # ----------------------------------------------------------------------------------
 # Reporting output lives here
 # ----------------------------------------------------------------------------------
index 7c68d04..25206b3 100644 (file)
@@ -999,6 +999,7 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
                <fields oils_persist:primary="id" oils_persist:sequence="config.hold_matrix_matchpoint_id_seq">
                        <field reporter:label="Matchpoint ID" name="id" reporter:datatype="id"/>
                        <field reporter:label="Active?" name="active" reporter:datatype="bool"/>
+                       <field reporter:label="Strict OU matches?" name="strict_ou_match" reporter:datatype="bool"/>
                        <field reporter:label="User Home Library" name="user_home_ou" reporter:datatype="org_unit"/>
                        <field reporter:label="Request Library" name="request_ou" reporter:datatype="org_unit"/>
                        <field reporter:label="Pickup Library" name="pickup_ou" reporter:datatype="org_unit"/>
@@ -1868,6 +1869,7 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
                        <field name="name" reporter:datatype="text"/>
                        <field name="normal" reporter:datatype="interval"/>
                        <field name="shrt" reporter:datatype="interval"/>
+                       <field name="date_ceiling" reporter:datatype="timestamp"/>
                </fields>
                <links>
                </links>
@@ -1880,6 +1882,21 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
             </actions>
         </permacrud>
        </class>
+
+       <class id="chdd" controller="open-ils.cstore open-ils.pcrud" oils_obj:fieldmapper="config::hard_due_date" oils_persist:tablename="config.hard_due_date" reporter:label="Hard Due Date">
+               <fields oils_persist:primary="id" oils_persist:sequence="config.hard_due_date_id_seq">
+                       <field reporter:label="ID" name="id" reporter:datatype="id"/>
+                       <field reporter:label="Duration Rule" name="duration_rule" reporter:datatype="link"/>
+                       <field reporter:label="Ceiling Date" name="ceiling_date" reporter:datatype="timestamp"/>
+                       <field reporter:label="Active Date" name="active_date" reporter:datatype="timestamp"/>
+               </fields>
+               <links>
+                       <link field="duration_rule" reltype="has_a" key="id" map="" class="crcd"/>
+               </links>
+               <permacrud xmlns="http://open-ils.org/spec/opensrf/IDL/permacrud/v1">
+               </permacrud>
+       </class>
+
        <class id="mobts" controller="open-ils.cstore" oils_obj:fieldmapper="money::open_billable_transaction_summary" oils_persist:tablename="money.open_billable_xact_summary" reporter:label="Open Billable Transaction Summary">
                <fields oils_persist:primary="id" oils_persist:sequence="">
                        <field name="balance_owed" reporter:datatype="money"/>
@@ -4515,6 +4532,7 @@ SELECT  usr,
                        <field reporter:label="User Expiration Interval" name="perm_interval" reporter:datatype="interval"/>
                        <field reporter:label="Required Permission" name="application_perm" reporter:datatype="text"/>
                        <field reporter:label="Is User Group" name="usergroup" reporter:datatype="bool"/>
+                       <field reporter:label="Hold Priority" name="hold_priority" reporter:datatype="int"/>
                </fields>
                <links>
                        <link field="parent" reltype="has_a" key="id" map="" class="pgt"/>
@@ -6523,7 +6541,7 @@ SELECT  usr,
                        <field name="owner" reporter:datatype="link"/>
                        <field name="create_time" reporter:datatype="timestamp"/>
                        <field name="template" reporter:datatype="link"/>
-                       <field name="data" reporter:datatype="link"/>
+                       <field name="data" reporter:datatype="text"/>
                        <field name="folder" reporter:datatype="link"/>
                        <field name="recur" reporter:datatype="bool"/>
                        <field name="recurrence" reporter:datatype="interval"/>
index 466c304..3111626 100644 (file)
                                        -->
                                </options>
 
+                <checkin_override>
+                    <event>COPY_ALERT_MESSAGE</event>
+                    <event>COPY_BAD_STATUS</event>
+                    <event>COPY_STATUS_MISSING</event>
+                    <!--
+                    <event>COPY_STATUS_LOST</event>
+                    -->
+                </checkin_override>
+
                 <!-- If uncommented, overrides the legacy_script_support value in opensrf.xml for SIP. -->
                 <!--
                 <legacy_script_support>false</legacy_script_support>
index 8f8184c..acbd585 100644 (file)
@@ -326,14 +326,14 @@ vim:et:ts=4:sw=4:
             <!-- memcache servers -->
             <global>
                 <servers>
-                    <server>localhost:11211</server>
+                    <server>127.0.0.1:11211</server>
                 </servers>
                 <max_cache_time>86400</max_cache_time>
             </global>
             <anon>
                 <!-- anonymous cache.  currently, primarily used for web session caching -->
                 <servers>
-                    <server>localhost:11211</server>
+                    <server>127.0.0.1:11211</server>
                 </servers>
                 <max_cache_time>1800</max_cache_time>
                 <!-- maximum size of a single cache entry / default = 100k-->
@@ -590,6 +590,15 @@ vim:et:ts=4:sw=4:
                     <min_spare_children>1</min_spare_children>
                     <max_spare_children>5</max_spare_children>
                 </unix_config>
+                <app_settings>
+                    <!-- number of parallel open-ils.trigger processes to use for collection and reaction -->
+                    <!--
+                    <parallel>
+                        <collect>3</collect>
+                        <react>3</react>
+                    </parallel>
+                    -->
+                </app_settings>
             </open-ils.trigger>
 
             <opensrf.math>
index c50725d..255f48c 100755 (executable)
@@ -3,7 +3,8 @@
 #    This CGI script might be useful for providing an easy way for EZproxy to authenticate
 #    users against an Evergreen instance.
 #    
-#    For example, if you modify your eg_vhost.conf by adding this:
+#    For example, if you modify your eg.conf by adding this:
+#    Alias "/cgi-bin/ezproxy/" "/openils/var/cgi-bin/ezproxy/"
 #    <Directory "/openils/var/cgi-bin/ezproxy">
 #        AddHandler cgi-script .pl
 #        AllowOverride None
@@ -29,47 +30,63 @@ use Digest::MD5 qw(md5_hex);
 use OpenSRF::EX qw(:try);
 use OpenSRF::System;
 
-
 my $bootstrap = '/openils/conf/opensrf_core.xml';
 my $cgi = new CGI;
 my $u = $cgi->param('user');
+my $usrname = $cgi->param('usrname');
+my $barcode = $cgi->param('barcode');
 my $p = $cgi->param('passwd');
 
 print $cgi->header(-type=>'text/html', -expires=>'-1d');
 
 OpenSRF::System->bootstrap_client( config_file => $bootstrap );
 
-if (!$u || !$p) {
-       print "+INCOMPLETE";
+if (!($u || $usrname || $barcode) || !$p) {
+       print '+INCOMPLETE';
 } else {
-       my $nametype = 'username';
-       $nametype = 'barcode' if ($u =~ /^\d+$/o);
+       my $nametype;
+    if ($usrname) {
+        $u = $usrname;
+           $nametype = 'username';
+    } elsif ($barcode) {
+        $u = $barcode;
+        $nametype = 'barcode';
+    } else {
+           $nametype = 'username';
+        my $regex_response = OpenSRF::AppSession
+            ->create('open-ils.actor')
+            ->request('open-ils.actor.ou_setting.ancestor_default', 1, 'opac.barcode_regex')
+            ->gather(1);
+        if ($regex_response) {
+            my $regexp = $regex_response->{'value'};
+            $nametype = 'barcode' if ($u =~ qr/$regexp/);
+        }
+    }
        my $seed = OpenSRF::AppSession
-               ->create("open-ils.auth")
+               ->create('open-ils.auth')
                ->request( 'open-ils.auth.authenticate.init', $u )
                ->gather(1);
        if ($seed) {
                my $response = OpenSRF::AppSession
-                       ->create("open-ils.auth")
-                       ->request( 'open-ils.auth.authenticate.complete', { $nametype => $u, password => md5_hex($seed . md5_hex($p)), type => 'temp' })
+                       ->create('open-ils.auth')
+                       ->request( 'open-ils.auth.authenticate.complete', { $nametype => $u, password => md5_hex($seed . md5_hex($p)), type => 'opac' })
                        ->gather(1);
                if ($response->{payload}->{authtoken}) {
                        my $user = OpenSRF::AppSession
-                               ->create("open-ils.auth")
-                               ->request( "open-ils.auth.session.retrieve", $response->{payload}->{authtoken} )
+                               ->create('open-ils.auth')
+                               ->request( 'open-ils.auth.session.retrieve', $response->{payload}->{authtoken} )
                                ->gather(1);
                        if (ref($user) eq 'HASH' && $user->{ilsevent} == 1001) {
-                               print "+NO";
+                               print '+NO';
                        } else {
-                               print "+VALID";
+                               print '+VALID';
                        }
                } else {
-                       print "+NO";
+                       print '+NO';
                }
        } else {
-               print "+BACKEND_ERROR";
+               print '+BACKEND_ERROR';
        }
-
 }
 
 1;
index 7a5cea8..76b9997 100644 (file)
@@ -90,8 +90,11 @@ static char* searchINPredicate ( const char*, osrfHash*,
                                                                 jsonObject*, const char*, osrfMethodContext* );
 static char* searchPredicate ( const ClassInfo*, osrfHash*, jsonObject*, osrfMethodContext* );
 static char* searchJOIN ( const jsonObject*, const ClassInfo* left_info );
-static char* searchWHERE ( const jsonObject*, const ClassInfo*, int, osrfMethodContext* );
-static char* buildSELECT ( jsonObject*, jsonObject*, osrfHash*, osrfMethodContext* );
+static char* searchWHERE ( const jsonObject* search_hash, const ClassInfo*, int, osrfMethodContext* );
+static char* buildSELECT( const jsonObject*, jsonObject* rest_of_query,
+       osrfHash* meta, osrfMethodContext* ctx );
+static char* buildOrderByFromArray( osrfMethodContext* ctx, const jsonObject* order_array );
+
 char* buildQuery( osrfMethodContext* ctx, jsonObject* query, int flags );
 
 char* SELECT ( osrfMethodContext*, jsonObject*, const jsonObject*, const jsonObject*,
@@ -4277,7 +4280,6 @@ char* SELECT (
                jsonIteratorFree( selclass_itr );
        }
 
-
        char* col_list = buffer_release( select_buf );
 
        // Make sure the SELECT list isn't empty.  This can happen, for example,
@@ -4392,172 +4394,23 @@ char* SELECT (
                        }
                }
 
-               growing_buffer* order_buf = NULL;  // to collect ORDER BY list
-
                // Build an ORDER BY clause, if there is one
                if( NULL == order_hash )
                        ;  // No ORDER BY? do nothing
                else if( JSON_ARRAY == order_hash->type ) {
-                       // Array of field specifications, each specification being a
-                       // hash to define the class, field, and other details
-                       int order_idx = 0;
-                       jsonObject* order_spec;
-                       while( (order_spec = jsonObjectGetIndex( order_hash, order_idx++ ) ) ) {
-
-                               if( JSON_HASH != order_spec->type ) {
-                                       osrfLogError( OSRF_LOG_MARK,
-                                                "%s: Malformed field specification in ORDER BY clause; expected JSON_HASH, found %s",
-                                               modulename, json_type( order_spec->type ) );
-                                       if( ctx )
-                                               osrfAppSessionStatus(
-                                                        ctx->session,
-                                                       OSRF_STATUS_INTERNALSERVERERROR,
-                                                       "osrfMethodException",
-                                                       ctx->request,
-                                                       "Malformed ORDER BY clause -- see error log for more details"
-                                               );
-                                       buffer_free( order_buf );
-                                       free( having_buf );
-                                       buffer_free( group_buf );
-                                       buffer_free( sql_buf );
-                                       if( defaultselhash )
-                                               jsonObjectFree( defaultselhash );
-                                       return NULL;
-                               }
-
-                               const char* class_alias =
-                                               jsonObjectGetString( jsonObjectGetKeyConst( order_spec, "class" ) );
-                               const char* field =
-                                               jsonObjectGetString( jsonObjectGetKeyConst( order_spec, "field" ) );
-
-                               if( order_buf )
-                                       OSRF_BUFFER_ADD( order_buf, ", " );
-                               else
-                                       order_buf = buffer_init( 128 );
-
-                               if( !field || !class_alias ) {
-                                       osrfLogError( OSRF_LOG_MARK,
-                                               "%s: Missing class or field name in field specification "
-                                               "of ORDER BY clause",
-                                               modulename );
-                                       if( ctx )
-                                               osrfAppSessionStatus(
-                                                       ctx->session,
-                                                       OSRF_STATUS_INTERNALSERVERERROR,
-                                                       "osrfMethodException",
-                                                       ctx->request,
-                                                       "Malformed ORDER BY clause -- see error log for more details"
-                                               );
-                                       buffer_free( order_buf );
-                                       free( having_buf );
-                                       buffer_free( group_buf );
-                                       buffer_free( sql_buf );
-                                       if( defaultselhash )
-                                               jsonObjectFree( defaultselhash );
-                                       return NULL;
-                               }
-
-                               ClassInfo* order_class_info = search_alias( class_alias );
-                               if( ! order_class_info ) {
-                                       osrfLogError( OSRF_LOG_MARK, "%s: ORDER BY clause references class \"%s\" "
-                                                       "not in FROM clause", modulename, class_alias );
-                                       if( ctx )
-                                               osrfAppSessionStatus(
-                                                       ctx->session,
-                                                       OSRF_STATUS_INTERNALSERVERERROR,
-                                                       "osrfMethodException",
-                                                       ctx->request,
-                                                       "Invalid class referenced in ORDER BY clause -- "
-                                                       "see error log for more details"
-                                               );
-                                       free( having_buf );
-                                       buffer_free( group_buf );
-                                       buffer_free( sql_buf );
-                                       if( defaultselhash )
-                                               jsonObjectFree( defaultselhash );
-                                       return NULL;
-                               }
-
-                               osrfHash* field_def = osrfHashGet( order_class_info->fields, field );
-                               if( !field_def ) {
-                                       osrfLogError( OSRF_LOG_MARK,
-                                               "%s: Invalid field \"%s\".%s referenced in ORDER BY clause",
-                                               modulename, class_alias, field );
-                                       if( ctx )
-                                               osrfAppSessionStatus(
-                                                       ctx->session,
-                                                       OSRF_STATUS_INTERNALSERVERERROR,
-                                                       "osrfMethodException",
-                                                       ctx->request,
-                                                       "Invalid field referenced in ORDER BY clause -- "
-                                                       "see error log for more details"
-                                               );
-                                       free( having_buf );
-                                       buffer_free( group_buf );
-                                       buffer_free( sql_buf );
-                                       if( defaultselhash )
-                                               jsonObjectFree( defaultselhash );
-                                       return NULL;
-                               } else if( str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
-                                       osrfLogError( OSRF_LOG_MARK, "%s: Virtual field \"%s\" in ORDER BY clause",
-                                                                modulename, field );
-                                       if( ctx )
-                                               osrfAppSessionStatus(
-                                                       ctx->session,
-                                                       OSRF_STATUS_INTERNALSERVERERROR,
-                                                       "osrfMethodException",
-                                                       ctx->request,
-                                                       "Virtual field in ORDER BY clause -- see error log for more details"
-                                               );
-                                       buffer_free( order_buf );
-                                       free( having_buf );
-                                       buffer_free( group_buf );
-                                       buffer_free( sql_buf );
-                                       if( defaultselhash )
-                                               jsonObjectFree( defaultselhash );
-                                       return NULL;
-                               }
-
-                               if( jsonObjectGetKeyConst( order_spec, "transform" ) ) {
-                                       char* transform_str = searchFieldTransform(
-                                                       class_alias, field_def, order_spec );
-                                       if( ! transform_str ) {
-                                               if( ctx )
-                                                       osrfAppSessionStatus(
-                                                               ctx->session,
-                                                               OSRF_STATUS_INTERNALSERVERERROR,
-                                                               "osrfMethodException",
-                                                               ctx->request,
-                                                               "Severe query error in ORDER BY clause -- "
-                                                               "see error log for more details"
-                                                       );
-                                               buffer_free( order_buf );
-                                               free( having_buf );
-                                               buffer_free( group_buf );
-                                               buffer_free( sql_buf );
-                                               if( defaultselhash )
-                                                       jsonObjectFree( defaultselhash );
-                                               return NULL;
-                                       }
-
-                                       OSRF_BUFFER_ADD( order_buf, transform_str );
-                                       free( transform_str );
-                               }
-                               else
-                                       buffer_fadd( order_buf, "\"%s\".%s", class_alias, field );
-
-                               const char* direction =
-                                               jsonObjectGetString( jsonObjectGetKeyConst( order_spec, "direction" ) );
-                               if( direction ) {
-                                       if( direction[ 0 ] || 'D' == direction[ 0 ] )
-                                               OSRF_BUFFER_ADD( order_buf, " DESC" );
-                                       else
-                                               OSRF_BUFFER_ADD( order_buf, " ASC" );
-                               }
+                       order_by_list = buildOrderByFromArray( ctx, order_hash );
+                       if( !order_by_list ) {
+                               free( having_buf );
+                               buffer_free( group_buf );
+                               buffer_free( sql_buf );
+                               if( defaultselhash )
+                                       jsonObjectFree( defaultselhash );
+                               return NULL;
                        }
                } else if( JSON_HASH == order_hash->type ) {
                        // This hash is keyed on class alias.  Each class has either
                        // an array of field names or a hash keyed on field name.
+                       growing_buffer* order_buf = NULL;  // to collect ORDER BY list
                        jsonIterator* class_itr = jsonNewIterator( order_hash );
                        while( (snode = jsonIteratorNext( class_itr )) ) {
 
@@ -4573,7 +4426,7 @@ char* SELECT (
                                                        "osrfMethodException",
                                                        ctx->request,
                                                        "Invalid class referenced in ORDER BY clause -- "
-                                                       "see error log for more details"
+                                                               "see error log for more details"
                                                );
                                        jsonIteratorFree( class_itr );
                                        buffer_free( order_buf );
@@ -4820,6 +4673,8 @@ char* SELECT (
                                }
                        } // end while
                        jsonIteratorFree( class_itr );
+                       if( order_buf )
+                               order_by_list = buffer_release( order_buf );
                } else {
                        osrfLogError( OSRF_LOG_MARK,
                                "%s: Malformed ORDER BY clause; expected JSON_HASH or JSON_ARRAY, found %s",
@@ -4832,7 +4687,6 @@ char* SELECT (
                                        ctx->request,
                                        "Malformed ORDER BY clause -- see error log for more details"
                                );
-                       buffer_free( order_buf );
                        free( having_buf );
                        buffer_free( group_buf );
                        buffer_free( sql_buf );
@@ -4840,12 +4694,8 @@ char* SELECT (
                                jsonObjectFree( defaultselhash );
                        return NULL;
                }
-
-               if( order_buf )
-                       order_by_list = buffer_release( order_buf );
        }
 
-
        string = buffer_release( group_buf );
 
        if( *string && ( aggregate_found || (flags & SELECT_DISTINCT) ) ) {
@@ -4891,26 +4741,199 @@ char* SELECT (
 
 } // end of SELECT()
 
-static char* buildSELECT ( jsonObject* search_hash, jsonObject* order_hash, osrfHash* meta, osrfMethodContext* ctx ) {
+/**
+       @brief Build a list of ORDER BY expressions.
+       @param ctx Pointer to the method context.
+       @param order_array Pointer to a JSON_ARRAY of field specifications.
+       @return Pointer to a string containing a comma-separated list of ORDER BY expressions.
+       Each expression may be either a column reference or a function call whose first parameter
+       is a column reference.
+
+       Each entry in @a order_array must be a JSON_HASH with values for "class" and "field".
+       It may optionally include entries for "direction" and/or "transform".
+
+       The calling code is responsible for freeing the returned string.
+*/
+static char* buildOrderByFromArray( osrfMethodContext* ctx, const jsonObject* order_array ) {
+       if( ! order_array ) {
+               osrfLogError( OSRF_LOG_MARK, "%s: Logic error: NULL pointer for ORDER BY clause",
+                       modulename );
+               if( ctx )
+                       osrfAppSessionStatus(
+                               ctx->session,
+                               OSRF_STATUS_INTERNALSERVERERROR,
+                               "osrfMethodException",
+                               ctx->request,
+                               "Logic error: ORDER BY clause expected, not found; "
+                                       "see error log for more details"
+                       );
+               return NULL;
+       } else if( order_array->type != JSON_ARRAY ) {
+               osrfLogError( OSRF_LOG_MARK,
+                       "%s: Logic error: Expected JSON_ARRAY for ORDER BY clause, not found", modulename );
+               if( ctx )
+                       osrfAppSessionStatus(
+                       ctx->session,
+                       OSRF_STATUS_INTERNALSERVERERROR,
+                       "osrfMethodException",
+                       ctx->request,
+                       "Logic error: Unexpected format for ORDER BY clause; see error log for more details" );
+               return NULL;
+       }
+
+       growing_buffer* order_buf = buffer_init( 128 );
+       int first = 1;        // boolean
+       int order_idx = 0;
+       jsonObject* order_spec;
+       while( (order_spec = jsonObjectGetIndex( order_array, order_idx++ ))) {
+
+               if( JSON_HASH != order_spec->type ) {
+                       osrfLogError( OSRF_LOG_MARK,
+                               "%s: Malformed field specification in ORDER BY clause; "
+                               "expected JSON_HASH, found %s",
+                               modulename, json_type( order_spec->type ) );
+                       if( ctx )
+                               osrfAppSessionStatus(
+                                        ctx->session,
+                                       OSRF_STATUS_INTERNALSERVERERROR,
+                                       "osrfMethodException",
+                                       ctx->request,
+                                       "Malformed ORDER BY clause -- see error log for more details"
+                               );
+                       buffer_free( order_buf );
+                       return NULL;
+               }
+
+               const char* class_alias =
+                       jsonObjectGetString( jsonObjectGetKeyConst( order_spec, "class" ));
+               const char* field =
+                       jsonObjectGetString( jsonObjectGetKeyConst( order_spec, "field" ));
+
+               if( !field || !class_alias ) {
+                       osrfLogError( OSRF_LOG_MARK,
+                               "%s: Missing class or field name in field specification of ORDER BY clause",
+                               modulename );
+                       if( ctx )
+                               osrfAppSessionStatus(
+                                       ctx->session,
+                                       OSRF_STATUS_INTERNALSERVERERROR,
+                                       "osrfMethodException",
+                                       ctx->request,
+                                       "Malformed ORDER BY clause -- see error log for more details"
+                               );
+                       buffer_free( order_buf );
+                       return NULL;
+               }
+
+               const ClassInfo* order_class_info = search_alias( class_alias );
+               if( ! order_class_info ) {
+                       osrfLogInternal( OSRF_LOG_MARK, "%s: ORDER BY clause references class \"%s\" "
+                               "not in FROM clause, skipping it", modulename, class_alias );
+                       continue;
+               }
+
+               // Add a separating comma, except at the beginning
+               if( first )
+                       first = 0;
+               else
+                       OSRF_BUFFER_ADD( order_buf, ", " );
+
+               osrfHash* field_def = osrfHashGet( order_class_info->fields, field );
+               if( !field_def ) {
+                       osrfLogError( OSRF_LOG_MARK,
+                               "%s: Invalid field \"%s\".%s referenced in ORDER BY clause",
+                               modulename, class_alias, field );
+                       if( ctx )
+                               osrfAppSessionStatus(
+                                       ctx->session,
+                                       OSRF_STATUS_INTERNALSERVERERROR,
+                                       "osrfMethodException",
+                                       ctx->request,
+                                       "Invalid field referenced in ORDER BY clause -- "
+                                       "see error log for more details"
+                               );
+                       free( order_buf );
+                       return NULL;
+               } else if( str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
+                       osrfLogError( OSRF_LOG_MARK, "%s: Virtual field \"%s\" in ORDER BY clause",
+                               modulename, field );
+                       if( ctx )
+                               osrfAppSessionStatus(
+                                       ctx->session,
+                                       OSRF_STATUS_INTERNALSERVERERROR,
+                                       "osrfMethodException",
+                                       ctx->request,
+                                       "Virtual field in ORDER BY clause -- see error log for more details"
+                               );
+                       buffer_free( order_buf );
+                       return NULL;
+               }
+
+               if( jsonObjectGetKeyConst( order_spec, "transform" )) {
+                       char* transform_str = searchFieldTransform( class_alias, field_def, order_spec );
+                       if( ! transform_str ) {
+                               if( ctx )
+                                       osrfAppSessionStatus(
+                                               ctx->session,
+                                               OSRF_STATUS_INTERNALSERVERERROR,
+                                               "osrfMethodException",
+                                               ctx->request,
+                                               "Severe query error in ORDER BY clause -- "
+                                               "see error log for more details"
+                                       );
+                               buffer_free( order_buf );
+                               return NULL;
+                       }
+
+                       OSRF_BUFFER_ADD( order_buf, transform_str );
+                       free( transform_str );
+               }
+               else
+                       buffer_fadd( order_buf, "\"%s\".%s", class_alias, field );
+
+               const char* direction =
+                       jsonObjectGetString( jsonObjectGetKeyConst( order_spec, "direction" ) );
+               if( direction ) {
+                       if( direction[ 0 ] || 'D' == direction[ 0 ] )
+                               OSRF_BUFFER_ADD( order_buf, " DESC" );
+                       else
+                               OSRF_BUFFER_ADD( order_buf, " ASC" );
+               }
+       }
+
+       return buffer_release( order_buf );
+}
+
+/**
+       @brief Build a SELECT statement.
+       @param search_hash Pointer to a JSON_HASH or JSON_ARRAY encoding the WHERE clause.
+       @param rest_of_query Pointer to a JSON_HASH containing any other SQL clauses.
+       @param meta Pointer to the class metadata for the core class.
+       @param ctx Pointer to the method context.
+       @return Pointer to a character string containing the WHERE clause; or NULL upon error.
+
+       Within the rest_of_query hash, the meaningful keys are "join", "select", "no_i18n",
+       "order_by", "limit", and "offset".
+
+       The SELECT statements built here are distinct from those built for the json_query method.
+*/
+static char* buildSELECT ( const jsonObject* search_hash, jsonObject* rest_of_query,
+       osrfHash* meta, osrfMethodContext* ctx ) {
 
        const char* locale = osrf_message_get_last_locale();
 
        osrfHash* fields = osrfHashGet( meta, "fields" );
-       char* core_class = osrfHashGet( meta, "classname" );
+       const char* core_class = osrfHashGet( meta, "classname" );
 
-       const jsonObject* join_hash = jsonObjectGetKeyConst( order_hash, "join" );
+       const jsonObject* join_hash = jsonObjectGetKeyConst( rest_of_query, "join" );
 
-       jsonObject* node = NULL;
-       jsonObject* snode = NULL;
-       jsonObject* onode = NULL;
-       const jsonObject* _tmp = NULL;
        jsonObject* selhash = NULL;
        jsonObject* defaultselhash = NULL;
 
        growing_buffer* sql_buf = buffer_init( 128 );
        growing_buffer* select_buf = buffer_init( 128 );
 
-       if( !(selhash = jsonObjectGetKey( order_hash, "select" )) ) {
+       if( !(selhash = jsonObjectGetKey( rest_of_query, "select" )) ) {
                defaultselhash = jsonNewObjectType( JSON_HASH );
                selhash = defaultselhash;
        }
@@ -4932,20 +4955,24 @@ static char* buildSELECT ( jsonObject* search_hash, jsonObject* order_hash, osrf
                jsonObjectSetKey( selhash, core_class, field_list );
        }
 
+       // Build a list of columns for the SELECT clause
        int first = 1;
+       const jsonObject* snode = NULL;
        jsonIterator* class_itr = jsonNewIterator( selhash );
-       while( (snode = jsonIteratorNext( class_itr )) ) {
+       while( (snode = jsonIteratorNext( class_itr )) ) {        // For each class
 
+               // If the class isn't in the IDL, ignore it
                const char* cname = class_itr->key;
                osrfHash* idlClass = osrfHashGet( oilsIDL(), cname );
                if( !idlClass )
                        continue;
 
-               if( strcmp(core_class,class_itr->key )) {
+               // If the class isn't the core class, and isn't in the JOIN clause, ignore it
+               if( strcmp( core_class, class_itr->key )) {
                        if( !join_hash )
                                continue;
 
-                       jsonObject* found =  jsonObjectFindPath( join_hash, "//%s", class_itr->key );
+                       jsonObject* found = jsonObjectFindPath( join_hash, "//%s", class_itr->key );
                        if( !found->size ) {
                                jsonObjectFree( found );
                                continue;
@@ -4954,6 +4981,7 @@ static char* buildSELECT ( jsonObject* search_hash, jsonObject* order_hash, osrf
                        jsonObjectFree( found );
                }
 
+               const jsonObject* node = NULL;
                jsonIterator* select_itr = jsonNewIterator( snode );
                while( (node = jsonIteratorNext( select_itr )) ) {
                        const char* item_str = jsonObjectGetString( node );
@@ -4971,7 +4999,7 @@ static char* buildSELECT ( jsonObject* search_hash, jsonObject* order_hash, osrf
 
                        if( locale ) {
                                const char* i18n;
-                               const jsonObject* no_i18n_obj = jsonObjectGetKeyConst( order_hash, "no_i18n" );
+                               const jsonObject* no_i18n_obj = jsonObjectGetKeyConst( rest_of_query, "no_i18n" );
                                if( obj_is_true( no_i18n_obj ) )    // Suppress internationalization?
                                        i18n = NULL;
                                else
@@ -5019,9 +5047,13 @@ static char* buildSELECT ( jsonObject* search_hash, jsonObject* order_hash, osrf
                                ctx->request,
                                "Unable to build query frame for core class"
                        );
+               buffer_free( sql_buf );
+               if( defaultselhash )
+                       jsonObjectFree( defaultselhash );
                return NULL;
        }
 
+       // Add the JOIN clauses, if any
        if( join_hash ) {
                char* join_clause = searchJOIN( join_hash, &curr_query->core );
                OSRF_BUFFER_ADD_CHAR( sql_buf, ' ' );
@@ -5030,10 +5062,11 @@ static char* buildSELECT ( jsonObject* search_hash, jsonObject* order_hash, osrf
        }
 
        osrfLogDebug( OSRF_LOG_MARK, "%s pre-predicate SQL =  %s",
-                                 modulename, OSRF_BUFFER_C_STR( sql_buf ));
+               modulename, OSRF_BUFFER_C_STR( sql_buf ));
 
        OSRF_BUFFER_ADD( sql_buf, " WHERE " );
 
+       // Add the conditions in the WHERE clause
        char* pred = searchWHERE( search_hash, &curr_query->core, AND_OP_JOIN, ctx );
        if( !pred ) {
                osrfAppSessionStatus(
@@ -5053,112 +5086,166 @@ static char* buildSELECT ( jsonObject* search_hash, jsonObject* order_hash, osrf
                free( pred );
        }
 
-       if( order_hash ) {
-               char* string = NULL;
-               if( (_tmp = jsonObjectGetKeyConst( order_hash, "order_by" )) ){
-
-                       growing_buffer* order_buf = buffer_init( 128 );
-
-                       first = 1;
-                       jsonIterator* class_itr = jsonNewIterator( _tmp );
-                       while( (snode = jsonIteratorNext( class_itr )) ) {
-
-                               if( !jsonObjectGetKeyConst( selhash,class_itr->key ))
-                                       continue;
-
-                               if( snode->type == JSON_HASH ) {
+       // Add the ORDER BY, LIMIT, and/or OFFSET clauses, if present
+       if( rest_of_query ) {
+               const jsonObject* order_by = NULL;
+               if( ( order_by = jsonObjectGetKeyConst( rest_of_query, "order_by" )) ){
 
-                                       jsonIterator* order_itr = jsonNewIterator( snode );
-                                       while( (onode = jsonIteratorNext( order_itr )) ) {
+                       char* order_by_list = NULL;
 
-                                               osrfHash* field_def = oilsIDLFindPath( "/%s/fields/%s",
-                                                               class_itr->key, order_itr->key );
-                                               if( !field_def )
-                                                       continue;
+                       if( JSON_ARRAY == order_by->type ) {
+                               order_by_list = buildOrderByFromArray( ctx, order_by );
+                               if( !order_by_list ) {
+                                       buffer_free( sql_buf );
+                                       if( defaultselhash )
+                                               jsonObjectFree( defaultselhash );
+                                       clear_query_stack();
+                                       return NULL;
+                               }
+                       } else if( JSON_HASH == order_by->type ) {
+                               // We expect order_by to be a JSON_HASH keyed on class names.  Traverse it
+                               // and build a list of ORDER BY expressions.
+                               growing_buffer* order_buf = buffer_init( 128 );
+                               first = 1;
+                               jsonIterator* class_itr = jsonNewIterator( order_by );
+                               while( (snode = jsonIteratorNext( class_itr )) ) {  // For each class:
+
+                                       ClassInfo* order_class_info = search_alias( class_itr->key );
+                                       if( ! order_class_info )
+                                               continue;    // class not referenced by FROM clause?  Ignore it.
+
+                                       if( JSON_HASH == snode->type ) {
+
+                                               // If the data for the current class is a JSON_HASH, then it is
+                                               // keyed on field name.
+
+                                               const jsonObject* onode = NULL;
+                                               jsonIterator* order_itr = jsonNewIterator( snode );
+                                               while( (onode = jsonIteratorNext( order_itr )) ) {  // For each field
+
+                                                       osrfHash* field_def = osrfHashGet(
+                                                               order_class_info->fields, order_itr->key );
+                                                       if( !field_def )
+                                                               continue;    // Field not defined in IDL?  Ignore it.
+                                                       if( str_is_true( osrfHashGet( field_def, "virtual")))
+                                                               continue;    // Field is virtual?  Ignore it.
+
+                                                       char* field_str = NULL;
+                                                       char* direction = NULL;
+                                                       if( onode->type == JSON_HASH ) {
+                                                               if( jsonObjectGetKeyConst( onode, "transform" ) ) {
+                                                                       field_str = searchFieldTransform(
+                                                                               class_itr->key, field_def, onode );
+                                                                       if( ! field_str ) {
+                                                                               osrfAppSessionStatus(
+                                                                                       ctx->session,
+                                                                                       OSRF_STATUS_INTERNALSERVERERROR,
+                                                                                       "osrfMethodException",
+                                                                                       ctx->request,
+                                                                                       "Severe query error in ORDER BY clause -- "
+                                                                                       "see error log for more details"
+                                                                               );
+                                                                               jsonIteratorFree( order_itr );
+                                                                               jsonIteratorFree( class_itr );
+                                                                               buffer_free( order_buf );
+                                                                               buffer_free( sql_buf );
+                                                                               if( defaultselhash )
+                                                                                       jsonObjectFree( defaultselhash );
+                                                                               clear_query_stack();
+                                                                               return NULL;
+                                                                       }
+                                                               } else {
+                                                                       growing_buffer* field_buf = buffer_init( 16 );
+                                                                       buffer_fadd( field_buf, "\"%s\".%s",
+                                                                               class_itr->key, order_itr->key );
+                                                                       field_str = buffer_release( field_buf );
+                                                               }
 
-                                               char* direction = NULL;
-                                               if( onode->type == JSON_HASH ) {
-                                                       if( jsonObjectGetKeyConst( onode, "transform" ) ) {
-                                                               string = searchFieldTransform( class_itr->key, field_def, onode );
-                                                               if( ! string ) {
-                                                                       osrfAppSessionStatus(
-                                                                               ctx->session,
-                                                                               OSRF_STATUS_INTERNALSERVERERROR,
-                                                                               "osrfMethodException",
-                                                                               ctx->request,
-                                                                               "Severe query error in ORDER BY clause -- "
-                                                                               "see error log for more details"
-                                                                       );
-                                                                       jsonIteratorFree( order_itr );
-                                                                       jsonIteratorFree( class_itr );
-                                                                       buffer_free( order_buf );
-                                                                       buffer_free( sql_buf );
-                                                                       if( defaultselhash )
-                                                                               jsonObjectFree( defaultselhash );
-                                                                       clear_query_stack();
-                                                                       return NULL;
+                                                               if( ( order_by = jsonObjectGetKeyConst( onode, "direction" )) ) {
+                                                                       const char* dir = jsonObjectGetString( order_by );
+                                                                       if(!strncasecmp( dir, "d", 1 )) {
+                                                                               direction = " DESC";
+                                                                       }
                                                                }
                                                        } else {
-                                                               growing_buffer* field_buf = buffer_init( 16 );
-                                                               buffer_fadd( field_buf, "\"%s\".%s",
-                                                                       class_itr->key, order_itr->key );
-                                                               string = buffer_release( field_buf );
-                                                       }
-
-                                                       if( (_tmp = jsonObjectGetKeyConst( onode, "direction" )) ) {
-                                                               const char* dir = jsonObjectGetString( _tmp );
-                                                               if(!strncasecmp( dir, "d", 1 )) {
+                                                               field_str = strdup( order_itr->key );
+                                                               const char* dir = jsonObjectGetString( onode );
+                                                               if( !strncasecmp( dir, "d", 1 )) {
                                                                        direction = " DESC";
+                                                               } else {
+                                                                       direction = " ASC";
                                                                }
                                                        }
-                                               } else {
-                                                       string = strdup( order_itr->key );
-                                                       const char* dir = jsonObjectGetString( onode );
-                                                       if( !strncasecmp( dir, "d", 1 )) {
-                                                               direction = " DESC";
+
+                                                       if( first ) {
+                                                               first = 0;
                                                        } else {
-                                                               direction = " ASC";
+                                                               buffer_add( order_buf, ", " );
                                                        }
-                                               }
 
-                                               if( first ) {
-                                                       first = 0;
-                                               } else {
-                                                       buffer_add( order_buf, ", " );
-                                               }
-
-                                               buffer_add( order_buf, string );
-                                               free( string );
+                                                       buffer_add( order_buf, field_str );
+                                                       free( field_str );
 
-                                               if( direction ) {
-                                                       buffer_add( order_buf, direction );
+                                                       if( direction ) {
+                                                               buffer_add( order_buf, direction );
+                                                       }
+                                               } // end while; looping over ORDER BY expressions
+
+                                               jsonIteratorFree( order_itr );
+
+                                       } else if( JSON_STRING == snode->type ) {
+                                               // We expect a comma-separated list of sort fields.
+                                               const char* str = jsonObjectGetString( snode );
+                                               if( strchr( str, ';' )) {
+                                                       // No semicolons allowed.  It is theoretically possible for a
+                                                       // legitimate semicolon to occur within quotes, but it's not likely
+                                                       // to occur in practice in the context of an ORDER BY list.
+                                                       osrfLogError( OSRF_LOG_MARK, "%s: Possible attempt at SOL injection -- "
+                                                               "semicolon found in ORDER BY list: \"%s\"", modulename, str );
+                                                       if( ctx ) {
+                                                               osrfAppSessionStatus(
+                                                                       ctx->session,
+                                                                       OSRF_STATUS_INTERNALSERVERERROR,
+                                                                       "osrfMethodException",
+                                                                       ctx->request,
+                                                                       "Possible attempt at SOL injection -- "
+                                                                               "semicolon found in ORDER BY list"
+                                                               );
+                                                       }
+                                                       jsonIteratorFree( class_itr );
+                                                       buffer_free( order_buf );
+                                                       buffer_free( sql_buf );
+                                                       if( defaultselhash )
+                                                               jsonObjectFree( defaultselhash );
+                                                       clear_query_stack();
+                                                       return NULL;
                                                }
+                                               buffer_add( order_buf, str );
+                                               break;
                                        }
 
-                                       jsonIteratorFree( order_itr );
+                               } // end while; looping over order_by classes
 
-                               } else {
-                                       const char* str = jsonObjectGetString( snode );
-                                       buffer_add( order_buf, str );
-                                       break;
-                               }
+                               jsonIteratorFree( class_itr );
+                               order_by_list = buffer_release( order_buf );
 
+                       } else {
+                               osrfLogWarning( OSRF_LOG_MARK,
+                                       "\"order_by\" object in a query is not a JSON_HASH or JSON_ARRAY;"
+                                       "no ORDER BY generated" );
                        }
 
-                       jsonIteratorFree( class_itr );
-
-                       string = buffer_release( order_buf );
-
-                       if( *string ) {
+                       if( order_by_list && *order_by_list ) {
                                OSRF_BUFFER_ADD( sql_buf, " ORDER BY " );
-                               OSRF_BUFFER_ADD( sql_buf, string );
+                               OSRF_BUFFER_ADD( sql_buf, order_by_list );
                        }
 
-                       free( string );
+                       free( order_by_list );
                }
 
-               if( (_tmp = jsonObjectGetKeyConst( order_hash, "limit" )) ) {
-                       const char* str = jsonObjectGetString( _tmp );
+               const jsonObject* limit = jsonObjectGetKeyConst( rest_of_query, "limit" );
+               if( limit ) {
+                       const char* str = jsonObjectGetString( limit );
                        buffer_fadd(
                                sql_buf,
                                " LIMIT %d",
@@ -5166,9 +5253,9 @@ static char* buildSELECT ( jsonObject* search_hash, jsonObject* order_hash, osrf
                        );
                }
 
-               _tmp = jsonObjectGetKeyConst( order_hash, "offset" );
-               if( _tmp ) {
-                       const char* str = jsonObjectGetString( _tmp );
+               const jsonObject* offset = jsonObjectGetKeyConst( rest_of_query, "offset" );
+               if( offset ) {
+                       const char* str = jsonObjectGetString( offset );
                        buffer_fadd(
                                sql_buf,
                                " OFFSET %d",
diff --git a/Open-ILS/src/edi_translator/install.RHEL.sh b/Open-ILS/src/edi_translator/install.RHEL.sh
new file mode 100755 (executable)
index 0000000..890451c
--- /dev/null
@@ -0,0 +1,22 @@
+#!/bin/bash
+#
+# JEDI converter scripts installation
+#
+# RHEL/CENTOS Install
+# note: need older version of rubygems since RHEL package for ruby is old
+#
+# run this script as root or with sudo
+
+yum install ruby ruby-devel ruby-rdoc
+
+wget http://production.cf.rubygems.org/rubygems/rubygems-1.3.5.tgz
+tar zxvf rubygems-1.3.5.tgz
+pushd rubygems-1.3.5
+ruby setup.rb        # this gives harmless errors about README files missing
+gem install rubygems-update
+update_rubygems
+popd
+
+# RHEL has a bug in json module, but mbklein moved to using yajl for us....
+gem install parseconfig rspec edi4r edi4r-tdid rcov openils-mapper # mkmf
+
index 5398ba1..0903e31 100644 (file)
@@ -16,10 +16,10 @@ my $U = 'OpenILS::Application::AppUtils';
 
 
 __PACKAGE__->register_method(
-       method => 'create_lineitem',
-       api_name        => 'open-ils.acq.lineitem.create',
-       signature => {
-        desc => 'Creates a lineitem',
+    method    => 'create_lineitem',
+    api_name  => 'open-ils.acq.lineitem.create',
+    signature => {
+        desc   => 'Creates a lineitem',
         params => [
             {desc => 'Authentication token', type => 'string'},
             {desc => 'The lineitem object to create', type => 'object'},
@@ -67,10 +67,10 @@ sub create_lineitem {
 
 
 __PACKAGE__->register_method(
-       method => 'retrieve_lineitem',
-       api_name        => 'open-ils.acq.lineitem.retrieve',
-       signature => {
-        desc => 'Retrieves a lineitem',
+    method    => 'retrieve_lineitem',
+    api_name  => 'open-ils.acq.lineitem.retrieve',
+    signature => {
+        desc   => 'Retrieves a lineitem',
         params => [
             {desc => 'Authentication token',    type => 'string'},
             {desc => 'lineitem ID to retrieve', type => 'number'},
@@ -149,15 +149,15 @@ sub retrieve_lineitem_impl {
         }
     }
 
-    return $e->event unless (
+    return $e->event unless ((
         $li->purchase_order and 
             ($no_auth or $e->allowed(['VIEW_PURCHASE_ORDER', 'CREATE_PURCHASE_ORDER'], 
                 $li->purchase_order->ordering_agency, $li->purchase_order))
-    ) or (
+        ) or (
         $li->picklist and !$li->purchase_order and # user doesn't have view_po perms
             ($no_auth or $e->allowed(['VIEW_PICKLIST', 'CREATE_PICKLIST'], 
                 $li->picklist->org_unit, $li->picklist))
-    );
+    ));
 
     unless ($$options{flesh_po}) {
         $li->purchase_order(
@@ -173,12 +173,12 @@ sub retrieve_lineitem_impl {
 
 
 __PACKAGE__->register_method(
-       method => 'delete_lineitem',
-       api_name        => 'open-ils.acq.lineitem.delete',
-       signature => {
-        desc => 'Deletes a lineitem',
+    method    => 'delete_lineitem',
+    api_name  => 'open-ils.acq.lineitem.delete',
+    signature => {
+        desc   => 'Deletes a lineitem',
         params => [
-            {desc => 'Authentication token', type => 'string'},
+            {desc => 'Authentication token',  type => 'string'},
             {desc => 'lineitem ID to delete', type => 'number'},
         ],
         return => {desc => '1 on success, Event on error'}
@@ -221,12 +221,12 @@ sub delete_lineitem {
 
 
 __PACKAGE__->register_method(
-       method => 'update_lineitem',
-       api_name        => 'open-ils.acq.lineitem.update',
-       signature => {
-        desc => 'Update one or many lineitems',
+    method    => 'update_lineitem',
+    api_name  => 'open-ils.acq.lineitem.update',
+    signature => {
+        desc   => 'Update one or many lineitems',
         params => [
-            {desc => 'Authentication token', type => 'string'},
+            {desc => 'Authentication token',   type => 'string'},
             {desc => 'lineitem object update', type => 'object'}
         ],
         return => {desc => '1 on success, Event on error'}
index f6c07ca..c8db485 100644 (file)
@@ -1581,12 +1581,12 @@ sub update_lineitem_fund_batch {
 
 
 __PACKAGE__->register_method(
-       method => 'lineitem_detail_CUD_batch_api',
-       api_name => 'open-ils.acq.lineitem_detail.cud.batch',
-    stream => 1,
-       signature => {
-        desc => q/Creates a new purchase order line item detail.  
-            Additionally creates the associated fund_debit/,
+    method    => 'lineitem_detail_CUD_batch_api',
+    api_name  => 'open-ils.acq.lineitem_detail.cud.batch',
+    stream    => 1,
+    signature => {
+        desc   => q/Creates a new purchase order line item detail. / .
+                  q/Additionally creates the associated fund_debit/,
         params => [
             {desc => 'Authentication token', type => 'string'},
             {desc => 'List of lineitem_details to create', type => 'array'},
@@ -1597,9 +1597,9 @@ __PACKAGE__->register_method(
 );
 
 __PACKAGE__->register_method(
-       method => 'lineitem_detail_CUD_batch_api',
-       api_name => 'open-ils.acq.lineitem_detail.cud.batch.dry_run',
-    stream => 1,
+    method    => 'lineitem_detail_CUD_batch_api',
+    api_name  => 'open-ils.acq.lineitem_detail.cud.batch.dry_run',
+    stream    => 1,
     signature => { 
         desc => q/
             Dry run version of open-ils.acq.lineitem_detail.cud.batch.
@@ -1691,8 +1691,8 @@ sub handle_changed_lid {
 
 
 __PACKAGE__->register_method(
-       method => 'receive_po_api',
-       api_name        => 'open-ils.acq.purchase_order.receive'
+    method   => 'receive_po_api',
+    api_name => 'open-ils.acq.purchase_order.receive'
 );
 
 sub receive_po_api {
@@ -1930,13 +1930,13 @@ sub rollback_receive_lineitem_detail_api {
 }
 
 __PACKAGE__->register_method(
-       method => 'rollback_receive_lineitem_api',
-       api_name        => 'open-ils.acq.lineitem.receive.rollback',
-       signature => {
-        desc => 'Mark a lineitem as Un-received',
+    method    => 'rollback_receive_lineitem_api',
+    api_name  => 'open-ils.acq.lineitem.receive.rollback',
+    signature => {
+        desc   => 'Mark a lineitem as Un-received',
         params => [
             {desc => 'Authentication token', type => 'string'},
-            {desc => 'lineitem ID', type => 'number'}
+            {desc => 'lineitem ID',          type => 'number'}
         ],
         return => {desc =>
             "on success, object describing changes to LI and possibly PO; " .
@@ -1975,13 +1975,13 @@ sub rollback_receive_lineitem_api {
 
 
 __PACKAGE__->register_method(
-       method => 'set_lineitem_price_api',
-       api_name        => 'open-ils.acq.lineitem.price.set',
-       signature => {
-        desc => 'Set lineitem price.  If debits already exist, update them as well',
+    method    => 'set_lineitem_price_api',
+    api_name  => 'open-ils.acq.lineitem.price.set',
+    signature => {
+        desc   => 'Set lineitem price.  If debits already exist, update them as well',
         params => [
             {desc => 'Authentication token', type => 'string'},
-            {desc => 'lineitem ID', type => 'number'}
+            {desc => 'lineitem ID',          type => 'number'}
         ],
         return => {desc => 'status blob, Event on error'}
     }
@@ -2024,10 +2024,10 @@ sub set_lineitem_price_api {
 
 
 __PACKAGE__->register_method(
-       method => 'clone_picklist_api',
-       api_name        => 'open-ils.acq.picklist.clone',
-       signature => {
-        desc => 'Clones a picklist, including lineitem and lineitem details',
+    method    => 'clone_picklist_api',
+    api_name  => 'open-ils.acq.picklist.clone',
+    signature => {
+        desc   => 'Clones a picklist, including lineitem and lineitem details',
         params => [
             {desc => 'Authentication token', type => 'string'},
             {desc => 'Picklist ID', type => 'number'},
@@ -2072,10 +2072,10 @@ sub clone_picklist_api {
 
 
 __PACKAGE__->register_method(
-       method => 'merge_picklist_api',
-       api_name        => 'open-ils.acq.picklist.merge',
-       signature => {
-        desc => 'Merges 2 or more picklists into a single list',
+    method    => 'merge_picklist_api',
+    api_name  => 'open-ils.acq.picklist.merge',
+    signature => {
+        desc   => 'Merges 2 or more picklists into a single list',
         params => [
             {desc => 'Authentication token', type => 'string'},
             {desc => 'Lead Picklist ID', type => 'number'},
@@ -2119,13 +2119,13 @@ sub merge_picklist_api {
 
 
 __PACKAGE__->register_method(
-       method => 'delete_picklist_api',
-       api_name        => 'open-ils.acq.picklist.delete',
-       signature => {
-        desc => q/Deletes a picklist.  It also deletes any lineitems in the "new" state.  
-            Other attached lineitems are detached'/,
+    method    => 'delete_picklist_api',
+    api_name  => 'open-ils.acq.picklist.delete',
+    signature => {
+        desc   => q/Deletes a picklist.  It also deletes any lineitems in the "new" state. / .
+                  q/Other attached lineitems are detached/,
         params => [
-            {desc => 'Authentication token', type => 'string'},
+            {desc => 'Authentication token',  type => 'string'},
             {desc => 'Picklist ID to delete', type => 'number'}
         ],
         return => {desc => '1 on success, Event on error'}
@@ -2146,17 +2146,16 @@ sub delete_picklist_api {
 
 
 __PACKAGE__->register_method(
-       method => 'activate_purchase_order',
-       api_name        => 'open-ils.acq.purchase_order.activate.dry_run'
+    method   => 'activate_purchase_order',
+    api_name => 'open-ils.acq.purchase_order.activate.dry_run'
 );
 
 __PACKAGE__->register_method(
-       method => 'activate_purchase_order',
-       api_name        => 'open-ils.acq.purchase_order.activate',
-       signature => {
-        desc => q/Activates a purchase order.  This updates the status of the PO
-            and Lineitems to 'on-order'.  Activated PO's are ready for EDI delivery
-            if appropriate./,
+    method    => 'activate_purchase_order',
+    api_name  => 'open-ils.acq.purchase_order.activate',
+    signature => {
+        desc => q/Activates a purchase order.  This updates the status of the PO / .
+                q/and Lineitems to 'on-order'.  Activated PO's are ready for EDI delivery if appropriate./,
         params => [
             {desc => 'Authentication token', type => 'string'},
             {desc => 'Purchase ID', type => 'number'}
@@ -2238,21 +2237,21 @@ sub activate_purchase_order_impl {
     }
 
     # tell the world we activated a PO
-    $U->create_events_for_hook('acqpo.activated', $po, $po->ordering_agency);
+    $U->create_events_for_hook('acqpo.activated', $po, $po->ordering_agency) unless $dry_run;
 
     return undef;
 }
 
 
 __PACKAGE__->register_method(
-       method => 'split_purchase_order_by_lineitems',
-       api_name        => 'open-ils.acq.purchase_order.split_by_lineitems',
-       signature => {
-        desc => q/Splits a PO into many POs, 1 per lineitem.  Only works for
-        POs a) with more than one lineitems, and b) in the "pending" state./,
+    method    => 'split_purchase_order_by_lineitems',
+    api_name  => 'open-ils.acq.purchase_order.split_by_lineitems',
+    signature => {
+        desc   => q/Splits a PO into many POs, 1 per lineitem.  Only works for / .
+                  q/POs a) with more than one lineitems, and b) in the "pending" state./,
         params => [
             {desc => 'Authentication token', type => 'string'},
-            {desc => 'Purchase order ID', type => 'number'}
+            {desc => 'Purchase order ID',    type => 'number'}
         ],
         return => {desc => 'list of new PO IDs on success, Event on error'}
     }
index 3295096..2fcacd2 100644 (file)
@@ -174,6 +174,7 @@ sub set_ou_settings {
 
 __PACKAGE__->register_method(
     method   => "user_settings",
+    authoritative => 1,
     api_name => "open-ils.actor.patron.settings.retrieve",
 );
 sub user_settings {
@@ -1275,10 +1276,11 @@ sub update_passwd {
     my $api = $self->api_name;
 
     if( $api =~ /password/o ) {
-
         # make sure the original password matches the in-database password
-        return OpenILS::Event->new('INCORRECT_PASSWORD')
-            if md5_hex($orig_pw) ne $db_user->passwd;
+        if (md5_hex($orig_pw) ne $db_user->passwd) {
+            $e->rollback;
+            return new OpenILS::Event('INCORRECT_PASSWORD');
+        }
         $db_user->passwd($new_val);
 
     } else {
@@ -1291,7 +1293,10 @@ sub update_passwd {
 
             # make sure no one else has this username
             my $exist = $e->search_actor_user({usrname=>$new_val},{idlist=>1}); 
-                       return OpenILS::Event->new('USERNAME_EXISTS') if @$exist;
+            if (@$exist) {
+                $e->rollback;
+                return new OpenILS::Event('USERNAME_EXISTS');
+            }
             $db_user->usrname($new_val);
 
         } elsif( $api =~ /email/o ) {
@@ -2477,13 +2482,13 @@ __PACKAGE__->register_method(
 sub update_user_note {
        my( $self, $conn, $auth, $note ) = @_;
        my $e = new_editor(authtoken=>$auth, xact=>1);
-       return $e->event unless $e->checkauth;
+       return $e->die_event unless $e->checkauth;
        my $patron = $e->retrieve_actor_user($note->usr)
-               or return $e->event;
-       return $e->event unless 
+               or return $e->die_event;
+       return $e->die_event unless 
                $e->allowed('UPDATE_USER', $patron->home_ou);
        $e->update_actor_user_note($note)
-               or return $e->event;
+               or return $e->die_event;
        $e->commit;
        return 1;
 }
@@ -2883,7 +2888,7 @@ sub new_flesh_user {
                "flesh_fields" =>  { "au" => $fields }
        }
        ]
-       ) or return $e->event;
+       ) or return $e->die_event;
 
 
        if( grep { $_ eq 'addresses' } @$fields ) {
@@ -3258,7 +3263,7 @@ __PACKAGE__->register_method (
 sub apply_friend_perms {
     my($self, $conn, $auth, $user_id, $delegate_id, @perms) = @_;
     my $e = new_editor(authtoken => $auth, xact => 1);
-    return $e->event unless $e->checkauth;
+    return $e->die_event unless $e->checkauth;
 
     if($user_id != $e->requestor->id) {
         my $user = $e->retrieve_actor_user($user_id) or return $e->die_event;
@@ -3285,7 +3290,7 @@ __PACKAGE__->register_method (
 sub update_user_pending_address {
     my($self, $conn, $auth, $addr) = @_;
     my $e = new_editor(authtoken => $auth, xact => 1);
-    return $e->event unless $e->checkauth;
+    return $e->die_event unless $e->checkauth;
 
     if($addr->usr != $e->requestor->id) {
         my $user = $e->retrieve_actor_user($addr->usr) or return $e->die_event;
index e648a8f..74f270f 100644 (file)
@@ -1176,6 +1176,35 @@ sub is_true {
 }
 
 
+sub patientreq {
+    my ($self, $client, $service, $method, @params) = @_;
+    my ($response, $err);
+
+    my $session = create OpenSRF::AppSession($service);
+    my $request = $session->request($method, @params);
+
+    my $spurt = 10;
+    my $give_up = time + 1000;
+
+    try {
+        while (time < $give_up) {
+            $response = $request->recv("timeout" => $spurt);
+            last if $request->complete;
+
+            $client->status(new OpenSRF::DomainObject::oilsContinueStatus);
+        }
+    } catch Error with {
+        $err = shift;
+    };
+
+    if ($err) {
+        warn "received error : service=$service : method=$method : params=".Dumper(\@params) . "\n $err";
+        throw $err ("Call to $service for method $method \n failed with exception: $err : " );
+    }
+
+    return $response->content;
+}
+
 # This logic now lives in storage
 sub __patron_money_owed {
        my( $self, $patronid ) = @_;
@@ -1596,7 +1625,7 @@ sub find_event_def_by_hook {
 # most appropriate event.  create the event, fire it, then return the resulting
 # event with fleshed template_output and error_output
 sub fire_object_event {
-    my($self, $event_def, $hook, $object, $context_org, $granularity, $user_data) = @_;
+    my($self, $event_def, $hook, $object, $context_org, $granularity, $user_data, $client) = @_;
 
     my $e = OpenILS::Utils::CStoreEditor->new;
     my $def;
@@ -1616,6 +1645,8 @@ sub fire_object_event {
             or return $e->event;
     }
 
+    my $final_resp;
+
     if($def->group_field) {
         # we have a list of objects
         $object = [$object] unless ref $object eq 'ARRAY';
@@ -1632,37 +1663,74 @@ sub fire_object_event {
 
         $logger->info("EVENTS = " . OpenSRF::Utils::JSON->perl2JSON(\@event_ids));
 
-        my $resp = $self->simplereq(
-            'open-ils.trigger', 
-            'open-ils.trigger.event_group.fire',
-            \@event_ids);
+        my $resp;
+        if (not defined $client) {
+            $resp = $self->simplereq(
+                'open-ils.trigger',
+                'open-ils.trigger.event_group.fire',
+                \@event_ids);
+        } else {
+            $resp = $self->patientreq(
+                $client,
+                "open-ils.trigger", "open-ils.trigger.event_group.fire",
+                \@event_ids
+            );
+        }
 
-        return undef unless $resp and $resp->{events} and @{$resp->{events}};
+        if($resp and $resp->{events} and @{$resp->{events}}) {
 
-        return $e->retrieve_action_trigger_event([
-            $resp->{events}->[0]->id,
-            {flesh => 1, flesh_fields => {atev => ['template_output', 'error_output']}}
-        ]);
+            $e->xact_begin;
+            $final_resp = $e->retrieve_action_trigger_event([
+                $resp->{events}->[0]->id,
+                {flesh => 1, flesh_fields => {atev => ['template_output', 'error_output']}}
+            ]);
+            $e->rollback;
+        }
 
     } else {
 
         $object = $$object[0] if ref $object eq 'ARRAY';
 
-        my $event_id = $self->simplereq(
-            'open-ils.trigger', $auto_method, $def->id, $object, $context_org, $user_data);
-
-        my $resp = $self->simplereq(
-            'open-ils.trigger', 
-            'open-ils.trigger.event.fire', 
-            $event_id);
-
-        return undef unless $resp and $resp->{event};
-
-        return $e->retrieve_action_trigger_event([
-            $resp->{event}->id,
-            {flesh => 1, flesh_fields => {atev => ['template_output', 'error_output']}}
-        ]);
+        my $event_id;
+        my $resp;
+
+        if (not defined $client) {
+            $event_id = $self->simplereq(
+                'open-ils.trigger',
+                $auto_method, $def->id, $object, $context_org, $user_data
+            );
+
+            $resp = $self->simplereq(
+                'open-ils.trigger',
+                'open-ils.trigger.event.fire',
+                $event_id
+            );
+        } else {
+            $event_id = $self->patientreq(
+                $client,
+                'open-ils.trigger',
+                $auto_method, $def->id, $object, $context_org, $user_data
+            );
+
+            $resp = $self->patientreq(
+                $client,
+                'open-ils.trigger',
+                'open-ils.trigger.event.fire',
+                $event_id
+            );
+        }
+        
+        if($resp and $resp->{event}) {
+            $e->xact_begin;
+            $final_resp = $e->retrieve_action_trigger_event([
+                $resp->{event}->id,
+                {flesh => 1, flesh_fields => {atev => ['template_output', 'error_output']}}
+            ]);
+            $e->rollback;
+        }
     }
+
+    return $final_resp;
 }
 
 
index ff9572a..5e75fa8 100644 (file)
@@ -1291,12 +1291,13 @@ sub get_bresv_by_returnable_resource_barcode {
     }) or return $e->die_event;
 
     if (@$rows < 1) {
+        $e->rollback;
         return $rows;
     } else {
         # More than one result might be possible, but we don't want to return
         # more than one at this time.
         my $id = $rows->[0]->{"id"};
-        return $e->retrieve_booking_reservation([
+        my $resp =$e->retrieve_booking_reservation([
             $id, {
                 "flesh" => 2,
                 "flesh_fields" => {
@@ -1305,6 +1306,8 @@ sub get_bresv_by_returnable_resource_barcode {
                 }
             }
         ]) or $e->die_event;
+        $e->rollback;
+        return $resp;
     }
 }
 
index c6e579b..df16b4e 100644 (file)
@@ -214,6 +214,116 @@ sub template_overlay_biblio_record_entry {
 }
 
 __PACKAGE__->register_method(
+       method  => "template_overlay_container",
+       api_name        => "open-ils.cat.container.template_overlay",
+    stream  => 1,
+    signature => q#
+        Overlays biblio.record_entry MARC values
+        @param auth The authtoken
+        @param container The container, um, containing the records to be updated by the template
+        @param template The overlay template, or nothing and the method will look for a negative bib id in the container
+        @return Stream of hashes record id in the key "record" and t or f for the success of the overlay operation in key "success"
+    #
+);
+
+__PACKAGE__->register_method(
+       method  => "template_overlay_container",
+       api_name        => "open-ils.cat.container.template_overlay.background",
+    stream  => 1,
+    signature => q#
+        Overlays biblio.record_entry MARC values
+        @param auth The authtoken
+        @param container The container, um, containing the records to be updated by the template
+        @param template The overlay template, or nothing and the method will look for a negative bib id in the container
+        @return Cache key to check for status of the container overlay
+    #
+);
+
+sub template_overlay_container {
+    my($self, $conn, $auth, $container, $template) = @_;
+    my $e = new_editor(authtoken=>$auth, xact=>1);
+    return $e->die_event unless $e->checkauth;
+
+    my $actor = OpenSRF::AppSession->create('open-ils.actor') if ($self->api_name =~ /background$/);
+
+    my $items = $e->search_container_biblio_record_entry_bucket_item({ bucket => $container });
+
+    my $titem;
+    if (!$template) {
+        ($titem) = grep { $_->target_biblio_record_entry < 0 } @$items;
+        if (!$titem) {
+            $e->rollback;
+            return undef;
+        }
+        $items = [grep { $_->target_biblio_record_entry > 0 } @$items];
+
+        $template = $e->retrieve_biblio_record_entry( $titem->target_biblio_record_entry )->marc;
+    }
+
+    my $responses = [];
+    my $some_failed = 0;
+
+    $self->respond_complete(
+        $actor->request('open-ils.actor.anon_cache.set_value', $auth, res_list => $responses)->gather(1)
+    ) if ($actor);
+
+    for my $item ( @$items ) {
+        my $rec = $e->retrieve_biblio_record_entry($item->target_biblio_record_entry);
+        next unless $rec;
+
+        my $success = 'f';
+        if ($e->allowed('UPDATE_RECORD', $rec->owner, $rec)) {
+            $success = $e->json_query(
+                { from => [ 'vandelay.template_overlay_bib_record', $template, $rec->id ] }
+            )->[0]->{'vandelay.template_overlay_bib_record'};
+        }
+
+        $some_failed++ if ($success eq 'f');
+
+        if ($actor) {
+            push @$responses, { record => $rec->id, success => $success };
+            $actor->request('open-ils.actor.anon_cache.set_value', $auth, res_list => $responses);
+        } else {
+            $conn->respond({ record => $rec->id, success => $success });
+        }
+
+        if ($success eq 't') {
+            unless ($e->delete_container_biblio_record_entry_bucket_item($item)) {
+                $e->rollback;
+                if ($actor) {
+                    push @$responses, { complete => 1, success => 'f' };
+                    $actor->request('open-ils.actor.anon_cache.set_value', $auth, res_list => $responses);
+                    return undef;
+                } else {
+                    return { complete => 1, success => 'f' };
+                }
+            }
+        }
+    }
+
+    if ($titem && !$some_failed) {
+        return $e->die_event unless ($e->delete_container_biblio_record_entry_bucket_item($titem));
+    }
+
+    if ($e->commit) {
+        if ($actor) {
+            push @$responses, { complete => 1, success => 't' };
+            $actor->request('open-ils.actor.anon_cache.set_value', $auth, res_list => $responses);
+        } else {
+            return { complete => 1, success => 't' };
+        }
+    } else {
+        if ($actor) {
+            push @$responses, { complete => 1, success => 'f' };
+            $actor->request('open-ils.actor.anon_cache.set_value', $auth, res_list => $responses);
+        } else {
+            return { complete => 1, success => 'f' };
+        }
+    }
+    return undef;
+}
+
+__PACKAGE__->register_method(
        method  => "update_biblio_record_entry",
        api_name        => "open-ils.cat.biblio.record_entry.update",
     signature => q/
index f85c754..89a06fa 100644 (file)
@@ -1487,7 +1487,7 @@ __PACKAGE__->register_method(
 sub fire_circ_events {
     my($self, $conn, $auth, $org_id, $event_def, $hook, $granularity, $target_ids, $user_data) = @_;
 
-    my $e = new_editor(authtoken => $auth);
+    my $e = new_editor(authtoken => $auth, xact => 1);
        return $e->event unless $e->checkauth;
 
     my $targets;
@@ -1502,6 +1502,10 @@ sub fire_circ_events {
         return $e->event unless $e->allowed('VIEW_CIRCULATIONS', $org_id);
         $targets = $e->batch_retrieve_action_circulation($target_ids);
     }
+    $e->rollback; # FIXME using transaction because of pgpool/slony setups, but not
+                  # simply making this method authoritative because of weirdness
+                  # with transaction handling in A/T code that causes rollback
+                  # failure down the line if handling many targets
 
     return undef unless @$targets;
     return $U->fire_object_event($event_def, $hook, $targets, $org_id, $granularity, $user_data);
index 0463d29..66330c9 100644 (file)
@@ -1196,7 +1196,8 @@ sub get_circ_policy {
         max_fine_rule => $max_fine_rule->name,
         max_fine => $self->get_max_fine_amount($max_fine_rule),
         fine_interval => $recurring_fine_rule->recurrence_interval,
-        renewal_remaining => $duration_rule->max_renewals
+        renewal_remaining => $duration_rule->max_renewals,
+        duration_date_ceiling => $duration_rule->date_ceiling
     };
 
     $policy->{duration} = $duration_rule->shrt
@@ -1760,10 +1761,12 @@ sub build_checkout_circ_object {
    my $recurring  = $self->recurring_fines_rule;
    my $copy       = $self->copy;
    my $patron     = $self->patron;
+   my $duration_date_ceiling;
 
     if( $duration ) {
 
         my $policy = $self->get_circ_policy($duration, $recurring, $max);
+        $duration_date_ceiling = $policy->{duration_date_ceiling};
 
         my $dname = $duration->name;
         my $mname = $max->name;
@@ -1816,7 +1819,7 @@ sub build_checkout_circ_object {
 
     # if a patron is renewing, 'requestor' will be the patron
     $circ->circ_staff($self->editor->requestor->id);
-    $circ->due_date( $self->create_due_date($circ->duration) ) if $circ->duration;
+    $circ->due_date( $self->create_due_date($circ->duration, $duration_date_ceiling) ) if $circ->duration;
 
     $self->circ($circ);
 }
@@ -2016,7 +2019,7 @@ sub apply_modified_due_date {
 
 
 sub create_due_date {
-    my( $self, $duration ) = @_;
+    my( $self, $duration, $date_ceiling ) = @_;
 
     # if there is a raw time component (e.g. from postgres), 
     # turn it into an interval that interval_to_seconds can parse
@@ -2028,6 +2031,14 @@ sub create_due_date {
     # add the circ duration
     $due_date->add(seconds => OpenSRF::Utils->interval_to_seconds($duration));
 
+    if($date_ceiling) {
+        my $cdate = DateTime::Format::ISO8601->new->parse_datetime(cleanse_ISO8601($date_ceiling));
+        if ($cdate > DateTime->now and $cdate < $due_date) {
+            $logger->info("circulator: overriding due date with date ceiling: $date_ceiling");
+            $due_date = $cdate;
+        }
+    }
+
     # return ISO8601 time with timezone
     return $due_date->strftime('%FT%T%z');
 }
@@ -2323,7 +2334,6 @@ sub do_checkin {
             $self->update_copy;
         }
     }
-    $logger->info("LFW XXX: way down here"); # LFW XXX
 
     if($self->claims_never_checked_out and 
             $U->ou_ancestor_setting_value($self->circ->circ_lib, 'circ.claim_never_checked_out.mark_missing')) {
@@ -2682,7 +2692,7 @@ sub do_hold_notify {
             return;
         } 
 
-        $logger->warn("circulator:  * hold notify failed for hold $holdid");
+        $logger->debug("circulator:  * hold notify cancelled or failed for hold $holdid");
 
     } else {
         $logger->info("circulator: Not sending hold notification since the patron has no email address");
index 5ace445..d7f0773 100644 (file)
@@ -182,7 +182,39 @@ sub fetch_loc {
        return $cl;
 }
 
+__PACKAGE__->register_method(
+    api_name => "open-ils.circ.copy_location_order.update",
+    method => 'update_clo',
+    argc =>    2,
+);
+
+sub update_clo {
+    my($self, $client, $auth, $orders) = @_;
+    return [] unless $orders and @$orders;
+
+    my $e = new_editor(authtoken => $auth, xact =>1);
+    return $e->die_event unless $e->checkauth;
+
+    my $org = $$orders[0]->org;
+    return $e->die_event unless $e->allowed('ADMIN_COPY_LOCATION_ORDER', $org);
 
+    # clear out the previous order entries
+    my $existing = $e->search_asset_copy_location_order({org => $org});
+    $e->delete_asset_copy_location_order($_) or return $e->die_event for @$existing;
+
+    # create the new order entries
+    my $progress = 0; 
+    for my $order (@$orders) {
+        return $e->die_event(OpenILS::Event->new('BAD_PARAMS')) unless $order->org == $org;
+        $e->create_asset_copy_location_order($order) or return $e->die_event;
+        $client->respond({maximum => scalar(@$orders), progress => $progress}) unless ($progress++ % 10);
+    }
+
+    # fetch the new entries
+    $orders = $e->search_asset_copy_location_order({org => $org});
+    $e->commit;
+    return {orders => $orders};
+}
 
 
-23;
+1;
index 22b2188..020999a 100644 (file)
@@ -34,6 +34,8 @@ use OpenILS::Application::Actor::Friends;
 use DateTime;
 use DateTime::Format::ISO8601;
 use OpenSRF::Utils qw/:datetime/;
+use Digest::MD5 qw(md5_hex);
+use OpenSRF::Utils::Cache;
 my $apputils = "OpenILS::Application::AppUtils";
 my $U = $apputils;
 
@@ -119,10 +121,10 @@ __PACKAGE__->register_method(
 
 sub create_hold {
        my( $self, $conn, $auth, $hold ) = @_;
+    return -1 unless $hold;
        my $e = new_editor(authtoken=>$auth, xact=>1);
-       return $e->event unless $e->checkauth;
+       return $e->die_event unless $e->checkauth;
 
-    return -1 unless $hold;
        my $override = 1 if $self->api_name =~ /override/;
 
     my @events;
@@ -133,8 +135,8 @@ sub create_hold {
     if( $requestor->id ne $hold->usr ) {
         # Make sure the requestor is allowed to place holds for 
         # the recipient if they are not the same people
-        $recipient = $e->retrieve_actor_user($hold->usr)  or return $e->event;
-        $e->allowed('REQUEST_HOLDS', $recipient->home_ou) or return $e->event;
+        $recipient = $e->retrieve_actor_user($hold->usr)  or return $e->die_event;
+        $e->allowed('REQUEST_HOLDS', $recipient->home_ou) or return $e->die_event;
     }
 
     # If the related org setting tells us to, block if patron privs have expired
@@ -172,27 +174,30 @@ sub create_hold {
     push( @events, OpenILS::Event->new('HOLD_ITEM_CHECKED_OUT')) if $checked_out;
 
     if ( $t eq OILS_HOLD_TYPE_METARECORD ) {
-        return $e->event unless $e->allowed('MR_HOLDS',     $porg);
+        return $e->die_event unless $e->allowed('MR_HOLDS',     $porg);
     } elsif ( $t eq OILS_HOLD_TYPE_TITLE ) {
-        return $e->event unless $e->allowed('TITLE_HOLDS',  $porg);
+        return $e->die_event unless $e->allowed('TITLE_HOLDS',  $porg);
     } elsif ( $t eq OILS_HOLD_TYPE_VOLUME ) {
-        return $e->event unless $e->allowed('VOLUME_HOLDS', $porg);
+        return $e->die_event unless $e->allowed('VOLUME_HOLDS', $porg);
     } elsif ( $t eq OILS_HOLD_TYPE_ISSUANCE ) {
-        return $e->event unless $e->allowed('ISSUANCE_HOLDS', $porg);
+        return $e->die_event unless $e->allowed('ISSUANCE_HOLDS', $porg);
     } elsif ( $t eq OILS_HOLD_TYPE_COPY ) {
-        return $e->event unless $e->allowed('COPY_HOLDS',   $porg);
+        return $e->die_event unless $e->allowed('COPY_HOLDS',   $porg);
     } elsif ( $t eq OILS_HOLD_TYPE_FORCE ) {
-        return $e->event unless $e->allowed('COPY_HOLDS',   $porg);
+        return $e->die_event unless $e->allowed('COPY_HOLDS',   $porg);
     } elsif ( $t eq OILS_HOLD_TYPE_RECALL ) {
-        return $e->event unless $e->allowed('COPY_HOLDS',   $porg);
+        return $e->die_event unless $e->allowed('COPY_HOLDS',   $porg);
     }
 
     if( @events ) {
-        $override or return \@events;
+        if (!$override) {
+            $e->rollback;
+            return \@events;
+        }
         for my $evt (@events) {
             next unless $evt;
             my $name = $evt->{textcode};
-            return $e->event unless $e->allowed("$name.override", $porg);
+            return $e->die_event unless $e->allowed("$name.override", $porg);
         }
     }
 
@@ -208,7 +213,7 @@ sub create_hold {
     $hold->requestor($e->requestor->id); 
     $hold->request_lib($e->requestor->ws_ou);
     $hold->selection_ou($hold->pickup_lib) unless $hold->selection_ou;
-    $hold = $e->create_action_hold_request($hold) or return $e->event;
+    $hold = $e->create_action_hold_request($hold) or return $e->die_event;
 
        $e->commit;
 
@@ -528,14 +533,20 @@ __PACKAGE__->register_method(
 sub uncancel_hold {
        my($self, $client, $auth, $hold_id) = @_;
        my $e = new_editor(authtoken=>$auth, xact=>1);
-       return $e->event unless $e->checkauth;
+       return $e->die_event unless $e->checkauth;
 
        my $hold = $e->retrieve_action_hold_request($hold_id)
                or return $e->die_event;
     return $e->die_event unless $e->allowed('CANCEL_HOLDS', $hold->request_lib);
 
-    return 0 if $hold->fulfillment_time;
-    return 1 unless $hold->cancel_time;
+    if ($hold->fulfillment_time) {
+        $e->rollback;
+        return 0;
+    }
+    unless ($hold->cancel_time) {
+        $e->rollback;
+        return 1;
+    }
 
     # if configured to reset the request time, also reset the expire time
     if($U->ou_ancestor_setting_value(
@@ -583,22 +594,25 @@ sub cancel_hold {
        my($self, $client, $auth, $holdid, $cause, $note) = @_;
 
        my $e = new_editor(authtoken=>$auth, xact=>1);
-       return $e->event unless $e->checkauth;
+       return $e->die_event unless $e->checkauth;
 
        my $hold = $e->retrieve_action_hold_request($holdid)
-               or return $e->event;
+               or return $e->die_event;
 
        if( $e->requestor->id ne $hold->usr ) {
-               return $e->event unless $e->allowed('CANCEL_HOLDS');
+               return $e->die_event unless $e->allowed('CANCEL_HOLDS');
        }
 
-       return 1 if $hold->cancel_time;
+       if ($hold->cancel_time) {
+        $e->rollback;
+        return 1;
+    }
 
        # If the hold is captured, reset the copy status
        if( $hold->capture_time and $hold->current_copy ) {
 
                my $copy = $e->retrieve_asset_copy($hold->current_copy)
-                       or return $e->event;
+                       or return $e->die_event;
 
                if( $copy->status == OILS_COPY_STATUS_ON_HOLDS_SHELF ) {
          $logger->info("canceling hold $holdid whose item is on the holds shelf");
@@ -630,11 +644,15 @@ sub cancel_hold {
     $hold->cancel_cause($cause);
     $hold->cancel_note($note);
        $e->update_action_hold_request($hold)
-               or return $e->event;
+               or return $e->die_event;
 
        delete_hold_copy_maps($self, $e, $hold->id);
 
        $e->commit;
+
+    $U->create_events_for_hook('hold_request.cancel.staff', $hold, $hold->pickup_lib)
+        if $e->requestor->id != $hold->usr;
+
        return 1;
 }
 
@@ -698,7 +716,10 @@ sub update_hold {
     my $e = new_editor(authtoken=>$auth, xact=>1);
     return $e->die_event unless $e->checkauth;
     my $resp = update_hold_impl($self, $e, $hold, $values);
-    return $resp if $U->event_code($resp);
+    if ($U->event_code($resp)) {
+        $e->rollback;
+        return $resp;
+    }
     $e->commit;     # FIXME: update_hold_impl already does $e->commit  ??
     return $resp;
 }
@@ -1033,11 +1054,7 @@ sub retrieve_hold_queue_status_impl {
         # fetch cut_in_line and request_time since they're in the order_by
         # and we're asking for distinct values
         select => {ahr => ['id', 'cut_in_line', 'request_time']},
-        from   => {
-            ahr => {
-                ahcm => {type => 'left'} # there may be no copy maps 
-            }
-        },
+        from   => { ahr => 'ahcm' },
         order_by => [
             {
                 "class" => "ahr",
@@ -1049,28 +1066,40 @@ sub retrieve_hold_queue_status_impl {
             { "class" => "ahr", "field" => "request_time" }
         ],
         distinct => 1,
-        where    => {
-            '-or' => [
+        where => {
+            '+ahcm' => {
+                target_copy => {
+                    in => {
+                        select => {ahcm => ['target_copy']},
+                        from   => 'ahcm',
+                        where  => {hold => $hold->id}
+                    } 
+                } 
+            }
+        }
+    });
+
+    if (!@$q_holds) { # none? maybe we don't have a map ... 
+        $q_holds = $e->json_query({
+            select => {ahr => ['id', 'cut_in_line', 'request_time']},
+            from   => 'ahr',
+            order_by => [
                 {
-                    '+ahcm' => {
-                        target_copy => {
-                            in => {
-                                select => {ahcm => ['target_copy']},
-                                from   => 'ahcm',
-                                where  => {hold => $hold->id}
-                            } 
-                        } 
-                    }
+                    "class" => "ahr",
+                    "field" => "cut_in_line",
+                    "transform" => "coalesce",
+                    "params" => [ 0 ],
+                    "direction" => "desc"
                 },
-                {
-                    '+ahr' => {
-                        hold_type => $hold->hold_type,
-                        target    => $hold->target
-                    }
-                }
-            ]
-        }, 
-    });
+                { "class" => "ahr", "field" => "request_time" }
+            ],
+            where    => {
+                hold_type => $hold->hold_type, 
+                target    => $hold->target 
+           } 
+        });
+    }
+
 
     my $qpos = 1;
     for my $h (@$q_holds) {
@@ -1256,23 +1285,157 @@ __PACKAGE__->register_method(
 sub print_hold_pull_list {
     my($self, $client, $auth, $org_id) = @_;
 
-    my $e = new_editor(authtoken=>$auth, xact=>1);
-    return $e->die_event unless $e->checkauth;
+    my $e = new_editor(authtoken=>$auth);
+    return $e->event unless $e->checkauth;
 
     $org_id = (defined $org_id) ? $org_id : $e->requestor->ws_ou;
-    return $e->die_event unless $e->allowed('VIEW_HOLD', $org_id);
+    return $e->event unless $e->allowed('VIEW_HOLD', $org_id);
 
     my $hold_ids = $U->storagereq(
         'open-ils.storage.direct.action.hold_request.pull_list.id_list.current_copy_circ_lib.status_filtered.atomic',
         $org_id, 10000);
 
     return undef unless @$hold_ids;
+
     $client->status(new OpenSRF::DomainObject::oilsContinueStatus);
 
+    # Holds will /NOT/ be in order after this ...
     my $holds = $e->search_action_hold_request({id => $hold_ids}, {substream => 1});
     $client->status(new OpenSRF::DomainObject::oilsContinueStatus);
 
-    return $U->fire_object_event(undef, 'ahr.format.pull_list', $holds, $org_id);
+    # ... so we must resort.
+    my $hold_map = +{map { $_->id => $_ } @$holds};
+    my $sorted_holds = [];
+    push @$sorted_holds, $hold_map->{$_} foreach @$hold_ids;
+
+    return $U->fire_object_event(
+        undef, "ahr.format.pull_list", $sorted_holds,
+        $org_id, undef, undef, $client
+    );
+
+}
+
+__PACKAGE__->register_method(
+    method    => "print_hold_pull_list_stream",
+    stream   => 1,
+    api_name  => "open-ils.circ.hold_pull_list.print.stream",
+    signature => {
+        desc   => 'Returns a stream of fleshed holds',
+        params => [
+            { desc => 'Authtoken', type => 'string'},
+            { desc => 'Hash of optional param: Org unit ID (defaults to workstation org unit), limit, offset, sort (array of: acplo.position, call_number, request_time)',
+              type => 'object'
+            },
+        ],
+        return => {
+            desc => 'A stream of fleshed holds',
+            type => 'object'
+        }
+    }
+);
+
+sub print_hold_pull_list_stream {
+    my($self, $client, $auth, $params) = @_;
+
+    my $e = new_editor(authtoken=>$auth);
+    return $e->die_event unless $e->checkauth;
+
+    delete($$params{org_id}) unless (int($$params{org_id}));
+    delete($$params{limit}) unless (int($$params{limit}));
+    delete($$params{offset}) unless (int($$params{offset}));
+    delete($$params{chunk_size}) unless (int($$params{chunk_size}));
+    delete($$params{chunk_size}) if  ($$params{chunk_size} && $$params{chunk_size} > 50); # keep the size reasonable
+    $$params{chunk_size} ||= 10;
+
+    $$params{org_id} = (defined $$params{org_id}) ? $$params{org_id}: $e->requestor->ws_ou;
+    return $e->die_event unless $e->allowed('VIEW_HOLD', $$params{org_id });
+
+    my $sort = [];
+    if ($$params{sort} && @{ $$params{sort} }) {
+        for my $s (@{ $$params{sort} }) {
+            if ($s eq 'acplo.position') {
+                push @$sort, {
+                    "class" => "acplo", "field" => "position",
+                    "transform" => "coalesce", "params" => [999]
+                };
+            } elsif ($s eq 'call_number') {
+                push @$sort, {"class" => "acn", "field" => "label"};
+            } elsif ($s eq 'request_time') {
+                push @$sort, {"class" => "ahr", "field" => "request_time"};
+            }
+        }
+    } else {
+        push @$sort, {"class" => "ahr", "field" => "request_time"};
+    }
+
+    my $holds_ids = $e->json_query(
+        {
+            "select" => {"ahr" => ["id"]},
+            "from" => {
+                "ahr" => {
+                    "acp" => { 
+                        "field" => "id",
+                        "fkey" => "current_copy",
+                        "filter" => {
+                            "circ_lib" => $$params{org_id}, "status" => [0,7]
+                        },
+                        "join" => {
+                            "acn" => {
+                                "field" => "id",
+                                "fkey" => "call_number" 
+                            },
+                            "acplo" => {
+                                "field" => "org",
+                                "fkey" => "circ_lib", 
+                                "type" => "left",
+                                "filter" => {
+                                    "location" => {"=" => {"+acp" => "location"}}
+                                }
+                            }
+                        }
+                    }
+                }
+            },
+            "where" => {
+                "+ahr" => {
+                    "capture_time" => undef,
+                    "cancel_time" => undef,
+                    "-or" => [
+                        {"expire_time" => undef },
+                        {"expire_time" => {">" => "now"}}
+                    ]
+                }
+            },
+            (@$sort ? (order_by => $sort) : ()),
+            ($$params{limit} ? (limit => $$params{limit}) : ()),
+            ($$params{offset} ? (offset => $$params{offset}) : ())
+        }, {"substream" => 1}
+    ) or return $e->die_event;
+
+    $logger->info("about to stream back " . scalar(@$holds_ids) . " holds");
+
+    my @chunk;
+    for my $hid (@$holds_ids) {
+        push @chunk, $e->retrieve_action_hold_request([
+            $hid->{"id"}, {
+                "flesh" => 3,
+                "flesh_fields" => {
+                    "ahr" => ["usr", "current_copy"],
+                    "au"  => ["card"],
+                    "acp" => ["location", "call_number"],
+                    "acn" => ["record"]
+                }
+            }
+        ]);
+
+        if (@chunk >= $$params{chunk_size}) {
+            $client->respond( \@chunk );
+            @chunk = ();
+        }
+    }
+    $client->respond_complete( \@chunk ) if (@chunk);
+    $e->disconnect;
+    return undef;
 }
 
 
@@ -1332,7 +1495,7 @@ sub create_hold_notify {
    return $e->die_event unless 
       $e->allowed('CREATE_HOLD_NOTIFICATION', $patron->home_ou);
 
-       $note->notify_staff($e->requestor->id);
+   $note->notify_staff($e->requestor->id);
    $e->create_action_hold_notification($note) or return $e->die_event;
    $e->commit;
    return $note->id;
@@ -1430,14 +1593,14 @@ sub _reset_hold {
        if( $hold->capture_time and $hold->current_copy ) {
 
                my $copy = $e->retrieve_asset_copy($hold->current_copy)
-                       or return $e->event;
+                       or return $e->die_event;
 
                if( $copy->status == OILS_COPY_STATUS_ON_HOLDS_SHELF ) {
                        $logger->info("setting copy to status 'reshelving' on hold retarget");
                        $copy->status(OILS_COPY_STATUS_RESHELVING);
                        $copy->editor($e->requestor->id);
                        $copy->edit_date('now');
-                       $e->update_asset_copy($copy) or return $e->event;
+                       $e->update_asset_copy($copy) or return $e->die_event;
 
                } elsif( $copy->status == OILS_COPY_STATUS_IN_TRANSIT ) {
 
@@ -1452,7 +1615,10 @@ sub _reset_hold {
                                        $logger->info("Aborting transit [$transid] on hold [$hid] reset...");
                                        my $evt = OpenILS::Application::Circ::Transit::__abort_transit($e, $trans, $copy, 1);
                                        $logger->info("Transit abort completed with result $evt");
-                                       return $evt unless "$evt" eq 1;
+                                       unless ("$evt" eq 1) {
+                        $e->rollback;
+                                           return $evt;
+                    }
                                }
                        }
                }
@@ -1463,7 +1629,7 @@ sub _reset_hold {
        $hold->clear_shelf_time;
        $hold->clear_shelf_expire_time;
 
-       $e->update_action_hold_request($hold) or return $e->event;
+       $e->update_action_hold_request($hold) or return $e->die_event;
        $e->commit;
 
        $U->storagereq(
@@ -1585,8 +1751,8 @@ sub fetch_captured_holds {
        my( $self, $conn, $auth, $org ) = @_;
 
        my $e = new_editor(authtoken => $auth);
-       return $e->event unless $e->checkauth;
-       return $e->event unless $e->allowed('VIEW_HOLD'); # XXX rely on editor perm
+       return $e->die_event unless $e->checkauth;
+       return $e->die_event unless $e->allowed('VIEW_HOLD'); # XXX rely on editor perm
 
        $org ||= $e->requestor->ws_ou;
 
@@ -1639,6 +1805,84 @@ sub fetch_captured_holds {
 }
 
 __PACKAGE__->register_method(
+    method    => "print_expired_holds_stream",
+    api_name  => "open-ils.circ.captured_holds.expired.print.stream",
+    stream    => 1
+);
+
+sub print_expired_holds_stream {
+    my ($self, $client, $auth, $params) = @_;
+
+    # No need to check specific permissions: we're going to call another method
+    # that will do that.
+    my $e = new_editor("authtoken" => $auth);
+    return $e->die_event unless $e->checkauth;
+
+    delete($$params{org_id}) unless (int($$params{org_id}));
+    delete($$params{limit}) unless (int($$params{limit}));
+    delete($$params{offset}) unless (int($$params{offset}));
+    delete($$params{chunk_size}) unless (int($$params{chunk_size}));
+    delete($$params{chunk_size}) if  ($$params{chunk_size} && $$params{chunk_size} > 50); # keep the size reasonable
+    $$params{chunk_size} ||= 10;
+
+    $$params{org_id} = (defined $$params{org_id}) ? $$params{org_id}: $e->requestor->ws_ou;
+
+    my @hold_ids = $self->method_lookup(
+        "open-ils.circ.captured_holds.id_list.expired_on_shelf.retrieve"
+    )->run($auth, $params->{"org_id"});
+
+    if (!@hold_ids) {
+        $e->disconnect;
+        return;
+    } elsif (defined $U->event_code($hold_ids[0])) {
+        $e->disconnect;
+        return $hold_ids[0];
+    }
+
+    $logger->info("about to stream back up to " . scalar(@hold_ids) . " expired holds");
+
+    while (@hold_ids) {
+        my @hid_chunk = splice @hold_ids, 0, $params->{"chunk_size"};
+
+        my $result_chunk = $e->json_query({
+            "select" => {
+                "acp" => ["barcode"],
+                "au" => [qw/
+                    first_given_name second_given_name family_name alias
+                /],
+                "acn" => ["label"],
+                "bre" => ["marc"],
+                "acpl" => ["name"]
+            },
+            "from" => {
+                "ahr" => {
+                    "acp" => {
+                        "field" => "id", "fkey" => "current_copy",
+                        "join" => {
+                            "acn" => {
+                                "field" => "id", "fkey" => "call_number",
+                                "join" => {
+                                    "bre" => {
+                                        "field" => "id", "fkey" => "record"
+                                    }
+                                }
+                            },
+                            "acpl" => {"field" => "id", "fkey" => "location"}
+                        }
+                    },
+                    "au" => {"field" => "id", "fkey" => "usr"}
+                }
+            },
+            "where" => {"+ahr" => {"id" => \@hid_chunk}}
+        }) or return $e->die_event;
+        $client->respond($result_chunk);
+    }
+
+    $e->disconnect;
+    undef;
+}
+
+__PACKAGE__->register_method(
     method    => "check_title_hold_batch",
     api_name  => "open-ils.circ.title_hold.is_possible.batch",
     stream    => 1,
@@ -2248,6 +2492,7 @@ sub find_nearest_permitted_hold {
                                copy                            => $copy,
                                pickup_lib                      => $hold->pickup_lib,
                                request_lib                     => $rlib,
+                               retarget                        => 1
                        } 
                );
 
@@ -2447,6 +2692,7 @@ sub uber_hold_impl {
         patron_first   => $user->first_given_name,
         patron_last    => $user->family_name,
         patron_barcode => $card->barcode,
+        patron_alias   => $user->alias,
         %$details
     };
 }
@@ -2510,6 +2756,86 @@ sub find_hold_mvr {
        return ( $U->record_to_mvr($title), $volume, $copy, $issuance );
 }
 
+__PACKAGE__->register_method(
+    method    => 'clear_shelf_cache',
+    api_name  => 'open-ils.circ.hold.clear_shelf.get_cache',
+    stream    => 1,
+    signature => {
+        desc => q/
+            Returns the holds processed with the given cache key
+        /
+    }
+);
+
+sub clear_shelf_cache {
+    my($self, $client, $auth, $cache_key, $chunk_size) = @_;
+    my $e = new_editor(authtoken => $auth, xact => 1);
+    return $e->die_event unless $e->checkauth and $e->allowed('VIEW_HOLD');
+
+    $chunk_size ||= 25;
+    my $hold_data = OpenSRF::Utils::Cache->new('global')->get_cache($cache_key);
+
+    if (!$hold_data) {
+        $logger->info("no hold data found in cache"); # XXX TODO return event
+        $e->rollback;
+        return undef;
+    }
+
+    my $maximum = 0;
+    foreach (keys %$hold_data) {
+        $maximum += scalar(@{ $hold_data->{$_} });
+    }
+    $client->respond({"maximum" => $maximum, "progress" => 0});
+
+    for my $action (sort keys %$hold_data) {
+        while (@{$hold_data->{$action}}) {
+            my @hid_chunk = splice @{$hold_data->{$action}}, 0, $chunk_size;
+
+            my $result_chunk = $e->json_query({
+                "select" => {
+                    "acp" => ["barcode"],
+                    "au" => [qw/
+                        first_given_name second_given_name family_name alias
+                    /],
+                    "acn" => ["label"],
+                    "bre" => ["marc"],
+                    "acpl" => ["name"],
+                    "ahr" => ["id"]
+                },
+                "from" => {
+                    "ahr" => {
+                        "acp" => {
+                            "field" => "id", "fkey" => "current_copy",
+                            "join" => {
+                                "acn" => {
+                                    "field" => "id", "fkey" => "call_number",
+                                    "join" => {
+                                        "bre" => {
+                                            "field" => "id", "fkey" => "record"
+                                        }
+                                    }
+                                },
+                                "acpl" => {"field" => "id", "fkey" => "location"}
+                            }
+                        },
+                        "au" => {"field" => "id", "fkey" => "usr"}
+                    }
+                },
+                "where" => {"+ahr" => {"id" => \@hid_chunk}}
+            }, {"substream" => 1}) or return $e->die_event;
+
+            $client->respond([
+                map {
+                    +{"action" => $action, "hold_details" => $_}
+                } @$result_chunk
+            ]);
+        }
+    }
+
+    $e->rollback;
+    return undef;
+}
+
 
 __PACKAGE__->register_method(
     method    => 'clear_shelf_process',
@@ -2532,6 +2858,7 @@ sub clear_shelf_process {
 
        my $e = new_editor(authtoken=>$auth, xact => 1);
        $e->checkauth or return $e->die_event;
+       my $cache = OpenSRF::Utils::Cache->new('global');
 
     $org_id ||= $e->requestor->ws_ou;
        $e->allowed('UPDATE_HOLD', $org_id) or return $e->die_event;
@@ -2544,13 +2871,16 @@ sub clear_shelf_process {
             pickup_lib        => $org_id,
             cancel_time       => undef,
             fulfillment_time  => undef,
-            shelf_time        => {'!=' => undef}
+            shelf_time        => {'!=' => undef},
+            capture_time      => {'!=' => undef},
+            current_copy      => {'!=' => undef},
         },
         { idlist => 1 }
     );
 
-
     my @holds;
+    my $chunk_size = 25; # chunked status updates
+    my $counter = 0;
     for my $hold_id (@$hold_ids) {
 
         $logger->info("Clear shelf processing hold $hold_id");
@@ -2577,51 +2907,47 @@ sub clear_shelf_process {
         }
 
         push(@holds, $hold);
+        $client->respond({maximum => scalar(@holds), progress => $counter}) if ( (++$counter % $chunk_size) == 0);
     }
 
     if ($e->commit) {
 
+        my %cache_data = (
+            hold => [],
+            transit => [],
+            shelf => []
+        );
+
         for my $hold (@holds) {
 
             my $copy = $hold->current_copy;
-
             my ($alt_hold) = __PACKAGE__->find_nearest_permitted_hold($e, $copy, $e->requestor, 1);
 
             if($alt_hold) {
 
-                # copy is needed for a hold
-                $client->respond({action => 'hold', copy => $copy, hold_id => $hold->id});
+                push(@{$cache_data{hold}}, $hold->id); # copy is needed for a hold
 
             } elsif($copy->circ_lib != $e->requestor->ws_ou) {
 
-                # copy needs to transit
-                $client->respond({action => 'transit', copy => $copy, hold_id => $hold->id});
+                push(@{$cache_data{transit}}, $hold->id); # copy needs to transit
 
             } else {
 
-                # copy needs to go back to the shelf
-                $client->respond({action => 'shelf', copy => $copy, hold_id => $hold->id});
+                push(@{$cache_data{shelf}}, $hold->id); # copy needs to go back to the shelf
             }
         }
 
-        # tell the client we're done
-        $client->respond_complete;
-
-        # fire off the hold cancelation trigger
-        my $trigger = OpenSRF::AppSession->connect('open-ils.trigger');
-
-        for my $hold (@holds) {
-
-            my $req = $trigger->request(
-                'open-ils.trigger.event.autocreate', 
-                'hold_request.cancel.expire_holds_shelf', 
-                $hold, $org_id);
+        my $cache_key = md5_hex(time . $$ . rand());
+        $logger->info("clear_shelf_cache: storing under $cache_key");
+        $cache->put_cache($cache_key, \%cache_data, 7200); # TODO: 2 hours.  configurable?
 
-            # wait for response so don't flood the service
-            $req->recv;
-        }
+        # tell the client we're done
+        $client->respond_complete({cache_key => $cache_key});
 
-        $trigger->disconnect;
+        # fire off the hold cancelation trigger and wait for response so don't flood the service
+        $U->create_events_for_hook(
+            'hold_request.cancel.expire_holds_shelf', 
+            $_, $org_id, undef, undef, 1) for @holds;
 
     } else {
         # tell the client we're done
@@ -2858,7 +3184,7 @@ sub change_hold_title {
     my( $self, $client, $auth, $new_bib_id, $bib_ids ) = @_;
 
     my $e = new_editor(authtoken=>$auth, xact=>1);
-    return $e->event unless $e->checkauth;
+    return $e->die_event unless $e->checkauth;
 
     my $holds = $e->search_action_hold_request(
         [
index 7dad749..96ca5d2 100644 (file)
@@ -1108,6 +1108,7 @@ sub staged_search {
     my $search_duration;
     my $user_offset = $search_hash->{offset} ||  0; # user-specified offset
     my $user_limit  = $search_hash->{limit}  || 10;
+    my $ignore_facet_classes  = $search_hash->{ignore_facet_classes};
     $user_offset = ($user_offset >= 0) ? $user_offset :  0;
     $user_limit  = ($user_limit  >= 0) ? $user_limit  : 10;
 
@@ -1262,7 +1263,7 @@ sub staged_search {
         }
     );
 
-    cache_facets($facet_key, $new_ids, $IAmMetabib) if $docache;
+    cache_facets($facet_key, $new_ids, $IAmMetabib, $ignore_facet_classes) if $docache;
 
     return undef;
 }
@@ -1368,10 +1369,14 @@ __PACKAGE__->register_method(
 
 sub cache_facets {
     # add facets for this search to the facet cache
-    my($key, $results, $metabib) = @_;
+    my($key, $results, $metabib, $ignore) = @_;
     my $data = $cache->get_cache($key);
     $data ||= {};
 
+    if (!ref($ignore)) {
+        $ignore = ['identifier']; # ignore the identifier class by default
+    }
+
     return undef unless (@$results);
 
     # The query we're constructing
@@ -1398,11 +1403,13 @@ sub cache_facets {
             },
             from    => {
                 mfae => {
-                    mmrsm => { field => 'source', fkey => 'source' }
+                    mmrsm => { field => 'source', fkey => 'source' },
+                    cmf   => { field => 'id', fkey => 'field' }
                 }
             },
             where   => {
-                '+mmrsm' => { $count_field => $results }
+                '+mmrsm' => { $count_field => $results },
+                '+cmf'   => { field_class => { 'not in' => $ignore } }
             }
         }
     );
index dca9fe4..39eb01b 100644 (file)
@@ -10,7 +10,7 @@ use base qw/asset/;
 
 __PACKAGE__->table( 'asset_copy_location' );
 __PACKAGE__->columns( Primary => qw/id/ );
-__PACKAGE__->columns( Essential => qw/name owning_lib holdable hold_verify opac_visible circulate/ );
+__PACKAGE__->columns( Essential => qw/name owning_lib holdable hold_verify opac_visible circulate label_prefix label_suffix/ );
 
 #-------------------------------------------------------------------------------
 package asset::copy_location_order;
@@ -35,7 +35,7 @@ use base qw/asset/;
 __PACKAGE__->table( 'asset_call_number' );
 __PACKAGE__->columns( Primary => qw/id/ );
 __PACKAGE__->columns( Essential => qw/record label creator create_date editor
-                                  edit_date record label owning_lib deleted/ );
+                                  edit_date record label owning_lib deleted label_class label_sortkey/ );
 
 #-------------------------------------------------------------------------------
 package asset::call_number_note;
@@ -56,7 +56,7 @@ __PACKAGE__->columns( Essential => qw/call_number barcode creator create_date ed
                                   fine_level circulate deposit price ref opac_visible
                                   circ_as_type circ_modifier deposit_amount location mint_condition
                                   holdable dummy_title dummy_author deleted alert_message
-                                  age_protect floating/ );
+                                  age_protect floating cost status_changed_time/ );
 
 #-------------------------------------------------------------------------------
 package asset::stat_cat;
index 7247740..f56e1e5 100644 (file)
@@ -16,7 +16,7 @@ use base qw/permission/;
 __PACKAGE__->table('permission_grp_tree');
 __PACKAGE__->columns(Primary => qw/id/);
 __PACKAGE__->columns(Essential => qw/name parent description perm_interval
-                                    application_perm usergroup/);
+                                    application_perm usergroup hold_priority/);
 #-------------------------------------------------------------------------------
 package permission::usr_grp_map;
 use base qw/permission/;
index 3e8f382..7879cb7 100644 (file)
@@ -46,5 +46,16 @@ __PACKAGE__->columns( Essential => qw/call_number barcode creator create_date ed
                                   holdable dummy_title dummy_author deleted alert_message label
                                   age_protect floating label_sort_key contents/ );
 
+#-------------------------------------------------------------------------------
+package serial::record_entry;
+use base qw/serial/;
+
+__PACKAGE__->table( 'serial_record_entry' );
+__PACKAGE__->columns( Primary => qw/id/ );
+__PACKAGE__->columns( Essential => qw/active record create_date creator
+                        deleted edit_date editor id last_xact_id marc source
+                        owning_lib/ );
+
+
 1;
 
index ba596fb..94db0a4 100644 (file)
@@ -419,11 +419,11 @@ sub toSQL {
     }
 
     if (($filters{preferred_language} || $self->QueryParser->default_preferred_language) && ($filters{preferred_language_multiplier} || $self->QueryParser->default_preferred_language_multiplier)) {
-        $rel = "($rel * CASE WHEN FIRST(mrd.item_lang) = ". $self->QueryParser->quote_value( $filters{preferred_language} ? $filters{preferred_language} : $self->QueryParser->default_preferred_language ) . " THEN ";
-        $rel .= $filters{preferred_language_multiplier} ? $filters{preferred_language_multiplier} : $self->QueryParser->default_preferred_language_multiplier;
-        $rel .= " ELSE 1 END)";
+        my $pl = $self->QueryParser->quote_value( $filters{preferred_language} ? $filters{preferred_language} : $self->QueryParser->default_preferred_language );
+        my $plw = $filters{preferred_language_multiplier} ? $filters{preferred_language_multiplier} : $self->QueryParser->default_preferred_language_multiplier;
+        $rel = "($rel * COALESCE( NULLIF( FIRST(mrd.item_lang) = $pl , FALSE )::INT * $plw, 1))";
     }
-    $rel .= "::NUMERIC";
+    $rel .= '::NUMERIC';
 
     for my $f ( qw/audience vr_format item_type item_form lit_form language bib_level/ ) {
         my $col = $f;
@@ -449,44 +449,22 @@ sub toSQL {
     $desc = 'DESC' if ($self->find_modifier('descending'));
 
     if ($sort_filter eq 'rel') { # relevance ranking flips sort dir
-         if ($desc eq  'ASC') {
+        if ($desc eq  'ASC') {
             $desc = 'DESC';
         } else {
             $desc = 'ASC';
         }
     } else {
         if ($sort_filter eq 'title') {
-            my $default = $desc eq 'DESC' ? '       ' : 'zzzzzz';
-            $rank = <<"            SQL";
-( COALESCE( FIRST ((
-                SELECT  frt.value
-                  FROM  metabib.full_rec frt
-                  WHERE frt.record = m.source
-                    AND frt.tag = 'tnf'
-                    AND frt.subfield = 'a'
-                  LIMIT 1
-        )),'$default'))::TEXT
-            SQL
+            $rank = "FIRST((SELECT frt.value FROM metabib.full_rec frt WHERE frt.record = m.source AND frt.tag = 'tnf' AND frt.subfield = 'a' LIMIT 1))";
         } elsif ($sort_filter eq 'pubdate') {
-            my $default = $desc eq 'DESC' ? '0' : '99999';
-            $rank = "COALESCE( FIRST(NULLIF(LPAD(REGEXP_REPLACE(mrd.date1, E'\\\\D+', '0', 'g'),4,'0'),'0000')), '$default' )::INT";
+            $rank = "FIRST(mrd.date1)::NUMERIC";
         } elsif ($sort_filter eq 'create_date') {
-            $rank = "( FIRST (( SELECT create_date FROM biblio.record_entry rbr WHERE rbr.id = m.source)) )::TIMESTAMPTZ";
+            $rank = "FIRST((SELECT create_date FROM biblio.record_entry rbr WHERE rbr.id = m.source))";
         } elsif ($sort_filter eq 'edit_date') {
-            $rank = "( FIRST (( SELECT edit_date FROM biblio.record_entry rbr WHERE rbr.id = m.source)) )::TIMESTAMPTZ";
+            $rank = "FIRST((SELECT edit_date FROM biblio.record_entry rbr WHERE rbr.id = m.source))";
         } elsif ($sort_filter eq 'author') {
-            my $default = $desc eq 'DESC' ? '       ' : 'zzzzzz';
-            $rank = <<"            SQL"
-( COALESCE( FIRST ((
-                SELECT  LTRIM(fra.value)
-                  FROM  metabib.full_rec fra
-                  WHERE fra.record = m.source
-                    AND fra.tag LIKE '1%'
-                    AND fra.subfield = 'a'
-                  ORDER BY fra.tag::text::int
-                  LIMIT 1
-        )),'$default'))::TEXT
-            SQL
+            $rank = "FIRST((SELECT fra.value FROM metabib.full_rec fra WHERE fra.record = m.source AND fra.tag LIKE '1%' AND fra.subfield = 'a' ORDER BY fra.tag LIMIT 1))";
         } else {
             # default to rel ranking
             $rank = $rel;
@@ -532,7 +510,7 @@ SELECT  $key AS id,
         ARRAY_ACCUM(DISTINCT m.source) AS records,
         $rel AS rel,
         $rank AS rank, 
-        COALESCE( FIRST(NULLIF(LPAD(REGEXP_REPLACE(mrd.date1, E'\\\\D+', '0', 'g'),4,'0'),'0000')), '0' )::INT AS tie_break
+        FIRST(mrd.date1) AS tie_break
   FROM  metabib.metarecord_source_map m
         JOIN metabib.rec_descriptor mrd ON (m.source = mrd.record)
         $$flat_plan{from}
@@ -550,7 +528,7 @@ SELECT  $key AS id,
         $bib_level
         AND $$flat_plan{where}
   GROUP BY 1
-  ORDER BY 4 $desc, 5 DESC, 3 DESC
+  ORDER BY 4 $desc NULLS LAST, 5 DESC NULLS LAST, 3 DESC
   LIMIT $core_limit
 SQL
 
@@ -570,13 +548,13 @@ sub rel_bump {
     return '' if (!@$only_atoms);
 
     if ($bump eq 'first_word') {
-        return " /* first_word */ COALESCE(NULLIF( (naco_normalize(".$node->table_alias.".value) ~ ('^'||naco_normalize(".$self->QueryParser->quote_value($only_atoms->[0]->content).")))::BOOL::INT, 0 ) * $multiplier, 1)";
+        return " /* first_word */ COALESCE(NULLIF( (naco_normalize(".$node->table_alias.".value) ~ ('^'||naco_normalize(".$self->QueryParser->quote_value($only_atoms->[0]->content)."))), FALSE )::INT * $multiplier, 1)";
     } elsif ($bump eq 'full_match') {
         return " /* full_match */ COALESCE(NULLIF( (naco_normalize(".$node->table_alias.".value) ~ ('^'||".
-                    join( "||' '||", map { "naco_normalize(".$self->QueryParser->quote_value($_->content).")" } @$only_atoms )."||'\$'))::BOOL::INT, 0 ) * $multiplier, 1)";
+                    join( "||' '||", map { "naco_normalize(".$self->QueryParser->quote_value($_->content).")" } @$only_atoms )."||'\$')), FALSE )::INT * $multiplier, 1)";
     } elsif ($bump eq 'word_order') {
         return " /* word_order */ COALESCE(NULLIF( (naco_normalize(".$node->table_alias.".value) ~ (".
-                    join( "||'.*'||", map { "naco_normalize(".$self->QueryParser->quote_value($_->content).")" } @$only_atoms )."))::BOOL::INT, 0 ) * $multiplier, 1)";
+                    join( "||'.*'||", map { "naco_normalize(".$self->QueryParser->quote_value($_->content).")" } @$only_atoms ).")), FALSE )::INT * $multiplier, 1)";
     }
 
     return '';
@@ -605,21 +583,24 @@ sub flatten {
                 my $node_rank = $node->rank . " * ${talias}.weight";
 
                 my $core_limit = $self->QueryParser->core_limit || 25000;
-                $from .= "\n\tLEFT JOIN (\n\t\tSELECT fe.*, fe_weight.weight /* search */\n\t\t  FROM  $table AS fe";
+                $from .= "\n\tLEFT JOIN (\n\t\tSELECT fe.*, fe_weight.weight, x.tsq /* search */\n\t\t  FROM  $table AS fe";
                 $from .= "\n\t\t\tJOIN config.metabib_field AS fe_weight ON (fe_weight.id = fe.field)";
-                $from .= "\n\t\t  WHERE fe.index_vector @@ (" .$node->tsquery . ')';
+                $from .= "\n\t\t\tJOIN (SELECT ".$node->tsquery ." AS tsq ) AS x ON (fe.index_vector @@ x.tsq)";
 
                 my @bump_fields;
                 if (@{$node->fields} > 0) {
                     @bump_fields = @{$node->fields};
-                    $from .= "\n\t\t\tAND fe_weight.field_class = ". $self->QueryParser->quote_value($node->classname) ." AND fe_weight.name IN (";
-                    $from .= join(",", map { $self->QueryParser->quote_value($_) } @{$node->fields}) . ")";
+
+                    my @field_ids;
+                    push(@field_ids, $self->QueryParser->search_field_ids_by_class( $node->classname, $_ )->[0]) for (@bump_fields);
+                    $from .= "\n\t\t\tWHERE fe_weight.id IN  (". join(',', @field_ids) .")";
 
                 } else {
                     @bump_fields = @{$self->QueryParser->search_fields->{$node->classname}};
                 }
 
-                $from .= "\n\t\tLIMIT $core_limit\n\t) AS $talias ON (m.source = $talias.source)";
+                ###$from .= "\n\t\tLIMIT $core_limit";
+                $from .= "\n\t) AS $talias ON (m.source = ${talias}.source)";
 
 
                 my %used_bumps;
@@ -638,7 +619,7 @@ sub flatten {
                 }
 
                 $where .= '(' . $talias . ".id IS NOT NULL";
-                $where .= ' AND ' . join(' AND ', map {"$talias.value ~* ".$self->QueryParser->quote_value($_)} @{$node->phrases}) if (@{$node->phrases});
+                $where .= ' AND ' . join(' AND ', map {"${talias}.value ~* ".$self->QueryParser->quote_value($_)} @{$node->phrases}) if (@{$node->phrases});
                 $where .= ')';
 
                 push @rank_list, $node_rank;
@@ -648,18 +629,16 @@ sub flatten {
                 my $table = $node->table;
                 my $talias = $node->table_alias;
 
-                $from .= "\n\tJOIN (\n\t\tSELECT * /* facet */\n\t\t  FROM metabib.facet_entry\n\t\t  WHERE ".
-                         "SUBSTRING(value,1,1024) IN (" . join(",", map { $self->QueryParser->quote_value($_) } @{$node->values}) . ")".
-                         "\n\t\t\tAND field IN (SELECT id FROM config.metabib_field WHERE field_class = ". $self->QueryParser->quote_value($node->classname) ." AND facet_field";
-
+                my @field_ids;
                 if (@{$node->fields} > 0) {
-                    $from .= " AND name IN (";
-                    $from .= join(",", map { $self->QueryParser->quote_value($_) } @{$node->fields}) . ")";
+                    push(@field_ids, $self->QueryParser->facet_field_ids_by_class( $node->classname, $_ )->[0]) for (@{$node->fields});
+                } else {
+                    @field_ids = @{ $self->QueryParser->facet_field_ids_by_class( $node->classname ) };
                 }
 
-                $from .= ")";
-
-                $from .= "\n\t\t) AS $talias ON (m.source = $talias.source)";
+                $from .= "\n\tJOIN /* facet */ metabib.facet_entry $talias ON (\n\t\tm.source = ${talias}.source\n\t\t".
+                         "AND SUBSTRING(${talias}.value,1,1024) IN (" . join(",", map { $self->QueryParser->quote_value($_) } @{$node->values}) . ")\n\t\t".
+                         "AND ${talias}.field IN (". join(',', @field_ids) . ")\n\t)";
 
                 $where .= 'TRUE';
 
@@ -836,7 +815,7 @@ sub tsquery {
 sub rank {
     my $self = shift;
     return $self->{rank} if ($self->{rank});
-    return $self->{rank} = 'rank(' . $self->table_alias . '.index_vector, ' . $self->tsquery . ')';
+    return $self->{rank} = 'rank(' . $self->table_alias . '.index_vector, ' . $self->table_alias . '.tsq)';
 }
 
 
index eff5a0a..b520cbb 100644 (file)
@@ -286,14 +286,16 @@ sub nearest_hold {
        my $fifo = shift();
 
        my $holdsort = isTrue($fifo) ?
-            "CASE WHEN h.cut_in_line IS TRUE THEN 0 ELSE 1 END, h.request_time, h.selection_depth DESC, p.prox " :
-            "p.prox, CASE WHEN h.cut_in_line IS TRUE THEN 0 ELSE 1 END, h.selection_depth DESC, h.request_time ";
+            "pgt.hold_priority, CASE WHEN h.cut_in_line IS TRUE THEN 0 ELSE 1 END, h.request_time, h.selection_depth DESC, p.prox " :
+            "p.prox, pgt.hold_priority, CASE WHEN h.cut_in_line IS TRUE THEN 0 ELSE 1 END, h.selection_depth DESC, h.request_time ";
 
        my $ids = action::hold_request->db_Main->selectcol_arrayref(<<" SQL", {}, $here, $cp, $age);
                SELECT  h.id
                  FROM  action.hold_request h
                        JOIN actor.org_unit_proximity p ON (p.from_org = ? AND p.to_org = h.pickup_lib)
                        JOIN action.hold_copy_map hm ON (hm.hold = h.id)
+                       JOIN actor.usr au ON (au.id = h.usr)
+                       JOIN permission.grp_tree pgt ON (au.profile = pgt.id)
                  WHERE hm.target_copy = ?
                        AND (AGE(NOW(),h.request_time) >= CAST(? AS INTERVAL) OR p.prox = 0)
                        AND h.capture_time IS NULL
@@ -1271,11 +1273,12 @@ sub new_hold_copy_targeter {
                        $$prox_list[0] =
                        [
                                grep {
-                                       ''.$_->circ_lib eq $pu_lib
+                                       ''.$_->circ_lib eq $pu_lib &&
+                    ( $_->status == 0 || $_->status == 7 )
                                } @good_copies
                        ];
 
-                       $all_copies = [grep {''.$_->circ_lib ne $pu_lib } @good_copies];
+                       $all_copies = [grep { $_->status == 0 || $_->status == 7 } grep {''.$_->circ_lib ne $pu_lib } @good_copies];
                        # $all_copies is now a list of copies not at the pickup library
 
                        my $best = choose_nearest_copy($hold, $prox_list);
@@ -1294,7 +1297,7 @@ sub new_hold_copy_targeter {
                                        my %circ_lib_map =  map { (''.$_->circ_lib => 1) } @$all_copies;
                                        my $circ_lib_list = [keys %circ_lib_map];
        
-                                       my $cstore = OpenSRF::AppSession->connect('open-ils.cstore');
+                                       my $cstore = OpenSRF::AppSession->create('open-ils.cstore');
        
                                        # Grab the "biggest" loop for this hold so far
                                        my $current_loop = $cstore->request(
@@ -1639,162 +1642,6 @@ __PACKAGE__->register_method(
 my $locations;
 my $statuses;
 my %cache = (titles => {}, cns => {});
-sub hold_copy_targeter {
-       my $self = shift;
-       my $client = shift;
-       my $check_expire = shift;
-       my $one_hold = shift;
-
-       $self->{user_filter} = OpenSRF::AppSession->create('open-ils.circ');
-       $self->{user_filter}->connect;
-       $self->{client} = $client;
-
-       my $time = time;
-       $check_expire ||= '12h';
-       $check_expire = interval_to_seconds( $check_expire );
-
-       my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = gmtime(time() - $check_expire);
-       $year += 1900;
-       $mon += 1;
-       my $expire_threshold = sprintf(
-               '%s-%0.2d-%0.2dT%0.2d:%0.2d:%0.2d-00',
-               $year, $mon, $mday, $hour, $min, $sec
-       );
-
-
-       $statuses ||= [ config::copy_status->search(holdable => 't') ];
-
-       $locations ||= [ asset::copy_location->search(holdable => 't') ];
-
-       my $holds;
-
-       %cache = (titles => {}, cns => {});
-
-       try {
-               if ($one_hold) {
-                       $holds = [ action::hold_request->search(id => $one_hold) ];
-               } else {
-                       $holds = [ action::hold_request->search_where(
-                                                       { capture_time => undef,
-                                                         prev_check_time => { '<=' => $expire_threshold },
-                                                       },
-                                                       { order_by => 'request_time,prev_check_time' } ) ];
-                       push @$holds, action::hold_request->search_where(
-                                                       { capture_time => undef,
-                                                         prev_check_time => undef,
-                                                       },
-                                                       { order_by => 'request_time' } );
-               }
-       } catch Error with {
-               my $e = shift;
-               die "Could not retrieve uncaptured hold requests:\n\n$e\n";
-       };
-
-       for my $hold (@$holds) {
-               try {
-                       #action::hold_request->db_Main->begin_work;
-                       if ($self->method_lookup('open-ils.storage.transaction.current')->run) {
-                               $client->respond("Cleaning up after previous transaction\n");
-                               $self->method_lookup('open-ils.storage.transaction.rollback')->run;
-                       }
-                       $self->method_lookup('open-ils.storage.transaction.begin')->run( $client );
-                       $client->respond("Processing hold ".$hold->id."...\n");
-
-                       my $copies;
-
-                       $copies = $self->metarecord_hold_capture($hold) if ($hold->hold_type eq 'M');
-                       $self->{client}->status( new OpenSRF::DomainObject::oilsContinueStatus );
-
-                       $copies = $self->title_hold_capture($hold) if ($hold->hold_type eq 'T');
-                       $self->{client}->status( new OpenSRF::DomainObject::oilsContinueStatus );
-                       
-                       $copies = $self->volume_hold_capture($hold) if ($hold->hold_type eq 'V');
-                       $self->{client}->status( new OpenSRF::DomainObject::oilsContinueStatus );
-                       
-                       $copies = $self->copy_hold_capture($hold) if ($hold->hold_type eq 'C');
-
-                       unless (ref $copies || !@$copies) {
-                               $client->respond("\tNo copies available for targeting at all!\n");
-                       }
-
-                       my @good_copies;
-                       for my $c (@$copies) {
-                               next if ( grep {$c->id == $hold->current_copy} @good_copies);
-                               push @good_copies, $c if ($c);
-                       }
-
-                       $client->respond("\t".scalar(@good_copies)." (non-current) copies available for targeting...\n");
-
-                       my $old_best = $hold->current_copy;
-                       $hold->update({ current_copy => undef });
-       
-                       if (!scalar(@good_copies)) {
-                               $client->respond("\tNo (non-current) copies available to fill the hold.\n");
-                               if ( $old_best && grep {$_->id == $hold->current_copy} @$copies ) {
-                                       $client->respond("\tPushing current_copy back onto the targeting list\n");
-                                       push @good_copies, asset::copy->retrieve( $old_best );
-                               } else {
-                                       $client->respond("\tcurrent_copy is no longer available for targeting... NEXT HOLD, PLEASE!\n");
-                                       next;
-                               }
-                       }
-
-                       my $prox_list;
-                       $$prox_list[0] = [grep {$_->circ_lib == $hold->pickup_lib } @good_copies];
-                       $copies = [grep {$_->circ_lib != $hold->pickup_lib } @good_copies];
-
-                       my $best = choose_nearest_copy($hold, $prox_list);
-
-                       if (!$best) {
-                               $prox_list = create_prox_list( $self, $hold->pickup_lib, $copies );
-                               $best = choose_nearest_copy($hold, $prox_list);
-                       }
-
-                       if ($old_best) {
-                               # hold wasn't fulfilled, record the fact
-                       
-                               $client->respond("\tHold was not (but should have been) fulfilled by ".$old_best->id.".\n");
-                               action::unfulfilled_hold_list->create(
-                                               { hold => ''.$hold->id,
-                                                 current_copy => ''.$old_best->id,
-                                                 circ_lib => ''.$old_best->circ_lib,
-                                               });
-                       }
-
-                       if ($best) {
-                               $hold->update( { current_copy => ''.$best->id } );
-                               $client->respond("\tTargeting copy ".$best->id." for hold fulfillment.\n");
-                       }
-
-                       $hold->update( { prev_check_time => 'now' } );
-                       $client->respond("\tUpdating hold ".$hold->id." with new 'current_copy' for hold fulfillment.\n");
-
-                       $client->respond("\tProcessing of hold ".$hold->id." complete.\n");
-                       $self->method_lookup('open-ils.storage.transaction.commit')->run;
-
-                       #action::hold_request->dbi_commit;
-
-               } otherwise {
-                       my $e = shift;
-                       $log->error("Processing of hold failed:  $e");
-                       $client->respond("\tProcessing of hold failed!.\n\t\t$e\n");
-                       $self->method_lookup('open-ils.storage.transaction.rollback')->run;
-                       #action::hold_request->dbi_rollback;
-               };
-       }
-
-       $self->{user_filter}->disconnect;
-       $self->{user_filter}->finish;
-       delete $$self{user_filter};
-       return undef;
-}
-#__PACKAGE__->register_method(
-#      api_name        => 'open-ils.storage.action.hold_request.copy_targeter',
-#      api_level       => 0,
-#      stream          => 1,
-#      method          => 'hold_copy_targeter',
-#);
-
 
 sub copy_hold_capture {
        my $self = shift;
@@ -1864,21 +1711,24 @@ sub choose_nearest_copy {
        for my $p ( 0 .. int( scalar(@$prox_list) - 1) ) {
                next unless (ref $$prox_list[$p]);
 
-               my @capturable = grep { $_->status == 0 || $_->status == 7 } @{ $$prox_list[$p] };
+               my @capturable = @{ $$prox_list[$p] };
                next unless (@capturable);
 
                my $rand = int(rand(scalar(@capturable)));
-               while (my ($c) = splice(@capturable,$rand)) {
-                       return $c if ( OpenILS::Utils::PermitHold::permit_copy_hold(
+               my %seen = ();
+               while (my ($c) = splice(@capturable, $rand, 1)) {
+                       return $c if !exists($seen{$c->id}) && ( OpenILS::Utils::PermitHold::permit_copy_hold(
                                { title => $c->call_number->record->to_fieldmapper,
                                  title_descriptor => $c->call_number->record->record_descriptor->next->to_fieldmapper,
                                  patron => $hold->usr->to_fieldmapper,
                                  copy => $c->to_fieldmapper,
                                  requestor => $hold->requestor->to_fieldmapper,
                                  request_lib => $hold->request_lib->to_fieldmapper,
-                                  pickup_lib => $hold->pickup_lib->id,
+                                 pickup_lib => $hold->pickup_lib->id,
+                                 retarget => 1
                                }
                        ));
+                       $seen{$c->id}++;
 
                        last unless(@capturable);
                        $rand = int(rand(scalar(@capturable)));
@@ -1898,15 +1748,16 @@ sub create_prox_list {
                my ($prox) = $self->method_lookup('open-ils.storage.asset.copy.proximity')->run( $cp, $lib );
                next unless (defined($prox));
 
+        my $copy_circ_lib = ''.$cp->circ_lib;
                # Fetch the weighting value for hold targeting, defaulting to 1
-               $self->{target_weight}{$lib} ||= $actor->request(
-                       'open-ils.actor.ou_setting.ancestor_default' => $lib.'' => 'circ.holds.org_unit_target_weight'
+               $self->{target_weight}{$copy_circ_lib} ||= $actor->request(
+                       'open-ils.actor.ou_setting.ancestor_default' => $copy_circ_lib.'' => 'circ.holds.org_unit_target_weight'
                )->gather(1);
-        $self->{target_weight}{$lib} = $self->{target_weight}{$lib}{value} if (ref $self->{target_weight}{$lib});
-        $self->{target_weight}{$lib} ||= 1;
+        $self->{target_weight}{$copy_circ_lib} = $self->{target_weight}{$copy_circ_lib}{value} if (ref $self->{target_weight}{$copy_circ_lib});
+        $self->{target_weight}{$copy_circ_lib} ||= 1;
 
                $prox_list[$prox] = [] unless defined($prox_list[$prox]);
-               for my $w ( 1 .. $self->{target_weight}{$lib} ) {
+               for my $w ( 1 .. $self->{target_weight}{$copy_circ_lib} ) {
                        push @{$prox_list[$prox]}, $cp;
                }
        }
index 8f49f4b..77b213d 100644 (file)
@@ -230,9 +230,9 @@ sub cn_browse_pagedown {
                        $table cn
                where
                        not deleted
-                       and (upper(label) > ? or ( cn.id > ? and upper(label) = ? ))
+                       and (oils_text_as_bytea(upper(label)) > ? or ( cn.id > ? and oils_text_as_bytea(upper(label)) = ? ))
                        and owning_lib in ($orgs)
-               order by upper(label), 4, 2
+               order by oils_text_as_bytea(upper(label)), 4, 2
                limit $size;
        SQL
 
@@ -285,9 +285,9 @@ sub cn_browse_pageup {
                                $table cn
                        where
                                not deleted
-                               and (upper(label) < ? or ( cn.id < ? and upper(label) = ? ))
+                               and (oils_text_as_bytea(upper(label)) < ? or ( cn.id < ? and oils_text_as_bytea(upper(label)) = ? ))
                                and owning_lib in ($orgs)
-                       order by upper(label) desc, 4 desc, 2 desc
+                       order by oils_text_as_bytea(upper(label)) desc, 4 desc, 2 desc
                        limit $size
                ) as bar
                order by 1,4,2;
@@ -343,9 +343,9 @@ sub cn_browse_target {
                                $table cn
                        where
                                not deleted
-                               and upper(label) < ?
+                               and oils_text_as_bytea(upper(label)) < ?
                                and owning_lib in ($orgs)
-                       order by upper(label) desc, 4 desc, 2 desc
+                       order by oils_text_as_bytea(upper(label)) desc, 4 desc, 2 desc
                        limit $topsize
                ) as bar
                order by 1,4,2;
@@ -361,9 +361,9 @@ sub cn_browse_target {
                        $table cn
                where
                        not deleted
-                       and upper(label) >= ?
+                       and oils_text_as_bytea(upper(label)) >= ?
                        and owning_lib in ($orgs)
-               order by upper(label),4,2
+               order by oils_text_as_bytea(upper(label)),4,2
                limit $bottomsize;
        SQL
 
index 3f5127c..62091ce 100644 (file)
@@ -33,11 +33,7 @@ sub validate_tag {
        for my $t ( @tags ) {
                for my $search ( @searches ) {
                        my $sf = $$search{subfield};
-                       my $term = NFD(lc($$search{term}));
-
-                       $term =~ s/\pM+//sgo;
-                       $term =~ s/\pC+//sgo;
-                       $term =~ s/\W+$//o;
+                       my $term = OpenILS::Application::Storage::FTS::naco_normalize($$search{term}, $sf);
 
                        $tag = [$tag] if (!ref($tag));
 
index e13a7b3..413bf8f 100644 (file)
@@ -57,7 +57,7 @@ sub ordered_records_from_metarecord {
                        item_type,
                        item_form,
                        quality,
-                       FIRST(COALESCE(LTRIM(SUBSTR( value, COALESCE(SUBSTRING(ind2 FROM '\\\\d+'),'0')::INT + 1 )),'zzzzzzzz')) AS title
+                       FIRST(COALESCE(LTRIM(SUBSTR( value, COALESCE(SUBSTRING(ind2 FROM E'\\\\d+'),'0')::INT + 1 )),'zzzzzzzz')) AS title
                FROM    (
                        SELECT  rd.record,
                                rd.item_type,
@@ -556,7 +556,7 @@ sub biblio_multi_search_full_rec {
        if (lc($sort) eq 'pubdate') {
                $rank = <<"             RANK";
                        ( FIRST ((
-                               SELECT  COALESCE(SUBSTRING(frp.value FROM '\\\\d+'),'9999')::INT
+                               SELECT  COALESCE(SUBSTRING(frp.value FROM E'\\\\d+'),'9999')::INT
                                  FROM  $metabib_full_rec frp
                                  WHERE frp.record = f.record
                                        AND frp.tag = '260'
@@ -575,7 +575,7 @@ sub biblio_multi_search_full_rec {
        } elsif (lc($sort) eq 'title') {
                $rank = <<"             RANK";
                        ( FIRST ((
-                               SELECT  COALESCE(LTRIM(SUBSTR( frt.value, COALESCE(SUBSTRING(frt.ind2 FROM '\\\\d+'),'0')::INT + 1 )),'zzzzzzzz')
+                               SELECT  COALESCE(LTRIM(SUBSTR( frt.value, COALESCE(SUBSTRING(frt.ind2 FROM E'\\\\d+'),'0')::INT + 1 )),'zzzzzzzz')
                                  FROM  $metabib_full_rec frt
                                  WHERE frt.record = f.record
                                        AND frt.tag = '245'
@@ -1196,7 +1196,7 @@ sub postfilter_search_class_fts {
        if (lc($sort) eq 'pubdate') {
                $rank = <<"             RANK";
                        ( FIRST ((
-                               SELECT  COALESCE(SUBSTRING(frp.value FROM '\\\\d+'),'$number_default_sort')::INT
+                               SELECT  COALESCE(SUBSTRING(frp.value FROM E'\\\\d+'),'$number_default_sort')::INT
                                  FROM  $metabib_full_rec frp
                                  WHERE frp.record = mr.master_record
                                        AND frp.tag = '260'
@@ -1215,7 +1215,7 @@ sub postfilter_search_class_fts {
        } elsif (lc($sort) eq 'title') {
                $rank = <<"             RANK";
                        ( FIRST ((
-                               SELECT  COALESCE(LTRIM(SUBSTR( frt.value, COALESCE(SUBSTRING(frt.ind2 FROM '\\\\d+'),'0')::INT + 1 )),'$string_default_sort')
+                               SELECT  COALESCE(LTRIM(SUBSTR( frt.value, COALESCE(SUBSTRING(frt.ind2 FROM E'\\\\d+'),'0')::INT + 1 )),'$string_default_sort')
                                  FROM  $metabib_full_rec frt
                                  WHERE frt.record = mr.master_record
                                        AND frt.tag = '245'
@@ -1668,7 +1668,7 @@ sub postfilter_search_multi_class_fts {
 
        my $secondary_sort = <<"        SORT";
                ( FIRST ((
-                       SELECT  COALESCE(LTRIM(SUBSTR( sfrt.value, COALESCE(SUBSTRING(sfrt.ind2 FROM '\\\\d+'),'0')::INT + 1 )),'$string_default_sort')
+                       SELECT  COALESCE(LTRIM(SUBSTR( sfrt.value, COALESCE(SUBSTRING(sfrt.ind2 FROM E'\\\\d+'),'0')::INT + 1 )),'$string_default_sort')
                          FROM  $metabib_full_rec sfrt,
                                $metabib_metarecord mr
                          WHERE sfrt.record = mr.master_record
@@ -1682,7 +1682,7 @@ sub postfilter_search_multi_class_fts {
        if (lc($sort) eq 'pubdate') {
                $rank = <<"             RANK";
                        ( FIRST ((
-                               SELECT  COALESCE(SUBSTRING(frp.value FROM '\\\\d+'),'$number_default_sort')::INT
+                               SELECT  COALESCE(SUBSTRING(frp.value FROM E'\\\\d+'),'$number_default_sort')::INT
                                  FROM  $metabib_full_rec frp
                                  WHERE frp.record = mr.master_record
                                        AND frp.tag = '260'
@@ -1701,7 +1701,7 @@ sub postfilter_search_multi_class_fts {
        } elsif (lc($sort) eq 'title') {
                $rank = <<"             RANK";
                        ( FIRST ((
-                               SELECT  COALESCE(LTRIM(SUBSTR( frt.value, COALESCE(SUBSTRING(frt.ind2 FROM '\\\\d+'),'0')::INT + 1 )),'$string_default_sort')
+                               SELECT  COALESCE(LTRIM(SUBSTR( frt.value, COALESCE(SUBSTRING(frt.ind2 FROM E'\\\\d+'),'0')::INT + 1 )),'$string_default_sort')
                                  FROM  $metabib_full_rec frt
                                  WHERE frt.record = mr.master_record
                                        AND frt.tag = '245'
@@ -1711,7 +1711,7 @@ sub postfilter_search_multi_class_fts {
                RANK
                $secondary_sort = <<"           SORT";
                        ( FIRST ((
-                               SELECT  COALESCE(SUBSTRING(sfrp.value FROM '\\\\d+'),'$number_default_sort')::INT
+                               SELECT  COALESCE(SUBSTRING(sfrp.value FROM E'\\\\d+'),'$number_default_sort')::INT
                                  FROM  $metabib_full_rec sfrp
                                  WHERE sfrp.record = mr.master_record
                                        AND sfrp.tag = '260'
@@ -2166,7 +2166,7 @@ sub biblio_search_multi_class_fts {
        if (lc($sort) eq 'pubdate') {
                $rank = <<"             RANK";
                        ( FIRST ((
-                               SELECT  COALESCE(SUBSTRING(frp.value FROM '\\\\d{4}'),'$number_default_sort')::INT
+                               SELECT  COALESCE(SUBSTRING(frp.value FROM E'\\\\d{4}'),'$number_default_sort')::INT
                                  FROM  $metabib_full_rec frp
                                  WHERE frp.record = b.id
                                        AND frp.tag = '260'
@@ -2185,7 +2185,7 @@ sub biblio_search_multi_class_fts {
        } elsif (lc($sort) eq 'title') {
                $rank = <<"             RANK";
                        ( FIRST ((
-                               SELECT  COALESCE(LTRIM(SUBSTR( frt.value, COALESCE(SUBSTRING(frt.ind2 FROM '\\\\d+'),'0')::INT + 1 )),'$string_default_sort')
+                               SELECT  COALESCE(LTRIM(SUBSTR( frt.value, COALESCE(SUBSTRING(frt.ind2 FROM E'\\\\d+'),'0')::INT + 1 )),'$string_default_sort')
                                  FROM  $metabib_full_rec frt
                                  WHERE frt.record = b.id
                                        AND frt.tag = '245'
@@ -2977,7 +2977,7 @@ sub query_parser_fts {
        my $metarecord = ($self->api_name =~ /metabib/ or $query->parse_tree->find_modifier('metabib') or $query->parse_tree->find_modifier('metarecord')) ? "'t'" : "'f'";
 
        my $sth = metabib::metarecord_source_map->db_Main->prepare(<<"    SQL");
-        SELECT  *
+        SELECT  * /* bib search */
           FROM  search.query_parser_fts(
                     $param_search_ou\:\:INT,
                     $param_depth\:\:INT,
index 05ff6be..6338384 100644 (file)
@@ -442,7 +442,7 @@ sub decompose {
 
             for my $alias ( @{$pkg->search_field_aliases->{$class}{$field}} ) {
                 $alias = qr/$alias/;
-                s/\b$alias[:=]/$class\|$field:/g;
+                s/(^|\s+)$alias[:=]/$1$class\|$field:/g;
             }
         }
 
index 92edc8e..de5bdf9 100644 (file)
@@ -305,7 +305,7 @@ sub cn_browse {
                        },
                        { flesh         => 1,
                          flesh_fields  => { acn => [qw/record owning_lib/] },
-                         order_by      => { acn => "label_sortkey, upper(label) desc, id desc, owning_lib desc" },
+                         order_by      => { acn => "oils_text_as_bytea(label_sortkey) desc, oils_text_as_bytea(upper(label)) desc, id desc, owning_lib desc" },
                          limit         => $before_limit,
                          offset        => abs($page) * $page_size - $before_offset,
                        }
@@ -323,7 +323,7 @@ sub cn_browse {
                        },
                        { flesh         => 1,
                          flesh_fields  => { acn => [qw/record owning_lib/] },
-                         order_by      => { acn => "label_sortkey, upper(label), id, owning_lib" },
+                         order_by      => { acn => "oils_text_as_bytea(label_sortkey), oils_text_as_bytea(upper(label)), id, owning_lib" },
                          limit         => $after_limit,
                          offset        => abs($page) * $page_size - $after_offset,
                        }
@@ -428,7 +428,7 @@ sub cn_startwith {
                        },
                        { flesh         => 1,
                          flesh_fields  => { acn => [qw/record owning_lib/] },
-                         order_by      => { acn => "label_sortkey, upper(label) desc, id desc, owning_lib desc" },
+                         order_by      => { acn => "oils_text_as_bytea(label_sortkey) desc, oils_text_as_bytea(upper(label)) desc, id desc, owning_lib desc" },
                          limit         => $limit,
                          offset        => $offset,
                        }
@@ -446,7 +446,7 @@ sub cn_startwith {
                        },
                        { flesh         => 1,
                          flesh_fields  => { acn => [qw/record owning_lib/] },
-                         order_by      => { acn => "label_sortkey, upper(label), id, owning_lib" },
+                         order_by      => { acn => "oils_text_as_bytea(label_sortkey), oils_text_as_bytea(upper(label)), id, owning_lib" },
                          limit         => $limit,
                          offset        => $offset,
                        }
index 6a916c0..ae83a77 100644 (file)
@@ -7,6 +7,7 @@ use OpenSRF::EX qw/:try/;
 use OpenSRF::Utils::JSON;
 
 use OpenSRF::AppSession;
+use OpenSRF::MultiSession;
 use OpenSRF::Utils::SettingsClient;
 use OpenSRF::Utils::Logger qw/$logger/;
 use OpenSRF::Utils qw/:datetime/;
@@ -21,8 +22,16 @@ use OpenILS::Application::Trigger::EventGroup;
 
 
 my $log = 'OpenSRF::Utils::Logger';
+my $parallel_collect;
+my $parallel_react;
 
-sub initialize {}
+sub initialize {
+
+    my $conf = OpenSRF::Utils::SettingsClient->new;
+    $parallel_collect = $conf->config_value( apps => 'open-ils.trigger' => app_settings => parallel => 'collect') || 1;
+    $parallel_react = $conf->config_value( apps => 'open-ils.trigger' => app_settings => parallel => 'react') || 1;
+
+}
 sub child_init {}
 
 sub create_active_events_for_object {
@@ -68,7 +77,7 @@ sub create_active_events_for_object {
             my $uid = $target->$ufield;
             $uid = $uid->id if (ref $uid); # fleshed user object, unflesh it
 
-            my $opt_in_setting = $editor->search_actor_usr_setting(
+            my $opt_in_setting = $editor->search_actor_user_setting(
                 { usr   => $uid,
                   name  => $def->opt_in_setting,
                   value => 'true'
@@ -154,7 +163,7 @@ sub create_event_for_object_and_def {
             my $uid = $target->$ufield;
             $uid = $uid->id if (ref $uid); # fleshed user object, unflesh it
 
-            my $opt_in_setting = $editor->search_actor_usr_setting(
+            my $opt_in_setting = $editor->search_actor_user_setting(
                 { usr   => $uid,
                   name  => $def->opt_in_setting,
                   value => 'true'
@@ -328,7 +337,7 @@ sub events_by_target {
             for (grep { $_ ne '-and' } keys %{$$filter{event}});
     }
 
-    my $e = new_editor();
+    my $e = new_editor(xact=>1);
 
     my $events = $e->json_query($query);
 
@@ -469,7 +478,7 @@ sub create_batch_events {
                 '-exists' => {
                     from  => 'aus',
                     where => {
-                        name => $def->id,
+                        name => $def->opt_in_setting,
                         usr  => { '=' => { '+' . $hook_hash{$def->hook}->core_type => $def->usr_field } },
                         value=> 'true'
                     }
@@ -502,7 +511,6 @@ sub create_batch_events {
             $event->event_def( $def->id );
             $event->run_time( $run_time );
             $event->user_data( OpenSRF::Utils::JSON->perl2JSON($user_data) ) if (defined($user_data));
-            $event->granularity($granularity) if (defined $granularity);
 
             $editor->create_action_trigger_event( $event );
 
@@ -547,6 +555,7 @@ sub fire_single_event {
     }
 
     $e->editor->disconnect;
+    OpenILS::Application::Trigger::Event->ClearObjectCache();
 
     return {
         valid     => $e->valid,
@@ -575,6 +584,7 @@ sub fire_event_group {
     }
 
     $e->editor->disconnect;
+    OpenILS::Application::Trigger::Event->ClearObjectCache();
 
     return {
         valid     => $e->valid,
@@ -594,18 +604,21 @@ sub pending_events {
     my $self = shift;
     my $client = shift;
     my $granularity = shift;
-
-    my $editor = new_editor();
+    my $granflag = shift;
 
     my $query = [{ state => 'pending', run_time => {'<' => 'now'} }, { order_by => { atev => [ qw/run_time add_time/] }, 'join' => 'atevdef' }];
 
     if (defined $granularity) {
-        $query->[0]->{'+atevdef'} = {'-or' => [ {granularity => $granularity}, {granularity => undef} ] };
+        if ($granflag) {
+            $query->[0]->{'+atevdef'} = {granularity => $granularity};
+        } else {
+            $query->[0]->{'+atevdef'} = {'-or' => [ {granularity => $granularity}, {granularity => undef} ] };
+        }
     } else {
         $query->[0]->{'+atevdef'} = {granularity => undef};
     }
 
-    return $editor->search_action_trigger_event(
+    return new_editor(xact=>1)->search_action_trigger_event(
         $query, { idlist=> 1, timeout => 7200, substream => 1 }
     );
 }
@@ -615,12 +628,52 @@ __PACKAGE__->register_method(
     api_level=> 1
 );
 
+sub gather_events {
+    my $self = shift;
+    my $client = shift;
+    my $e_ids = shift;
+
+    $e_ids = [$e_ids] if (!ref($e_ids));
+
+    my @events;
+    for my $e_id (@$e_ids) {
+        my $e;
+        try {
+           $e = OpenILS::Application::Trigger::Event->new($e_id);
+        } catch Error with {
+            $logger->error("trigger: Event creation failed with ".shift());
+        };
+
+        next if !$e or $e->event->state eq 'invalid'; 
+
+        try {
+            $e->build_environment;
+        } catch Error with {
+            $logger->error("trigger: Event environment building failed with ".shift());
+        };
+
+        $e->editor->disconnect;
+        $e->environment->{EventProcessor} = undef; # remove circular ref for json encoding
+        $client->respond($e);
+    }
+
+    OpenILS::Application::Trigger::Event->ClearObjectCache();
+
+    return undef;
+}
+__PACKAGE__->register_method(
+    api_name => 'open-ils.trigger.event.gather',
+    method   => 'gather_events',
+    api_level=> 1
+);
+
 sub grouped_events {
     my $self = shift;
     my $client = shift;
     my $granularity = shift;
+    my $granflag = shift;
 
-    my ($events) = $self->method_lookup('open-ils.trigger.event.find_pending')->run($granularity);
+    my ($events) = $self->method_lookup('open-ils.trigger.event.find_pending')->run($granularity, $granflag);
 
     my %groups = ( '*' => [] );
 
@@ -631,51 +684,67 @@ sub grouped_events {
         return \%groups;
     }
 
-    for my $e_id ( @$events ) {
-        $logger->info("trigger: processing event $e_id");
+    my @fleshed_events;
 
-        # let the client know we're still chugging along TODO add osrf support for method_lookup $client's
+    if ($parallel_collect == 1 or @$events == 1) { # use method lookup
+        @fleshed_events = $self->method_lookup('open-ils.trigger.event.gather')->run($events);
+    } else {
+        my $self_multi = OpenSRF::MultiSession->new(
+            app                 => 'open-ils.trigger',
+            cap                 => $parallel_collect,
+            success_handler     => sub {
+                my $self = shift;
+                my $req = shift;
+
+                push @fleshed_events,
+                    map { OpenILS::Application::Trigger::Event->new($_) }
+                    map { $_->content }
+                    @{ $req->{response} };
+            },
+        );
+
+        $self_multi->request( 'open-ils.trigger.event.gather' => $_ ) for ( @$events );
         $client->status( new OpenSRF::DomainObject::oilsContinueStatus );
 
-        my $e;
-        try {
-           $e = OpenILS::Application::Trigger::Event->new($e_id);
-        } catch Error with {
-            $logger->error("trigger: Event creation failed with ".shift());
-        };
-
-        next unless $e; 
-
-        try {
-            $e->build_environment;
-        } catch Error with {
-            $logger->error("trigger: Event environment building failed with ".shift());
-        };
+        $self_multi->session_wait(1);
+        $client->status( new OpenSRF::DomainObject::oilsContinueStatus );
+    }
 
+    for my  $e (@fleshed_events) {
         if (my $group = $e->event->event_def->group_field) {
 
             # split the grouping link steps
             my @steps = split /\./, $group;
+            my $group_field = pop(@steps); # we didn't flesh to this, it's a field not an object
+
+            my $node;
+            eval {
+                $node = $e->target;
+                $node = $node->$_() for ( @steps );
+            };
 
-            # find the grouping object
-            my $node = $e->target;
-            $node = $node->$_() for ( @steps );
+            unless($node) { # should not get here, but to be safe..
+                $e->update_state('invalid');
+                next;
+            }
 
-            # get the pkey value for the grouping object on this event
-            my $node_ident = $node->Identity;
-            my $ident_value = $node->$node_ident();
+            # get the grouping value for the grouping object on this event
+            my $ident_value = $node->$group_field();
+            if(ref $ident_value) {
+                my $ident_field = $ident_value->Identity; 
+                $ident_value = $ident_value->$ident_field()
+            }
 
-            # push this event onto the event+grouping_pkey_value stack
+            # push this event onto the event+grouping_value stack
             $groups{$e->event->event_def->id}{$ident_value} ||= [];
             push @{ $groups{$e->event->event_def->id}{$ident_value} }, $e;
         } else {
             # it's a non-grouped event
             push @{ $groups{'*'} }, $e;
         }
-
-        $e->editor->disconnect;
     }
 
+
     return \%groups;
 }
 __PACKAGE__->register_method(
@@ -688,44 +757,80 @@ sub run_all_events {
     my $self = shift;
     my $client = shift;
     my $granularity = shift;
-
-    my ($groups) = $self->method_lookup('open-ils.trigger.event.find_pending_by_group')->run($granularity);
+    my $granflag = shift;
+
+    my ($groups) = $self->method_lookup('open-ils.trigger.event.find_pending_by_group')->run($granularity, $granflag);
+    $client->respond({"status" => "found"}) if (keys(%$groups) > 1 || @{$$groups{'*'}});
+
+    my $self_multi;
+    if ($parallel_react > 1 and (keys(%$groups) > 1 || @{$$groups{'*'}} > 1)) {
+        $self_multi = OpenSRF::MultiSession->new(
+            app                   => 'open-ils.trigger',
+            cap                   => $parallel_react,
+            session_hash_function => sub {
+                my $args = shift;
+                return $args->{target_id};
+            },
+            success_handler       => sub {
+                my $me = shift;
+                my $req = shift;
+                $client->respond( $req->{response}->[0]->content );
+            }
+        );
+    }
 
     for my $def ( keys %$groups ) {
         if ($def eq '*') {
             $logger->info("trigger: run_all_events firing un-grouped events");
             for my $event ( @{ $$groups{'*'} } ) {
                 try {
-                    $client->respond(
-                        $self
-                            ->method_lookup('open-ils.trigger.event.fire')
-                            ->run($event)
-                    );
+                    if ($self_multi) {
+                        $event->environment->{EventProcessor} = undef; # remove circular ref for json encoding
+                        $self_multi->request({target_id => $event->id}, 'open-ils.trigger.event.fire', $event);
+                    } else {
+                        $client->respond(
+                            $self
+                                ->method_lookup('open-ils.trigger.event.fire')
+                                ->run($event)
+                        );
+                    }
                 } catch Error with { 
                     $logger->error("trigger: event firing failed with ".shift());
                 };
             }
-            $logger->info("trigger: run_all_events completed firing un-grouped events");
+            $logger->info("trigger: run_all_events completed queuing un-grouped events");
+            $client->status( new OpenSRF::DomainObject::oilsContinueStatus );
 
         } else {
             my $defgroup = $$groups{$def};
             $logger->info("trigger: run_all_events firing events for grouped event def=$def");
             for my $ident ( keys %$defgroup ) {
+                $logger->info("trigger: run_all_events firing group for grouped event def=$def and grp ident $ident");
                 try {
-                    $client->respond(
-                        $self
-                            ->method_lookup('open-ils.trigger.event_group.fire')
-                            ->run($$defgroup{$ident})
-                    );
+                    if ($self_multi) {
+                        $_->environment->{EventProcessor} = undef for @{$$defgroup{$ident}}; # remove circular ref for json encoding
+                        $self_multi->request({target_id => $ident}, 'open-ils.trigger.event_group.fire', $$defgroup{$ident});
+                    } else {
+                        $client->respond(
+                            $self
+                                ->method_lookup('open-ils.trigger.event_group.fire')
+                                ->run($$defgroup{$ident})
+                        );
+                    }
+                    $client->status( new OpenSRF::DomainObject::oilsContinueStatus );
                 } catch Error with {
                     $logger->error("trigger: event firing failed with ".shift());
                 };
             }
-            $logger->info("trigger: run_all_events completed firing events for grouped event def=$def");
+            $logger->info("trigger: run_all_events completed queuing events for grouped event def=$def");
         }
     }
-                
-            
+
+    $self_multi->session_wait(1) if ($self_multi);
+    $logger->info("trigger: run_all_events completed firing events");
+
+    $client->respond_complete();
+    return undef;
 }
 __PACKAGE__->register_method(
     api_name => 'open-ils.trigger.event.run_all_pending',
index 5faef1b..2ec5d1b 100644 (file)
@@ -2,13 +2,10 @@ package OpenILS::Application::Trigger::Event;
 use strict; use warnings;
 use OpenSRF::EX qw/:try/;
 use OpenSRF::Utils::JSON;
-
 use OpenSRF::Utils::Logger qw/$logger/;
-
 use OpenILS::Utils::Fieldmapper;
 use OpenILS::Utils::CStoreEditor q/:funcs/;
 use OpenILS::Application::Trigger::ModRunner;
-
 use Safe;
 
 my $log = 'OpenSRF::Utils::Logger';
@@ -19,11 +16,17 @@ sub new {
     my $editor = shift;
     $class = ref($class) || $class;
 
-    return $id if (ref($id) && ref($id) eq $class);
-
     my $standalone = $editor ? 0 : 1;
     $editor ||= new_editor();
 
+    if (ref($id) && ref($id) eq $class) {
+        $id->environment->{EventProcessor} = $id
+             if ($id->environment->{complete}); # in case it came over an opensrf tube
+        $id->editor( $editor );
+        $id->standalone( $standalone );
+        return $id;
+    }
+
     my $self = bless { id => $id, editor => $editor, standalone => $standalone } => $class;
 
     return $self->init()
@@ -45,6 +48,10 @@ sub init {
 
     return $self if (!$self->id);
 
+    if ($self->standalone) {
+        $self->editor->xact_begin || return undef;
+    }
+
     $self->event(
         $self->editor->retrieve_action_trigger_event([
             $self->id, {
@@ -57,6 +64,10 @@ sub init {
         ])
     );
 
+    if ($self->standalone) {
+        $self->editor->xact_rollback || return undef;
+    }
+
     $self->user_data(OpenSRF::Utils::JSON->JSON2perl( $self->event->user_data ))
         if (defined( $self->event->user_data ));
 
@@ -91,8 +102,21 @@ sub init {
     $meth =~ s/Fieldmapper:://;
     $meth =~ s/::/_/;
     
+    if ($self->standalone) {
+        $self->editor->xact_begin || return undef;
+    }
+
     $self->target( $self->editor->$meth( $self->event->target ) );
 
+    if ($self->standalone) {
+        $self->editor->xact_rollback || return undef;
+    }
+
+    unless($self->target) {
+        $self->update_state('invalid');
+        $self->valid(0);
+    }
+
     return $self;
 }
 
@@ -336,6 +360,7 @@ sub update_state {
     $e->state( $state );
 
     $e->clear_start_time() if ($e->state eq 'pending');
+    $e->complete_time( 'now' ) if ($e->state eq 'complete');
 
     my $ok = $self->editor->update_action_trigger_event( $e );
     if (!$ok) {
@@ -397,7 +422,8 @@ sub build_environment {
 
         if ($self->event->event_def->group_field) {
             my @group_path = split(/\./, $self->event->event_def->group_field);
-            my $group_object = $self->_object_by_path( $self->target, undef, [], \@group_path );
+            pop(@group_path); # the last part is a field, should not get fleshed
+            my $group_object = $self->_object_by_path( $self->target, undef, [], \@group_path ) if (@group_path);
         }
     
         $self->environment->{complete} = 1;
@@ -426,17 +452,45 @@ sub _fm_class_by_hint {
     return $class;
 }
 
+my %_object_by_path_cache = ();
+sub ClearObjectCache {
+    for my $did ( keys %_object_by_path_cache ) {
+        my $phash = $_object_by_path_cache{$did};
+        for my $path ( keys %$phash ) {
+            my $shash = $$phash{$path};
+            for my $step ( keys %$shash ) {
+                my $fhash = $$shash{$step};
+                for my $ffield ( keys %$fhash ) {
+                    my $lhash = $$fhash{$ffield};
+                    for my $lfield ( keys %$lhash ) {
+                        delete $$lhash{$lfield};
+                    }
+                    delete $$fhash{$ffield};
+                }
+                delete $$shash{$step};
+            }
+            delete $$phash{$path};
+        }
+        delete $_object_by_path_cache{$did};
+    }
+}
+        
 sub _object_by_path {
     my $self = shift;
     my $context = shift;
     my $collector = shift;
     my $label = shift;
     my $path = shift;
+    my $ed = shift;
 
+    my $outer = 0;
+    if (!$ed) {
+        $ed = new_editor(xact=>1);
+        $outer = 1;
+    }
 
     my $step = shift(@$path);
 
-
     my $fhint = Fieldmapper->publish_fieldmapper->{$context->class_name}{links}{$step}{class};
     my $fclass = $self->_fm_class_by_hint( $fhint );
 
@@ -460,10 +514,6 @@ sub _object_by_path {
     $meth =~ s/Fieldmapper:://;
     $meth =~ s/::/_/g;
 
-    my $ed = grep( /open-ils.cstore/, @{$fclass->Controller} ) ?
-            $self->editor :
-            new_rstore_editor();
-
     my $obj = $context->$step(); 
 
     $logger->debug(
@@ -472,11 +522,19 @@ sub _object_by_path {
     );
 
     if (!ref $obj) {
-        $obj = $ed->$meth( 
-            ($multi) ?
-                { $ffield => $context->$lfield() } :
-                $context->$lfield()
-        );
+
+        my $lval = $context->$lfield();
+
+        if(defined $lval) {
+
+            my $def_id = $self->event->event_def->id;
+            my $str_path = join('.', @$path);
+
+            $obj = $_object_by_path_cache{$def_id}{$str_path}{$step}{$ffield}{$lval} ||
+                $ed->$meth( ($multi) ? { $ffield => $lval } : $lval);
+
+            $_object_by_path_cache{$def_id}{$str_path}{$step}{$ffield}{$lval} ||= $obj;
+        }
     }
 
     if (@$path) {
@@ -490,7 +548,7 @@ sub _object_by_path {
 
         for (@$obj_list) {
             my @path_clone = @$path;
-            $self->_object_by_path( $_, $collector, $label, \@path_clone );
+            $self->_object_by_path( $_, $collector, $label, \@path_clone, $ed );
         }
 
         $obj = $$obj_list[0] if (!$multi || $rtype eq 'might_have');
@@ -533,6 +591,7 @@ sub _object_by_path {
         }
     }
 
+    $ed->rollback if ($outer);
     return $obj;
 }
 
index 34b9423..2a3c6c6 100644 (file)
@@ -28,7 +28,7 @@ sub new {
                     OpenILS::Application::Trigger::Event->new($_, $editor)
             } @ids
         ],
-        ids         => \@ids,
+        ids         => [ map { ref($_) ? $_->id : $_ } @ids ],
         editor      => $editor
     } => $class;
 
@@ -212,11 +212,11 @@ sub update_state {
     if (scalar(@oks) < scalar(@{ $self->ids })) {
         $self->editor->xact_rollback;
         return undef;
-    } else {
-        $ok = $self->editor->xact_commit;
-    }
+    } 
 
     my $updated = $self->editor->retrieve_action_trigger_event($last_updated);
+    $ok = $self->editor->xact_commit;
+
     if ($ok) {
         for my $event ( @{ $self->events } ) {
             my $e = $event->event;
index 840964d..cc59bbd 100644 (file)
@@ -57,13 +57,13 @@ my $_TT_helpers = {
     # returns the calculated copy price
     get_copy_price => sub {
         my $copy_id = shift;
-        return $U->get_copy_price(new_editor(), $copy_id);
+        return $U->get_copy_price(new_editor(xact=>1), $copy_id);
     },
 
     # given a copy, returns the title and author in a hash
     get_copy_bib_basics => sub {
         my $copy_id = shift;
-        my $copy = new_editor()->retrieve_asset_copy([
+        my $copy = new_editor(xact=>1)->retrieve_asset_copy([
             $copy_id,
             {
                 flesh => 2,
@@ -156,6 +156,7 @@ sub run_TT {
         my $t_o = Fieldmapper::action_trigger::event_output->new;
         $t_o->data( ($error) ? $error : $output );
         $t_o->is_error( ($error) ? 't' : 'f' );
+        $logger->info("trigger: writing " . length($t_o->data) . " bytes to template output");
 
         $env->{EventProcessor}->editor->xact_begin;
         $t_o = $env->{EventProcessor}->editor->create_action_trigger_event_output( $t_o );
index 3572e81..3920bd7 100644 (file)
@@ -68,9 +68,10 @@ sub create_bib_queue {
     return $e->die_event unless $e->allowed('CREATE_BIB_IMPORT_QUEUE');
     $owner ||= $e->requestor->id;
 
-    return OpenILS::Event->new('BIB_QUEUE_EXISTS') 
-        if $e->search_vandelay_bib_queue(
-            {name => $name, owner => $owner, queue_type => $type})->[0];
+    if ($e->search_vandelay_bib_queue( {name => $name, owner => $owner, queue_type => $type})->[0]) {
+        $e->rollback;
+        return OpenILS::Event->new('BIB_QUEUE_EXISTS') 
+    }
 
     my $queue = new Fieldmapper::vandelay::bib_queue();
     $queue->name( $name );
@@ -106,9 +107,10 @@ sub create_auth_queue {
     return $e->die_event unless $e->allowed('CREATE_AUTHORITY_IMPORT_QUEUE');
     $owner ||= $e->requestor->id;
 
-    return OpenILS::Event->new('AUTH_QUEUE_EXISTS') 
-        if $e->search_vandelay_bib_queue(
-            {name => $name, owner => $owner, queue_type => $type})->[0];
+    if ($e->search_vandelay_bib_queue({name => $name, owner => $owner, queue_type => $type})->[0]) {
+        $e->rollback;
+        return OpenILS::Event->new('AUTH_QUEUE_EXISTS') 
+    }
 
     my $queue = new Fieldmapper::vandelay::authority_queue();
     $queue->name( $name );
@@ -242,7 +244,7 @@ sub process_spool {
     }
 
     my $evt = check_queue_perms($e, $type, $queue);
-    return $evt if $evt;
+    return $evt if ($evt);
 
     my $cache = new OpenSRF::Utils::Cache();
 
@@ -395,8 +397,8 @@ __PACKAGE__->register_method(
 
 sub retrieve_queued_records {
     my($self, $conn, $auth, $queue_id, $options) = @_;
-    my $e = new_editor(authtoken => $auth);
-    return $e->event unless $e->checkauth;
+    my $e = new_editor(authtoken => $auth, xact => 1);
+    return $e->die_event unless $e->checkauth;
     $options ||= {};
     my $limit = $$options{limit} || 20;
     my $offset = $$options{offset} || 0;
@@ -409,7 +411,7 @@ sub retrieve_queued_records {
         $queue = $e->retrieve_vandelay_authority_queue($queue_id) or return $e->die_event;
     }
     my $evt = check_queue_perms($e, $type, $queue);
-    return $evt if $evt;
+    return $evt if ($evt);
 
     my $class = ($type eq 'bib') ? 'vqbr' : 'vqar';
     my $search = ($type eq 'bib') ? 
@@ -443,6 +445,7 @@ sub retrieve_queued_records {
         $rec->clear_marc if $$options{clear_marc};
         $conn->respond($rec);
     }
+    $e->rollback;
     return undef;
 }
 
@@ -481,10 +484,11 @@ __PACKAGE__->register_method(
 
 sub import_record_list {
     my($self, $conn, $auth, $rec_ids, $args) = @_;
-    my $e = new_editor(authtoken => $auth);
-    return $e->event unless $e->checkauth;
+    my $e = new_editor(authtoken => $auth, xact => 1);
+    return $e->die_event unless $e->checkauth;
     $args ||= {};
     my $err = import_record_list_impl($self, $conn, $rec_ids, $e->requestor, $args);
+    $e->rollback;
     return $err if $err;
     return {complete => 1};
 }
@@ -532,8 +536,8 @@ __PACKAGE__->register_method(
 );
 sub import_queue {
     my($self, $conn, $auth, $q_id, $options) = @_;
-    my $e = new_editor(authtoken => $auth);
-    return $e->event unless $e->checkauth;
+    my $e = new_editor(authtoken => $auth, xact => 1);
+    return $e->die_event unless $e->checkauth;
     $options ||= {};
     my $type = $self->{record_type};
     my $class = ($type eq 'bib') ? 'vqbr' : 'vqar';
@@ -549,6 +553,7 @@ sub import_queue {
         'search_vandelay_queued_bib_record' : 'search_vandelay_queued_authority_record';
     my $rec_ids = $e->$search($query, {idlist => 1});
     my $err = import_record_list_impl($self, $conn, $rec_ids, $e->requestor, $options);
+    $e->rollback;
     return $err if $err;
     return {complete => 1};
 }
@@ -620,6 +625,7 @@ sub import_record_list_impl {
     my %bib_sources;
     my $editor = new_editor();
     my $sources = $editor->search_config_bib_source({id => {'!=' => undef}});
+
     foreach my $src (@$sources) {
         $bib_sources{$src->id} = $src->source;
     }
@@ -635,6 +641,7 @@ sub import_record_list_impl {
         $rec_class = 'vqar';
     }
 
+    my @success_rec_ids;
     for my $rec_id (@$rec_ids) {
 
         my $overlay_target = $overlay_map->{$rec_id};
@@ -650,7 +657,7 @@ sub import_record_list_impl {
         ]);
 
         unless($rec) {
-            $conn->respond({total => $total, progress => ++$count, imported => $rec_id, err_event => $e->die_event});
+            $conn->respond({total => $total, progress => ++$count, imported => $rec_id, err_event => $e->event});
             $e->rollback;
             next;
         }
@@ -746,6 +753,7 @@ sub import_record_list_impl {
                 if($U->event_code($record)) {
 
                     $e->event($record); 
+                    $e->rollback;
 
                 } else {
 
@@ -759,11 +767,11 @@ sub import_record_list_impl {
         }
 
         if($imported) {
+            push @success_rec_ids, $rec_id;
             $e->commit;
         } else {
-            $e->rollback;
             # Send an update whenever there's an error
-            $conn->respond({total => $total, progress => ++$count, imported => $rec_id, err_event => $e->die_event});
+            $conn->respond({total => $total, progress => ++$count, imported => $rec_id, err_event => $e->event});
         }
 
         $conn->respond({total => $total, progress => $count, imported => $rec_id}) if (!$report_all and ++$count % $step) == 0;
@@ -789,6 +797,8 @@ sub import_record_list_impl {
        $e->rollback;
     }
 
+    import_record_asset_list_impl($conn, \@success_rec_ids, $requestor);
+
     $conn->respond({total => $total, progress => $count});
     return undef;
 }
@@ -813,7 +823,7 @@ __PACKAGE__->register_method(
 
 sub owner_queue_retrieve {
     my($self, $conn, $auth, $owner_id, $filters) = @_;
-    my $e = new_editor(authtoken => $auth);
+    my $e = new_editor(authtoken => $auth, xact => 1);
     return $e->die_event unless $e->checkauth;
     $owner_id = $e->requestor->id; # XXX add support for viewing other's queues?
     my $queues;
@@ -829,6 +839,7 @@ sub owner_queue_retrieve {
             [$search, {order_by => {vaq => 'lower(name)'}}]);
     }
     $conn->respond($_) for @$queues;
+    $e->rollback;
     return undef;
 }
 
@@ -888,17 +899,18 @@ __PACKAGE__->register_method(
 
 sub queued_record_html {
     my($self, $conn, $auth, $rec_id) = @_;
-    my $e = new_editor(authtoken => $auth);
-    return $e->event unless $e->checkauth;
+    my $e = new_editor(xact=>1,authtoken => $auth);
+    return $e->die_event unless $e->checkauth;
     my $rec;
     if($self->{record_type} eq 'bib') {
         $rec = $e->retrieve_vandelay_queued_bib_record($rec_id)
-            or return $e->event;
+            or return $e->die_event;
     } else {
         $rec = $e->retrieve_vandelay_queued_authority_record($rec_id)
-            or return $e->event;
+            or return $e->die_event;
     }
 
+    $e->rollback;
     return $U->simplereq(
         'open-ils.search',
         'open-ils.search.biblio.record.html', undef, 1, $rec->marc);
@@ -924,17 +936,17 @@ __PACKAGE__->register_method(
 
 sub retrieve_queue_summary {
     my($self, $conn, $auth, $queue_id) = @_;
-    my $e = new_editor(authtoken => $auth);
-    return $e->event unless $e->checkauth;
+    my $e = new_editor(xact=>1, authtoken => $auth);
+    return $e->die_event unless $e->checkauth;
 
     my $queue;
     my $type = $self->{record_type};
     if($type eq 'bib') {
         $queue = $e->retrieve_vandelay_bib_queue($queue_id)
-            or return $e->event;
+            or return $e->die_event;
     } else {
         $queue = $e->retrieve_vandelay_authority_queue($queue_id)
-            or return $e->event;
+            or return $e->die_event;
     }
 
     my $evt = check_queue_perms($e, $type, $queue);
@@ -953,7 +965,7 @@ sub retrieve_queue_summary {
 
 __PACKAGE__->register_method(  
     api_name    => "open-ils.vandelay.bib_record.list.asset.import",
-    method      => 'import_record_list_assets',
+    method      => 'noop_import_items',
     api_level   => 1,
     argc        => 2,
     stream      => 1,
@@ -961,48 +973,52 @@ __PACKAGE__->register_method(
 );
 __PACKAGE__->register_method(  
     api_name    => "open-ils.vandelay.bib_record.queue.asset.import",
-    method      => 'import_record_queue_assets',
+    method      => 'noop_import_items',
     api_level   => 1,
     argc        => 2,
     stream      => 1,
     record_type => 'bib'
 );
 
-sub import_record_list_assets {
-    my($self, $conn, $auth, $import_def, $rec_ids) = @_;
-    my $e = new_editor(authtoken => $auth);
-    return $e->event unless $e->checkauth;
-    my $err = import_record_asset_list_impl($conn, $import_def, $rec_ids, $e->requestor);
-    return $err if $err;
-    return {complete => 1};
-}
-
-sub import_record_queue_assets {
-    my($self, $conn, $auth, $import_def, $q_id) = @_;
-    my $e = new_editor(authtoken => $auth);
-    return $e->event unless $e->checkauth;
-    my $rec_ids = $e->search_vandelay_queued_bib_record(
-        {queue => $q_id, import_time => {'!=' => undef}}, {idlist => 1});
-    my $err = import_record_asset_list_impl($conn, $import_def, $rec_ids, $e->requestor);
-    return $err if $err;
-    return {complete => 1};
-}
+sub noop_import_items { return {complete => 1} }
+
+#sub import_record_list_assets {
+#    my($self, $conn, $auth, $import_def, $rec_ids) = @_;
+#    my $e = new_editor(xact=>1, authtoken => $auth);
+#    return $e->die_event unless $e->checkauth;
+#    my $err = import_record_asset_list_impl($conn, $import_def, $rec_ids, $e->requestor);
+#    $e->rollback;
+#    return $err if $err;
+#    return {complete => 1};
+#}
+#
+#sub import_record_queue_assets {
+#    my($self, $conn, $auth, $import_def, $q_id) = @_;
+#    my $e = new_editor(xact=>1, authtoken => $auth);
+#    return $e->die_event unless $e->checkauth;
+#    my $rec_ids = $e->search_vandelay_queued_bib_record(
+#        {queue => $q_id, import_time => {'!=' => undef}}, {idlist => 1});
+#    my $err = import_record_asset_list_impl($conn, $import_def, $rec_ids, $e->requestor);
+#    $e->rollback;
+#    return $err if $err;
+#    return {complete => 1};
+#}
 
 # --------------------------------------------------------------------------------
 # Given a list of queued record IDs, imports all items attached to those records
 # --------------------------------------------------------------------------------
 sub import_record_asset_list_impl {
-    my($conn, $import_def, $rec_ids, $requestor) = @_;
+    my($conn, $rec_ids, $requestor) = @_;
 
     my $total = @$rec_ids;
     my $try_count = 0;
     my $in_count = 0;
-    my $roe = new_editor(requestor => $requestor);
+    my $roe = new_editor(xact=> 1, requestor => $requestor);
 
     for my $rec_id (@$rec_ids) {
         my $rec = $roe->retrieve_vandelay_queued_bib_record($rec_id);
         next unless $rec and $rec->import_time;
-        my $item_ids = $roe->search_vandelay_import_item({definition => $import_def, record => $rec->id}, {idlist=>1});
+        my $item_ids = $roe->search_vandelay_import_item({record => $rec->id}, {idlist=>1});
 
         for my $item_id (@$item_ids) {
             my $e = new_editor(requestor => $requestor, xact => 1);
@@ -1087,6 +1103,7 @@ sub import_record_asset_list_impl {
             respond_with_status($conn, $total, $try_count, ++$in_count, undef, imported_as => $copy->id);
         }
     }
+    $roe->rollback;
     return undef;
 }
 
index e101e4b..d22a5e6 100644 (file)
@@ -381,7 +381,7 @@ sub checkin {
        $xact->do_checkin( $self, $inst_id, $trans_date, $return_date, $current_loc, $item_props );
        
        if ($xact->ok) {
-        $xact->patron($self->find_patron($item->{patron}));
+        $xact->patron($self->find_patron(usr => $xact->{circ_user_id})) if $xact->{circ_user_id};
         delete $item->{patron};
         delete $item->{due_date};
         syslog('LOG_INFO', "OILS: Checkin succeeded");
index 5197da3..7eb815c 100644 (file)
@@ -125,12 +125,15 @@ sub new {
         syslog('LOG_DEBUG', "OILS: Open circulation exists on $item_id : user = $bc");
     }
 
-    $self->{id} = $item_id;
-    $self->{copy}        = $copy;
-    $self->{volume}      = $copy->call_number;
-    $self->{record}      = $copy->call_number->record;
+    $self->{id}         = $item_id;
+    $self->{copy}       = $copy;
+    $self->{volume}     = $copy->call_number;
+    $self->{record}     = $copy->call_number->record;
     $self->{call_number} = $copy->call_number->label;
-    $self->{mods} = $U->record_to_mvr($self->{record}) if $self->{record}->marc;
+    $self->{mods}       = $U->record_to_mvr($self->{record}) if $self->{record}->marc;
+    $self->{transit}    = $self->fetch_transit;
+    $self->{hold}       = $self->fetch_hold;
+
 
     # use the non-translated version of the copy location as the
     # collection code, since it may be used for additional routing
@@ -139,26 +142,12 @@ sub new {
         $e->retrieve_asset_copy_location([
             $copy->location, {no_i18n => 1}])->name;
 
-    if ($copy->status->id == OILS_COPY_STATUS_IN_TRANSIT) {
-        my $transit = $e->search_action_transit_copy([
-            {
-                target_copy    => $copy->id,    # NOT barcode ($self->id)
-                dest_recv_time => undef
-            },
-            {
-                flesh => 1,
-                flesh_fields => {
-                    atc => [ 'dest' ]
-                }
-            }
-        ])->[0];
 
-        if ($transit) {
-            $self->{transit} = $transit;
-            $self->{destination_loc} = $transit->dest->shortname;
-        } else {
-            syslog('LOG_WARNING', "OILS: Item('$item_id') status is In Transit, but no action.transit_copy found!");
-        }
+    if($self->{transit}) {
+        $self->{destination_loc} = $self->{transit}->dest->shortname;
+
+    } elsif($self->{hold}) {
+        $self->{destination_loc} = $self->{hold}->pickup_lib->shortname;
     }
 
     syslog("LOG_DEBUG", "OILS: Item('$item_id'): found with title '%s'", $self->title_id);
@@ -182,6 +171,71 @@ sub new {
     return $self;
 }
 
+# fetch copy transit
+sub fetch_transit {
+    my $self = shift;
+    my $copy = $self->{copy} or return;
+    my $e = OpenILS::SIP->editor();
+
+    if ($copy->status->id == OILS_COPY_STATUS_IN_TRANSIT) {
+        my $transit = $e->search_action_transit_copy([
+            {
+                target_copy    => $copy->id,    # NOT barcode ($self->id)
+                dest_recv_time => undef
+            },
+            {
+                flesh => 1,
+                flesh_fields => {
+                    atc => ['dest']
+                }
+            }
+        ])->[0];
+
+        syslog('LOG_WARNING', "OILS: Item(".$copy->barcode.
+            ") status is In Transit, but no action.transit_copy found!") unless $transit;
+            
+        return $transit;
+    }
+    
+    return undef;
+}
+
+# fetch captured hold.
+# Assume transit has already beeen fetched
+sub fetch_hold {
+    my $self = shift;
+    my $copy = $self->{copy} or return;
+    my $e = OpenILS::SIP->editor();
+
+    if( ($copy->status->id == OILS_COPY_STATUS_ON_HOLDS_SHELF) ||
+        ($self->{transit} and $self->{transit}->copy_status == OILS_COPY_STATUS_ON_HOLDS_SHELF) ) {
+        # item has been captured for a hold
+
+        my $hold = $e->search_action_hold_request([
+            {
+                current_copy        => $copy->id,
+                capture_time        => {'!=' => undef},
+                cancel_time         => undef,
+                fulfillment_time    => undef
+            },
+            {
+                limit => 1,
+                flesh => 1,
+                flesh_fields => {
+                    ahr => ['pickup_lib']
+                }
+            }
+        ])->[0];
+
+        syslog('LOG_WARNING', "OILS: Item(".$copy->barcode.
+            ") is captured for a hold, but there is no matching hold request") unless $hold;
+
+        return $hold;
+    }
+
+    return undef;
+}
+
 sub run_attr_script {
        my $self = shift;
        return 1 if $self->{ran_script};
@@ -373,47 +427,29 @@ my %shelf_expire_setting_cache;
 sub hold_pickup_date {  
     my $self = shift;
     my $copy = $self->{copy};
+    my $hold = $self->{hold} or return 0;
 
-    if( ($copy->status->id == OILS_COPY_STATUS_ON_HOLDS_SHELF) ||
-        ($self->{transit} and $self->{transit}->copy_status == OILS_COPY_STATUS_ON_HOLDS_SHELF) ) {
+    my $date = $hold->shelf_expire_time;
 
-        # item has been captured for a hold
+    if(!$date) {
+        # hold has not hit the shelf.  create a best guess.
 
-        my $e = OpenILS::SIP->editor();
-        my $hold = $e->search_action_hold_request([
-            {
-                current_copy        => $copy->id,
-                capture_time        => {'!=' => undef},
-                cancel_time         => undef,
-                fulfillment_time    => undef
-            },
-            {limit => 1}
-        ])->[0];
-        
-        if($hold) {
-            my $date = $hold->shelf_expire_time;
-
-            if(!$date) {
-                # hold has not hit the shelf.  create a best guess.
-
-                my $interval = $shelf_expire_setting_cache{$hold->pickup_lib} ||
-                    $U->ou_ancestor_setting_value(
-                        $hold->pickup_lib, 
-                        'circ.holds.default_shelf_expire_interval');
+        my $interval = $shelf_expire_setting_cache{$hold->pickup_lib->id} ||
+            $U->ou_ancestor_setting_value(
+                $hold->pickup_lib->id, 
+                'circ.holds.default_shelf_expire_interval');
 
-                $shelf_expire_setting_cache{$hold->pickup_lib} = $interval;
+        $shelf_expire_setting_cache{$hold->pickup_lib->id} = $interval;
 
-                if($interval) {
-                    my $seconds = OpenSRF::Utils->interval_to_seconds($interval);
-                    $date = DateTime->now->add(seconds => $seconds);
-                    $date = $date->strftime('%FT%T%z') if $date;
-                }
-            }
-
-            return OpenILS::SIP->format_date($date) if $date;
+        if($interval) {
+            my $seconds = OpenSRF::Utils->interval_to_seconds($interval);
+            $date = DateTime->now->add(seconds => $seconds);
+            $date = $date->strftime('%FT%T%z') if $date;
         }
     }
 
+    return OpenILS::SIP->format_date($date) if $date;
+
     return 0;
 }
 
index fd690bf..c435bc0 100644 (file)
@@ -41,7 +41,12 @@ sub new {
 
     if ($key ne 'usr' and $key ne 'barcode') {
         syslog("LOG_ERROR", "Patron (card) lookup requested by illegeal key '$key'");
-        return;
+        return undef;
+    }
+
+    unless(defined $patron_id) {
+        syslog("LOG_WARNING", "No patron ID provided to ILS::Patron->new");
+        return undef;
     }
 
     my $type = ref($class) || $class;
@@ -50,36 +55,34 @@ sub new {
     syslog("LOG_DEBUG", "OILS: new OpenILS Patron(%s => %s): searching...", $key, $patron_id);
 
     my $e = OpenILS::SIP->editor();
+    
+    my $user_id = $patron_id;
+    if($key eq 'barcode') {
+        my $card = $e->search_actor_card({barcode => $patron_id})->[0];
+        unless($card) {
+            syslog("LOG_WARNING", "No such patron barcode: $patron_id");
+            return undef;
+        }
+        $user_id = $card->usr;
+    }
 
-    my $c = $e->search_actor_card({$key => $patron_id}, {idlist=>1});
-       my $user;
-
-       if( @$c ) {
-
-               $user = $e->search_actor_user(
-                       [
-                               { card => $$c[0] },
-                               {
-                                       flesh => 2,
-                                       flesh_fields => {
-                                               "au" => [
-                                                       #"cards",
-                                                       "card",
-                                                       "standing_penalties",
-                                                       "addresses",
-                                                       "billing_address",
-                                                       "mailing_address",
-                                                       #"stat_cat_entries",
-                                                       'profile',
-                                               ],
-                        ausp => ['standing_penalty']
-                                       }
-                               }
-                       ]
-               );
-
-               $user = (@$user) ? $$user[0] : undef;
-        }
+       my $user = $e->retrieve_actor_user([
+        $user_id,
+        {
+            flesh => 2,
+            flesh_fields => {
+                au => [
+                    "card",
+                    "standing_penalties",
+                    "addresses",
+                    "billing_address",
+                    "mailing_address",
+                    'profile',
+                ],
+                ausp => ['standing_penalty']
+            }
+        }
+    ]);
 
     if(!$user) {
         syslog("LOG_WARNING", "OILS: Unable to find patron %s => %s", $key, $patron_id);
@@ -218,20 +221,19 @@ sub card_lost {
     return $self->{user}->card->active eq 'f';
 }
 
-sub recall_overdue {
+sub recall_overdue {        # not implemented
     my $self = shift;
     return 0;
 }
 
-
 sub check_password {
        my ($self, $pwd) = @_;
        syslog('LOG_DEBUG', 'OILS: Patron->check_password()');
+    return 0 unless (defined $pwd and $self->{user});
        return md5_hex($pwd) eq $self->{user}->passwd;
 }
 
-
-sub currency {
+sub currency {              # not really implemented
        my $self = shift;
        syslog('LOG_DEBUG', 'OILS: Patron->currency()');
        return 'USD';
@@ -280,12 +282,12 @@ sub screen_msg {
        return 'OK';
 }
 
-sub print_line {
+sub print_line {            # not implemented
     my $self = shift;
        return '';
 }
 
-sub too_many_charged {
+sub too_many_charged {      # not implemented
     my $self = shift;
        return 0;
 }
index 66b09c6..c662104 100644 (file)
@@ -44,6 +44,8 @@ sub new {
 
     @{$self}{keys %fields} = values %fields;        # copying defaults into object
 
+    $self->load_override_events;
+
     return bless $self, $class;
 }
 
@@ -53,6 +55,16 @@ sub resensitize {
     return !$self->{item}->magnetic;
 }
 
+my %override_events;
+sub load_override_events {
+    return if %override_events;
+    my $override = OpenILS::SIP->config->{implementation_config}->{checkin_override};
+    return unless $override;
+    my $events = $override->{event};
+    $events = [$events] unless ref $events eq 'ARRAY';
+    $override_events{$_} = 1 for @$events;
+}
+
 my %org_sn_cache;
 sub do_checkin {
     my $self = shift;
@@ -71,6 +83,13 @@ sub do_checkin {
 
     my $args = {barcode => $self->{item}->id};
 
+    if($return_date) {
+        # SIP date format is YYYYMMDD.  Translate to ISO8601
+        $return_date =~ s/(\d{4})(\d{2})(\d{2}).*/$1-$2-$3/;
+        syslog('LOG_INFO', "Checking in with backdate $return_date");
+        $args->{backdate} = $return_date;
+    }
+
     if($current_loc) { # SIP client specified a physical location
 
         my $org_id = (defined $org_sn_cache{$current_loc}) ? 
@@ -83,24 +102,36 @@ sub do_checkin {
         $args->{circ_lib} = $phys_location = $org_id if defined $org_id;
     }
 
-    my $resp = $U->simplereq(
-        'open-ils.circ',
-        'open-ils.circ.checkin',
-        $self->{authtoken}, $args
-    );
+    my $override = 0;
+    my ($resp, $txt, $code);
 
-    if ($debug) {
-        my $s = Dumper($resp);
-        $s =~ s/\n//mog;
-        syslog('LOG_INFO', "OILS: Checkin response: $s");
-    }
+    while(1) {
+
+        my $method = 'open-ils.circ.checkin';
+        $method .= '.override' if $override;
+
+        $resp = $U->simplereq('open-ils.circ', $method, $self->{authtoken}, $args);
 
-    # In oddball cases, we can receive an array of events.
-    # The first event received should be treated as the main result.
-    $resp = $$resp[0] if ref($resp) eq 'ARRAY';
+        if ($debug) {
+            my $s = Dumper($resp);
+            $s =~ s/\n//mog;
+            syslog('LOG_INFO', "OILS: Checkin response: $s");
+        }
 
-    my $code = $U->event_code($resp);
-    my $txt  = (defined $code) ? $resp->{textcode} : '';
+        # In oddball cases, we can receive an array of events.
+        # The first event received should be treated as the main result.
+        $resp = $$resp[0] if ref($resp) eq 'ARRAY';
+        $code = $U->event_code($resp);
+        $txt  = (defined $code) ? $resp->{textcode} : '';
+
+        last if $override;
+
+        if ( $override_events{$txt} ) {
+            $override = 1;
+        } else {
+            last;
+        }
+    }
 
     syslog('LOG_INFO', "OILS: Checkin resulted in event: $txt, phys_location: $phys_location");
 
@@ -119,13 +150,21 @@ sub do_checkin {
     
     my $payload = $resp->{payload} || {};
 
-    # Two places to look for hold data.  These are more important and more definitive than above.
-    if ($payload->{remote_hold}) {
-        # actually only used for checkin at non-owning branch w/ hold at same branch
-        $self->item->hold($payload->{remote_hold});     
+    my ($circ, $copy);
+
+    if(ref $payload eq 'HASH') {
 
-    } elsif ($payload->{hold}) {
-        $self->item->hold($payload->{hold});
+        # Two places to look for hold data.  These are more important and more definitive than above.
+        if ($payload->{remote_hold}) {
+            # actually only used for checkin at non-owning branch w/ hold at same branch
+            $self->item->hold($payload->{remote_hold});     
+
+        } elsif ($payload->{hold}) {
+            $self->item->hold($payload->{hold});
+        }
+
+        $circ = $resp->{payload}->{circ} || '';
+        $copy = $resp->{payload}->{copy} || '';
     }
 
     if ($self->item->hold) {
@@ -142,10 +181,8 @@ sub do_checkin {
 
     $self->alert(1) if defined $self->alert_type;  # alert_type could be "00", hypothetically
 
-    my $circ = $resp->{payload}->{circ} || '';
-    my $copy = $resp->{payload}->{copy} || '';
-
     if ( $circ ) {
+        $self->{circ_user_id} = $circ->usr;
         $self->ok(1);
     } elsif ($txt eq 'NO_CHANGE' or $txt eq 'SUCCESS' or $txt eq 'ROUTE_ITEM') {
         $self->ok(1); # NO_CHANGE means it wasn't checked out anyway, no problem
index 89ccfa6..9845623 100644 (file)
@@ -76,6 +76,11 @@ sub new {
        return $self;
 }
 
+sub DESTROY {
+        my $self = shift;
+        $self->reset;
+        return undef;
+}
 
 sub app {
        my( $self, $app ) = @_;
@@ -289,8 +294,17 @@ sub rollback_savepoint {
 # -----------------------------------------------------------------------------
 sub rollback {
        my $self = shift;
-       $self->xact_rollback;
-       $self->disconnect;
+    my $err;
+    my $ret;
+       try {
+        $self->xact_rollback;
+    } catch Error with  {
+        $err = shift
+    } finally {
+        $ret = $self->disconnect
+    };
+    throw $err if ($err);
+    return $ret;
 }
 
 sub disconnect {
@@ -329,8 +343,17 @@ sub reset {
 # -----------------------------------------------------------------------------
 sub finish {
        my $self = shift;
-       $self->commit;
-       $self->reset;
+    my $err;
+    my $ret;
+       try {
+        $self->commit;
+    } catch Error with  {
+        $err = shift
+    } finally {
+        $ret = $self->reset
+    };
+    throw $err if ($err);
+    return $ret;
 }
 
 
index 4edc51c..f259345 100644 (file)
@@ -26,41 +26,53 @@ sub calculate_penalties {
     my $penalties = $e->json_query({from => ['actor.calculate_system_penalties',$user_id, $context_org]});
 
     my $user = $e->retrieve_actor_user( $user_id );
-    my $ses = OpenSRF::AppSession->create('open-ils.trigger') if (@$penalties);
+    my @existing_penalties = grep { defined $_->{id} } @$penalties;
+    my @wanted_penalties = grep { !defined $_->{id} } @$penalties;
+    my @trigger_events;
 
     my %csp;
-    for my $pen_obj (@$penalties) {
-
-        next if grep { # leave duplicate penalties in place
-            $_->{org_unit} == $pen_obj->{org_unit} and
-            $_->{standing_penalty} == $pen_obj->{standing_penalty} and
-            ($_->{id} || '') ne ($pen_obj->{id} || '') } @$penalties;
+    for my $pen_obj (@wanted_penalties) {
 
         my $pen = Fieldmapper::actor::user_standing_penalty->new;
         $pen->$_($pen_obj->{$_}) for keys %$pen_obj;
 
-        if(defined $pen_obj->{id}) {
-            $e->delete_actor_user_standing_penalty($pen) or return $e->die_event;
+        # let's see if this penalty is accounted for already
+        my ($existing) = grep { 
+                $_->{org_unit} == $pen_obj->{org_unit} and
+                $_->{standing_penalty} == $pen_obj->{standing_penalty}
+            } @existing_penalties;
+
+        if($existing) { 
+            # we have one of these already.  Leave it be, but remove it from the 
+            # existing set so it's not deleted in the subsequent loop
+            @existing_penalties = grep { $_->{id} ne $existing->{id} }  @existing_penalties;
 
         } else {
+
+            # this is a new penalty
             $e->create_actor_user_standing_penalty($pen) or return $e->die_event;
 
-            my $csp_obj = $csp{$pen->standing_penalty} ||
+            my $csp_obj = $csp{$pen->standing_penalty} || 
                 $e->retrieve_config_standing_penalty( $pen->standing_penalty );
 
             # cache for later
             $csp{$pen->standing_penalty} = $csp_obj;
 
-            $ses->request(
-                'open-ils.trigger.event.autocreate',
-                'penalty.' . $csp_obj->name,
-                $pen,
-                $pen->org_unit
-            );
+            push(@trigger_events, ['penalty.' . $csp_obj->name, $pen, $pen->org_unit]);
         }
     }
 
+    # at this point, any penalties remaining in the existing 
+    # penalty set are unaccounted for and should be removed
+    for my $pen_obj (@existing_penalties) {
+        my $pen = Fieldmapper::actor::user_standing_penalty->new;
+        $pen->$_($pen_obj->{$_}) for keys %$pen_obj;
+        $e->delete_actor_user_standing_penalty($pen) or return $e->die_event;
+    }
+
     $e->commit if $commit;
+
+    $U->create_events_for_hook($$_[0], $$_[1], $$_[2]) for @trigger_events;
     return undef;
 }
 
index b42c6d5..47a561a 100644 (file)
@@ -204,6 +204,7 @@ my $LEGACY_HOLD_EVENT_MAP = {
 sub indb_hold_permit {
     my $params = shift;
 
+    my $function = $$params{retarget} ? 'action.hold_retarget_permit_test' : 'action.hold_request_permit_test';
     my $patron_id = 
         ref($$params{patron}) ? $$params{patron}->id : $$params{patron_id};
     my $request_lib = 
@@ -211,7 +212,7 @@ sub indb_hold_permit {
 
     my $HOLD_TEST = {
         from => [
-            'action.hold_request_permit_test',
+            $function,
             $$params{pickup_lib}, 
             $request_lib,
             $$params{copy}->id, 
index ca1c900..7eb9749 100644 (file)
@@ -1,4 +1,4 @@
-package   OpenILS::Utils::RemoteAccount;
+package OpenILS::Utils::RemoteAccount;
 
 # use OpenSRF::Utils::SettingsClient;
 use OpenSRF::Utils::Logger qw/:logger/;
@@ -45,12 +45,18 @@ my %fields = (
 );
 
 
-=pod 
+=head1 NAME 
+
+OpenILS::Utils::RemoteAccount - Encapsulate FTP, SFTP and SSH file transactions for Evergreen
+
+=head1 DESCRIPTION
 
 The Remote Account module attempts to transfer a file to/from a remote server.
 Net::uFTP is used, encapsulating the available options of SCP, FTP and SFTP.
 
-All information is expected to be gathered by the Event Definition through event parameters:
+=head1 PARAMETERS
+
+All information is expected to be supplied by the caller via parameters:
    ~ remote_host (required)
    ~ remote_user
    ~ remote_password
@@ -79,15 +85,16 @@ Note that specifying a password will require you to specify a user.
 Similarly, specifying an account requires both user and password.
 That is, there are no assumed defaults when the latter arguments are used.
 
-SSH KEYS:
+=head2 SSH KEYS:
 
-The use of ssh keys is preferred. 
+The use of ssh keys is preferred.  Explicit specification of connection type will prevent
+multiple attempts to the same server.  Therefore, using the type parameter is also recommended.
 
-We attempt to use SSH keys where they are specified or otherwise found
+If the type is not explicit, we attempt to use SSH keys where they are specified or otherwise found
 in the runtime environment.  If only one key is specified, we attempt to derive
 the corresponding filename based on the ssh-keygen defaults.  If either key is
 specified, but both are not found (and readable) then the result is failure.  If
-no key is specified, but keys are found, the key-based connections will be attempted,
+no key or type is specified, but keys are found, the key-based connections will be attempted,
 but failure will be non-fatal.
 
 =cut
index 1fd0994..6c5f3da 100644 (file)
@@ -80,7 +80,7 @@ sub handler {
         my $base = $cgi->url(-base=>1);
                $base =~ s/^http:/https:/o;
                print "Location: $base".$apache->unparsed_uri."\n\n";
-               return Apache2::Const::OK;
+               return Apache2::Const::REDIRECT;
        }
 
        if (!$auth_ses) {
diff --git a/Open-ILS/src/perlmods/OpenILS/WWW/TemplateBatchBibUpdate.pm b/Open-ILS/src/perlmods/OpenILS/WWW/TemplateBatchBibUpdate.pm
new file mode 100644 (file)
index 0000000..8f9f0ce
--- /dev/null
@@ -0,0 +1,671 @@
+package OpenILS::WWW::TemplateBatchBibUpdate;
+use strict;
+use warnings;
+use bytes;
+
+use Apache2::Log;
+use Apache2::Const -compile => qw(OK REDIRECT DECLINED NOT_FOUND :log);
+use APR::Const    -compile => qw(:error SUCCESS);
+use APR::Table;
+
+use Apache2::RequestRec ();
+use Apache2::RequestIO ();
+use Apache2::RequestUtil;
+use CGI;
+use Data::Dumper;
+use Text::CSV;
+
+use OpenSRF::EX qw(:try);
+use OpenSRF::Utils qw/:datetime/;
+use OpenSRF::Utils::Cache;
+use OpenSRF::System;
+use OpenSRF::AppSession;
+use XML::LibXML;
+use XML::LibXSLT;
+
+use Encode;
+use Unicode::Normalize;
+use OpenILS::Utils::Fieldmapper;
+use OpenSRF::Utils::Logger qw/$logger/;
+
+use MARC::Record;
+use MARC::File::XML;
+
+use UNIVERSAL::require;
+
+our @formats = qw/USMARC UNIMARC XML BRE/;
+
+# set the bootstrap config and template include directory when
+# this module is loaded
+my $bootstrap;
+
+sub import {
+    my $self = shift;
+    $bootstrap = shift;
+}
+
+
+sub child_init {
+    OpenSRF::System->bootstrap_client( config_file => $bootstrap );
+    Fieldmapper->import(IDL => OpenSRF::Utils::SettingsClient->new->config_value("IDL"));
+}
+
+sub handler {
+    my $r = shift;
+    my $cgi = new CGI;
+
+    my $authid = $cgi->cookie('ses') || $cgi->param('ses');
+    my $usr = verify_login($authid);
+    return show_template($r) unless ($usr);
+
+    my $template = $cgi->param('template');
+    return show_template($r) unless ($template);
+
+
+    my $rsource = $cgi->param('recordSource');
+    # find some IDs ...
+    my @records;
+
+    if ($rsource eq 'r') {
+        @records = map { $_ ? ($_) : () } $cgi->param('recid');
+    }
+
+    if ($rsource eq 'c') { # try for a file
+        my $file = $cgi->param('idfile');
+        if ($file) {
+            my $col = $cgi->param('idcolumn') || 0;
+            my $csv = new Text::CSV;
+
+            while (<$file>) {
+                $csv->parse($_);
+                my @data = $csv->fields;
+                my $id = $data[$col];
+                $id =~ s/\D+//o;
+                next unless ($id);
+                push @records, $id;
+            }
+        }
+    }
+
+    my $e = OpenSRF::AppSession->connect('open-ils.cstore');
+    $e->request('open-ils.cstore.transaction.begin')->gather(1);
+
+    # still no records ...
+    my $container = $cgi->param('containerid');
+    if ($rsource eq 'b') {
+        if ($container) {
+            my $bucket = $e->request(
+                'open-ils.cstore.direct.container.biblio_record_entry_bucket.retrieve',
+                $container
+            )->gather(1);
+            unless($bucket) {
+                $e->request('open-ils.cstore.transaction.rollback')->gather(1);
+                $e->disconnect;
+                $r->log->error("No such bucket $container");
+                $logger->error("No such bucket $container");
+                return Apache2::Const::NOT_FOUND;
+            }
+            my $recs = $e->request(
+                'open-ils.cstore.direct.container.biblio_record_entry_bucket_item.search.atomic',
+                { bucket => $container }
+            )->gather(1);
+            @records = map { ($_->target_biblio_record_entry) } @$recs;
+        }
+    }
+
+    unless (@records) {
+        $e->request('open-ils.cstore.transaction.rollback')->gather(1);
+        $e->disconnect;
+        return show_template($r);
+    }
+
+    # we have a template and some record ids, so...
+
+    # insert the template record
+    my $min_id = $e->request(
+        'open-ils.cstore.json_query',
+        { select => { bre => [{ column => 'id', transform => 'min', aggregate => 1}] }, from => 'bre' }
+    )->gather(1)->{id} - 1;
+
+    warn "new template bib id = $min_id\n";
+
+    my $tmpl_rec = Fieldmapper::biblio::record_entry->new;
+    $tmpl_rec->id($min_id);
+    $tmpl_rec->deleted('t');
+    $tmpl_rec->active('f');
+    $tmpl_rec->marc($template);
+    $tmpl_rec->creator($usr->id);
+    $tmpl_rec->editor($usr->id);
+
+    warn "about to create bib $min_id\n";
+    $e->request('open-ils.cstore.direct.biblio.record_entry.create', $tmpl_rec )->gather(1);
+
+    # create the new container for the records and the template
+    my $bucket = Fieldmapper::container::biblio_record_entry_bucket->new;
+    $bucket->owner($usr->id);
+    $bucket->btype('template_merge');
+
+    my $bname = $cgi->param('bname') || 'Temporary Merge Bucket '. localtime() . ' ' . $usr->id;
+    $bucket->name($bname);
+
+    $bucket = $e->request('open-ils.cstore.direct.container.biblio_record_entry_bucket.create', $bucket )->gather(1);
+
+    # create items in the bucket
+    my $item = Fieldmapper::container::biblio_record_entry_bucket_item->new;
+    $item->bucket($bucket->id);
+    $item->target_biblio_record_entry($min_id);
+
+    $e->request('open-ils.cstore.direct.container.biblio_record_entry_bucket_item.create', $item )->gather(1);
+
+    my %seen;
+    for my $r (@records) {
+        next if ($seen{$r});
+        $item->target_biblio_record_entry($r);
+        $e->request('open-ils.cstore.direct.container.biblio_record_entry_bucket_item.create', $item )->gather(1);
+        $seen{$r}++;
+    }
+
+    $e->request('open-ils.cstore.transaction.commit')->gather(1);
+    $e->disconnect;
+
+    # fire the background bucket processor
+    my $cache_key = OpenSRF::AppSession
+        ->create('open-ils.cat')
+        ->request('open-ils.cat.container.template_overlay.background', $authid, $bucket->id)
+        ->gather(1);
+
+    return show_processing_template($r, $bucket->id, \@records, $cache_key);
+}
+
+sub verify_login {
+        my $auth_token = shift;
+        return undef unless $auth_token;
+
+        my $user = OpenSRF::AppSession
+                ->create("open-ils.auth")
+                ->request( "open-ils.auth.session.retrieve", $auth_token )
+                ->gather(1);
+
+        if (ref($user) eq 'HASH' && $user->{ilsevent} == 1001) {
+                return undef;
+        }
+
+        return $user if ref($user);
+        return undef;
+}
+
+sub show_processing_template {
+    my $r = shift;
+    my $bid = shift;
+    my $recs = shift;
+    my $cache_key = shift;
+
+    my $rec_string = @$recs;
+
+    $r->content_type('text/html');
+    $r->print(<<HTML);
+<html xmlns="http://www.w3.org/1999/xhtml">
+
+    <head>
+        <title>Merging records...</title>
+        <style type="text/css">
+            \@import '/js/dojo/dojo/resources/dojo.css';
+            \@import '/js/dojo/dijit/themes/tundra/tundra.css';
+            .hide_me { display: none; visibility: hidden; }
+            th       { font-weight: bold; }
+        </style>
+
+        <script type="text/javascript">
+            var djConfig= {
+                isDebug: false,
+                parseOnLoad: true,
+                AutoIDL: ['aou','aout','pgt','au','cbreb']
+            }
+        </script>
+
+        <script src='/js/dojo/dojo/dojo.js'></script>
+        <!-- <script src="/js/dojo/dojo/openils_dojo.js"></script> -->
+
+        <script type="text/javascript">
+
+            dojo.require('fieldmapper.AutoIDL');
+            dojo.require('fieldmapper.dojoData');
+            dojo.require('openils.User');
+            dojo.require('openils.CGI');
+            dojo.require('openils.widget.ProgressDialog');
+
+            var cgi = new openils.CGI();
+            var u = new openils.User({ authcookie : 'ses' });
+
+            dojo.addOnLoad(function () {
+                progress_dialog.show(true);
+                progress_dialog.update({maximum:$rec_string});
+
+                var interval;
+                interval = setInterval( function() {
+                    fieldmapper.standardRequest(
+                        ['open-ils.actor','open-ils.actor.anon_cache.get_value'],
+                        { async : false,
+                          params: [ u.authtoken, 'res_list' ],
+                          onerror : function (r) { progress_dialog.hide(); },
+                          onresponse : function (r) {
+                            var counter = { success : 0, fail : 0, total : 0 };
+                            dojo.forEach( openils.Util.readResponse(r), function(x) {
+                                if (x.complete) {
+                                    clearInterval(interval);
+                                    progress_dialog.hide();
+                                    if (x.success == 't') dojo.byId('complete_msg').innerHTML = 'Overlay completed successfully';
+                                    else dojo.byId('complete_msg').innerHTML = 'Overlay did not complet successfully';
+                                } else {
+                                    counter.total++;
+                                    switch (x.success) {
+                                        case 't':
+                                            counter.success++;
+                                            break;
+                                        default:
+                                            counter.fail++;
+                                            break;
+                                    }
+                                }
+                            });
+
+                            // update the progress dialog
+                            progress_dialog.update({progress:counter.total});
+                            dojo.byId('success_count').innerHTML = counter.success;
+                            dojo.byId('fail_count').innerHTML = counter.fail;
+                            dojo.byId('total_count').innerHTML = counter.total;
+                          }
+                        }
+                    );
+                }, 1000);
+
+            });
+        </script>
+    </head>
+
+    <body style="margin:10px;" class='tundra'>
+        <div class="hide_me"><div dojoType="openils.widget.ProgressDialog" jsId="progress_dialog"></div></div>
+
+        <table style="width:100%; margin-top:100px;">
+            <th>
+                <td>Status</td>
+                <td>Record Count</td>
+            </th>
+            <tr>
+                <td>Success</td>
+                <td id='success_count'></td>
+            </tr>
+            <tr>
+                <td>Failure</td>
+                <td id='fail_count'></td>
+            </tr>
+            <tr>
+                <td></td>
+                <td id='total_count'></td>
+            </tr>
+        </table>
+
+        <div id='complete_msg'></div>
+
+    </body>
+</html>
+HTML
+
+    return Apache2::Const::OK;
+}
+
+
+sub show_template {
+    my $r = shift;
+
+    $r->content_type('text/html');
+    $r->print(<<'HTML');
+<html xmlns="http://www.w3.org/1999/xhtml">
+
+    <head>
+        <title>Merge Template Builder</title>
+        <style type="text/css">
+            @import '/js/dojo/dojo/resources/dojo.css';
+            @import '/js/dojo/dijit/themes/tundra/tundra.css';
+            .hide_me { display: none; visibility: hidden; }
+            table.ruleTable th { padding: 5px; border-collapse: collapse; border-bottom: solid 1px gray; font-weight: bold; }
+            table.ruleTable td { padding: 5px; border-collapse: collapse; border-bottom: solid 1px gray; }
+        </style>
+
+        <script type="text/javascript">
+            var djConfig= {
+                isDebug: false,
+                parseOnLoad: true,
+                AutoIDL: ['aou','aout','pgt','au','cbreb']
+            }
+        </script>
+
+        <script src='/js/dojo/dojo/dojo.js'></script>
+        <!-- <script src="/js/dojo/dojo/openils_dojo.js"></script> -->
+
+        <script type="text/javascript">
+
+            dojo.require('dojo.data.ItemFileReadStore');
+            dojo.require('dijit.form.Form');
+            dojo.require('dijit.form.NumberSpinner');
+            dojo.require('dijit.form.FilteringSelect');
+            dojo.require('dijit.form.TextBox');
+            dojo.require('dijit.form.Textarea');
+            dojo.require('dijit.form.Button');
+            dojo.require('MARC.Batch');
+            dojo.require('fieldmapper.AutoIDL');
+            dojo.require('fieldmapper.dojoData');
+            dojo.require('openils.User');
+            dojo.require('openils.CGI');
+
+            var cgi = new openils.CGI();
+            var u = new openils.User({ authcookie : 'ses' });
+
+            var bucketStore = new dojo.data.ItemFileReadStore(
+                { data : cbreb.toStoreData(
+                        fieldmapper.standardRequest(
+                            ['open-ils.actor','open-ils.actor.container.retrieve_by_class'],
+                            [u.authtoken, u.user.id(), 'biblio', 'staff_client']
+                        )
+                    )
+                }
+            );
+
+            function render_preview () {
+                var rec = ruleset_to_record();
+                dojo.byId('marcPreview').innerHTML = rec.toBreaker();
+            }
+
+            function render_from_template () {
+                var kid_number = dojo.byId('ruleList').childNodes.length;
+                var clone = dojo.query('*[name=ruleTable]', dojo.byId('ruleTemplate'))[0].cloneNode(true);
+
+                var typeSelect = dojo.query('*[name=typeSelect]',clone).instantiate(dijit.form.FilteringSelect, {
+                    onChange : function (val) {
+                        switch (val) {
+                            case 'a':
+                            case 'r':
+                                dijit.byNode(dojo.query('*[name=marcDataContainer] .dijit',clone)[0]).attr('disabled',false);
+                                break;
+                            default :
+                                dijit.byNode(dojo.query('*[name=marcDataContainer] .dijit',clone)[0]).attr('disabled',true);
+                        };
+                        render_preview();
+                    }
+                })[0];
+
+                var marcData = dojo.query('*[name=marcData]',clone).instantiate(dijit.form.TextBox, {
+                    onChange : render_preview
+                })[0];
+
+
+                var tag = dojo.query('*[name=tag]',clone).instantiate(dijit.form.TextBox, {
+                    onChange : function (newtag) {
+                        var md = dijit.byNode(dojo.query('*[name=marcDataContainer] .dijit',clone)[0]);
+                        var current_marc = md.attr('value');
+
+                        if (newtag.length == 3) {
+                            if (current_marc.length == 0) newtag += ' \\\\';
+                            if (current_marc.substr(0,3) != newtag) current_marc = newtag + current_marc.substr(3);
+                        }
+                        md.attr('value', current_marc);
+                        render_preview();
+                    }
+                })[0];
+
+                var sf = dojo.query('*[name=sf]',clone).instantiate(dijit.form.TextBox, {
+                    onChange : function (newsf) {
+                        var md = dijit.byNode(dojo.query('*[name=marcDataContainer] .dijit',clone)[0]);
+                        var current_marc = md.attr('value');
+                        var sf_list = newsf.split('');
+
+                        for (var i in sf_list) {
+                            var re = '\\$' + sf_list[i];
+                            if (current_marc.match(re)) continue;
+                            current_marc += '$' + sf_list[i];
+                        }
+
+                        md.attr('value', current_marc);
+                        render_preview();
+                    }
+                })[0];
+
+                var matchSF = dojo.query('*[name=matchSF]',clone).instantiate(dijit.form.TextBox, {
+                    onChange : render_preview
+                })[0];
+
+                var matchRE = dojo.query('*[name=matchRE]',clone).instantiate(dijit.form.TextBox, {
+                    onChange : render_preview
+                })[0];
+
+                var removeButton = dojo.query('*[name=removeButton]',clone).instantiate(dijit.form.Button, {
+                    onClick : function() {
+                        dojo.addClass(
+                            dojo.byId('ruleList').childNodes[kid_number],
+                            'hide_me'
+                        );
+                        render_preview();
+                    }
+                })[0];
+
+                dojo.place(clone,'ruleList');
+            }
+
+            function ruleset_to_record () {
+                var rec = new MARC.Record ({ delimiter : '$' });
+
+                dojo.forEach( 
+                    dojo.query('#ruleList *[name=ruleTable]').filter( function (node) {
+                        if (node.className.match(/hide_me/)) return false;
+                        return true;
+                    }),
+                    function (tbl) {
+                        var rule_tag = new MARC.Field ({
+                            tag : '905',
+                            ind1 : ' ',
+                            ind2 : ' '
+                        });
+                        var rule_txt = dijit.byNode(dojo.query('*[name=tagContainer] .dijit',tbl)[0]).attr('value');
+                        rule_txt += dijit.byNode(dojo.query('*[name=sfContainer] .dijit',tbl)[0]).attr('value');
+
+                        var reSF = dijit.byNode(dojo.query('*[name=matchSFContainer] .dijit',tbl)[0]).attr('value');
+                        if (reSF) {
+                            var reRE = dijit.byNode(dojo.query('*[name=matchREContainer] .dijit',tbl)[0]).attr('value');
+                            rule_txt += '[' + reSF + '~' + reRE + ']';
+                        }
+
+                        var rtype = dijit.byNode(dojo.query('*[name=typeSelectContainer] .dijit',tbl)[0]).attr('value');
+                        rule_tag.addSubfields( rtype, rule_txt )
+                        rec.appendFields( rule_tag );
+
+                        if (rtype == 'a' || rtype == 'r') {
+                            rec.appendFields(
+                                new MARC.Record ({
+                                    delimiter : '$',
+                                    marcbreaker : dijit.byNode(dojo.query('*[name=marcDataContainer] .dijit',tbl)[0]).attr('value')
+                                }).fields[0]
+                            );
+                        }
+                    }
+                );
+
+                return rec;
+            }
+        </script>
+    </head>
+
+    <body style="margin:10px;" class='tundra'>
+
+        <div dojoType="dijit.form.Form" id="myForm" jsId="myForm" encType="multipart/form-data" action="" method="POST">
+                <script type='dojo/method' event='onSubmit'>
+                    var rec = ruleset_to_record();
+
+                    if (rec.subfield('905','r') == '') { // no-op to force replace mode
+                        rec.appendFields(
+                            new MARC.Field ({
+                                tag : '905',
+                                ind1 : ' ',
+                                ind2 : ' ',
+                                subfields : [['r','901c']]
+                            })
+                        );
+                    }
+
+                    dojo.byId('template_value').value = rec.toXmlString();
+                    return true;
+                </script>
+
+            <input type='hidden' id='template_value' name='template'/>
+
+            <label for='inputTypeSelect'>Record source:</label>
+            <select name='recordSource' dojoType='dijit.form.FilteringSelect'>
+                <script type='dojo/method' event='onChange' args="val">
+                    switch (val) {
+                        case 'b':
+                            dojo.removeClass('bucketListContainer','hide_me');
+                            dojo.addClass('csvContainer','hide_me');
+                            dojo.addClass('recordContainer','hide_me');
+                            break;
+                        case 'c':
+                            dojo.addClass('bucketListContainer','hide_me');
+                            dojo.removeClass('csvContainer','hide_me');
+                            dojo.addClass('recordContainer','hide_me');
+                            break;
+                        case 'r':
+                            dojo.addClass('bucketListContainer','hide_me');
+                            dojo.addClass('csvContainer','hide_me');
+                            dojo.removeClass('recordContainer','hide_me');
+                            break;
+                    };
+                </script>
+                <script type='dojo/method' event='postCreate'>
+                    if (cgi.param('recordSource')) {
+                        this.attr('value',cgi.param('recordSource'));
+                        this.onChange(cgi.param('recordSource'));
+                    }
+                </script>
+                <option value='b'>a Bucket</option>
+                <option value='c'>a CSV File</option>
+                <option value='r'>a specific record ID</option>
+            </select>
+
+            <table style='margin:10px; margin-bottom:20px;'>
+<!--
+                <tr>
+                    <th>Merge template name (optional):</th>
+                    <td><input id='bucketName' jsId='bucketName' type='text' dojoType='dijit.form.TextBox' name='bname' value=''/></td>
+                </tr>
+-->
+                <tr class='' id='bucketListContainer'>
+                    <td>Bucket named: 
+                        <div name='containerid' jsId='bucketList' dojoType='dijit.form.FilteringSelect' store='bucketStore' searchAttr='name' id='bucketList'>
+                            <script type='dojo/method' event='postCreate'>
+                                if (cgi.param('containerid')) this.attr('value',cgi.param('containerid'));
+                            </script>
+                        </div>
+                    </td>
+                </tr>
+                <tr class='hide_me' id='csvContainer'>
+                    <td>
+                        Column <input style='width:75px;' type='text' dojoType='dijit.form.NumberSpinner' name='idcolumn' value='0' constraints='{min:0,max:100,places:0}' /> of: 
+                        <input id='idfile' type="file" name="idfile"/>
+                        <br/>
+                        <br/>
+                        Columns are numbered starting at 0.  For instance, when looking at a CSV file in Excel, the column labeled A is the same as column 0, and the column labeled B is the same as column 1.
+                    </td>
+                </tr>
+                <tr class='hide_me' id='recordContainer'>
+                    <td>Record ID: <input dojoType='dijit.form.TextBox' name='recid' style='width:75px;' type='text' value=''/></td>
+                </tr>
+            </table>
+
+            <button type="submit" dojoType='dijit.form.Button'>GO!</button> (After setting up your template below.)
+
+            <br/>
+            <br/>
+
+        </div> <!-- end of the form -->
+
+        <hr/>
+        <table style='width: 100%'>
+            <tr>
+                <td style='width: 50%'><div id='ruleList'></div></td>
+                <td valign='top'>Update Template Preview:<br/><pre id='marcPreview'></pre></td>
+            </tr>
+        </table>
+
+        <button dojoType='dijit.form.Button'>Add Merge Rule
+            <script type='dojo/connect' event='onClick'>render_from_template()</script>
+            <script type='dojo/method' event='postCreate'>render_from_template()</script>
+        </button>
+
+        <div class='hide_me' id='ruleTemplate'>
+        <div name='ruleTable'>
+            <table class='ruleTable'>
+                <tbody>
+                    <tr>
+                        <th style="text-align:center;">Rule Setup</th>
+                        <th style="text-align:center;">Data</th>
+                        <th style="text-align:center;">Help</th>
+                    </tr>
+                    <tr>
+                        <th>Action (Rule Type)</th>
+                        <td name='typeSelectContainer'>
+                            <select name='typeSelect'>
+                                <option value='r'>Replace</option>
+                                <option value='a'>Add</option>
+                                <option value='d'>Delete</option>
+                            </select>
+                        </td>
+                        <td>How to change the existing records</td>
+                    </tr>
+                    <tr>
+                        <th>MARC Tag</th>
+                        <td name='tagContainer'><input style='with: 2em;' name='tag' type='text'></input</td>
+                        <td>Three characters, no spaces, no indicators, etc. eg: 245</td>
+                    </td>
+                    <tr>
+                        <th>Subfields (optional)</th>
+                        <td name='sfContainer'><input name='sf' type='text'/></td>
+                        <td>No spaces, no delimiters, eg: abcnp</td>
+                    </tr>
+                    <tr>
+                        <th>MARC Data</th>
+                        <td name='marcDataContainer'><input name='marcData' type='text'/></td>
+                        <td>MARC-Breaker formatted data with indicators and subfield delimiters, eg:<br/>245 04$aThe End</td>
+                    </tr>
+                    <tr>
+                        <th colspan='3' style='padding-top: 20px; text-align: center;'>Advanced Matching Restriction (Optional)</th>
+                    </tr>
+                    <tr>
+                        <th>Subfield</th>
+                        <td name='matchSFContainer'><input style='with: 2em;' name='matchSF' type='text'></input</td>
+                        <td>A single subfield code, no delimiters, eg: a</td>
+                    <tr>
+                        <th>Regular Expression</th>
+                        <td name='matchREContainer'><input name='matchRE' type='text'/></td>
+                        <td>See <a href="http://perldoc.perl.org/perlre.html#Regular-Expressions" target="_blank">the Perl documentation</a>
+                            for an explanation of Regular Expressions.
+                        </td>
+                    </tr>
+                    <tr>
+                        <td colspan='3' style='padding-top: 20px; text-align: center;'>
+                            <button name='removeButton'>Remove this Template Rule</button>
+                        </td>
+                    </tr>
+                </tbody>
+            </table>
+        <hr/>
+        </div>
+        </div>
+
+    </body>
+</html>
+HTML
+
+    return Apache2::Const::OK;
+}
+
+1;
+
+
index 52dc6fd..196297e 100755 (executable)
@@ -423,6 +423,8 @@ sub build_excel {
        $sheetname =~ s/\W/_/gos;
        
        my $sheet = $xls->add_worksheet($sheetname);
+       # don't try to write formulas, just write anything that starts with = as a text cell
+       $sheet->add_write_handler(qr/^=/, sub { return shift->write_string(@_); } );
 
        $sheet->write_row('A1', $r->{column_labels});
 
index 6b43ceb..cc97d92 100644 (file)
@@ -272,33 +272,37 @@ BEGIN
     select_list := ARRAY_APPEND( select_list, key || '::INT AS key' );
 
     FOR i IN 1 .. ARRAY_UPPER(xpath_list,1) LOOP
-        select_list := ARRAY_APPEND(
-            select_list,
-            $sel$
-            EXPLODE_ARRAY(
-                COALESCE(
-                    NULLIF(
-                        oils_xpath(
-                            $sel$ ||
-                                quote_literal(
-                                    CASE
-                                        WHEN xpath_list[i] ~ $re$/[^/[]*@[^/]+$$re$ OR xpath_list[i] ~ $re$text\(\)$$re$ THEN xpath_list[i]
-                                        ELSE xpath_list[i] || '//text()'
-                                    END
-                                ) ||
-                            $sel$,
-                            $sel$ || document_field || $sel$
+        IF xpath_list[i] = 'null()' THEN
+            select_list := ARRAY_APPEND( select_list, 'NULL::TEXT AS c_' || i );
+        ELSE
+            select_list := ARRAY_APPEND(
+                select_list,
+                $sel$
+                EXPLODE_ARRAY(
+                    COALESCE(
+                        NULLIF(
+                            oils_xpath(
+                                $sel$ ||
+                                    quote_literal(
+                                        CASE
+                                            WHEN xpath_list[i] ~ $re$/[^/[]*@[^/]+$$re$ OR xpath_list[i] ~ $re$text\(\)$$re$ THEN xpath_list[i]
+                                            ELSE xpath_list[i] || '//text()'
+                                        END
+                                    ) ||
+                                $sel$,
+                                $sel$ || document_field || $sel$
+                            ),
+                           '{}'::TEXT[]
                         ),
-                       '{}'::TEXT[]
-                    ),
-                    '{NULL}'::TEXT[]
-                )
-            ) AS c_$sel$ || i
-        );
-        where_list := ARRAY_APPEND(
-            where_list,
-            'c_' || i || ' IS NOT NULL'
-        );
+                        '{NULL}'::TEXT[]
+                    )
+                ) AS c_$sel$ || i
+            );
+            where_list := ARRAY_APPEND(
+                where_list,
+                'c_' || i || ' IS NOT NULL'
+            );
+        END IF;
     END LOOP;
 
     q := $q$
@@ -593,5 +597,9 @@ if ($create or $munge) {
 return;
 $func$ LANGUAGE PLPERLU;
 
+CREATE OR REPLACE FUNCTION oils_text_as_bytea (TEXT) RETURNS BYTEA AS $_$
+    SELECT CAST(REGEXP_REPLACE($1, $$\\$$, $$\\\\$$, 'g') AS BYTEA);
+$_$ LANGUAGE SQL IMMUTABLE;
+
 COMMIT;
 
index fca4b6b..85f7a4e 100644 (file)
@@ -70,7 +70,7 @@ CREATE TABLE config.upgrade_log (
     install_date    TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW()
 );
 
-INSERT INTO config.upgrade_log (version) VALUES ('0412'); -- phasefx
+INSERT INTO config.upgrade_log (version) VALUES ('0439'); -- miker
 
 CREATE TABLE config.bib_source (
        id              SERIAL  PRIMARY KEY,
@@ -316,7 +316,8 @@ CREATE TABLE config.rule_circ_duration (
        extended        INTERVAL        NOT NULL,
        normal          INTERVAL        NOT NULL,
        shrt            INTERVAL        NOT NULL,
-       max_renewals    INT             NOT NULL
+       max_renewals    INT             NOT NULL,
+       date_ceiling    TIMESTAMPTZ
 );
 COMMENT ON TABLE config.rule_circ_duration IS $$
 /*
@@ -342,6 +343,14 @@ COMMENT ON TABLE config.rule_circ_duration IS $$
  */
 $$;
 
+CREATE TABLE config.hard_due_date (
+    id              SERIAL      PRIMARY KEY,
+    duration_rule   INT         NOT NULL REFERENCES config.rule_circ_duration (id)
+                                DEFERRABLE INITIALLY DEFERRED,
+    ceiling_date    TIMESTAMPTZ NOT NULL,
+    active_date     TIMESTAMPTZ NOT NULL
+);
+
 CREATE TABLE config.rule_max_fine (
     id          SERIAL          PRIMARY KEY,
     name        TEXT            NOT NULL UNIQUE CHECK ( name ~ E'^\\w+$' ),
index c734d94..4be5c7d 100644 (file)
@@ -38,7 +38,8 @@ CREATE TABLE permission.grp_tree (
        usergroup               BOOL    NOT NULL DEFAULT TRUE,
        perm_interval           INTERVAL DEFAULT '3 years'::interval NOT NULL,
        description             TEXT,
-       application_perm        TEXT
+       application_perm        TEXT,
+       hold_priority       INT   NOT NULL DEFAULT 0
 );
 CREATE INDEX grp_tree_parent_idx ON permission.grp_tree (parent);
 
index 41372e2..95ff5eb 100644 (file)
@@ -77,7 +77,7 @@ ALTER TABLE vandelay.bib_queue ADD PRIMARY KEY (id);
 CREATE TABLE vandelay.queued_bib_record (
        queue           INT             NOT NULL REFERENCES vandelay.bib_queue (id) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED,
        bib_source      INT             REFERENCES config.bib_source (id) DEFERRABLE INITIALLY DEFERRED,
-       imported_as     INT             REFERENCES biblio.record_entry (id) DEFERRABLE INITIALLY DEFERRED
+       imported_as     BIGINT  REFERENCES biblio.record_entry (id) DEFERRABLE INITIALLY DEFERRED
 ) INHERITS (vandelay.queued_record);
 ALTER TABLE vandelay.queued_bib_record ADD PRIMARY KEY (id);
 CREATE INDEX queued_bib_record_queue_idx ON vandelay.queued_bib_record (queue);
@@ -347,12 +347,18 @@ CREATE OR REPLACE FUNCTION vandelay.add_field ( target_xml TEXT, source_xml TEXT
     for my $f ( keys %fields) {
         if ( @{$fields{$f}{sf}} ) {
             for my $from_field ($source_r->field( $f )) {
-                for my $to_field ($target_r->field( $f )) {
-                    if (exists($fields{$f}{match})) {
-                        next unless (grep { $_ =~ $fields{$f}{match}{re} } $to_field->subfield($fields{$f}{match}{sf}));
+                my @tos = $target_r->field( $f );
+                if (!@tos) {
+                    my @new_fields = map { $_->clone } $source_r->field( $f );
+                    $target_r->insert_fields_ordered( @new_fields );
+                } else {
+                    for my $to_field (@tos) {
+                        if (exists($fields{$f}{match})) {
+                            next unless (grep { $_ =~ $fields{$f}{match}{re} } $to_field->subfield($fields{$f}{match}{sf}));
+                        }
+                        my @new_sf = map { ($_ => $from_field->subfield($_)) } @{$fields{$f}{sf}};
+                        $to_field->add_subfields( @new_sf );
                     }
-                    my @new_sf = map { ($_ => $from_field->subfield($_)) } @{$fields{$f}{sf}};
-                    $to_field->add_subfields( @new_sf );
                 }
             }
         } else {
@@ -469,10 +475,10 @@ BEGIN
         END IF;
     END IF;
 
-    add_rule := add_rule || ',' || COALESCE(ARRAY_TO_STRING(oils_xpath('//*[@tag="905"]/*[@code="a"]/text()',incoming_xml),''),'');
-    strip_rule := strip_rule || ',' || COALESCE(ARRAY_TO_STRING(oils_xpath('//*[@tag="905"]/*[@code="d"]/text()',incoming_xml),''),'');
-    replace_rule := replace_rule || ',' || COALESCE(ARRAY_TO_STRING(oils_xpath('//*[@tag="905"]/*[@code="r"]/text()',incoming_xml),''),'');
-    preserve_rule := preserve_rule || ',' || COALESCE(ARRAY_TO_STRING(oils_xpath('//*[@tag="905"]/*[@code="p"]/text()',incoming_xml),''),'');
+    add_rule := add_rule || ',' || COALESCE(ARRAY_TO_STRING(oils_xpath('//*[@tag="905"]/*[@code="a"]/text()',incoming_xml),','),'');
+    strip_rule := strip_rule || ',' || COALESCE(ARRAY_TO_STRING(oils_xpath('//*[@tag="905"]/*[@code="d"]/text()',incoming_xml),','),'');
+    replace_rule := replace_rule || ',' || COALESCE(ARRAY_TO_STRING(oils_xpath('//*[@tag="905"]/*[@code="r"]/text()',incoming_xml),','),'');
+    preserve_rule := preserve_rule || ',' || COALESCE(ARRAY_TO_STRING(oils_xpath('//*[@tag="905"]/*[@code="p"]/text()',incoming_xml),','),'');
 
     output.add_rule := BTRIM(add_rule,',');
     output.replace_rule := BTRIM(replace_rule,',');
index 8efc3da..41b0874 100644 (file)
@@ -729,8 +729,8 @@ BEGIN
                             JOIN config.marc21_physical_characteristic_subfield_map s ON (s.id = p.subfield)
                             JOIN config.marc21_physical_characteristic_value_map v ON (v.id = p.value)
                       WHERE p.ptype = 'v' AND s.subfield = 'e'    ),
-                biblio.marc21_extract_fixed_field( bib_id, 'Date1'),
-                biblio.marc21_extract_fixed_field( bib_id, 'Date2');
+                LPAD(NULLIF(REGEXP_REPLACE(NULLIF(biblio.marc21_extract_fixed_field( bib_id, 'Date1'), ''), E'\\D', '0', 'g')::INT,0)::TEXT,4,'0'),
+                LPAD(NULLIF(REGEXP_REPLACE(NULLIF(biblio.marc21_extract_fixed_field( bib_id, 'Date2'), ''), E'\\D', '9', 'g')::INT,9999)::TEXT,4,'0');
 
     RETURN;
 END;
index 1c52cb0..6b604df 100644 (file)
@@ -293,8 +293,8 @@ CREATE INDEX asset_call_number_record_idx ON asset.call_number (record);
 CREATE INDEX asset_call_number_creator_idx ON asset.call_number (creator);
 CREATE INDEX asset_call_number_editor_idx ON asset.call_number (editor);
 CREATE INDEX asset_call_number_dewey_idx ON asset.call_number (public.call_number_dewey(label));
-CREATE INDEX asset_call_number_upper_label_id_owning_lib_idx ON asset.call_number (upper(label),id,owning_lib);
-CREATE INDEX asset_call_number_label_sortkey ON asset.call_number(label_sortkey);
+CREATE INDEX asset_call_number_upper_label_id_owning_lib_idx ON asset.call_number (oils_text_as_bytea(upper(label)),id,owning_lib);
+CREATE INDEX asset_call_number_label_sortkey ON asset.call_number(oils_text_as_bytea(label_sortkey));
 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;
 CREATE RULE protect_cn_delete AS ON DELETE TO asset.call_number DO INSTEAD UPDATE asset.call_number SET deleted = TRUE WHERE OLD.id = asset.call_number.id;
 CREATE TRIGGER asset_label_sortkey_trigger
index beceec9..6bbd17c 100644 (file)
@@ -161,7 +161,7 @@ CREATE TABLE container.biblio_record_entry_bucket_item (
                                                        ON UPDATE CASCADE
                                                        DEFERRABLE
                                                        INITIALLY DEFERRED,
-       target_biblio_record_entry      INT     NOT NULL
+       target_biblio_record_entry      BIGINT  NOT NULL
                                                REFERENCES biblio.record_entry (id)
                                                        ON DELETE CASCADE
                                                        ON UPDATE CASCADE
index be8c368..fc86d8a 100644 (file)
@@ -146,6 +146,7 @@ CREATE INDEX circ_open_date_idx ON "action".circulation (xact_start) WHERE xact_
 CREATE INDEX circ_all_usr_idx       ON action.circulation ( usr );
 CREATE INDEX circ_circ_staff_idx    ON action.circulation ( circ_staff );
 CREATE INDEX circ_checkin_staff_idx ON action.circulation ( checkin_staff );
+CREATE INDEX action_circulation_target_copy_idx ON action.circulation (target_copy);
 CREATE UNIQUE INDEX circ_parent_idx ON action.circulation ( parent_circ ) WHERE parent_circ IS NOT NULL;
 CREATE UNIQUE INDEX only_one_concurrent_checkout_per_copy ON action.circulation(target_copy) WHERE checkin_time IS NULL;
 
@@ -163,7 +164,7 @@ BEGIN
 END;
 $$ LANGUAGE PLPGSQL;
 
-CREATE TRIGGER push_due_date_tgr BEFORE INSERT ON action.circulation FOR EACH ROW EXECUTE PROCEDURE action.push_circ_due_time();
+CREATE TRIGGER push_due_date_tgr BEFORE INSERT OR UPDATE ON action.circulation FOR EACH ROW EXECUTE PROCEDURE action.push_circ_due_time();
 
 CREATE TABLE action.aged_circulation (
        usr_post_code           TEXT,
@@ -185,6 +186,7 @@ CREATE INDEX aged_circ_start_idx ON "action".aged_circulation (xact_start);
 CREATE INDEX aged_circ_copy_circ_lib_idx ON "action".aged_circulation (copy_circ_lib);
 CREATE INDEX aged_circ_copy_owning_lib_idx ON "action".aged_circulation (copy_owning_lib);
 CREATE INDEX aged_circ_copy_location_idx ON "action".aged_circulation (copy_location);
+CREATE INDEX action_aged_circulation_target_copy_idx ON action.aged_circulation (target_copy);
 
 CREATE OR REPLACE VIEW action.all_circulation AS
     SELECT  id,usr_post_code, usr_home_ou, usr_profile, usr_birth_year, copy_call_number, copy_location,
@@ -409,7 +411,7 @@ CREATE INDEX ahn_hold_idx ON action.hold_notification (hold);
 CREATE INDEX ahn_notify_staff_idx ON action.hold_notification ( notify_staff );
 
 CREATE TABLE action.hold_copy_map (
-       id              SERIAL  PRIMARY KEY,
+       id              BIGSERIAL       PRIMARY KEY,
        hold            INT     NOT NULL REFERENCES action.hold_request (id) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED,
        target_copy     BIGINT  NOT NULL, -- REFERENCES asset.copy (id) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED, -- XXX could be an serial.issuance
        CONSTRAINT copy_once_per_hold UNIQUE (hold,target_copy)
@@ -776,7 +778,7 @@ BEGIN
             SELECT * INTO circ_chain_tail FROM action.circ_chain(circ_chain_head.id) ORDER BY xact_start DESC LIMIT 1;
             EXIT WHEN circ_chain_tail.xact_finish IS NULL;
 
-            -- Now get the user setings, if any, to block purging if the user wants to keep more circs
+            -- Now get the user settings, if any, to block purging if the user wants to keep more circs
             usr_keep_age.value := NULL;
             SELECT * INTO usr_keep_age FROM actor.usr_setting WHERE usr = circ_chain_head.usr AND name = 'history.circ.retention_age';
 
@@ -792,7 +794,7 @@ BEGIN
             ELSIF usr_keep_start.value IS NOT NULL THEN
                 keep_age := AGE(NOW(), oils_json_to_text(usr_keep_start.value)::TIMESTAMPTZ);
             ELSE
-                keep_age := COALESCE( org_keep_age::INTERVAL, '2000 years'::INTEVAL );
+                keep_age := COALESCE( org_keep_age::INTERVAL, '2000 years'::INTERVAL );
             END IF;
 
             EXIT WHEN AGE(NOW(), circ_chain_tail.xact_finish) < keep_age;
index f79d968..6d75582 100644 (file)
@@ -32,9 +32,9 @@ CREATE TABLE booking.resource_type (
                                       DEFERRABLE INITIALLY DEFERRED,
        catalog_item   BOOLEAN         NOT NULL DEFAULT FALSE,
        transferable   BOOLEAN         NOT NULL DEFAULT FALSE,
-    record         INT             REFERENCES biblio.record_entry (id)
+    record         BIGINT          REFERENCES biblio.record_entry (id)
                                    DEFERRABLE INITIALLY DEFERRED,
-       CONSTRAINT brt_name_once_per_owner UNIQUE(owner, name, record)
+       CONSTRAINT brt_name_and_record_once_per_owner UNIQUE(owner, name, record)
 );
 
 CREATE TABLE booking.resource (
index 0a300d3..0edf98b 100644 (file)
@@ -1,24 +1,44 @@
+-- Before starting the transaction: drop some constraints that
+-- may or may not exist.
+
+DROP INDEX asset.asset_call_number_upper_label_id_owning_lib_idx;
+CREATE INDEX asset_call_number_upper_label_id_owning_lib_idx ON asset.call_number (oils_text_as_bytea(upper(label)),id,owning_lib);
+
+
+\qecho Before starting the transaction: drop some constraints.
+\qecho If a DROP fails because the constraint doesn't exist, ignore the failure.
+
+ALTER TABLE permission.grp_perm_map        DROP CONSTRAINT grp_perm_map_perm_fkey;
+ALTER TABLE permission.usr_perm_map        DROP CONSTRAINT usr_perm_map_perm_fkey;
+ALTER TABLE permission.usr_object_perm_map DROP CONSTRAINT usr_object_perm_map_perm_fkey;
+ALTER TABLE booking.resource_type          DROP CONSTRAINT brt_name_or_record_once_per_owner;
+ALTER TABLE booking.resource_type          DROP CONSTRAINT brt_name_once_per_owner;
+
+\qecho Beginning the transaction now
+
 BEGIN;
 
--- Highest-numbered individual upgrade script
--- incorporated herein:
+-- Highest-numbered individual upgrade script incorporated herein:
 
-INSERT INTO config.upgrade_log (version) VALUES ('0403');
+INSERT INTO config.upgrade_log (version) VALUES ('0433');
 
--- Begin by upgrading permission.perm_list.  This is fairly complicated.
+-- Recreate one of the constraints that we just dropped,
+-- under a different name:
+
+ALTER TABLE booking.resource_type
+       ADD CONSTRAINT brt_name_and_record_once_per_owner UNIQUE(owner, name, record);
+
+-- Now upgrade permission.perm_list.  This is fairly complicated.
 
 -- Add ON UPDATE CASCADE to some foreign keys so that, when we renumber the
 -- permissions, the dependents will follow and stay in sync:
 
-ALTER TABLE permission.grp_perm_map DROP CONSTRAINT grp_perm_map_perm_fkey;
 ALTER TABLE permission.grp_perm_map ADD CONSTRAINT grp_perm_map_perm_fkey FOREIGN KEY (perm)
     REFERENCES permission.perm_list (id) ON UPDATE CASCADE ON DELETE RESTRICT DEFERRABLE INITIALLY DEFERRED;
 
-ALTER TABLE permission.usr_perm_map DROP CONSTRAINT usr_perm_map_perm_fkey;
 ALTER TABLE permission.usr_perm_map ADD CONSTRAINT usr_perm_map_perm_fkey FOREIGN KEY (perm)
     REFERENCES permission.perm_list (id) ON UPDATE CASCADE ON DELETE RESTRICT DEFERRABLE INITIALLY DEFERRED;
 
-ALTER TABLE permission.usr_object_perm_map DROP CONSTRAINT usr_object_perm_map_perm_fkey;
 ALTER TABLE permission.usr_object_perm_map ADD CONSTRAINT usr_object_perm_map_perm_fkey FOREIGN KEY (perm)
     REFERENCES permission.perm_list (id) ON UPDATE CASCADE ON DELETE RESTRICT DEFERRABLE INITIALLY DEFERRED;
 
@@ -885,99 +905,108 @@ INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 389, 'VIEW_C
      'View org unit settings related to credit card processing' );
 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 390, 'ADMIN_CREDIT_CARD_PROCESSING',
      'Update org unit settings related to credit card processing' );
+INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 391, 'ADMIN_SERIAL_CAPTION_PATTERN',
+       'Create/update/delete serial caption and pattern objects' );
+INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 392, 'ADMIN_SERIAL_SUBSCRIPTION',
+       'Create/update/delete serial subscription objects' );
+INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 393, 'ADMIN_SERIAL_DISTRIBUTION',
+       'Create/update/delete serial distribution objects' );
+INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 394, 'ADMIN_SERIAL_STREAM',
+       'Create/update/delete serial stream objects' );
+INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 395, 'RECEIVE_SERIAL',
+       'Receive serial items' );
 
 -- Now for the permissions from the IDL.  We don't have descriptions for them.
 
-INSERT INTO permission.temp_perm ( id, code ) VALUES ( 391, 'ADMIN_ACQ_CLAIM' );
-INSERT INTO permission.temp_perm ( id, code ) VALUES ( 392, 'ADMIN_ACQ_CLAIM_EVENT_TYPE' );
-INSERT INTO permission.temp_perm ( id, code ) VALUES ( 393, 'ADMIN_ACQ_CLAIM_TYPE' );
-INSERT INTO permission.temp_perm ( id, code ) VALUES ( 394, 'ADMIN_ACQ_DISTRIB_FORMULA' );
-INSERT INTO permission.temp_perm ( id, code ) VALUES ( 395, 'ADMIN_ACQ_FISCAL_YEAR' );
-INSERT INTO permission.temp_perm ( id, code ) VALUES ( 396, 'ADMIN_ACQ_FUND_ALLOCATION_PERCENT' );
-INSERT INTO permission.temp_perm ( id, code ) VALUES ( 397, 'ADMIN_ACQ_FUND_TAG' );
-INSERT INTO permission.temp_perm ( id, code ) VALUES ( 398, 'ADMIN_ACQ_LINEITEM_ALERT_TEXT' );
-INSERT INTO permission.temp_perm ( id, code ) VALUES ( 399, 'ADMIN_AGE_PROTECT_RULE' );
-INSERT INTO permission.temp_perm ( id, code ) VALUES ( 400, 'ADMIN_ASSET_COPY_TEMPLATE' );
-INSERT INTO permission.temp_perm ( id, code ) VALUES ( 401, 'ADMIN_BOOKING_RESERVATION_ATTR_MAP' );
-INSERT INTO permission.temp_perm ( id, code ) VALUES ( 402, 'ADMIN_CIRC_MATRIX_MATCHPOINT' );
-INSERT INTO permission.temp_perm ( id, code ) VALUES ( 403, 'ADMIN_CIRC_MOD' );
-INSERT INTO permission.temp_perm ( id, code ) VALUES ( 404, 'ADMIN_CLAIM_POLICY' );
-INSERT INTO permission.temp_perm ( id, code ) VALUES ( 405, 'ADMIN_CONFIG_REMOTE_ACCOUNT' );
-INSERT INTO permission.temp_perm ( id, code ) VALUES ( 406, 'ADMIN_FIELD_DOC' );
-INSERT INTO permission.temp_perm ( id, code ) VALUES ( 407, 'ADMIN_GLOBAL_FLAG' );
-INSERT INTO permission.temp_perm ( id, code ) VALUES ( 408, 'ADMIN_GROUP_PENALTY_THRESHOLD' );
-INSERT INTO permission.temp_perm ( id, code ) VALUES ( 409, 'ADMIN_HOLD_CANCEL_CAUSE' );
-INSERT INTO permission.temp_perm ( id, code ) VALUES ( 410, 'ADMIN_HOLD_MATRIX_MATCHPOINT' );
-INSERT INTO permission.temp_perm ( id, code ) VALUES ( 411, 'ADMIN_IDENT_TYPE' );
-INSERT INTO permission.temp_perm ( id, code ) VALUES ( 412, 'ADMIN_IMPORT_ITEM_ATTR_DEF' );
-INSERT INTO permission.temp_perm ( id, code ) VALUES ( 413, 'ADMIN_INDEX_NORMALIZER' );
-INSERT INTO permission.temp_perm ( id, code ) VALUES ( 414, 'ADMIN_INVOICE' );
-INSERT INTO permission.temp_perm ( id, code ) VALUES ( 415, 'ADMIN_INVOICE_METHOD' );
-INSERT INTO permission.temp_perm ( id, code ) VALUES ( 416, 'ADMIN_INVOICE_PAYMENT_METHOD' );
-INSERT INTO permission.temp_perm ( id, code ) VALUES ( 417, 'ADMIN_LINEITEM_MARC_ATTR_DEF' );
-INSERT INTO permission.temp_perm ( id, code ) VALUES ( 418, 'ADMIN_MARC_CODE' );
-INSERT INTO permission.temp_perm ( id, code ) VALUES ( 419, 'ADMIN_MAX_FINE_RULE' );
-INSERT INTO permission.temp_perm ( id, code ) VALUES ( 420, 'ADMIN_MERGE_PROFILE' );
-INSERT INTO permission.temp_perm ( id, code ) VALUES ( 421, 'ADMIN_ORG_UNIT_SETTING_TYPE' );
-INSERT INTO permission.temp_perm ( id, code ) VALUES ( 422, 'ADMIN_RECURRING_FINE_RULE' );
-INSERT INTO permission.temp_perm ( id, code ) VALUES ( 423, 'ADMIN_SERIAL_SUBSCRIPTION' );
-INSERT INTO permission.temp_perm ( id, code ) VALUES ( 424, 'ADMIN_STANDING_PENALTY' );
-INSERT INTO permission.temp_perm ( id, code ) VALUES ( 425, 'ADMIN_SURVEY' );
-INSERT INTO permission.temp_perm ( id, code ) VALUES ( 426, 'ADMIN_USER_REQUEST_TYPE' );
-INSERT INTO permission.temp_perm ( id, code ) VALUES ( 427, 'ADMIN_USER_SETTING_GROUP' );
-INSERT INTO permission.temp_perm ( id, code ) VALUES ( 428, 'ADMIN_USER_SETTING_TYPE' );
-INSERT INTO permission.temp_perm ( id, code ) VALUES ( 429, 'ADMIN_Z3950_SOURCE' );
-INSERT INTO permission.temp_perm ( id, code ) VALUES ( 430, 'CREATE_BIB_BTYPE' );
-INSERT INTO permission.temp_perm ( id, code ) VALUES ( 431, 'CREATE_BIBLIO_FINGERPRINT' );
-INSERT INTO permission.temp_perm ( id, code ) VALUES ( 432, 'CREATE_BIB_SOURCE' );
-INSERT INTO permission.temp_perm ( id, code ) VALUES ( 433, 'CREATE_BILLING_TYPE' );
-INSERT INTO permission.temp_perm ( id, code ) VALUES ( 434, 'CREATE_CN_BTYPE' );
-INSERT INTO permission.temp_perm ( id, code ) VALUES ( 435, 'CREATE_COPY_BTYPE' );
-INSERT INTO permission.temp_perm ( id, code ) VALUES ( 436, 'CREATE_INVOICE' );
-INSERT INTO permission.temp_perm ( id, code ) VALUES ( 437, 'CREATE_INVOICE_ITEM_TYPE' );
-INSERT INTO permission.temp_perm ( id, code ) VALUES ( 438, 'CREATE_INVOICE_METHOD' );
-INSERT INTO permission.temp_perm ( id, code ) VALUES ( 439, 'CREATE_MERGE_PROFILE' );
-INSERT INTO permission.temp_perm ( id, code ) VALUES ( 440, 'CREATE_METABIB_CLASS' );
-INSERT INTO permission.temp_perm ( id, code ) VALUES ( 441, 'CREATE_METABIB_SEARCH_ALIAS' );
-INSERT INTO permission.temp_perm ( id, code ) VALUES ( 442, 'CREATE_USER_BTYPE' );
-INSERT INTO permission.temp_perm ( id, code ) VALUES ( 443, 'DELETE_BIB_BTYPE' );
-INSERT INTO permission.temp_perm ( id, code ) VALUES ( 444, 'DELETE_BIBLIO_FINGERPRINT' );
-INSERT INTO permission.temp_perm ( id, code ) VALUES ( 445, 'DELETE_BIB_SOURCE' );
-INSERT INTO permission.temp_perm ( id, code ) VALUES ( 446, 'DELETE_BILLING_TYPE' );
-INSERT INTO permission.temp_perm ( id, code ) VALUES ( 447, 'DELETE_CN_BTYPE' );
-INSERT INTO permission.temp_perm ( id, code ) VALUES ( 448, 'DELETE_COPY_BTYPE' );
-INSERT INTO permission.temp_perm ( id, code ) VALUES ( 449, 'DELETE_INVOICE_ITEM_TYPE' );
-INSERT INTO permission.temp_perm ( id, code ) VALUES ( 450, 'DELETE_INVOICE_METHOD' );
-INSERT INTO permission.temp_perm ( id, code ) VALUES ( 451, 'DELETE_MERGE_PROFILE' );
-INSERT INTO permission.temp_perm ( id, code ) VALUES ( 452, 'DELETE_METABIB_CLASS' );
-INSERT INTO permission.temp_perm ( id, code ) VALUES ( 453, 'DELETE_METABIB_SEARCH_ALIAS' );
-INSERT INTO permission.temp_perm ( id, code ) VALUES ( 454, 'DELETE_USER_BTYPE' );
-INSERT INTO permission.temp_perm ( id, code ) VALUES ( 455, 'MANAGE_CLAIM' );
-INSERT INTO permission.temp_perm ( id, code ) VALUES ( 456, 'UPDATE_BIB_BTYPE' );
-INSERT INTO permission.temp_perm ( id, code ) VALUES ( 457, 'UPDATE_BIBLIO_FINGERPRINT' );
-INSERT INTO permission.temp_perm ( id, code ) VALUES ( 458, 'UPDATE_BIB_SOURCE' );
-INSERT INTO permission.temp_perm ( id, code ) VALUES ( 459, 'UPDATE_BILLING_TYPE' );
-INSERT INTO permission.temp_perm ( id, code ) VALUES ( 460, 'UPDATE_CN_BTYPE' );
-INSERT INTO permission.temp_perm ( id, code ) VALUES ( 461, 'UPDATE_COPY_BTYPE' );
-INSERT INTO permission.temp_perm ( id, code ) VALUES ( 462, 'UPDATE_INVOICE_ITEM_TYPE' );
-INSERT INTO permission.temp_perm ( id, code ) VALUES ( 463, 'UPDATE_INVOICE_METHOD' );
-INSERT INTO permission.temp_perm ( id, code ) VALUES ( 464, 'UPDATE_MERGE_PROFILE' );
-INSERT INTO permission.temp_perm ( id, code ) VALUES ( 465, 'UPDATE_METABIB_CLASS' );
-INSERT INTO permission.temp_perm ( id, code ) VALUES ( 466, 'UPDATE_METABIB_SEARCH_ALIAS' );
-INSERT INTO permission.temp_perm ( id, code ) VALUES ( 467, 'UPDATE_USER_BTYPE' );
-INSERT INTO permission.temp_perm ( id, code ) VALUES ( 468, 'user_request.create' );
-INSERT INTO permission.temp_perm ( id, code ) VALUES ( 469, 'user_request.delete' );
-INSERT INTO permission.temp_perm ( id, code ) VALUES ( 470, 'user_request.update' );
-INSERT INTO permission.temp_perm ( id, code ) VALUES ( 471, 'user_request.view' );
-INSERT INTO permission.temp_perm ( id, code ) VALUES ( 472, 'VIEW_ACQ_FUND_ALLOCATION_PERCENT' );
-INSERT INTO permission.temp_perm ( id, code ) VALUES ( 473, 'VIEW_CIRC_MATRIX_MATCHPOINT' );
-INSERT INTO permission.temp_perm ( id, code ) VALUES ( 474, 'VIEW_CLAIM' );
-INSERT INTO permission.temp_perm ( id, code ) VALUES ( 475, 'VIEW_GROUP_PENALTY_THRESHOLD' );
-INSERT INTO permission.temp_perm ( id, code ) VALUES ( 476, 'VIEW_HOLD_MATRIX_MATCHPOINT' );
-INSERT INTO permission.temp_perm ( id, code ) VALUES ( 477, 'VIEW_INVOICE' );
-INSERT INTO permission.temp_perm ( id, code ) VALUES ( 478, 'VIEW_MERGE_PROFILE' );
-INSERT INTO permission.temp_perm ( id, code ) VALUES ( 479, 'VIEW_SERIAL_SUBSCRIPTION' );
-INSERT INTO permission.temp_perm ( id, code ) VALUES ( 480, 'VIEW_STANDING_PENALTY' );
+INSERT INTO permission.temp_perm ( id, code ) VALUES ( 396, 'ADMIN_ACQ_CLAIM' );
+INSERT INTO permission.temp_perm ( id, code ) VALUES ( 397, 'ADMIN_ACQ_CLAIM_EVENT_TYPE' );
+INSERT INTO permission.temp_perm ( id, code ) VALUES ( 398, 'ADMIN_ACQ_CLAIM_TYPE' );
+INSERT INTO permission.temp_perm ( id, code ) VALUES ( 399, 'ADMIN_ACQ_DISTRIB_FORMULA' );
+INSERT INTO permission.temp_perm ( id, code ) VALUES ( 400, 'ADMIN_ACQ_FISCAL_YEAR' );
+INSERT INTO permission.temp_perm ( id, code ) VALUES ( 401, 'ADMIN_ACQ_FUND_ALLOCATION_PERCENT' );
+INSERT INTO permission.temp_perm ( id, code ) VALUES ( 402, 'ADMIN_ACQ_FUND_TAG' );
+INSERT INTO permission.temp_perm ( id, code ) VALUES ( 403, 'ADMIN_ACQ_LINEITEM_ALERT_TEXT' );
+INSERT INTO permission.temp_perm ( id, code ) VALUES ( 404, 'ADMIN_AGE_PROTECT_RULE' );
+INSERT INTO permission.temp_perm ( id, code ) VALUES ( 405, 'ADMIN_ASSET_COPY_TEMPLATE' );
+INSERT INTO permission.temp_perm ( id, code ) VALUES ( 406, 'ADMIN_BOOKING_RESERVATION_ATTR_MAP' );
+INSERT INTO permission.temp_perm ( id, code ) VALUES ( 407, 'ADMIN_CIRC_MATRIX_MATCHPOINT' );
+INSERT INTO permission.temp_perm ( id, code ) VALUES ( 408, 'ADMIN_CIRC_MOD' );
+INSERT INTO permission.temp_perm ( id, code ) VALUES ( 409, 'ADMIN_CLAIM_POLICY' );
+INSERT INTO permission.temp_perm ( id, code ) VALUES ( 410, 'ADMIN_CONFIG_REMOTE_ACCOUNT' );
+INSERT INTO permission.temp_perm ( id, code ) VALUES ( 411, 'ADMIN_FIELD_DOC' );
+INSERT INTO permission.temp_perm ( id, code ) VALUES ( 412, 'ADMIN_GLOBAL_FLAG' );
+INSERT INTO permission.temp_perm ( id, code ) VALUES ( 413, 'ADMIN_GROUP_PENALTY_THRESHOLD' );
+INSERT INTO permission.temp_perm ( id, code ) VALUES ( 414, 'ADMIN_HOLD_CANCEL_CAUSE' );
+INSERT INTO permission.temp_perm ( id, code ) VALUES ( 415, 'ADMIN_HOLD_MATRIX_MATCHPOINT' );
+INSERT INTO permission.temp_perm ( id, code ) VALUES ( 416, 'ADMIN_IDENT_TYPE' );
+INSERT INTO permission.temp_perm ( id, code ) VALUES ( 417, 'ADMIN_IMPORT_ITEM_ATTR_DEF' );
+INSERT INTO permission.temp_perm ( id, code ) VALUES ( 418, 'ADMIN_INDEX_NORMALIZER' );
+INSERT INTO permission.temp_perm ( id, code ) VALUES ( 419, 'ADMIN_INVOICE' );
+INSERT INTO permission.temp_perm ( id, code ) VALUES ( 420, 'ADMIN_INVOICE_METHOD' );
+INSERT INTO permission.temp_perm ( id, code ) VALUES ( 421, 'ADMIN_INVOICE_PAYMENT_METHOD' );
+INSERT INTO permission.temp_perm ( id, code ) VALUES ( 422, 'ADMIN_LINEITEM_MARC_ATTR_DEF' );
+INSERT INTO permission.temp_perm ( id, code ) VALUES ( 423, 'ADMIN_MARC_CODE' );
+INSERT INTO permission.temp_perm ( id, code ) VALUES ( 424, 'ADMIN_MAX_FINE_RULE' );
+INSERT INTO permission.temp_perm ( id, code ) VALUES ( 425, 'ADMIN_MERGE_PROFILE' );
+INSERT INTO permission.temp_perm ( id, code ) VALUES ( 426, 'ADMIN_ORG_UNIT_SETTING_TYPE' );
+INSERT INTO permission.temp_perm ( id, code ) VALUES ( 427, 'ADMIN_RECURRING_FINE_RULE' );
+INSERT INTO permission.temp_perm ( id, code ) VALUES ( 428, 'ADMIN_STANDING_PENALTY' );
+INSERT INTO permission.temp_perm ( id, code ) VALUES ( 429, 'ADMIN_SURVEY' );
+INSERT INTO permission.temp_perm ( id, code ) VALUES ( 430, 'ADMIN_USER_REQUEST_TYPE' );
+INSERT INTO permission.temp_perm ( id, code ) VALUES ( 431, 'ADMIN_USER_SETTING_GROUP' );
+INSERT INTO permission.temp_perm ( id, code ) VALUES ( 432, 'ADMIN_USER_SETTING_TYPE' );
+INSERT INTO permission.temp_perm ( id, code ) VALUES ( 433, 'ADMIN_Z3950_SOURCE' );
+INSERT INTO permission.temp_perm ( id, code ) VALUES ( 434, 'CREATE_BIB_BTYPE' );
+INSERT INTO permission.temp_perm ( id, code ) VALUES ( 435, 'CREATE_BIBLIO_FINGERPRINT' );
+INSERT INTO permission.temp_perm ( id, code ) VALUES ( 436, 'CREATE_BIB_SOURCE' );
+INSERT INTO permission.temp_perm ( id, code ) VALUES ( 437, 'CREATE_BILLING_TYPE' );
+INSERT INTO permission.temp_perm ( id, code ) VALUES ( 438, 'CREATE_CN_BTYPE' );
+INSERT INTO permission.temp_perm ( id, code ) VALUES ( 439, 'CREATE_COPY_BTYPE' );
+INSERT INTO permission.temp_perm ( id, code ) VALUES ( 440, 'CREATE_INVOICE' );
+INSERT INTO permission.temp_perm ( id, code ) VALUES ( 441, 'CREATE_INVOICE_ITEM_TYPE' );
+INSERT INTO permission.temp_perm ( id, code ) VALUES ( 442, 'CREATE_INVOICE_METHOD' );
+INSERT INTO permission.temp_perm ( id, code ) VALUES ( 443, 'CREATE_MERGE_PROFILE' );
+INSERT INTO permission.temp_perm ( id, code ) VALUES ( 444, 'CREATE_METABIB_CLASS' );
+INSERT INTO permission.temp_perm ( id, code ) VALUES ( 445, 'CREATE_METABIB_SEARCH_ALIAS' );
+INSERT INTO permission.temp_perm ( id, code ) VALUES ( 446, 'CREATE_USER_BTYPE' );
+INSERT INTO permission.temp_perm ( id, code ) VALUES ( 447, 'DELETE_BIB_BTYPE' );
+INSERT INTO permission.temp_perm ( id, code ) VALUES ( 448, 'DELETE_BIBLIO_FINGERPRINT' );
+INSERT INTO permission.temp_perm ( id, code ) VALUES ( 449, 'DELETE_BIB_SOURCE' );
+INSERT INTO permission.temp_perm ( id, code ) VALUES ( 450, 'DELETE_BILLING_TYPE' );
+INSERT INTO permission.temp_perm ( id, code ) VALUES ( 451, 'DELETE_CN_BTYPE' );
+INSERT INTO permission.temp_perm ( id, code ) VALUES ( 452, 'DELETE_COPY_BTYPE' );
+INSERT INTO permission.temp_perm ( id, code ) VALUES ( 453, 'DELETE_INVOICE_ITEM_TYPE' );
+INSERT INTO permission.temp_perm ( id, code ) VALUES ( 454, 'DELETE_INVOICE_METHOD' );
+INSERT INTO permission.temp_perm ( id, code ) VALUES ( 455, 'DELETE_MERGE_PROFILE' );
+INSERT INTO permission.temp_perm ( id, code ) VALUES ( 456, 'DELETE_METABIB_CLASS' );
+INSERT INTO permission.temp_perm ( id, code ) VALUES ( 457, 'DELETE_METABIB_SEARCH_ALIAS' );
+INSERT INTO permission.temp_perm ( id, code ) VALUES ( 458, 'DELETE_USER_BTYPE' );
+INSERT INTO permission.temp_perm ( id, code ) VALUES ( 459, 'MANAGE_CLAIM' );
+INSERT INTO permission.temp_perm ( id, code ) VALUES ( 460, 'UPDATE_BIB_BTYPE' );
+INSERT INTO permission.temp_perm ( id, code ) VALUES ( 461, 'UPDATE_BIBLIO_FINGERPRINT' );
+INSERT INTO permission.temp_perm ( id, code ) VALUES ( 462, 'UPDATE_BIB_SOURCE' );
+INSERT INTO permission.temp_perm ( id, code ) VALUES ( 463, 'UPDATE_BILLING_TYPE' );
+INSERT INTO permission.temp_perm ( id, code ) VALUES ( 464, 'UPDATE_CN_BTYPE' );
+INSERT INTO permission.temp_perm ( id, code ) VALUES ( 465, 'UPDATE_COPY_BTYPE' );
+INSERT INTO permission.temp_perm ( id, code ) VALUES ( 466, 'UPDATE_INVOICE_ITEM_TYPE' );
+INSERT INTO permission.temp_perm ( id, code ) VALUES ( 467, 'UPDATE_INVOICE_METHOD' );
+INSERT INTO permission.temp_perm ( id, code ) VALUES ( 468, 'UPDATE_MERGE_PROFILE' );
+INSERT INTO permission.temp_perm ( id, code ) VALUES ( 469, 'UPDATE_METABIB_CLASS' );
+INSERT INTO permission.temp_perm ( id, code ) VALUES ( 470, 'UPDATE_METABIB_SEARCH_ALIAS' );
+INSERT INTO permission.temp_perm ( id, code ) VALUES ( 471, 'UPDATE_USER_BTYPE' );
+INSERT INTO permission.temp_perm ( id, code ) VALUES ( 472, 'user_request.create' );
+INSERT INTO permission.temp_perm ( id, code ) VALUES ( 473, 'user_request.delete' );
+INSERT INTO permission.temp_perm ( id, code ) VALUES ( 474, 'user_request.update' );
+INSERT INTO permission.temp_perm ( id, code ) VALUES ( 475, 'user_request.view' );
+INSERT INTO permission.temp_perm ( id, code ) VALUES ( 476, 'VIEW_ACQ_FUND_ALLOCATION_PERCENT' );
+INSERT INTO permission.temp_perm ( id, code ) VALUES ( 477, 'VIEW_CIRC_MATRIX_MATCHPOINT' );
+INSERT INTO permission.temp_perm ( id, code ) VALUES ( 478, 'VIEW_CLAIM' );
+INSERT INTO permission.temp_perm ( id, code ) VALUES ( 479, 'VIEW_GROUP_PENALTY_THRESHOLD' );
+INSERT INTO permission.temp_perm ( id, code ) VALUES ( 480, 'VIEW_HOLD_MATRIX_MATCHPOINT' );
+INSERT INTO permission.temp_perm ( id, code ) VALUES ( 481, 'VIEW_INVOICE' );
+INSERT INTO permission.temp_perm ( id, code ) VALUES ( 482, 'VIEW_MERGE_PROFILE' );
+INSERT INTO permission.temp_perm ( id, code ) VALUES ( 483, 'VIEW_SERIAL_SUBSCRIPTION' );
+INSERT INTO permission.temp_perm ( id, code ) VALUES ( 484, 'VIEW_STANDING_PENALTY' );
 
 -- For every permission in the temp_perm table that has a matching
 -- permission in the real table: record the original id.
@@ -3234,8 +3263,19 @@ INSERT INTO action_trigger.environment (
 INSERT INTO action_trigger.event_definition
 (id, active, owner, name, hook, validator, reactor, cleanup_success, cleanup_failure, delay, delay_field, group_field, template) VALUES
 (23, true, 1, 'PO JEDI', 'acqpo.activated', 'Acq::PurchaseOrderEDIRequired', 'GeneratePurchaseOrderJEDI', NULL, NULL, '00:05:00', NULL, NULL,
-$$[%- USE date -%]
-[%# start JEDI document -%]
+$$
+[%- USE date -%]
+[%# start JEDI document 
+  # Vendor specific kludges:
+  # BT      - vendcode goes to NAD/BY *suffix*  w/ 91 qualifier
+  # INGRAM  - vendcode goes to NAD/BY *segment* w/ 91 qualifier (separately)
+  # BRODART - vendcode goes to FTX segment (lineitem level)
+-%]
+[%- 
+IF target.provider.edi_default.vendcode && target.provider.code == 'BRODART';
+    xtra_ftx = target.provider.edi_default.vendcode;
+END;
+-%]
 [%- BLOCK big_block -%]
 {
    "recipient":"[% target.provider.san %]",
@@ -3244,23 +3284,27 @@ $$[%- USE date -%]
      "ORDERS":[ "order", {
         "po_number":[% target.id %],
         "date":"[% date.format(date.now, '%Y%m%d') %]",
-        "buyer":[{
-            [%- IF target.provider.edi_default.vendcode -%]
-                "id":"[% target.ordering_agency.mailing_address.san _ ' ' _ target.provider.edi_default.vendcode %]", 
-                "id-qualifier": 91
+        "buyer":[
+            [%   IF   target.provider.edi_default.vendcode && (target.provider.code == 'BT' || target.provider.name.match('(?i)^BAKER & TAYLOR'))  -%]
+                {"id-qualifier": 91, "id":"[% target.ordering_agency.mailing_address.san _ ' ' _ target.provider.edi_default.vendcode %]"}
+            [%- ELSIF target.provider.edi_default.vendcode && target.provider.code == 'INGRAM' -%]
+                {"id":"[% target.ordering_agency.mailing_address.san %]"},
+                {"id-qualifier": 91, "id":"[% target.provider.edi_default.vendcode %]"}
             [%- ELSE -%]
-                "id":"[% target.ordering_agency.mailing_address.san %]"
-            [%- END  -%]
-        }],
-        "vendor":[ 
+                {"id":"[% target.ordering_agency.mailing_address.san %]"}
+            [%- END -%]
+        ],
+        "vendor":[
             [%- # target.provider.name (target.provider.id) -%]
             "[% target.provider.san %]",
             {"id-qualifier": 92, "id":"[% target.provider.id %]"}
         ],
         "currency":"[% target.provider.currency_type %]",
+                
         "items":[
-        [% FOR li IN target.lineitems %]
+        [%- FOR li IN target.lineitems %]
         {
+            "line_index":"[% li.id %]",
             "identifiers":[   [%-# li.isbns = helpers.get_li_isbns(li.attributes) %]
             [% FOR isbn IN helpers.get_li_isbns(li.attributes) -%]
                 [% IF isbn.length == 13 -%]
@@ -3269,28 +3313,40 @@ $$[%- USE date -%]
                 {"id-qualifier":"IB","id":"[% isbn %]"},
                 [%- END %]
             [% END %]
-                {"id-qualifier":"SA","id":"[% li.id %]"}
+                {"id-qualifier":"IN","id":"[% li.id %]"}
             ],
             "price":[% li.estimated_unit_price || '0.00' %],
             "desc":[
-                {"BTI":"[% helpers.get_li_attr('title',     '', li.attributes) %]"}, 
+                {"BTI":"[% helpers.get_li_attr('title',     '', li.attributes) %]"},
                 {"BPU":"[% helpers.get_li_attr('publisher', '', li.attributes) %]"},
                 {"BPD":"[% helpers.get_li_attr('pubdate',   '', li.attributes) %]"},
                 {"BPH":"[% helpers.get_li_attr('pagination','', li.attributes) %]"}
             ],
+            [%- ftx_vals = []; 
+                FOR note IN li.lineitem_notes; 
+                    NEXT UNLESS note.vendor_public == 't'; 
+                    ftx_vals.push(note.value); 
+                END; 
+                IF xtra_ftx;           ftx_vals.unshift(xtra_ftx); END; 
+                IF ftx_vals.size == 0; ftx_vals.unshift('');       END;  # BT needs FTX+LIN for every LI, even if it is an empty one
+            -%]
+
+            "free-text":[ 
+                [% FOR note IN ftx_vals -%] "[% note %]"[% UNLESS loop.last %], [% END %][% END %] 
+            ],            
             "quantity":[% li.lineitem_details.size %]
         }[% UNLESS loop.last %],[% END %]
         [%-# TODO: lineitem details (later) -%]
         [% END %]
         ],
         "line_items":[% target.lineitems.size %]
-     }]  [% # close ORDERS array %]
-   }]    [% # close  body  array %]
+     }]  [%# close ORDERS array %]
+   }]    [%# close  body  array %]
 }
 [% END %]
 [% tempo = PROCESS big_block; helpers.escape_json(tempo) %]
-$$
-);
+
+$$);
 
 INSERT INTO action_trigger.environment (event_def, path) VALUES 
   (23, 'lineitems.attributes'), 
@@ -6049,10 +6105,10 @@ ALTER TABLE auditor.asset_copy_history
 CREATE OR REPLACE FUNCTION asset.acp_status_changed()
 RETURNS TRIGGER AS $$
 BEGIN
-       IF NEW.status <> OLD.status THEN
-               NEW.status_changed_time := now();
-       END IF;
-       RETURN NEW;
+    IF NEW.status <> OLD.status THEN
+        NEW.status_changed_time := now();
+    END IF;
+    RETURN NEW;
 END;
 $$ LANGUAGE plpgsql;
 
@@ -6263,6 +6319,23 @@ CREATE UNIQUE INDEX only_one_concurrent_checkout_per_copy ON action.circulation(
 
 ALTER TABLE action.circulation DROP CONSTRAINT action_circulation_target_copy_fkey;
 
+-- Rebuild dependent views
+
+DROP VIEW IF EXISTS action.billable_circulations;
+
+CREATE OR REPLACE VIEW action.billable_circulations AS
+    SELECT  *
+      FROM  action.circulation
+      WHERE xact_finish IS NULL;
+
+DROP VIEW IF EXISTS action.open_circulation;
+
+CREATE OR REPLACE VIEW action.open_circulation AS
+    SELECT  *
+      FROM  action.circulation
+      WHERE checkin_time IS NULL
+      ORDER BY due_date;
+
 CREATE OR REPLACE FUNCTION action.age_circ_on_delete () RETURNS TRIGGER AS $$
 DECLARE
 found char := 'N';
@@ -6308,7 +6381,122 @@ UPDATE config.z3950_attr SET truncation = 1 WHERE source = 'biblios' AND truncat
 
 -- Adding circ.holds.target_skip_me OU setting logic to the pre-matchpoint tests
 
-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$
+CREATE OR REPLACE FUNCTION action.find_hold_matrix_matchpoint( pickup_ou INT, request_ou INT, match_item BIGINT, match_user INT, match_requestor INT ) RETURNS INT AS $func$
+DECLARE
+    current_requestor_group    permission.grp_tree%ROWTYPE;
+    requestor_object    actor.usr%ROWTYPE;
+    user_object        actor.usr%ROWTYPE;
+    item_object        asset.copy%ROWTYPE;
+    item_cn_object        asset.call_number%ROWTYPE;
+    rec_descriptor        metabib.rec_descriptor%ROWTYPE;
+    current_mp_weight    FLOAT;
+    matchpoint_weight    FLOAT;
+    tmp_weight        FLOAT;
+    current_mp        config.hold_matrix_matchpoint%ROWTYPE;
+    matchpoint        config.hold_matrix_matchpoint%ROWTYPE;
+BEGIN
+    SELECT INTO user_object * FROM actor.usr WHERE id = match_user;
+    SELECT INTO requestor_object * FROM actor.usr WHERE id = match_requestor;
+    SELECT INTO item_object * FROM asset.copy WHERE id = match_item;
+    SELECT INTO item_cn_object * FROM asset.call_number WHERE id = item_object.call_number;
+    SELECT INTO rec_descriptor r.* FROM metabib.rec_descriptor r WHERE r.record = item_cn_object.record;
+
+    PERFORM * FROM config.internal_flag WHERE name = 'circ.holds.usr_not_requestor' AND enabled;
+
+    IF NOT FOUND THEN
+        SELECT INTO current_requestor_group * FROM permission.grp_tree WHERE id = requestor_object.profile;
+    ELSE
+        SELECT INTO current_requestor_group * FROM permission.grp_tree WHERE id = user_object.profile;
+    END IF;
+
+    LOOP 
+        -- for each potential matchpoint for this ou and group ...
+        FOR current_mp IN
+            SELECT    m.*
+              FROM    config.hold_matrix_matchpoint m
+              WHERE    m.requestor_grp = current_requestor_group.id AND m.active
+              ORDER BY    CASE WHEN m.circ_modifier    IS NOT NULL THEN 16 ELSE 0 END +
+                    CASE WHEN m.juvenile_flag    IS NOT NULL THEN 16 ELSE 0 END +
+                    CASE WHEN m.marc_type        IS NOT NULL THEN 8 ELSE 0 END +
+                    CASE WHEN m.marc_form        IS NOT NULL THEN 4 ELSE 0 END +
+                    CASE WHEN m.marc_vr_format    IS NOT NULL THEN 2 ELSE 0 END +
+                    CASE WHEN m.ref_flag        IS NOT NULL THEN 1 ELSE 0 END DESC LOOP
+
+            current_mp_weight := 5.0;
+
+            IF current_mp.circ_modifier IS NOT NULL THEN
+                CONTINUE WHEN current_mp.circ_modifier <> item_object.circ_modifier OR item_object.circ_modifier IS NULL;
+            END IF;
+
+            IF current_mp.marc_type IS NOT NULL THEN
+                IF item_object.circ_as_type IS NOT NULL THEN
+                    CONTINUE WHEN current_mp.marc_type <> item_object.circ_as_type;
+                ELSE
+                    CONTINUE WHEN current_mp.marc_type <> rec_descriptor.item_type;
+                END IF;
+            END IF;
+
+            IF current_mp.marc_form IS NOT NULL THEN
+                CONTINUE WHEN current_mp.marc_form <> rec_descriptor.item_form;
+            END IF;
+
+            IF current_mp.marc_vr_format IS NOT NULL THEN
+                CONTINUE WHEN current_mp.marc_vr_format <> rec_descriptor.vr_format;
+            END IF;
+
+            IF current_mp.juvenile_flag IS NOT NULL THEN
+                CONTINUE WHEN current_mp.juvenile_flag <> user_object.juvenile;
+            END IF;
+
+            IF current_mp.ref_flag IS NOT NULL THEN
+                CONTINUE WHEN current_mp.ref_flag <> item_object.ref;
+            END IF;
+
+
+            -- caclulate the rule match weight
+            IF current_mp.item_owning_ou IS NOT NULL THEN
+                SELECT INTO tmp_weight 1.0 / (actor.org_unit_proximity(current_mp.item_owning_ou, item_cn_object.owning_lib)::FLOAT + 1.0)::FLOAT;
+                current_mp_weight := current_mp_weight - tmp_weight;
+            END IF; 
+
+            IF current_mp.item_circ_ou IS NOT NULL THEN
+                SELECT INTO tmp_weight 1.0 / (actor.org_unit_proximity(current_mp.item_circ_ou, item_object.circ_lib)::FLOAT + 1.0)::FLOAT;
+                current_mp_weight := current_mp_weight - tmp_weight;
+            END IF; 
+
+            IF current_mp.pickup_ou IS NOT NULL THEN
+                SELECT INTO tmp_weight 1.0 / (actor.org_unit_proximity(current_mp.pickup_ou, pickup_ou)::FLOAT + 1.0)::FLOAT;
+                current_mp_weight := current_mp_weight - tmp_weight;
+            END IF; 
+
+            IF current_mp.request_ou IS NOT NULL THEN
+                SELECT INTO tmp_weight 1.0 / (actor.org_unit_proximity(current_mp.request_ou, request_ou)::FLOAT + 1.0)::FLOAT;
+                current_mp_weight := current_mp_weight - tmp_weight;
+            END IF; 
+
+            IF current_mp.user_home_ou IS NOT NULL THEN
+                SELECT INTO tmp_weight 1.0 / (actor.org_unit_proximity(current_mp.user_home_ou, user_object.home_ou)::FLOAT + 1.0)::FLOAT;
+                current_mp_weight := current_mp_weight - tmp_weight;
+            END IF; 
+
+            -- set the matchpoint if we found the best one
+            IF matchpoint_weight IS NULL OR matchpoint_weight > current_mp_weight THEN
+                matchpoint = current_mp;
+                matchpoint_weight = current_mp_weight;
+            END IF;
+
+        END LOOP;
+
+        EXIT WHEN current_requestor_group.parent IS NULL OR matchpoint.id IS NOT NULL;
+
+        SELECT INTO current_requestor_group * FROM permission.grp_tree WHERE id = current_requestor_group.parent;
+    END LOOP;
+
+    RETURN matchpoint.id;
+END;
+$func$ LANGUAGE plpgsql;
+
+CREATE OR REPLACE FUNCTION action.hold_request_permit_test( pickup_ou INT, request_ou INT, match_item BIGINT, match_user INT, match_requestor INT, retargetting BOOL ) RETURNS SETOF action.matrix_test_result AS $func$
 DECLARE
     matchpoint_id        INT;
     user_object        actor.usr%ROWTYPE;
@@ -6410,22 +6598,7 @@ BEGIN
         END IF;
     END IF;
  
-    FOR standing_penalty IN
-        SELECT  DISTINCT csp.*
-          FROM  actor.usr_standing_penalty usp
-                JOIN config.standing_penalty csp ON (csp.id = usp.standing_penalty)
-          WHERE usr = match_user
-                AND usp.org_unit IN ( SELECT * FROM explode_array(context_org_list) )
-                AND (usp.stop_date IS NULL or usp.stop_date > NOW())
-                AND csp.block_list LIKE '%HOLD%' LOOP
-
-        result.fail_part := standing_penalty.name;
-        result.success := FALSE;
-        done := TRUE;
-        RETURN NEXT result;
-    END LOOP;
-
-    IF hold_test.stop_blocked_user IS TRUE THEN
+    IF NOT retargetting THEN
         FOR standing_penalty IN
             SELECT  DISTINCT csp.*
               FROM  actor.usr_standing_penalty usp
@@ -6433,28 +6606,45 @@ BEGIN
               WHERE usr = match_user
                     AND usp.org_unit IN ( SELECT * FROM explode_array(context_org_list) )
                     AND (usp.stop_date IS NULL or usp.stop_date > NOW())
-                    AND csp.block_list LIKE '%CIRC%' LOOP
+                    AND csp.block_list LIKE '%HOLD%' LOOP
     
             result.fail_part := standing_penalty.name;
             result.success := FALSE;
             done := TRUE;
             RETURN NEXT result;
         END LOOP;
-    END IF;
-
-    IF hold_test.max_holds IS NOT NULL THEN
-        SELECT    INTO hold_count COUNT(*)
-          FROM    action.hold_request
-          WHERE    usr = match_user
-            AND fulfillment_time IS NULL
-            AND cancel_time IS NULL
-            AND CASE WHEN hold_test.include_frozen_holds THEN TRUE ELSE frozen IS FALSE END;
-
-        IF hold_count >= hold_test.max_holds THEN
-            result.fail_part := 'config.hold_matrix_test.max_holds';
-            result.success := FALSE;
-            done := TRUE;
-            RETURN NEXT result;
+    
+        IF hold_test.stop_blocked_user IS TRUE THEN
+            FOR standing_penalty IN
+                SELECT  DISTINCT csp.*
+                  FROM  actor.usr_standing_penalty usp
+                        JOIN config.standing_penalty csp ON (csp.id = usp.standing_penalty)
+                  WHERE usr = match_user
+                        AND usp.org_unit IN ( SELECT * FROM explode_array(context_org_list) )
+                        AND (usp.stop_date IS NULL or usp.stop_date > NOW())
+                        AND csp.block_list LIKE '%CIRC%' LOOP
+        
+                result.fail_part := standing_penalty.name;
+                result.success := FALSE;
+                done := TRUE;
+                RETURN NEXT result;
+            END LOOP;
+        END IF;
+    
+        IF hold_test.max_holds IS NOT NULL THEN
+            SELECT    INTO hold_count COUNT(*)
+              FROM    action.hold_request
+              WHERE    usr = match_user
+                AND fulfillment_time IS NULL
+                AND cancel_time IS NULL
+                AND CASE WHEN hold_test.include_frozen_holds THEN TRUE ELSE frozen IS FALSE END;
+    
+            IF hold_count >= hold_test.max_holds THEN
+                result.fail_part := 'config.hold_matrix_test.max_holds';
+                result.success := FALSE;
+                done := TRUE;
+                RETURN NEXT result;
+            END IF;
         END IF;
     END IF;
 
@@ -6485,6 +6675,14 @@ BEGIN
 END;
 $func$ LANGUAGE plpgsql;
 
+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$
+    SELECT * FROM action.hold_request_permit_test( $1, $2, $3, $4, $5, FALSE);
+$func$ LANGUAGE SQL;
+
+CREATE OR REPLACE FUNCTION action.hold_retarget_permit_test( pickup_ou INT, request_ou INT, match_item BIGINT, match_user INT, match_requestor INT ) RETURNS SETOF action.matrix_test_result AS $func$
+    SELECT * FROM action.hold_request_permit_test( $1, $2, $3, $4, $5, TRUE );
+$func$ LANGUAGE SQL;
+
 -- New post-delete trigger to propagate deletions to parent(s)
 
 CREATE OR REPLACE FUNCTION action.age_parent_circ_on_delete () RETURNS TRIGGER AS $$
@@ -7107,7 +7305,7 @@ BEGIN
         END;
         $func$ LANGUAGE 'plpgsql';
     $$;
-    RETURN TRUE;
+       RETURN TRUE;
 END;
 $creator$ LANGUAGE 'plpgsql';
 
@@ -7147,7 +7345,7 @@ BEGIN
     PERFORM auditor.create_auditor_func(sch, tbl);
     PERFORM auditor.create_auditor_update_trigger(sch, tbl);
     PERFORM auditor.create_auditor_lifecycle(sch, tbl);
-       RETURN TRUE;
+    RETURN TRUE;
 END;
 $creator$ LANGUAGE 'plpgsql';
 
@@ -7175,7 +7373,7 @@ ALTER TABLE actor.usr ADD COLUMN
 ALTER TABLE AUDITOR.actor_usr_history ADD COLUMN 
        claims_never_checked_out_count INT;
 
-DROP VIEW auditor.actor_usr_lifecycle;
+DROP VIEW IF EXISTS auditor.actor_usr_lifecycle;
 
 SELECT auditor.create_auditor_lifecycle( 'actor', 'usr' );
 
@@ -7941,7 +8139,7 @@ CREATE TABLE booking.resource_type (
                                       DEFERRABLE INITIALLY DEFERRED,
     max_fine       NUMERIC(8,2),
     elbow_room     INTERVAL,
-    CONSTRAINT brt_name_or_record_once_per_owner UNIQUE(owner, name, record)
+    CONSTRAINT brt_name_and_record_once_per_owner UNIQUE(owner, name, record)
 );
 
 CREATE TABLE booking.resource (
@@ -8564,7 +8762,7 @@ ADD COLUMN fiscal_calendar INT NOT NULL
 ALTER TABLE auditor.actor_org_unit_history
        ADD COLUMN fiscal_calendar INT;
 
-DROP VIEW auditor.actor_org_unit_lifecycle;
+DROP VIEW IF EXISTS auditor.actor_org_unit_lifecycle;
 
 SELECT auditor.create_auditor_lifecycle( 'actor', 'org_unit' );
 
@@ -8969,6 +9167,133 @@ BEGIN
 END;
 $$ LANGUAGE plpgsql;
 
+COMMENT ON FUNCTION actor.usr_purge_data(INT, INT) IS $$
+/**
+ * Finds rows dependent on a given row in actor.usr and either deletes them
+ * or reassigns them to a different user.
+ */
+$$;
+
+CREATE OR REPLACE FUNCTION actor.usr_delete(
+       src_usr  IN INTEGER,
+       dest_usr IN INTEGER
+) RETURNS VOID AS $$
+DECLARE
+       old_profile actor.usr.profile%type;
+       old_home_ou actor.usr.home_ou%type;
+       new_profile actor.usr.profile%type;
+       new_home_ou actor.usr.home_ou%type;
+       new_name    text;
+       new_dob     actor.usr.dob%type;
+BEGIN
+       SELECT
+               id || '-PURGED-' || now(),
+               profile,
+               home_ou,
+               dob
+       INTO
+               new_name,
+               old_profile,
+               old_home_ou,
+               new_dob
+       FROM
+               actor.usr
+       WHERE
+               id = src_usr;
+       --
+       -- Quit if no such user
+       --
+       IF old_profile IS NULL THEN
+               RETURN;
+       END IF;
+       --
+       perform actor.usr_purge_data( src_usr, dest_usr );
+       --
+       -- Find the root grp_tree and the root org_unit.  This would be simpler if we 
+       -- could assume that there is only one root.  Theoretically, someday, maybe,
+       -- there could be multiple roots, so we take extra trouble to get the right ones.
+       --
+       SELECT
+               id
+       INTO
+               new_profile
+       FROM
+               permission.grp_ancestors( old_profile )
+       WHERE
+               parent is null;
+       --
+       SELECT
+               id
+       INTO
+               new_home_ou
+       FROM
+               actor.org_unit_ancestors( old_home_ou )
+       WHERE
+               parent_ou is null;
+       --
+       -- Truncate date of birth
+       --
+       IF new_dob IS NOT NULL THEN
+               new_dob := date_trunc( 'year', new_dob );
+       END IF;
+       --
+       UPDATE
+               actor.usr
+               SET
+                       card = NULL,
+                       profile = new_profile,
+                       usrname = new_name,
+                       email = NULL,
+                       passwd = random()::text,
+                       standing = DEFAULT,
+                       ident_type = 
+                       (
+                               SELECT MIN( id )
+                               FROM config.identification_type
+                       ),
+                       ident_value = NULL,
+                       ident_type2 = NULL,
+                       ident_value2 = NULL,
+                       net_access_level = DEFAULT,
+                       photo_url = NULL,
+                       prefix = NULL,
+                       first_given_name = new_name,
+                       second_given_name = NULL,
+                       family_name = new_name,
+                       suffix = NULL,
+                       alias = NULL,
+                       day_phone = NULL,
+                       evening_phone = NULL,
+                       other_phone = NULL,
+                       mailing_address = NULL,
+                       billing_address = NULL,
+                       home_ou = new_home_ou,
+                       dob = new_dob,
+                       active = FALSE,
+                       master_account = DEFAULT, 
+                       super_user = DEFAULT,
+                       barred = FALSE,
+                       deleted = TRUE,
+                       juvenile = DEFAULT,
+                       usrgroup = 0,
+                       claims_returned_count = DEFAULT,
+                       credit_forward_balance = DEFAULT,
+                       last_xact_id = DEFAULT,
+                       alert_message = NULL,
+                       create_date = now(),
+                       expire_date = now()
+       WHERE
+               id = src_usr;
+END;
+$$ LANGUAGE plpgsql;
+
+COMMENT ON FUNCTION actor.usr_delete(INT, INT) IS $$
+/**
+ * Logically deletes a user.  Removes personally identifiable information,
+ * and purges associated data in other tables.
+ */
+$$;
+
 -- INSERT INTO config.copy_status (id,name) VALUES (15,oils_i18n_gettext(15, 'On reservation shelf', 'ccs', 'name'));
 
 ALTER TABLE acq.fund
@@ -9053,7 +9378,11 @@ COMMENT ON VIEW acq.ordered_funding_source_credit IS $$
 $$;
 
 CREATE OR REPLACE VIEW money.billable_xact_summary_location_view AS
-       SELECT * FROM money.materialized_billable_xact_summary;
+    SELECT  m.*, COALESCE(c.circ_lib, g.billing_location, r.pickup_lib) AS billing_location
+      FROM  money.materialized_billable_xact_summary m
+            LEFT JOIN action.circulation c ON (c.id = m.id)
+            LEFT JOIN money.grocery g ON (g.id = m.id)
+            LEFT JOIN booking.reservation r ON (r.id = m.id);
 
 CREATE TABLE config.marc21_rec_type_map (
     code        TEXT    PRIMARY KEY,
@@ -10359,6 +10688,8 @@ INSERT INTO config.internal_flag (name) VALUES ('ingest.disable_metabib_full_rec
 INSERT INTO config.internal_flag (name) VALUES ('ingest.disable_metabib_rec_descriptor');
 INSERT INTO config.internal_flag (name) VALUES ('ingest.disable_metabib_field_entry');
 INSERT INTO config.internal_flag (name) VALUES ('ingest.disable_authority_linking');
+INSERT INTO config.internal_flag (name) VALUES ('ingest.metarecord_mapping.skip_on_update');
+INSERT INTO config.internal_flag (name) VALUES ('ingest.assume_inserts_only');
 
 CREATE TABLE authority.bib_linking (
     id          BIGSERIAL   PRIMARY KEY,
@@ -10387,7 +10718,10 @@ $func$ LANGUAGE SQL;
 
 CREATE OR REPLACE FUNCTION metabib.reingest_metabib_rec_descriptor( bib_id BIGINT ) RETURNS VOID AS $func$
 BEGIN
-    DELETE FROM metabib.rec_descriptor WHERE record = bib_id;
+    PERFORM * FROM config.internal_flag WHERE name = 'ingest.assume_inserts_only' AND enabled;
+    IF NOT FOUND THEN
+        DELETE FROM metabib.rec_descriptor WHERE record = bib_id;
+    END IF;
     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)
         SELECT  bib_id,
                 biblio.marc21_extract_fixed_field( bib_id, 'Type' ),
@@ -10406,8 +10740,8 @@ BEGIN
                             JOIN config.marc21_physical_characteristic_subfield_map s ON (s.id = p.subfield)
                             JOIN config.marc21_physical_characteristic_value_map v ON (v.id = p.value)
                       WHERE p.ptype = 'v' AND s.subfield = 'e'    ),
-                biblio.marc21_extract_fixed_field( bib_id, 'Date1'),
-                biblio.marc21_extract_fixed_field( bib_id, 'Date2');
+                LPAD(NULLIF(REGEXP_REPLACE(NULLIF(biblio.marc21_extract_fixed_field( bib_id, 'Date1'), ''), E'\\D', '0', 'g')::INT,0)::TEXT,4,'0'),
+                LPAD(NULLIF(REGEXP_REPLACE(NULLIF(biblio.marc21_extract_fixed_field( bib_id, 'Date2'), ''), E'\\D', '9', 'g')::INT,9999)::TEXT,4,'0');
 
     RETURN;
 END;
@@ -10436,12 +10770,14 @@ DECLARE
     fclass          RECORD;
     ind_data        metabib.field_entry_template%ROWTYPE;
 BEGIN
-    FOR fclass IN SELECT * FROM config.metabib_class LOOP
-        -- RAISE NOTICE 'Emptying out %', fclass.name;
-        EXECUTE $$DELETE FROM metabib.$$ || fclass.name || $$_field_entry WHERE source = $$ || bib_id;
-    END LOOP;
-
-    DELETE FROM metabib.facet_entry WHERE source = bib_id;
+    PERFORM * FROM config.internal_flag WHERE name = 'ingest.assume_inserts_only' AND enabled;
+    IF NOT FOUND THEN
+        FOR fclass IN SELECT * FROM config.metabib_class LOOP
+            -- RAISE NOTICE 'Emptying out %', fclass.name;
+            EXECUTE $$DELETE FROM metabib.$$ || fclass.name || $$_field_entry WHERE source = $$ || bib_id;
+        END LOOP;
+        DELETE FROM metabib.facet_entry WHERE source = bib_id;
+    END IF;
 
     FOR ind_data IN SELECT * FROM biblio.extract_metabib_field_entry( bib_id ) LOOP
         IF ind_data.field < 0 THEN
@@ -10583,7 +10919,10 @@ $func$ LANGUAGE PLPGSQL;
 
 CREATE OR REPLACE FUNCTION metabib.reingest_metabib_full_rec( bib_id BIGINT ) RETURNS VOID AS $func$
 BEGIN
-    DELETE FROM metabib.real_full_rec WHERE record = bib_id;
+    PERFORM * FROM config.internal_flag WHERE name = 'ingest.assume_inserts_only' AND enabled;
+    IF NOT FOUND THEN
+        DELETE FROM metabib.real_full_rec WHERE record = bib_id;
+    END IF;
     INSERT INTO metabib.real_full_rec (record, tag, ind1, ind2, subfield, value)
         SELECT record, tag, ind1, ind2, subfield, value FROM biblio.flatten_marc( bib_id );
 
@@ -10648,7 +10987,10 @@ BEGIN
             PERFORM metabib.remap_metarecord_for_bib( NEW.id, NEW.fingerprint );
         END IF;
     ELSE -- we're doing an update, and we're not deleted, remap
-        PERFORM metabib.remap_metarecord_for_bib( NEW.id, NEW.fingerprint );
+        PERFORM * FROM config.internal_flag WHERE name = 'ingest.metarecord_mapping.skip_on_update' AND enabled;
+        IF NOT FOUND THEN
+            PERFORM metabib.remap_metarecord_for_bib( NEW.id, NEW.fingerprint );
+        END IF;
     END IF;
 
     RETURN NEW;
@@ -10660,8 +11002,7 @@ CREATE TRIGGER aaa_indexing_ingest_or_delete AFTER INSERT OR UPDATE ON biblio.re
 
 DROP TRIGGER IF EXISTS zzz_update_materialized_simple_rec_delete_tgr ON biblio.record_entry;
 
-CREATE OR REPLACE FUNCTION oils_xpath_table ( key TEXT, document_field TEXT, relation_name TEXT, xpaths TEXT, criteria TEXT )
-RETURNS SETOF RECORD AS $func$
+CREATE OR REPLACE FUNCTION oils_xpath_table ( key TEXT, document_field TEXT, relation_name TEXT, xpaths TEXT, criteria TEXT ) RETURNS SETOF RECORD AS $func$
 DECLARE
     xpath_list  TEXT[];
     select_list TEXT[];
@@ -10671,52 +11012,56 @@ DECLARE
     empty_test  RECORD;
 BEGIN
     xpath_list := STRING_TO_ARRAY( xpaths, '|' );
-
     select_list := ARRAY_APPEND( select_list, key || '::INT AS key' );
-
     FOR i IN 1 .. ARRAY_UPPER(xpath_list,1) LOOP
-        select_list := ARRAY_APPEND(
-            select_list,
-            $sel$
-            EXPLODE_ARRAY(
-                COALESCE(
-                    NULLIF(
-                        oils_xpath(
-                            $sel$ ||
-                                quote_literal(
-                                    CASE
-                                        WHEN xpath_list[i] ~ $re$/[^/[]*@[^/]+$$re$ OR xpath_list[i] ~ $re$text\(\)$$re$ THEN xpath_list[i]
-                                        ELSE xpath_list[i] || '//text()'
-                                    END
-                                ) ||
-                            $sel$,
-                            $sel$ || document_field || $sel$
+        IF xpath_list[i] = 'null()' THEN
+            select_list := ARRAY_APPEND( select_list, 'NULL::TEXT AS c_' || i );
+        ELSE
+            select_list := ARRAY_APPEND(
+                select_list,
+                $sel$
+                EXPLODE_ARRAY(
+                    COALESCE(
+                        NULLIF(
+                            oils_xpath(
+                                $sel$ ||
+                                    quote_literal(
+                                        CASE
+                                            WHEN xpath_list[i] ~ $re$/[^/[]*@[^/]+$$re$ OR xpath_list[i] ~ $re$text\(\)$$re$ THEN xpath_list[i]
+                                            ELSE xpath_list[i] || '//text()'
+                                        END
+                                    ) ||
+                                $sel$,
+                                $sel$ || document_field || $sel$
+                            ),
+                           '{}'::TEXT[]
                         ),
-                       '{}'::TEXT[]
-                    ),
-                    '{NULL}'::TEXT[]
-                )
-            ) AS c_$sel$ || i
-        );
-        where_list := ARRAY_APPEND(
-            where_list,
-            'c_' || i || ' IS NOT NULL'
-        );
+                        '{NULL}'::TEXT[]
+                    )
+                ) AS c_$sel$ || i
+            );
+            where_list := ARRAY_APPEND(
+                where_list,
+                'c_' || i || ' IS NOT NULL'
+            );
+        END IF;
     END LOOP;
-
     q := $q$
 SELECT * FROM (
     SELECT $q$ || ARRAY_TO_STRING( select_list, ', ' ) || $q$ FROM $q$ || relation_name || $q$ WHERE ($q$ || criteria || $q$)
 )x WHERE $q$ || ARRAY_TO_STRING( where_list, ' AND ' );
     -- RAISE NOTICE 'query: %', q;
-
     FOR out_record IN EXECUTE q LOOP
         RETURN NEXT out_record;
     END LOOP;
-
     RETURN;
 END;
-$func$ LANGUAGE PLPGSQL;
+$func$ LANGUAGE PLPGSQL IMMUTABLE;
 
 CREATE OR REPLACE FUNCTION vandelay.ingest_items ( import_id BIGINT, attr_def_id BIGINT ) RETURNS SETOF vandelay.import_item AS $$
 DECLARE
@@ -11120,11 +11465,7 @@ BEGIN
 END;
 $creator$ LANGUAGE 'plpgsql';
 
-SELECT acq.create_acq_auditor ( 'acq', 'purchase_order' );
-CREATE INDEX acq_po_hist_id_idx            ON acq.acq_purchase_order_history( id );
-
-SELECT acq.create_acq_auditor ( 'acq', 'lineitem' );
-CREATE INDEX acq_lineitem_hist_id_idx            ON acq.acq_lineitem_history( id );
+ALTER TABLE acq.lineitem DROP COLUMN item_count;
 
 CREATE OR REPLACE VIEW acq.fund_debit_total AS
     SELECT  fund.id AS fund,
@@ -12649,7 +12990,7 @@ CREATE TABLE acq.user_request (
 
 CREATE TABLE acq.lineitem_alert_text (
        id               SERIAL         PRIMARY KEY,
-       code             TEXT           UNIQUE NOT NULL,
+       code             TEXT           NOT NULL,
        description      TEXT,
        owning_lib       INT            NOT NULL
                                        REFERENCES actor.org_unit(id)
@@ -12914,13 +13255,7 @@ FROM
                 fund
         ) AS c USING ( fund );
 
-CREATE OR REPLACE FUNCTION actor.usr_merge(
-       src_usr INT,
-       dest_usr INT,
-       del_addrs BOOLEAN,
-       del_cards BOOLEAN,
-       deactivate_cards BOOLEAN
-) RETURNS VOID AS $$
+CREATE OR REPLACE FUNCTION actor.usr_merge( src_usr INT, dest_usr INT, del_addrs BOOLEAN, del_cards BOOLEAN, deactivate_cards BOOLEAN ) RETURNS VOID AS $$
 DECLARE
        suffix TEXT;
        bucket_row RECORD;
@@ -13098,7 +13433,7 @@ BEGIN
 
     -- acq.*
     UPDATE acq.fund_allocation SET allocator = dest_usr WHERE allocator = src_usr;
-    UPDATE acq.fund_transfer SET transfer_user = dest_usr WHERE transfer_user = src_usr;
+       UPDATE acq.fund_transfer SET transfer_user = dest_usr WHERE transfer_user = src_usr;
 
        -- transfer picklists the same way we transfer buckets (see above)
        FOR picklist_row in
@@ -13123,8 +13458,8 @@ BEGIN
     UPDATE acq.purchase_order SET owner = dest_usr WHERE owner = src_usr;
     UPDATE acq.po_note SET creator = dest_usr WHERE creator = src_usr;
     UPDATE acq.po_note SET editor = dest_usr WHERE editor = src_usr;
-       UPDATE acq.provider_note SET creator = dest_usr WHERE creator = src_usr;
-       UPDATE acq.provider_note SET editor = dest_usr WHERE editor = src_usr;
+    UPDATE acq.provider_note SET creator = dest_usr WHERE creator = src_usr;
+    UPDATE acq.provider_note SET editor = dest_usr WHERE editor = src_usr;
     UPDATE acq.lineitem_note SET creator = dest_usr WHERE creator = src_usr;
     UPDATE acq.lineitem_note SET editor = dest_usr WHERE editor = src_usr;
     UPDATE acq.lineitem_usr_attr_definition SET usr = dest_usr WHERE usr = src_usr;
@@ -13272,6 +13607,10 @@ $$ LANGUAGE PLPGSQL;
 TRUNCATE money.materialized_billable_xact_summary;
 INSERT INTO money.materialized_billable_xact_summary SELECT * FROM money.billable_xact_summary;
 
+-- Now redefine the view as a window onto the materialized view
+CREATE OR REPLACE VIEW money.billable_xact_summary AS
+    SELECT * FROM money.materialized_billable_xact_summary;
+
 CREATE OR REPLACE FUNCTION permission.usr_has_perm_at_nd(
     user_id    IN INTEGER,
     perm_code  IN TEXT
@@ -13486,40 +13825,33 @@ END;
 $$ LANGUAGE 'plpgsql';
 
 ALTER TABLE acq.purchase_order
-       ADD COLUMN cancel_reason        INT REFERENCES acq.cancel_reason( id )
-                                           DEFERRABLE INITIALLY DEFERRED;
-
-ALTER TABLE acq.acq_purchase_order_history
-       ADD COLUMN cancel_reason INTEGER;
-
-ALTER TABLE acq.purchase_order
+       ADD COLUMN cancel_reason INT
+               REFERENCES acq.cancel_reason( id )
+           DEFERRABLE INITIALLY DEFERRED,
        ADD COLUMN prepayment_required BOOLEAN NOT NULL DEFAULT FALSE;
 
-ALTER TABLE acq.acq_purchase_order_history
-       ADD COLUMN prepayment_required BOOLEAN NOT NULL DEFAULT FALSE;
+-- Build the history table and lifecycle view
+-- for acq.purchase_order
 
-ALTER TABLE acq.lineitem
-       ADD COLUMN cancel_reason        INT REFERENCES acq.cancel_reason( id )
-                                           DEFERRABLE INITIALLY DEFERRED;
-
-ALTER TABLE acq.acq_lineitem_history
-       ADD COLUMN cancel_reason INTEGER;
-
-ALTER TABLE acq.lineitem
-       ADD COLUMN estimated_unit_price NUMERIC;
+SELECT acq.create_acq_auditor ( 'acq', 'purchase_order' );
 
-ALTER TABLE acq.acq_lineitem_history
-       ADD COLUMN estimated_unit_price NUMERIC;
+CREATE INDEX acq_po_hist_id_idx            ON acq.acq_purchase_order_history( id );
 
 ALTER TABLE acq.lineitem
+       ADD COLUMN cancel_reason INT
+               REFERENCES acq.cancel_reason( id )
+           DEFERRABLE INITIALLY DEFERRED,
+       ADD COLUMN estimated_unit_price NUMERIC,
        ADD COLUMN claim_policy INT
                REFERENCES acq.claim_policy
-               DEFERRABLE INITIALLY DEFERRED;
+               DEFERRABLE INITIALLY DEFERRED,
+       ALTER COLUMN eg_bib_id SET DATA TYPE bigint;
 
-ALTER TABLE acq.acq_lineitem_history
-       ADD COLUMN claim_policy INT
-               REFERENCES acq.claim_policy
-               DEFERRABLE INITIALLY DEFERRED;
+-- Build the history table and lifecycle view
+-- for acq.lineitem
+
+SELECT acq.create_acq_auditor ( 'acq', 'lineitem' );
+CREATE INDEX acq_lineitem_hist_id_idx            ON acq.acq_lineitem_history( id );
 
 ALTER TABLE acq.lineitem_detail
        ADD COLUMN cancel_reason        INT REFERENCES acq.cancel_reason( id )
@@ -13818,10 +14150,10 @@ BEGIN
         END IF;
     END IF;
 
-    add_rule := add_rule || ',' || COALESCE(ARRAY_TO_STRING(oils_xpath('//*[@tag="905"]/*[@code="a"]/text()',incoming_xml),''),'');
-    strip_rule := strip_rule || ',' || COALESCE(ARRAY_TO_STRING(oils_xpath('//*[@tag="905"]/*[@code="d"]/text()',incoming_xml),''),'');
-    replace_rule := replace_rule || ',' || COALESCE(ARRAY_TO_STRING(oils_xpath('//*[@tag="905"]/*[@code="r"]/text()',incoming_xml),''),'');
-    preserve_rule := preserve_rule || ',' || COALESCE(ARRAY_TO_STRING(oils_xpath('//*[@tag="905"]/*[@code="p"]/text()',incoming_xml),''),'');
+    add_rule := add_rule || ',' || COALESCE(ARRAY_TO_STRING(oils_xpath('//*[@tag="905"]/*[@code="a"]/text()',incoming_xml),','),'');
+    strip_rule := strip_rule || ',' || COALESCE(ARRAY_TO_STRING(oils_xpath('//*[@tag="905"]/*[@code="d"]/text()',incoming_xml),','),'');
+    replace_rule := replace_rule || ',' || COALESCE(ARRAY_TO_STRING(oils_xpath('//*[@tag="905"]/*[@code="r"]/text()',incoming_xml),','),'');
+    preserve_rule := preserve_rule || ',' || COALESCE(ARRAY_TO_STRING(oils_xpath('//*[@tag="905"]/*[@code="p"]/text()',incoming_xml),','),'');
 
     output.add_rule := BTRIM(add_rule,',');
     output.replace_rule := BTRIM(replace_rule,',');
@@ -14398,7 +14730,8 @@ $function$ LANGUAGE PLPGSQL;
 UPDATE config.metabib_field SET label = name;
 ALTER TABLE config.metabib_field ALTER COLUMN label SET NOT NULL;
 
-ALTER TABLE config.metabib_field ADD CONSTRAINT field_class_fkey FOREIGN KEY (field_class) REFERENCES config.metabib_class (name);
+ALTER TABLE config.metabib_field ADD CONSTRAINT metabib_field_field_class_fkey
+        FOREIGN KEY (field_class) REFERENCES config.metabib_class (name);
 
 ALTER TABLE config.metabib_field DROP CONSTRAINT metabib_field_field_class_check;
 
@@ -14753,7 +15086,12 @@ BEGIN
 END;
 $func$ LANGUAGE PLPGSQL;
 
-ALTER TABLE biblio.record_entry ADD COLUMN owner INT REFERENCES actor.org_unit (id);
+ALTER TABLE biblio.record_entry ADD COLUMN owner INT;
+ALTER TABLE biblio.record_entry
+        ADD CONSTRAINT biblio_record_entry_owner_fkey FOREIGN KEY (owner)
+        REFERENCES actor.org_unit (id)
+        DEFERRABLE INITIALLY DEFERRED;
+
 ALTER TABLE biblio.record_entry ADD COLUMN share_depth INT;
 
 ALTER TABLE auditor.biblio_record_entry_history ADD COLUMN owner INT;
@@ -15239,23 +15577,23 @@ BEGIN
             SELECT * INTO circ_chain_tail FROM action.circ_chain(circ_chain_head.id) ORDER BY xact_start DESC LIMIT 1;
             EXIT WHEN circ_chain_tail.xact_finish IS NULL;
 
-            -- Now get the user setings, if any, to block purging if the user wants to keep more circs
+            -- Now get the user settings, if any, to block purging if the user wants to keep more circs
             usr_keep_age.value := NULL;
             SELECT * INTO usr_keep_age FROM actor.usr_setting WHERE usr = circ_chain_head.usr AND name = 'history.circ.retention_age';
 
             usr_keep_start.value := NULL;
-            SELECT * INTO usr_keep_start FROM actor.usr_setting WHERE usr = circ_chain_head.usr AND name = 'history.circ.retention_start_date';
+            SELECT * INTO usr_keep_start FROM actor.usr_setting WHERE usr = circ_chain_head.usr AND name = 'history.circ.retention_start';
 
             IF usr_keep_age.value IS NOT NULL AND usr_keep_start.value IS NOT NULL THEN
-                IF oils_json_to_string(usr_keep_age.value)::INTERVAL > AGE(NOW(), oils_json_to_string(usr_keep_start.value)::TIMESTAMPTZ) THEN
-                    keep_age := AGE(NOW(), oils_json_to_string(usr_keep_start.value)::TIMESTAMPTZ);
+                IF oils_json_to_text(usr_keep_age.value)::INTERVAL > AGE(NOW(), oils_json_to_text(usr_keep_start.value)::TIMESTAMPTZ) THEN
+                    keep_age := AGE(NOW(), oils_json_to_text(usr_keep_start.value)::TIMESTAMPTZ);
                 ELSE
-                    keep_age := oils_json_to_string(usr_keep_age.value)::INTERVAL;
+                    keep_age := oils_json_to_text(usr_keep_age.value)::INTERVAL;
                 END IF;
             ELSIF usr_keep_start.value IS NOT NULL THEN
-                keep_age := AGE(NOW(), oils_json_to_string(usr_keep_start.value)::TIMESTAMPTZ);
+                keep_age := AGE(NOW(), oils_json_to_text(usr_keep_start.value)::TIMESTAMPTZ);
             ELSE
-                keep_age := COALESCE( org_keep_age::INTERVAL, '2000 years'::INTEVAL );
+                keep_age := COALESCE( org_keep_age::INTERVAL, '2000 years'::INTERVAL );
             END IF;
 
             EXIT WHEN AGE(NOW(), circ_chain_tail.xact_finish) < keep_age;
@@ -15405,6 +15743,8 @@ CREATE TABLE serial.subscription (
        expected_date_offset   INTERVAL
        -- acquisitions/business-side tables link to here
 );
+CREATE INDEX serial_subscription_record_idx ON serial.subscription (record_entry);
+CREATE INDEX serial_subscription_owner_idx ON serial.subscription (owning_lib);
 
 --at least one distribution per org_unit holding issues
 CREATE TABLE serial.distribution (
@@ -15436,6 +15776,8 @@ CREATE TABLE serial.distribution (
        unit_label_prefix     TEXT,
        unit_label_suffix     TEXT
 );
+CREATE INDEX serial_distribution_sub_idx ON serial.distribution (subscription);
+CREATE INDEX serial_distribution_holding_lib_idx ON serial.distribution (holding_lib);
 
 CREATE UNIQUE INDEX one_dist_per_sre_idx ON serial.distribution (record_entry);
 
@@ -15447,6 +15789,7 @@ CREATE TABLE serial.stream (
                                DEFERRABLE INITIALLY DEFERRED,
        routing_label   TEXT
 );
+CREATE INDEX serial_stream_dist_idx ON serial.stream (distribution);
 
 CREATE UNIQUE INDEX label_once_per_dist
        ON serial.stream (distribution, routing_label)
@@ -15472,6 +15815,8 @@ CREATE TABLE serial.routing_list_user (
                (reader IS NULL AND department IS NOT NULL)
        )
 );
+CREATE INDEX serial_routing_list_user_stream_idx ON serial.routing_list_user (stream);
+CREATE INDEX serial_routing_list_user_reader_idx ON serial.routing_list_user (reader);
 
 CREATE TABLE serial.caption_and_pattern (
        id           SERIAL       PRIMARY KEY,
@@ -15498,6 +15843,7 @@ CREATE TABLE serial.caption_and_pattern (
        chron_4      TEXT,
        chron_5      TEXT
 );
+CREATE INDEX serial_caption_and_pattern_sub_idx ON serial.caption_and_pattern (subscription);
 
 CREATE TABLE serial.issuance (
        id              SERIAL    PRIMARY KEY,
@@ -15526,12 +15872,20 @@ CREATE TABLE serial.issuance (
        holding_link_id INT
        -- TODO: add columns for separate enumeration/chronology values
 );
+CREATE INDEX serial_issuance_sub_idx ON serial.issuance (subscription);
+CREATE INDEX serial_issuance_caption_and_pattern_idx ON serial.issuance (caption_and_pattern);
+CREATE INDEX serial_issuance_date_published_idx ON serial.issuance (date_published);
 
 CREATE TABLE serial.unit (
        label           TEXT,
        label_sort_key  TEXT,
        contents        TEXT    NOT NULL
 ) INHERITS (asset.copy);
+CREATE UNIQUE INDEX unit_barcode_key ON serial.unit (barcode) WHERE deleted = FALSE OR deleted IS FALSE;
+CREATE INDEX unit_cn_idx ON serial.unit (call_number);
+CREATE INDEX unit_avail_cn_idx ON serial.unit (call_number);
+CREATE INDEX unit_creator_idx  ON serial.unit ( creator );
+CREATE INDEX unit_editor_idx   ON serial.unit ( editor );
 
 ALTER TABLE serial.unit ADD PRIMARY KEY (id);
 
@@ -15573,6 +15927,12 @@ CREATE TABLE serial.item (
                             DEFAULT 'Expected',
        shadowed        BOOL    NOT NULL DEFAULT FALSE
 );
+CREATE INDEX serial_item_stream_idx ON serial.item (stream);
+CREATE INDEX serial_item_issuance_idx ON serial.item (issuance);
+CREATE INDEX serial_item_unit_idx ON serial.item (unit);
+CREATE INDEX serial_item_uri_idx ON serial.item (uri);
+CREATE INDEX serial_item_date_received_idx ON serial.item (date_received);
+CREATE INDEX serial_item_status_idx ON serial.item (status);
 
 CREATE TABLE serial.item_note (
        id          SERIAL  PRIMARY KEY,
@@ -15588,6 +15948,7 @@ CREATE TABLE serial.item_note (
        title       TEXT    NOT NULL,
        value       TEXT    NOT NULL
 );
+CREATE INDEX serial_item_note_item_idx ON serial.item_note (item);
 
 CREATE TABLE serial.basic_summary (
        id                  SERIAL  PRIMARY KEY,
@@ -15599,6 +15960,7 @@ CREATE TABLE serial.basic_summary (
        textual_holdings    TEXT,
        show_generated      BOOL    NOT NULL DEFAULT TRUE
 );
+CREATE INDEX serial_basic_summary_dist_idx ON serial.basic_summary (distribution);
 
 CREATE TABLE serial.supplement_summary (
        id                  SERIAL  PRIMARY KEY,
@@ -15610,6 +15972,7 @@ CREATE TABLE serial.supplement_summary (
        textual_holdings    TEXT,
        show_generated      BOOL    NOT NULL DEFAULT TRUE
 );
+CREATE INDEX serial_supplement_summary_dist_idx ON serial.supplement_summary (distribution);
 
 CREATE TABLE serial.index_summary (
        id                  SERIAL  PRIMARY KEY,
@@ -15621,6 +15984,7 @@ CREATE TABLE serial.index_summary (
        textual_holdings    TEXT,
        show_generated      BOOL    NOT NULL DEFAULT TRUE
 );
+CREATE INDEX serial_index_summary_dist_idx ON serial.index_summary (distribution);
 
 -- 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.
 
@@ -16070,6 +16434,64 @@ CREATE OR REPLACE FUNCTION authority.propagate_changes (aid BIGINT) RETURNS SETO
     SELECT authority.propagate_changes( authority, bib ) FROM authority.bib_linking WHERE authority = $1;
 $func$ LANGUAGE SQL;
 
+CREATE OR REPLACE FUNCTION authority.flatten_marc ( TEXT ) RETURNS SETOF authority.full_rec AS $func$
+
+use MARC::Record;
+use MARC::File::XML (BinaryEncoding => 'UTF-8');
+
+my $xml = shift;
+my $r = MARC::Record->new_from_xml( $xml );
+
+return_next( { tag => 'LDR', value => $r->leader } );
+
+for my $f ( $r->fields ) {
+    if ($f->is_control_field) {
+        return_next({ tag => $f->tag, value => $f->data });
+    } else {
+        for my $s ($f->subfields) {
+            return_next({
+                tag      => $f->tag,
+                ind1     => $f->indicator(1),
+                ind2     => $f->indicator(2),
+                subfield => $s->[0],
+                value    => $s->[1]
+            });
+
+        }
+    }
+}
+
+return undef;
+
+$func$ LANGUAGE PLPERLU;
+
+CREATE OR REPLACE FUNCTION authority.flatten_marc ( rid BIGINT ) RETURNS SETOF authority.full_rec AS $func$
+DECLARE
+    auth    authority.record_entry%ROWTYPE;
+    output    authority.full_rec%ROWTYPE;
+    field    RECORD;
+BEGIN
+    SELECT INTO auth * FROM authority.record_entry WHERE id = rid;
+
+    FOR field IN SELECT * FROM authority.flatten_marc( auth.marc ) LOOP
+        output.record := rid;
+        output.ind1 := field.ind1;
+        output.ind2 := field.ind2;
+        output.tag := field.tag;
+        output.subfield := field.subfield;
+        IF field.subfield IS NOT NULL THEN
+            output.value := naco_normalize(field.value, field.subfield);
+        ELSE
+            output.value := field.value;
+        END IF;
+
+        CONTINUE WHEN output.value IS NULL;
+
+        RETURN NEXT output;
+    END LOOP;
+END;
+$func$ LANGUAGE PLPGSQL;
+
 -- authority.rec_descriptor appears to be unused currently
 CREATE OR REPLACE FUNCTION authority.reingest_authority_rec_descriptor( auth_id BIGINT ) RETURNS VOID AS $func$
 BEGIN
@@ -16152,8 +16574,6 @@ BEGIN
             NEW.marc,
             E'(</(?:[^:]*?:)?record>)',
             E'<datafield tag="901" ind1=" " ind2=" ">' ||
-                '<subfield code="a">' || NEW.arn_value || E'</subfield>' ||
-                '<subfield code="b">' || NEW.arn_source || E'</subfield>' ||
                 '<subfield code="c">' || NEW.id || E'</subfield>' ||
                 '<subfield code="t">' || TG_TABLE_SCHEMA || E'</subfield>' ||
              E'</datafield>\\1'
@@ -16255,6 +16675,18 @@ INSERT INTO config.global_flag (name, label, enabled)
         TRUE
     );
 
+INSERT INTO config.global_flag (name, label, enabled)
+    VALUES (
+        'circ.holds.usr_not_requestor',
+        oils_i18n_gettext(
+            'circ.holds.usr_not_requestor',
+            'Holds: When testing hold matrix matchpoints, use the profile group of the receiving user instead of that of the requestor (affects staff-placed holds)',
+            'cgf',
+            'label'
+        ),
+        TRUE
+    );
+
 CREATE OR REPLACE FUNCTION maintain_control_numbers() RETURNS TRIGGER AS $func$
 use strict;
 use MARC::Record;
@@ -16721,6 +17153,7 @@ CREATE TABLE serial.distribution_note (
        title        TEXT   NOT NULL,
        value        TEXT   NOT NULL
 );
+CREATE INDEX serial_distribution_note_dist_idx ON serial.distribution_note (distribution);
 
 ------- Begin surgery on serial.unit
 
@@ -17341,7 +17774,7 @@ ALTER TABLE asset.call_number
        ADD COLUMN label_sortkey TEXT;
 
 CREATE INDEX asset_call_number_label_sortkey
-       ON asset.call_number(label_sortkey);
+       ON asset.call_number(oils_text_as_bytea(label_sortkey));
 
 ALTER TABLE auditor.asset_call_number_history
        ADD COLUMN label_class BIGINT;
@@ -17970,13 +18403,6 @@ DROP INDEX authority.authority_record_unique_tcn;
 ALTER TABLE authority.record_entry DROP COLUMN arn_value;
 ALTER TABLE authority.record_entry DROP COLUMN arn_source;
 
-DROP INDEX IF EXISTS authority.unique_by_heading_and_thesaurus;
-
-CREATE INDEX by_heading_and_thesaurus
-    ON authority.record_entry (authority.normalize_heading(marc))
-    WHERE deleted IS FALSE or deleted = FALSE
-;
-
 ALTER TABLE acq.provider_contact
        ALTER COLUMN name SET NOT NULL;
 
@@ -18026,6 +18452,198 @@ CREATE INDEX aud_bib_rec_entry_hist_creator_idx
 CREATE INDEX aud_bib_rec_entry_hist_editor_idx
        ON auditor.biblio_record_entry_history ( editor );
 
+CREATE TABLE action.hold_request_note (
+
+    id     BIGSERIAL PRIMARY KEY,
+    hold   BIGINT    NOT NULL REFERENCES action.hold_request (id)
+                              ON DELETE CASCADE
+                              DEFERRABLE INITIALLY DEFERRED,
+    title  TEXT      NOT NULL,
+    body   TEXT      NOT NULL,
+    slip   BOOL      NOT NULL DEFAULT FALSE,
+    pub    BOOL      NOT NULL DEFAULT FALSE,
+    staff  BOOL      NOT NULL DEFAULT FALSE  -- created by staff
+
+);
+CREATE INDEX ahrn_hold_idx ON action.hold_request_note (hold);
+
+-- Tweak a constraint to add a CASCADE
+
+ALTER TABLE action.hold_notification DROP CONSTRAINT hold_notification_hold_fkey;
+
+ALTER TABLE action.hold_notification
+       ADD CONSTRAINT hold_notification_hold_fkey
+               FOREIGN KEY (hold) REFERENCES action.hold_request (id)
+               ON DELETE CASCADE
+               DEFERRABLE INITIALLY DEFERRED;
+
+CREATE TRIGGER asset_label_sortkey_trigger
+    BEFORE UPDATE OR INSERT ON asset.call_number
+    FOR EACH ROW EXECUTE PROCEDURE asset.label_normalizer();
+
+CREATE OR REPLACE FUNCTION container.clear_all_expired_circ_history_items( )
+RETURNS VOID AS $$
+--
+-- Delete expired circulation bucket items for all users that have
+-- a setting for patron.max_reading_list_interval.
+--
+DECLARE
+    today        TIMESTAMP WITH TIME ZONE;
+    threshold    TIMESTAMP WITH TIME ZONE;
+       usr_setting  RECORD;
+BEGIN
+       SELECT date_trunc( 'day', now() ) INTO today;
+       --
+       FOR usr_setting in
+               SELECT
+                       usr,
+                       value
+               FROM
+                       actor.usr_setting
+               WHERE
+                       name = 'patron.max_reading_list_interval'
+       LOOP
+               --
+               -- Make sure the setting is a valid interval
+               --
+               BEGIN
+                       threshold := today - CAST( translate( usr_setting.value, '"', '' ) AS INTERVAL );
+               EXCEPTION
+                       WHEN OTHERS THEN
+                               RAISE NOTICE 'Invalid setting patron.max_reading_list_interval for user %: ''%''',
+                                       usr_setting.usr, usr_setting.value;
+                               CONTINUE;
+               END;
+               --
+               --RAISE NOTICE 'User % threshold %', usr_setting.usr, threshold;
+               --
+       DELETE FROM container.copy_bucket_item
+       WHERE
+               bucket IN
+               (
+                   SELECT
+                       id
+                   FROM
+                       container.copy_bucket
+                   WHERE
+                       owner = usr_setting.usr
+                       AND btype = 'circ_history'
+               )
+               AND create_time < threshold;
+       END LOOP;
+       --
+END;
+$$ LANGUAGE plpgsql;
+
+COMMENT ON FUNCTION container.clear_all_expired_circ_history_items( ) IS $$
+/*
+ * Delete expired circulation bucket items for all users that have
+ * a setting for patron.max_reading_list_interval.
+*/
+$$;
+
+CREATE OR REPLACE FUNCTION container.clear_expired_circ_history_items( 
+        ac_usr IN INTEGER
+) RETURNS VOID AS $$
+--
+-- Delete old circulation bucket items for a specified user.
+-- "Old" means older than the interval specified by a
+-- user-level setting, if it is so specified.
+--
+DECLARE
+    threshold TIMESTAMP WITH TIME ZONE;
+BEGIN
+       -- Sanity check
+       IF ac_usr IS NULL THEN
+               RETURN;
+       END IF;
+       -- Determine the threshold date that defines "old".  Subtract the
+       -- interval from the system date, then truncate to midnight.
+       SELECT
+               date_trunc( 
+                       'day',
+                       now() - CAST( translate( value, '"', '' ) AS INTERVAL )
+               )
+       INTO
+               threshold
+       FROM
+               actor.usr_setting
+       WHERE
+               usr = ac_usr
+               AND name = 'patron.max_reading_list_interval';
+       --
+       IF threshold is null THEN
+               -- No interval defined; don't delete anything
+               -- RAISE NOTICE 'No interval defined for user %', ac_usr;
+               return;
+       END IF;
+       --
+       -- RAISE NOTICE 'Date threshold: %', threshold;
+       --
+       -- Threshold found; do the delete
+       delete from container.copy_bucket_item
+       where
+               bucket in
+               (
+                       select
+                               id
+                       from
+                               container.copy_bucket
+                       where
+                               owner = ac_usr
+                               and btype = 'circ_history'
+               )
+               and create_time < threshold;
+       --
+       RETURN;
+END;
+$$ LANGUAGE plpgsql;
+
+COMMENT ON FUNCTION container.clear_expired_circ_history_items( INTEGER ) IS $$
+/*
+ * Delete old circulation bucket items for a specified user.
+ * "Old" means older than the interval specified by a
+ * user-level setting, if it is so specified.
+*/
+$$;
+
+CREATE OR REPLACE VIEW reporter.hold_request_record AS
+SELECT  id,
+    target,
+    hold_type,
+    CASE
+        WHEN hold_type = 'T'
+            THEN target
+        WHEN hold_type = 'I'
+            THEN (SELECT ssub.record_entry FROM serial.subscription ssub JOIN serial.issuance si ON (si.subscription = ssub.id) WHERE si.id = ahr.target)
+        WHEN hold_type = 'V'
+            THEN (SELECT cn.record FROM asset.call_number cn WHERE cn.id = ahr.target)
+        WHEN hold_type IN ('C','R','F')
+            THEN (SELECT cn.record FROM asset.call_number cn JOIN asset.copy cp ON (cn.id = cp.call_number) WHERE cp.id = ahr.target)
+        WHEN hold_type = 'M'
+            THEN (SELECT mr.master_record FROM metabib.metarecord mr WHERE mr.id = ahr.target)
+    END AS bib_record
+  FROM  action.hold_request ahr;
+
+UPDATE  metabib.rec_descriptor
+  SET   date1=LPAD(NULLIF(REGEXP_REPLACE(NULLIF(date1, ''), E'\\D', '0', 'g')::INT,0)::TEXT,4,'0'),
+        date2=LPAD(NULLIF(REGEXP_REPLACE(NULLIF(date2, ''), E'\\D', '9', 'g')::INT,9999)::TEXT,4,'0');
+
+-- Change some ints to bigints:
+
+ALTER TABLE container.biblio_record_entry_bucket_item
+       ALTER COLUMN target_biblio_record_entry SET DATA TYPE bigint;
+
+ALTER TABLE vandelay.queued_bib_record
+       ALTER COLUMN imported_as SET DATA TYPE bigint;
+
+ALTER TABLE action.hold_copy_map
+       ALTER COLUMN id SET DATA TYPE bigint;
+
+-- Make due times get pushed to 23:59:59 on insert OR update
+DROP TRIGGER IF EXISTS push_due_date_tgr ON action.circulation;
+CREATE TRIGGER push_due_date_tgr BEFORE INSERT OR UPDATE ON action.circulation FOR EACH ROW EXECUTE PROCEDURE action.push_circ_due_time();
+
 COMMIT;
 
 -- Some operations go outside of the transaction, because they may
@@ -18046,39 +18664,51 @@ ADD COLUMN shelf_expire_time TIMESTAMPTZ;
 \qecho If any of these CREATE INDEX statements fails because the index already
 \qecho exists, ignore the failure.
 
-CREATE INDEX serial_subscription_record_idx ON serial.subscription (record_entry);
-CREATE INDEX serial_subscription_owner_idx ON serial.subscription (owning_lib);
-CREATE INDEX serial_caption_and_pattern_sub_idx ON serial.caption_and_pattern (subscription);
-CREATE INDEX serial_distribution_sub_idx ON serial.distribution (subscription);
-CREATE INDEX serial_distribution_holding_lib_idx ON serial.distribution (holding_lib);
-CREATE INDEX serial_distribution_note_dist_idx ON serial.distribution_note (distribution);
-CREATE INDEX serial_stream_dist_idx ON serial.stream (distribution);
-CREATE INDEX serial_routing_list_user_stream_idx ON serial.routing_list_user (stream);
-CREATE INDEX serial_routing_list_user_reader_idx ON serial.routing_list_user (reader);
-CREATE INDEX serial_issuance_sub_idx ON serial.issuance (subscription);
-CREATE INDEX serial_issuance_caption_and_pattern_idx ON serial.issuance (caption_and_pattern);
-CREATE INDEX serial_issuance_date_published_idx ON serial.issuance (date_published);
-CREATE UNIQUE INDEX unit_barcode_key ON serial.unit (barcode) WHERE deleted = FALSE OR deleted IS FALSE;
-CREATE INDEX unit_cn_idx ON serial.unit (call_number);
-CREATE INDEX unit_avail_cn_idx ON serial.unit (call_number);
-CREATE INDEX unit_creator_idx  ON serial.unit ( creator );
-CREATE INDEX unit_editor_idx   ON serial.unit ( editor );
-CREATE INDEX serial_item_stream_idx ON serial.item (stream);
-CREATE INDEX serial_item_issuance_idx ON serial.item (issuance);
-CREATE INDEX serial_item_unit_idx ON serial.item (unit);
-CREATE INDEX serial_item_uri_idx ON serial.item (uri);
-CREATE INDEX serial_item_date_received_idx ON serial.item (date_received);
-CREATE INDEX serial_item_status_idx ON serial.item (status);
-CREATE INDEX serial_item_note_item_idx ON serial.item_note (item);
-CREATE INDEX serial_basic_summary_dist_idx ON serial.basic_summary (distribution);
-CREATE INDEX serial_supplement_summary_dist_idx ON serial.supplement_summary (distribution);
-CREATE INDEX serial_index_summary_dist_idx ON serial.index_summary (distribution);
+CREATE INDEX acq_picklist_owner_idx   ON acq.picklist ( owner );
+CREATE INDEX acq_picklist_creator_idx ON acq.picklist ( creator );
+CREATE INDEX acq_picklist_editor_idx  ON acq.picklist ( editor );
+CREATE INDEX acq_po_note_creator_idx  ON acq.po_note ( creator );
+CREATE INDEX acq_po_note_editor_idx   ON acq.po_note ( editor );
+CREATE INDEX fund_alloc_allocator_idx ON acq.fund_allocation ( allocator );
+CREATE INDEX li_creator_idx   ON acq.lineitem ( creator );
+CREATE INDEX li_editor_idx    ON acq.lineitem ( editor );
+CREATE INDEX li_selector_idx  ON acq.lineitem ( selector );
+CREATE INDEX li_note_creator_idx  ON acq.lineitem_note ( creator );
+CREATE INDEX li_note_editor_idx   ON acq.lineitem_note ( editor );
+CREATE INDEX li_usr_attr_def_usr_idx  ON acq.lineitem_usr_attr_definition ( usr );
+CREATE INDEX po_editor_idx   ON acq.purchase_order ( editor );
+CREATE INDEX po_creator_idx  ON acq.purchase_order ( creator );
+CREATE INDEX acq_po_org_name_order_date_idx ON acq.purchase_order( ordering_agency, name, order_date );
+CREATE INDEX action_in_house_use_staff_idx  ON action.in_house_use ( staff );
+CREATE INDEX action_non_cat_circ_patron_idx ON action.non_cataloged_circulation ( patron );
+CREATE INDEX action_non_cat_circ_staff_idx  ON action.non_cataloged_circulation ( staff );
+CREATE INDEX action_survey_response_usr_idx ON action.survey_response ( usr );
+CREATE INDEX ahn_notify_staff_idx           ON action.hold_notification ( notify_staff );
+CREATE INDEX circ_all_usr_idx               ON action.circulation ( usr );
+CREATE INDEX circ_circ_staff_idx            ON action.circulation ( circ_staff );
+CREATE INDEX circ_checkin_staff_idx         ON action.circulation ( checkin_staff );
+CREATE INDEX hold_request_fulfillment_staff_idx ON action.hold_request ( fulfillment_staff );
+CREATE INDEX hold_request_requestor_idx     ON action.hold_request ( requestor );
+CREATE INDEX non_cat_in_house_use_staff_idx ON action.non_cat_in_house_use ( staff );
+CREATE INDEX actor_usr_note_creator_idx     ON actor.usr_note ( creator );
+CREATE INDEX actor_usr_standing_penalty_staff_idx ON actor.usr_standing_penalty ( staff );
+CREATE INDEX usr_org_unit_opt_in_staff_idx  ON actor.usr_org_unit_opt_in ( staff );
+CREATE INDEX asset_call_number_note_creator_idx ON asset.call_number_note ( creator );
+CREATE INDEX asset_copy_note_creator_idx    ON asset.copy_note ( creator );
+CREATE INDEX cp_creator_idx                 ON asset.copy ( creator );
+CREATE INDEX cp_editor_idx                  ON asset.copy ( editor );
 
 CREATE INDEX actor_card_barcode_lower_idx ON actor.card (lower(barcode));
 
-\qecho if the following CREATE INDEX fails, It will be necessary to do some
+DROP INDEX IF EXISTS authority.unique_by_heading_and_thesaurus;
+
+\qecho If the following CREATE INDEX fails, It will be necessary to do some
 \qecho data cleanup as described in the comments.
 
+CREATE UNIQUE INDEX unique_by_heading_and_thesaurus
+    ON authority.record_entry (authority.normalize_heading(marc))
+       WHERE deleted IS FALSE or deleted = FALSE;
+
 -- If the unique index fails, uncomment the following to create
 -- a regular index that will help find the duplicates in a hurry:
 --CREATE INDEX by_heading_and_thesaurus
index a34d192..08b253d 100644 (file)
@@ -31,6 +31,7 @@ BEGIN;
 CREATE TABLE config.hold_matrix_matchpoint (
     id                      SERIAL    PRIMARY KEY,
     active                  BOOL    NOT NULL DEFAULT TRUE,
+    strict_ou_match         BOOL    NOT NULL DEFAULT FALSE,
     user_home_ou            INT        REFERENCES actor.org_unit (id) DEFERRABLE INITIALLY DEFERRED,    -- Set to the top OU for the matchpoint applicability range; we can use org_unit_prox to choose the "best"
     request_ou              INT        REFERENCES actor.org_unit (id) DEFERRABLE INITIALLY DEFERRED,    -- Set to the top OU for the matchpoint applicability range; we can use org_unit_prox to choose the "best"
     pickup_ou               INT        REFERENCES actor.org_unit (id) DEFERRABLE INITIALLY DEFERRED,    -- Set to the top OU for the matchpoint applicability range; we can use org_unit_prox to choose the "best"
@@ -57,7 +58,6 @@ CREATE TABLE config.hold_matrix_matchpoint (
 CREATE OR REPLACE FUNCTION action.find_hold_matrix_matchpoint( pickup_ou INT, request_ou INT, match_item BIGINT, match_user INT, match_requestor INT ) RETURNS INT AS $func$
 DECLARE
     current_requestor_group    permission.grp_tree%ROWTYPE;
-    root_ou            actor.org_unit%ROWTYPE;
     requestor_object    actor.usr%ROWTYPE;
     user_object        actor.usr%ROWTYPE;
     item_object        asset.copy%ROWTYPE;
@@ -69,7 +69,6 @@ DECLARE
     current_mp        config.hold_matrix_matchpoint%ROWTYPE;
     matchpoint        config.hold_matrix_matchpoint%ROWTYPE;
 BEGIN
-    SELECT INTO root_ou * FROM actor.org_unit WHERE parent_ou IS NULL;
     SELECT INTO user_object * FROM actor.usr WHERE id = match_user;
     SELECT INTO requestor_object * FROM actor.usr WHERE id = match_requestor;
     SELECT INTO item_object * FROM asset.copy WHERE id = match_item;
@@ -97,7 +96,11 @@ BEGIN
                     CASE WHEN m.marc_vr_format    IS NOT NULL THEN 2 ELSE 0 END +
                     CASE WHEN m.ref_flag        IS NOT NULL THEN 1 ELSE 0 END DESC LOOP
 
-            current_mp_weight := 5.0;
+            IF NOT current_mp.strict_ou_match THEN
+                current_mp_weight := 5.0;
+            ELSE
+                current_mp_weight := 0.0;
+            END IF;
 
             IF current_mp.circ_modifier IS NOT NULL THEN
                 CONTINUE WHEN current_mp.circ_modifier <> item_object.circ_modifier OR item_object.circ_modifier IS NULL;
@@ -129,28 +132,48 @@ BEGIN
 
 
             -- caclulate the rule match weight
-            IF current_mp.item_owning_ou IS NOT NULL AND current_mp.item_owning_ou <> root_ou.id THEN
-                SELECT INTO tmp_weight 1.0 / (actor.org_unit_proximity(current_mp.item_owning_ou, item_cn_object.owning_lib)::FLOAT + 1.0)::FLOAT;
+            IF current_mp.item_owning_ou IS NOT NULL THEN
+                IF NOT current_mp.strict_ou_match THEN
+                    SELECT INTO tmp_weight 1.0 / (actor.org_unit_proximity(current_mp.item_owning_ou, item_cn_object.owning_lib)::FLOAT + 1.0)::FLOAT;
+                ELSE
+                    tmp_weight := CASE WHEN current_mp.item_owning_ou = item_cn_object.owning_lib THEN 1.0 ELSE 0.0 END;
+                END IF;
                 current_mp_weight := current_mp_weight - tmp_weight;
             END IF; 
 
-            IF current_mp.item_circ_ou IS NOT NULL AND current_mp.item_circ_ou <> root_ou.id THEN
-                SELECT INTO tmp_weight 1.0 / (actor.org_unit_proximity(current_mp.item_circ_ou, item_object.circ_lib)::FLOAT + 1.0)::FLOAT;
+            IF current_mp.item_circ_ou IS NOT NULL THEN
+                IF NOT current_mp.strict_ou_match THEN
+                    SELECT INTO tmp_weight 1.0 / (actor.org_unit_proximity(current_mp.item_circ_ou, item_object.circ_lib)::FLOAT + 1.0)::FLOAT;
+                ELSE
+                    tmp_weight := CASE WHEN current_mp.item_circ_ou = item_object.circ_lib THEN 1.0 ELSE 0.0 END;
+                END IF;
                 current_mp_weight := current_mp_weight - tmp_weight;
             END IF; 
 
-            IF current_mp.pickup_ou IS NOT NULL AND current_mp.pickup_ou <> root_ou.id THEN
-                SELECT INTO tmp_weight 1.0 / (actor.org_unit_proximity(current_mp.pickup_ou, pickup_ou)::FLOAT + 1.0)::FLOAT;
+            IF current_mp.pickup_ou IS NOT NULL THEN
+                IF NOT current_mp.strict_ou_match THEN
+                    SELECT INTO tmp_weight 1.0 / (actor.org_unit_proximity(current_mp.pickup_ou, pickup_ou)::FLOAT + 1.0)::FLOAT;
+                ELSE
+                    tmp_weight := CASE WHEN current_mp.pickup_ou = pickiup_ou THEN 1.0 ELSE 0.0 END;
+                END IF;
                 current_mp_weight := current_mp_weight - tmp_weight;
             END IF; 
 
-            IF current_mp.request_ou IS NOT NULL AND current_mp.request_ou <> root_ou.id THEN
-                SELECT INTO tmp_weight 1.0 / (actor.org_unit_proximity(current_mp.request_ou, request_ou)::FLOAT + 1.0)::FLOAT;
+            IF current_mp.request_ou IS NOT NULL THEN
+                IF NOT current_mp.strict_ou_match THEN
+                    SELECT INTO tmp_weight 1.0 / (actor.org_unit_proximity(current_mp.request_ou, request_ou)::FLOAT + 1.0)::FLOAT;
+                ELSE
+                    tmp_weight := CASE WHEN current_mp.request_ou = request_ou THEN 1.0 ELSE 0.0 END;
+                END IF;
                 current_mp_weight := current_mp_weight - tmp_weight;
             END IF; 
 
-            IF current_mp.user_home_ou IS NOT NULL AND current_mp.user_home_ou <> root_ou.id THEN
-                SELECT INTO tmp_weight 1.0 / (actor.org_unit_proximity(current_mp.user_home_ou, user_object.home_ou)::FLOAT + 1.0)::FLOAT;
+            IF current_mp.user_home_ou IS NOT NULL THEN
+                IF NOT current_mp.strict_ou_match THEN
+                    SELECT INTO tmp_weight 1.0 / (actor.org_unit_proximity(current_mp.user_home_ou, user_object.home_ou)::FLOAT + 1.0)::FLOAT;
+                ELSE
+                    tmp_weight := CASE WHEN current_mp.user_home_ou = user_object.home_ou THEN 1.0 ELSE 0.0 END;
+                END IF;
                 current_mp_weight := current_mp_weight - tmp_weight;
             END IF; 
 
@@ -172,7 +195,7 @@ END;
 $func$ LANGUAGE plpgsql;
 
 
-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$
+CREATE OR REPLACE FUNCTION action.hold_request_permit_test( pickup_ou INT, request_ou INT, match_item BIGINT, match_user INT, match_requestor INT, retargetting BOOL ) RETURNS SETOF action.matrix_test_result AS $func$
 DECLARE
     matchpoint_id        INT;
     user_object        actor.usr%ROWTYPE;
@@ -306,7 +329,7 @@ BEGIN
         END LOOP;
     END IF;
 
-    IF hold_test.max_holds IS NOT NULL THEN
+    IF hold_test.max_holds IS NOT NULL AND NOT retargetting THEN
         SELECT    INTO hold_count COUNT(*)
           FROM    action.hold_request
           WHERE    usr = match_user
@@ -349,5 +372,13 @@ BEGIN
 END;
 $func$ LANGUAGE plpgsql;
 
+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$
+    SELECT * FROM action.hold_request_permit_test( $1, $2, $3, $4, $5, FALSE );
+$func$ LANGUAGE SQL;
+
+CREATE OR REPLACE FUNCTION action.hold_retarget_permit_test( pickup_ou INT, request_ou INT, match_item BIGINT, match_user INT, match_requestor INT ) RETURNS SETOF action.matrix_test_result AS $func$
+    SELECT * FROM action.hold_request_permit_test( $1, $2, $3, $4, $5, TRUE );
+$func$ LANGUAGE SQL;
+
 COMMIT;
 
index 13db4da..0db32d7 100644 (file)
@@ -482,7 +482,7 @@ CREATE TABLE acq.lineitem (
        create_time         TIMESTAMP WITH TIME ZONE    NOT NULL DEFAULT NOW(),
        edit_time           TIMESTAMP WITH TIME ZONE    NOT NULL DEFAULT NOW(),
        marc                TEXT                        NOT NULL,
-       eg_bib_id           INT                         REFERENCES biblio.record_entry (id) DEFERRABLE INITIALLY DEFERRED,
+       eg_bib_id           BIGINT                      REFERENCES biblio.record_entry (id) DEFERRABLE INITIALLY DEFERRED,
        source_label        TEXT,
        state               TEXT                        NOT NULL DEFAULT 'new',
        cancel_reason       INT                         REFERENCES acq.cancel_reason( id )
index ff139a2..dee49b8 100644 (file)
@@ -1390,6 +1390,15 @@ INSERT INTO permission.perm_list VALUES
     ,(399, 'ADMIN_SERIAL_DISTRIBUTION', oils_i18n_gettext(399, 'Create/update/delete serial distribution objects', 'ppl', 'description'))
     ,(400, 'ADMIN_SERIAL_STREAM', oils_i18n_gettext(400, 'Create/update/delete serial stream objects', 'ppl', 'description'))
     ,(401, 'RECEIVE_SERIAL', oils_i18n_gettext(401, 'Receive serial items', 'ppl', 'description'))
+    ,(402, 'ADMIN_ACQ_DISTRIB_FORMULA', oils_i18n_gettext(402, 'Create/update/delete distribution formulae', 'ppl', 'description'))
+    ,(403, 'ADMIN_ACQ_CLAIM', oils_i18n_gettext(403, 'Create/update/delete acquisitions claims', 'ppl', 'description'))
+    ,(404, 'ADMIN_ACQ_CLAIM_EVENT_TYPE', oils_i18n_gettext(404, 'Create/update/delete acquisitions claim event types', 'ppl', 'description'))
+    ,(405, 'ADMIN_ACQ_CLAIM_TYPE', oils_i18n_gettext(405, 'Create/update/delete acquisitions claim types', 'ppl', 'description'))
+    ,(406, 'ADMIN_ACQ_FISCAL_YEAR', oils_i18n_gettext(406, 'Create/update/delete acquisitions fiscal years', 'ppl', 'description'))
+    ,(407, 'ADMIN_ACQ_FUND_ALLOCATION_PERCENT', oils_i18n_gettext(407, 'Create/update/delete acquisitions fund allocation percentages', 'ppl', 'description'))
+    ,(408, 'ADMIN_ACQ_FUND_TAG', oils_i18n_gettext(408, 'Create/update/delete acquisitions fund tags', 'ppl', 'description'))
+    ,(409, 'ADMIN_ACQ_LINEITEM_ALERT_TEXT', oils_i18n_gettext(409, 'Create/update/delete acquisitions lineitem alert text', 'ppl', 'description'))
+
 ;
 
 
@@ -2336,6 +2345,7 @@ INSERT INTO container.biblio_record_entry_bucket_type (code,label) VALUES ('misc
 INSERT INTO container.biblio_record_entry_bucket_type (code,label) VALUES ('staff_client', oils_i18n_gettext('staff_client', 'General Staff Client container', 'cbrebt', 'label'));
 INSERT INTO container.biblio_record_entry_bucket_type (code,label) VALUES ('bookbag', oils_i18n_gettext('bookbag', 'Book Bag', 'cbrebt', 'label'));
 INSERT INTO container.biblio_record_entry_bucket_type (code,label) VALUES ('reading_list', oils_i18n_gettext('reading_list', 'Reading List', 'cbrebt', 'label'));
+INSERT INTO container.biblio_record_entry_bucket_type (code,label) VALUES ('template_merge',oils_i18n_gettext('template_merge','Template Merge Container', 'cbrebt', 'label'));
 
 INSERT INTO container.user_bucket_type (code,label) VALUES ('misc', oils_i18n_gettext('misc', 'Miscellaneous', 'cubt', 'label'));
 INSERT INTO container.user_bucket_type (code,label) VALUES ('folks', oils_i18n_gettext('folks', 'Friends', 'cubt', 'label'));
@@ -4872,8 +4882,8 @@ $$
         <tbody>
         [% FOREACH detail IN li.lineitem_details.sort('owning_lib') %]
             [% 
-                IF copy.eg_copy_id;
-                    SET copy = copy.eg_copy_id;
+                IF detail.eg_copy_id;
+                    SET copy = detail.eg_copy_id;
                     SET cn_label = copy.call_number.label;
                 ELSE; 
                     SET copy = detail; 
@@ -4940,9 +4950,19 @@ INSERT INTO action_trigger.reactor (module, description)
 
 
 INSERT INTO action_trigger.event_definition (id, active, owner, name, hook, validator, reactor, cleanup_success, cleanup_failure, delay, delay_field, group_field, template) 
-    VALUES (23, true, 1, 'PO JEDI', 'acqpo.activated', 'Acq::PurchaseOrderEDIRequired', 'GeneratePurchaseOrderJEDI', NULL, NULL, '00:05:00', NULL, NULL,
+    VALUES (23, true, 1, 'PO JEDI', 'acqpo.activated', 'Acq::PurchaseOrderEDIRequired', 'GeneratePurchaseOrderJEDI', NULL, NULL, '00:00:00', NULL, NULL,
 $$[%- USE date -%]
-[%# start JEDI document -%]
+[%# start JEDI document 
+  # Vendor specific kludges:
+  # BT      - vendcode goes to NAD/BY *suffix*  w/ 91 qualifier
+  # INGRAM  - vendcode goes to NAD/BY *segment* w/ 91 qualifier (separately)
+  # BRODART - vendcode goes to FTX segment (lineitem level)
+-%]
+[%- 
+IF target.provider.edi_default.vendcode && target.provider.code == 'BRODART';
+    xtra_ftx = target.provider.edi_default.vendcode;
+END;
+-%]
 [%- BLOCK big_block -%]
 {
    "recipient":"[% target.provider.san %]",
@@ -4951,23 +4971,27 @@ $$[%- USE date -%]
      "ORDERS":[ "order", {
         "po_number":[% target.id %],
         "date":"[% date.format(date.now, '%Y%m%d') %]",
-        "buyer":[{
-            [%- IF target.provider.edi_default.vendcode -%]
-                "id":"[% target.ordering_agency.mailing_address.san _ ' ' _ target.provider.edi_default.vendcode %]", 
-                "id-qualifier": 91
+        "buyer":[
+            [%   IF   target.provider.edi_default.vendcode && (target.provider.code == 'BT' || target.provider.name.match('(?i)^BAKER & TAYLOR'))  -%]
+                {"id-qualifier": 91, "id":"[% target.ordering_agency.mailing_address.san _ ' ' _ target.provider.edi_default.vendcode %]"}
+            [%- ELSIF target.provider.edi_default.vendcode && target.provider.code == 'INGRAM' -%]
+                {"id":"[% target.ordering_agency.mailing_address.san %]"},
+                {"id-qualifier": 91, "id":"[% target.provider.edi_default.vendcode %]"}
             [%- ELSE -%]
-                "id":"[% target.ordering_agency.mailing_address.san %]"
-            [%- END  -%]
-        }],
-        "vendor":[ 
+                {"id":"[% target.ordering_agency.mailing_address.san %]"}
+            [%- END -%]
+        ],
+        "vendor":[
             [%- # target.provider.name (target.provider.id) -%]
             "[% target.provider.san %]",
             {"id-qualifier": 92, "id":"[% target.provider.id %]"}
         ],
         "currency":"[% target.provider.currency_type %]",
+                
         "items":[
-        [% FOR li IN target.lineitems %]
+        [%- FOR li IN target.lineitems %]
         {
+            "line_index":"[% li.id %]",
             "identifiers":[   [%-# li.isbns = helpers.get_li_isbns(li.attributes) %]
             [% FOR isbn IN helpers.get_li_isbns(li.attributes) -%]
                 [% IF isbn.length == 13 -%]
@@ -4976,23 +5000,35 @@ $$[%- USE date -%]
                 {"id-qualifier":"IB","id":"[% isbn %]"},
                 [%- END %]
             [% END %]
-                {"id-qualifier":"SA","id":"[% li.id %]"}
+                {"id-qualifier":"IN","id":"[% li.id %]"}
             ],
             "price":[% li.estimated_unit_price || '0.00' %],
             "desc":[
-                {"BTI":"[% helpers.get_li_attr('title',     '', li.attributes) %]"}, 
+                {"BTI":"[% helpers.get_li_attr('title',     '', li.attributes) %]"},
                 {"BPU":"[% helpers.get_li_attr('publisher', '', li.attributes) %]"},
                 {"BPD":"[% helpers.get_li_attr('pubdate',   '', li.attributes) %]"},
                 {"BPH":"[% helpers.get_li_attr('pagination','', li.attributes) %]"}
             ],
+            [%- ftx_vals = []; 
+                FOR note IN li.lineitem_notes; 
+                    NEXT UNLESS note.vendor_public == 't'; 
+                    ftx_vals.push(note.value); 
+                END; 
+                IF xtra_ftx;           ftx_vals.unshift(xtra_ftx); END; 
+                IF ftx_vals.size == 0; ftx_vals.unshift('');       END;  # BT needs FTX+LIN for every LI, even if it is an empty one
+            -%]
+
+            "free-text":[ 
+                [% FOR note IN ftx_vals -%] "[% note %]"[% UNLESS loop.last %], [% END %][% END %] 
+            ],            
             "quantity":[% li.lineitem_details.size %]
         }[% UNLESS loop.last %],[% END %]
         [%-# TODO: lineitem details (later) -%]
         [% END %]
         ],
         "line_items":[% target.lineitems.size %]
-     }]  [% # close ORDERS array %]
-   }]    [% # close  body  array %]
+     }]  [%# close ORDERS array %]
+   }]    [%# close  body  array %]
 }
 [% END %]
 [% tempo = PROCESS big_block; helpers.escape_json(tempo) %]
index 4492d6c..a4fa038 100644 (file)
@@ -305,9 +305,11 @@ SELECT     id,
        CASE
                WHEN hold_type = 'T'
                        THEN target
+               WHEN hold_type = 'I'
+                       THEN (SELECT ssub.record_entry FROM serial.subscription ssub JOIN serial.issuance si ON (si.subscription = ssub.id) WHERE si.id = ahr.target)
                WHEN hold_type = 'V'
                        THEN (SELECT cn.record FROM asset.call_number cn WHERE cn.id = ahr.target)
-               WHEN hold_type = 'C'
+               WHEN hold_type IN ('C','R','F')
                        THEN (SELECT cn.record FROM asset.call_number cn JOIN asset.copy cp ON (cn.id = cp.call_number) WHERE cp.id = ahr.target)
                WHEN hold_type = 'M'
                        THEN (SELECT mr.master_record FROM metabib.metarecord mr WHERE mr.id = ahr.target)
diff --git a/Open-ILS/src/sql/Pg/upgrade/0414.schema.call-number-upd-ins-trigger.sql b/Open-ILS/src/sql/Pg/upgrade/0414.schema.call-number-upd-ins-trigger.sql
new file mode 100644 (file)
index 0000000..1149898
--- /dev/null
@@ -0,0 +1,13 @@
+BEGIN;
+
+-- Adding a trigger.  Upgrade # 0364 created the trigger function but not
+-- the trigger itself.  However the base install script 040.schema.asset.sql
+-- creates both the function and the trigger.
+
+INSERT INTO config.upgrade_log (version) VALUES ('0414'); -- Scott McKellar
+
+CREATE TRIGGER asset_label_sortkey_trigger
+    BEFORE UPDATE OR INSERT ON asset.call_number
+    FOR EACH ROW EXECUTE PROCEDURE asset.label_normalizer();
+
+COMMIT;
diff --git a/Open-ILS/src/sql/Pg/upgrade/0415.schema.rename-field-class-fkey.sql b/Open-ILS/src/sql/Pg/upgrade/0415.schema.rename-field-class-fkey.sql
new file mode 100644 (file)
index 0000000..81ceaaf
--- /dev/null
@@ -0,0 +1,22 @@
+-- Dropping and recreating a foreign key constraint for config.metabib_field,
+-- in order to change its name.  WHen this foreign key was first introduced,
+-- the upgrade script gave it one name and the base install script gave it
+-- a different name.  Here we bring the names into sync.
+
+BEGIN;
+
+INSERT INTO config.upgrade_log (version) VALUES ('0415'); -- Scott McKellar
+
+\qecho Dropping and recreating a foreign key in order to change its name.
+\qecho If the DROP fails because the constraint doesn't exist under the old
+\qecho name, or the ADD fails because it already exists under the new name,
+\qecho then ignore the failure.
+
+ALTER TABLE config.metabib_field
+       DROP CONSTRAINT field_class_fkey;
+
+ALTER TABLE config.metabib_field
+       ADD CONSTRAINT metabib_field_field_class_fkey
+       FOREIGN KEY (field_class) REFERENCES config.metabib_class(name);
+
+COMMIT;
diff --git a/Open-ILS/src/sql/Pg/upgrade/0416.schema.rename-recuring-idx.sql b/Open-ILS/src/sql/Pg/upgrade/0416.schema.rename-recuring-idx.sql
new file mode 100644 (file)
index 0000000..dde360e
--- /dev/null
@@ -0,0 +1,11 @@
+INSERT INTO config.upgrade_log (version) VALUES ('0416'); -- Scott McKellar
+
+\qecho No transaction.  Renaming two indexes to correct spelling.
+\qecho If either change fails, then the index was probably created
+\qecho correctly in the first place; ignore the failure.
+
+ALTER INDEX config.rule_recuring_fine_name_key
+       RENAME TO rule_recurring_fine_name_key;
+
+ALTER INDEX config.rule_recuring_fine_pkey
+       RENAME TO rule_recurring_fine_pkey;
diff --git a/Open-ILS/src/sql/Pg/upgrade/0417.schema.acq.drop-lineitem-item-count.sql b/Open-ILS/src/sql/Pg/upgrade/0417.schema.acq.drop-lineitem-item-count.sql
new file mode 100644 (file)
index 0000000..aadf5fa
--- /dev/null
@@ -0,0 +1,23 @@
+-- Drop the never-used column item_count from acq.lineitem.
+-- Drop it also from the associated history table, and rebuild
+-- the function that maintains it.  Finally, rebuild the
+-- associated lifecycle view.  
+
+-- Apply to trunk only; this column never existed in 2.0.
+
+BEGIN;
+
+INSERT INTO config.upgrade_log (version) VALUES ('0417'); -- Scott McKellar
+
+-- Have to drop the view first, because it's a dependent
+DROP VIEW IF EXISTS acq.acq_lineitem_lifecycle;
+
+ALTER TABLE acq.lineitem DROP COLUMN item_count;
+
+ALTER TABLE acq.acq_lineitem_history DROP COLUMN item_count;
+
+SELECT acq.create_acq_func( 'acq', 'lineitem' );
+
+SELECT acq.create_acq_lifecycle( 'acq', 'lineitem' );
+
+COMMIT;
diff --git a/Open-ILS/src/sql/Pg/upgrade/0418.function.action.fix-purge-circ.sql b/Open-ILS/src/sql/Pg/upgrade/0418.function.action.fix-purge-circ.sql
new file mode 100644 (file)
index 0000000..6cf5374
--- /dev/null
@@ -0,0 +1,99 @@
+-- Apply in an update script some fixes that were previously applied only
+-- to the base installation script 090.schema.action.sql.
+
+-- Also fix a typo: INTEVAL -> INTERVAL
+
+BEGIN;
+
+INSERT INTO config.upgrade_log (version) VALUES ('0418'); -- Scott McKellar
+
+CREATE OR REPLACE FUNCTION action.purge_circulations () RETURNS INT AS $func$
+DECLARE
+    usr_keep_age    actor.usr_setting%ROWTYPE;
+    usr_keep_start  actor.usr_setting%ROWTYPE;
+    org_keep_age    INTERVAL;
+    org_keep_count  INT;
+
+    keep_age        INTERVAL;
+
+    target_acp      RECORD;
+    circ_chain_head action.circulation%ROWTYPE;
+    circ_chain_tail action.circulation%ROWTYPE;
+
+    purge_position  INT;
+    count_purged    INT;
+BEGIN
+
+    count_purged := 0;
+
+    SELECT value::INTERVAL INTO org_keep_age FROM config.global_flag WHERE name = 'history.circ.retention_age' AND enabled;
+
+    SELECT value::INT INTO org_keep_count FROM config.global_flag WHERE name = 'history.circ.retention_count' AND enabled;
+    IF org_keep_count IS NULL THEN
+        RETURN count_purged; -- Gimme a count to keep, or I keep them all, forever
+    END IF;
+
+    -- First, find copies with more than keep_count non-renewal circs
+    FOR target_acp IN
+        SELECT  target_copy,
+                COUNT(*) AS total_real_circs
+          FROM  action.circulation
+          WHERE parent_circ IS NULL
+                AND xact_finish IS NOT NULL
+          GROUP BY target_copy
+          HAVING COUNT(*) > org_keep_count
+    LOOP
+        purge_position := 0;
+        -- And, for those, select circs that are finished and older than keep_age
+        FOR circ_chain_head IN
+            SELECT  *
+              FROM  action.circulation
+              WHERE target_copy = target_acp.target_copy
+                    AND parent_circ IS NULL
+              ORDER BY xact_start
+        LOOP
+
+            -- Stop once we've purged enough circs to hit org_keep_count
+            EXIT WHEN target_acp.total_real_circs - purge_position <= org_keep_count;
+
+            SELECT * INTO circ_chain_tail FROM action.circ_chain(circ_chain_head.id) ORDER BY xact_start DESC LIMIT 1;
+            EXIT WHEN circ_chain_tail.xact_finish IS NULL;
+
+            -- Now get the user settings, if any, to block purging if the user wants to keep more circs
+            usr_keep_age.value := NULL;
+            SELECT * INTO usr_keep_age FROM actor.usr_setting WHERE usr = circ_chain_head.usr AND name = 'history.circ.retention_age';
+
+            usr_keep_start.value := NULL;
+            SELECT * INTO usr_keep_start FROM actor.usr_setting WHERE usr = circ_chain_head.usr AND name = 'history.circ.retention_start';
+
+            IF usr_keep_age.value IS NOT NULL AND usr_keep_start.value IS NOT NULL THEN
+                IF oils_json_to_text(usr_keep_age.value)::INTERVAL > AGE(NOW(), oils_json_to_text(usr_keep_start.value)::TIMESTAMPTZ) THEN
+                    keep_age := AGE(NOW(), oils_json_to_text(usr_keep_start.value)::TIMESTAMPTZ);
+                ELSE
+                    keep_age := oils_json_to_text(usr_keep_age.value)::INTERVAL;
+                END IF;
+            ELSIF usr_keep_start.value IS NOT NULL THEN
+                keep_age := AGE(NOW(), oils_json_to_text(usr_keep_start.value)::TIMESTAMPTZ);
+            ELSE
+                keep_age := COALESCE( org_keep_age::INTERVAL, '2000 years'::INTERVAL );
+            END IF;
+
+            EXIT WHEN AGE(NOW(), circ_chain_tail.xact_finish) < keep_age;
+
+            -- We've passed the purging tests, purge the circ chain starting at the end
+            DELETE FROM action.circulation WHERE id = circ_chain_tail.id;
+            WHILE circ_chain_tail.parent_circ IS NOT NULL LOOP
+                SELECT * INTO circ_chain_tail FROM action.circulation WHERE id = circ_chain_tail.parent_circ;
+                DELETE FROM action.circulation WHERE id = circ_chain_tail.id;
+            END LOOP;
+
+            count_purged := count_purged + 1;
+            purge_position := purge_position + 1;
+
+        END LOOP;
+    END LOOP;
+END;
+$func$ LANGUAGE PLPGSQL;
+
+
+COMMIT;
diff --git a/Open-ILS/src/sql/Pg/upgrade/0419.schema.hold_record_view.sql b/Open-ILS/src/sql/Pg/upgrade/0419.schema.hold_record_view.sql
new file mode 100644 (file)
index 0000000..350dc78
--- /dev/null
@@ -0,0 +1,23 @@
+BEGIN;
+
+INSERT INTO config.upgrade_log (version) VALUES ('0419'); -- miker
+
+CREATE OR REPLACE VIEW reporter.hold_request_record AS
+SELECT  id,
+    target,
+    hold_type,
+    CASE
+        WHEN hold_type = 'T'
+            THEN target
+        WHEN hold_type = 'I'
+            THEN (SELECT ssub.record_entry FROM serial.subscription ssub JOIN serial.issuance si ON (si.subscription = ssub.id) WHERE si.id = ahr.target)
+        WHEN hold_type = 'V'
+            THEN (SELECT cn.record FROM asset.call_number cn WHERE cn.id = ahr.target)
+        WHEN hold_type IN ('C','R','F')
+            THEN (SELECT cn.record FROM asset.call_number cn JOIN asset.copy cp ON (cn.id = cp.call_number) WHERE cp.id = ahr.target)
+        WHEN hold_type = 'M'
+            THEN (SELECT mr.master_record FROM metabib.metarecord mr WHERE mr.id = ahr.target)
+    END AS bib_record
+  FROM  action.hold_request ahr;
+
+COMMIT;
diff --git a/Open-ILS/src/sql/Pg/upgrade/0420.schema.premunge_dates.sql b/Open-ILS/src/sql/Pg/upgrade/0420.schema.premunge_dates.sql
new file mode 100644 (file)
index 0000000..9914950
--- /dev/null
@@ -0,0 +1,40 @@
+BEGIN;
+
+INSERT INTO config.upgrade_log (version) VALUES ('0420'); -- miker
+
+CREATE OR REPLACE FUNCTION metabib.reingest_metabib_rec_descriptor( bib_id BIGINT ) RETURNS VOID AS $func$
+BEGIN
+    PERFORM * FROM config.internal_flag WHERE name = 'ingest.assume_inserts_only' AND enabled;
+    IF NOT FOUND THEN
+        DELETE FROM metabib.rec_descriptor WHERE record = bib_id;
+    END IF;
+    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)
+        SELECT  bib_id,
+                biblio.marc21_extract_fixed_field( bib_id, 'Type' ),
+                biblio.marc21_extract_fixed_field( bib_id, 'Form' ),
+                biblio.marc21_extract_fixed_field( bib_id, 'BLvl' ),
+                biblio.marc21_extract_fixed_field( bib_id, 'Ctrl' ),
+                biblio.marc21_extract_fixed_field( bib_id, 'ELvl' ),
+                biblio.marc21_extract_fixed_field( bib_id, 'Audn' ),
+                biblio.marc21_extract_fixed_field( bib_id, 'LitF' ),
+                biblio.marc21_extract_fixed_field( bib_id, 'TMat' ),
+                biblio.marc21_extract_fixed_field( bib_id, 'Desc' ),
+                biblio.marc21_extract_fixed_field( bib_id, 'DtSt' ),
+                biblio.marc21_extract_fixed_field( bib_id, 'Lang' ),
+                (   SELECT  v.value
+                      FROM  biblio.marc21_physical_characteristics( bib_id) p
+                            JOIN config.marc21_physical_characteristic_subfield_map s ON (s.id = p.subfield)
+                            JOIN config.marc21_physical_characteristic_value_map v ON (v.id = p.value)
+                      WHERE p.ptype = 'v' AND s.subfield = 'e'    ),
+                LPAD(NULLIF(REGEXP_REPLACE(NULLIF(biblio.marc21_extract_fixed_field( bib_id, 'Date1'), ''), E'\\D', '0', 'g')::INT,0)::TEXT,4,'0'),
+                LPAD(NULLIF(REGEXP_REPLACE(NULLIF(biblio.marc21_extract_fixed_field( bib_id, 'Date2'), ''), E'\\D', '9', 'g')::INT,9999)::TEXT,4,'0');
+
+    RETURN;
+END;
+$func$ LANGUAGE PLPGSQL;
+
+UPDATE  metabib.rec_descriptor
+  SET   date1=LPAD(NULLIF(REGEXP_REPLACE(NULLIF(date1, ''), E'\\D', '0', 'g')::INT,0)::TEXT,4,'0'),
+        date2=LPAD(NULLIF(REGEXP_REPLACE(NULLIF(date2, ''), E'\\D', '9', 'g')::INT,9999)::TEXT,4,'0');
+
+COMMIT;
diff --git a/Open-ILS/src/sql/Pg/upgrade/0421.schema.embiggen-ints.sql b/Open-ILS/src/sql/Pg/upgrade/0421.schema.embiggen-ints.sql
new file mode 100644 (file)
index 0000000..092ccc5
--- /dev/null
@@ -0,0 +1,44 @@
+-- 1. Turn some ints into bigints.
+
+-- 2. Rename a constraint for consistency and accuracy (currently it may
+-- have either of two different names).
+
+\qecho One of the following DROPs will fail, so we do them
+\qecho both outside of a transaction.  Ignore the failure.
+
+ALTER TABLE booking.resource_type
+       DROP CONSTRAINT brt_name_or_record_once_per_owner;
+
+ALTER TABLE booking.resource_type
+       DROP CONSTRAINT brt_name_once_per_owner;
+
+BEGIN;
+
+INSERT INTO config.upgrade_log (version) VALUES ('0421'); -- Scott McKellar
+
+ALTER TABLE booking.resource_type
+       ALTER COLUMN record SET DATA TYPE bigint,
+       ADD CONSTRAINT brt_name_and_record_once_per_owner UNIQUE(owner, name, record);
+
+ALTER TABLE container.biblio_record_entry_bucket_item
+       ALTER COLUMN target_biblio_record_entry SET DATA TYPE bigint;
+
+-- Before we can embiggen the next one, we must drop a view
+-- that depends on it (and recreate it later)
+
+DROP VIEW IF EXISTS acq.acq_lineitem_lifecycle;
+
+ALTER TABLE acq.lineitem
+       ALTER COLUMN eg_bib_id SET DATA TYPE bigint;
+
+-- Recreate the view
+
+SELECT acq.create_acq_lifecycle( 'acq', 'lineitem' );
+
+ALTER TABLE vandelay.queued_bib_record
+       ALTER COLUMN imported_as SET DATA TYPE bigint;
+
+ALTER TABLE action.hold_copy_map
+       ALTER COLUMN id SET DATA TYPE bigint;
+
+COMMIT;
diff --git a/Open-ILS/src/sql/Pg/upgrade/0422.schema.acq.lineitem-history-bigint.sql b/Open-ILS/src/sql/Pg/upgrade/0422.schema.acq.lineitem-history-bigint.sql
new file mode 100644 (file)
index 0000000..4b9bc17
--- /dev/null
@@ -0,0 +1,15 @@
+BEGIN;
+
+-- Turn an int into a bigint in acq.acq_lineitem_history, following up on
+-- a similar change to acq.lineitem
+
+INSERT INTO config.upgrade_log (version) VALUES ('0422'); -- Scott McKellar
+
+DROP VIEW IF EXISTS acq.acq_lineitem_lifecycle;
+
+ALTER TABLE acq.acq_lineitem_history
+       ALTER COLUMN eg_bib_id SET DATA TYPE bigint;
+
+SELECT acq.create_acq_lifecycle( 'acq', 'lineitem' );
+
+COMMIT;
diff --git a/Open-ILS/src/sql/Pg/upgrade/0423.schema.support-null-function-in-xpath_table.sql b/Open-ILS/src/sql/Pg/upgrade/0423.schema.support-null-function-in-xpath_table.sql
new file mode 100644 (file)
index 0000000..1d90b44
--- /dev/null
@@ -0,0 +1,67 @@
+BEGIN;
+
+INSERT INTO config.upgrade_log (version) VALUES ('0423'); --miker
+
+CREATE OR REPLACE FUNCTION oils_xpath_table ( key TEXT, document_field TEXT, relation_name TEXT, xpaths TEXT, criteria TEXT ) RETURNS SETOF RECORD AS $func$
+DECLARE
+    xpath_list  TEXT[];
+    select_list TEXT[];
+    where_list  TEXT[];
+    q           TEXT;
+    out_record  RECORD;
+    empty_test  RECORD;
+BEGIN
+    xpath_list := STRING_TO_ARRAY( xpaths, '|' );
+    select_list := ARRAY_APPEND( select_list, key || '::INT AS key' );
+    FOR i IN 1 .. ARRAY_UPPER(xpath_list,1) LOOP
+        IF xpath_list[i] = 'null()' THEN
+            select_list := ARRAY_APPEND( select_list, 'NULL::TEXT AS c_' || i );
+        ELSE
+            select_list := ARRAY_APPEND(
+                select_list,
+                $sel$
+                EXPLODE_ARRAY(
+                    COALESCE(
+                        NULLIF(
+                            oils_xpath(
+                                $sel$ ||
+                                    quote_literal(
+                                        CASE
+                                            WHEN xpath_list[i] ~ $re$/[^/[]*@[^/]+$$re$ OR xpath_list[i] ~ $re$text\(\)$$re$ THEN xpath_list[i]
+                                            ELSE xpath_list[i] || '//text()'
+                                        END
+                                    ) ||
+                                $sel$,
+                                $sel$ || document_field || $sel$
+                            ),
+                           '{}'::TEXT[]
+                        ),
+                        '{NULL}'::TEXT[]
+                    )
+                ) AS c_$sel$ || i
+            );
+            where_list := ARRAY_APPEND(
+                where_list,
+                'c_' || i || ' IS NOT NULL'
+            );
+        END IF;
+    END LOOP;
+    q := $q$
+SELECT * FROM (
+    SELECT $q$ || ARRAY_TO_STRING( select_list, ', ' ) || $q$ FROM $q$ || relation_name || $q$ WHERE ($q$ || criteria || $q$)
+)x WHERE $q$ || ARRAY_TO_STRING( where_list, ' AND ' );
+    -- RAISE NOTICE 'query: %', q;
+    FOR out_record IN EXECUTE q LOOP
+        RETURN NEXT out_record;
+    END LOOP;
+    RETURN;
+END;
+$func$ LANGUAGE PLPGSQL IMMUTABLE;
+
+COMMIT;
+
diff --git a/Open-ILS/src/sql/Pg/upgrade/0424.schema.circ_due_date_trigger.sql b/Open-ILS/src/sql/Pg/upgrade/0424.schema.circ_due_date_trigger.sql
new file mode 100644 (file)
index 0000000..6d2a8a0
--- /dev/null
@@ -0,0 +1,9 @@
+BEGIN;
+
+INSERT INTO config.upgrade_log (version) VALUES ('0424'); -- dbs
+
+DROP TRIGGER push_due_date_tgr ON action.circulation;
+
+CREATE TRIGGER push_due_date_tgr BEFORE INSERT OR UPDATE ON action.circulation FOR EACH ROW EXECUTE PROCEDURE action.push_circ_due_time();
+
+COMMIT;
diff --git a/Open-ILS/src/sql/Pg/upgrade/0425.schema.perm-grp-tree-hold-priority.sql b/Open-ILS/src/sql/Pg/upgrade/0425.schema.perm-grp-tree-hold-priority.sql
new file mode 100644 (file)
index 0000000..e556b0a
--- /dev/null
@@ -0,0 +1,8 @@
+BEGIN;
+
+INSERT INTO config.upgrade_log (version) VALUES ('0425'); -- Scott McKellar
+
+ALTER TABLE permission.grp_tree
+       ADD COLUMN hold_priority INT NOT NULL DEFAULT 0;
+
+COMMIT;
diff --git a/Open-ILS/src/sql/Pg/upgrade/0426.data.perm-list.misc-acq.sql b/Open-ILS/src/sql/Pg/upgrade/0426.data.perm-list.misc-acq.sql
new file mode 100644 (file)
index 0000000..b9ba2f1
--- /dev/null
@@ -0,0 +1,16 @@
+BEGIN;
+
+INSERT INTO config.upgrade_log (version) VALUES ('0426'); -- senator
+
+INSERT INTO permission.perm_list VALUES
+    (402, 'ADMIN_ACQ_DISTRIB_FORMULA', oils_i18n_gettext(402, 'Create/update/delete distribution formulae', 'ppl', 'description'))
+    ,(403, 'ADMIN_ACQ_CLAIM', oils_i18n_gettext(403, 'Create/update/delete acquisitions claims', 'ppl', 'description'))
+    ,(404, 'ADMIN_ACQ_CLAIM_EVENT_TYPE', oils_i18n_gettext(404, 'Create/update/delete acquisitions claim event types', 'ppl', 'description'))
+    ,(405, 'ADMIN_ACQ_CLAIM_TYPE', oils_i18n_gettext(405, 'Create/update/delete acquisitions claim types', 'ppl', 'description'))
+    ,(406, 'ADMIN_ACQ_FISCAL_YEAR', oils_i18n_gettext(406, 'Create/update/delete acquisitions fiscal years', 'ppl', 'description'))
+    ,(407, 'ADMIN_ACQ_FUND_ALLOCATION_PERCENT', oils_i18n_gettext(407, 'Create/update/delete acquisitions fund allocation percentages', 'ppl', 'description'))
+    ,(408, 'ADMIN_ACQ_FUND_TAG', oils_i18n_gettext(408, 'Create/update/delete acquisitions fund tags', 'ppl', 'description'))
+    ,(409, 'ADMIN_ACQ_LINEITEM_ALERT_TEXT', oils_i18n_gettext(409, 'Create/update/delete acquisitions lineitem alert text', 'ppl', 'description'))
+;
+
+COMMIT;
diff --git a/Open-ILS/src/sql/Pg/upgrade/0427.schema.hold_matrix_root_ou.sql b/Open-ILS/src/sql/Pg/upgrade/0427.schema.hold_matrix_root_ou.sql
new file mode 100644 (file)
index 0000000..822c0ca
--- /dev/null
@@ -0,0 +1,120 @@
+BEGIN;
+
+INSERT INTO config.upgrade_log (version) VALUES ('0427'); -- gmc
+
+CREATE OR REPLACE FUNCTION action.find_hold_matrix_matchpoint( pickup_ou INT, request_ou INT, match_item BIGINT, match_user INT, match_requestor INT ) RETURNS INT AS $func$
+DECLARE
+    current_requestor_group    permission.grp_tree%ROWTYPE;
+    requestor_object    actor.usr%ROWTYPE;
+    user_object        actor.usr%ROWTYPE;
+    item_object        asset.copy%ROWTYPE;
+    item_cn_object        asset.call_number%ROWTYPE;
+    rec_descriptor        metabib.rec_descriptor%ROWTYPE;
+    current_mp_weight    FLOAT;
+    matchpoint_weight    FLOAT;
+    tmp_weight        FLOAT;
+    current_mp        config.hold_matrix_matchpoint%ROWTYPE;
+    matchpoint        config.hold_matrix_matchpoint%ROWTYPE;
+BEGIN
+    SELECT INTO user_object * FROM actor.usr WHERE id = match_user;
+    SELECT INTO requestor_object * FROM actor.usr WHERE id = match_requestor;
+    SELECT INTO item_object * FROM asset.copy WHERE id = match_item;
+    SELECT INTO item_cn_object * FROM asset.call_number WHERE id = item_object.call_number;
+    SELECT INTO rec_descriptor r.* FROM metabib.rec_descriptor r WHERE r.record = item_cn_object.record;
+
+    PERFORM * FROM config.internal_flag WHERE name = 'circ.holds.usr_not_requestor' AND enabled;
+
+    IF NOT FOUND THEN
+        SELECT INTO current_requestor_group * FROM permission.grp_tree WHERE id = requestor_object.profile;
+    ELSE
+        SELECT INTO current_requestor_group * FROM permission.grp_tree WHERE id = user_object.profile;
+    END IF;
+
+    LOOP 
+        -- for each potential matchpoint for this ou and group ...
+        FOR current_mp IN
+            SELECT    m.*
+              FROM    config.hold_matrix_matchpoint m
+              WHERE    m.requestor_grp = current_requestor_group.id AND m.active
+              ORDER BY    CASE WHEN m.circ_modifier    IS NOT NULL THEN 16 ELSE 0 END +
+                    CASE WHEN m.juvenile_flag    IS NOT NULL THEN 16 ELSE 0 END +
+                    CASE WHEN m.marc_type        IS NOT NULL THEN 8 ELSE 0 END +
+                    CASE WHEN m.marc_form        IS NOT NULL THEN 4 ELSE 0 END +
+                    CASE WHEN m.marc_vr_format    IS NOT NULL THEN 2 ELSE 0 END +
+                    CASE WHEN m.ref_flag        IS NOT NULL THEN 1 ELSE 0 END DESC LOOP
+
+            current_mp_weight := 5.0;
+
+            IF current_mp.circ_modifier IS NOT NULL THEN
+                CONTINUE WHEN current_mp.circ_modifier <> item_object.circ_modifier OR item_object.circ_modifier IS NULL;
+            END IF;
+
+            IF current_mp.marc_type IS NOT NULL THEN
+                IF item_object.circ_as_type IS NOT NULL THEN
+                    CONTINUE WHEN current_mp.marc_type <> item_object.circ_as_type;
+                ELSE
+                    CONTINUE WHEN current_mp.marc_type <> rec_descriptor.item_type;
+                END IF;
+            END IF;
+
+            IF current_mp.marc_form IS NOT NULL THEN
+                CONTINUE WHEN current_mp.marc_form <> rec_descriptor.item_form;
+            END IF;
+
+            IF current_mp.marc_vr_format IS NOT NULL THEN
+                CONTINUE WHEN current_mp.marc_vr_format <> rec_descriptor.vr_format;
+            END IF;
+
+            IF current_mp.juvenile_flag IS NOT NULL THEN
+                CONTINUE WHEN current_mp.juvenile_flag <> user_object.juvenile;
+            END IF;
+
+            IF current_mp.ref_flag IS NOT NULL THEN
+                CONTINUE WHEN current_mp.ref_flag <> item_object.ref;
+            END IF;
+
+
+            -- caclulate the rule match weight
+            IF current_mp.item_owning_ou IS NOT NULL THEN
+                SELECT INTO tmp_weight 1.0 / (actor.org_unit_proximity(current_mp.item_owning_ou, item_cn_object.owning_lib)::FLOAT + 1.0)::FLOAT;
+                current_mp_weight := current_mp_weight - tmp_weight;
+            END IF; 
+
+            IF current_mp.item_circ_ou IS NOT NULL THEN
+                SELECT INTO tmp_weight 1.0 / (actor.org_unit_proximity(current_mp.item_circ_ou, item_object.circ_lib)::FLOAT + 1.0)::FLOAT;
+                current_mp_weight := current_mp_weight - tmp_weight;
+            END IF; 
+
+            IF current_mp.pickup_ou IS NOT NULL THEN
+                SELECT INTO tmp_weight 1.0 / (actor.org_unit_proximity(current_mp.pickup_ou, pickup_ou)::FLOAT + 1.0)::FLOAT;
+                current_mp_weight := current_mp_weight - tmp_weight;
+            END IF; 
+
+            IF current_mp.request_ou IS NOT NULL THEN
+                SELECT INTO tmp_weight 1.0 / (actor.org_unit_proximity(current_mp.request_ou, request_ou)::FLOAT + 1.0)::FLOAT;
+                current_mp_weight := current_mp_weight - tmp_weight;
+            END IF; 
+
+            IF current_mp.user_home_ou IS NOT NULL THEN
+                SELECT INTO tmp_weight 1.0 / (actor.org_unit_proximity(current_mp.user_home_ou, user_object.home_ou)::FLOAT + 1.0)::FLOAT;
+                current_mp_weight := current_mp_weight - tmp_weight;
+            END IF; 
+
+            -- set the matchpoint if we found the best one
+            IF matchpoint_weight IS NULL OR matchpoint_weight > current_mp_weight THEN
+                matchpoint = current_mp;
+                matchpoint_weight = current_mp_weight;
+            END IF;
+
+        END LOOP;
+
+        EXIT WHEN current_requestor_group.parent IS NULL OR matchpoint.id IS NOT NULL;
+
+        SELECT INTO current_requestor_group * FROM permission.grp_tree WHERE id = current_requestor_group.parent;
+    END LOOP;
+
+    RETURN matchpoint.id;
+END;
+$func$ LANGUAGE plpgsql;
+
+COMMIT;
diff --git a/Open-ILS/src/sql/Pg/upgrade/0428.schema.hold_retarget.sql b/Open-ILS/src/sql/Pg/upgrade/0428.schema.hold_retarget.sql
new file mode 100644 (file)
index 0000000..5033911
--- /dev/null
@@ -0,0 +1,193 @@
+BEGIN;
+
+INSERT INTO config.upgrade_log (version) VALUES ('0428'); -- miker
+
+CREATE OR REPLACE FUNCTION action.hold_request_permit_test( pickup_ou INT, request_ou INT, match_item BIGINT, match_user INT, match_requestor INT, retargetting BOOL ) RETURNS SETOF action.matrix_test_result AS $func$
+DECLARE
+    matchpoint_id        INT;
+    user_object        actor.usr%ROWTYPE;
+    age_protect_object    config.rule_age_hold_protect%ROWTYPE;
+    standing_penalty    config.standing_penalty%ROWTYPE;
+    transit_range_ou_type    actor.org_unit_type%ROWTYPE;
+    transit_source        actor.org_unit%ROWTYPE;
+    item_object        asset.copy%ROWTYPE;
+    ou_skip              actor.org_unit_setting%ROWTYPE;
+    result            action.matrix_test_result;
+    hold_test        config.hold_matrix_matchpoint%ROWTYPE;
+    hold_count        INT;
+    hold_transit_prox    INT;
+    frozen_hold_count    INT;
+    context_org_list    INT[];
+    done            BOOL := FALSE;
+BEGIN
+    SELECT INTO user_object * FROM actor.usr WHERE id = match_user;
+    SELECT INTO context_org_list ARRAY_ACCUM(id) FROM actor.org_unit_full_path( pickup_ou );
+
+    result.success := TRUE;
+
+    -- Fail if we couldn't find a user
+    IF user_object.id IS NULL THEN
+        result.fail_part := 'no_user';
+        result.success := FALSE;
+        done := TRUE;
+        RETURN NEXT result;
+        RETURN;
+    END IF;
+
+    SELECT INTO item_object * FROM asset.copy WHERE id = match_item;
+
+    -- Fail if we couldn't find a copy
+    IF item_object.id IS NULL THEN
+        result.fail_part := 'no_item';
+        result.success := FALSE;
+        done := TRUE;
+        RETURN NEXT result;
+        RETURN;
+    END IF;
+
+    SELECT INTO matchpoint_id action.find_hold_matrix_matchpoint(pickup_ou, request_ou, match_item, match_user, match_requestor);
+    result.matchpoint := matchpoint_id;
+
+    SELECT INTO ou_skip * FROM actor.org_unit_setting WHERE name = 'circ.holds.target_skip_me' AND org_unit = item_object.circ_lib;
+
+    -- Fail if the circ_lib for the item has circ.holds.target_skip_me set to true
+    IF ou_skip.id IS NOT NULL AND ou_skip.value = 'true' THEN
+        result.fail_part := 'circ.holds.target_skip_me';
+        result.success := FALSE;
+        done := TRUE;
+        RETURN NEXT result;
+        RETURN;
+    END IF;
+
+    -- Fail if user is barred
+    IF user_object.barred IS TRUE THEN
+        result.fail_part := 'actor.usr.barred';
+        result.success := FALSE;
+        done := TRUE;
+        RETURN NEXT result;
+        RETURN;
+    END IF;
+
+    -- Fail if we couldn't find any matchpoint (requires a default)
+    IF matchpoint_id IS NULL THEN
+        result.fail_part := 'no_matchpoint';
+        result.success := FALSE;
+        done := TRUE;
+        RETURN NEXT result;
+        RETURN;
+    END IF;
+
+    SELECT INTO hold_test * FROM config.hold_matrix_matchpoint WHERE id = matchpoint_id;
+
+    IF hold_test.holdable IS FALSE THEN
+        result.fail_part := 'config.hold_matrix_test.holdable';
+        result.success := FALSE;
+        done := TRUE;
+        RETURN NEXT result;
+    END IF;
+
+    IF hold_test.transit_range IS NOT NULL THEN
+        SELECT INTO transit_range_ou_type * FROM actor.org_unit_type WHERE id = hold_test.transit_range;
+        IF hold_test.distance_is_from_owner THEN
+            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;
+        ELSE
+            SELECT INTO transit_source * FROM actor.org_unit WHERE id = item_object.circ_lib;
+        END IF;
+
+        PERFORM * FROM actor.org_unit_descendants( transit_source.id, transit_range_ou_type.depth ) WHERE id = pickup_ou;
+
+        IF NOT FOUND THEN
+            result.fail_part := 'transit_range';
+            result.success := FALSE;
+            done := TRUE;
+            RETURN NEXT result;
+        END IF;
+    END IF;
+    IF NOT retargetting THEN
+        FOR standing_penalty IN
+            SELECT  DISTINCT csp.*
+              FROM  actor.usr_standing_penalty usp
+                    JOIN config.standing_penalty csp ON (csp.id = usp.standing_penalty)
+              WHERE usr = match_user
+                    AND usp.org_unit IN ( SELECT * FROM explode_array(context_org_list) )
+                    AND (usp.stop_date IS NULL or usp.stop_date > NOW())
+                    AND csp.block_list LIKE '%HOLD%' LOOP
+    
+            result.fail_part := standing_penalty.name;
+            result.success := FALSE;
+            done := TRUE;
+            RETURN NEXT result;
+        END LOOP;
+    
+        IF hold_test.stop_blocked_user IS TRUE THEN
+            FOR standing_penalty IN
+                SELECT  DISTINCT csp.*
+                  FROM  actor.usr_standing_penalty usp
+                        JOIN config.standing_penalty csp ON (csp.id = usp.standing_penalty)
+                  WHERE usr = match_user
+                        AND usp.org_unit IN ( SELECT * FROM explode_array(context_org_list) )
+                        AND (usp.stop_date IS NULL or usp.stop_date > NOW())
+                        AND csp.block_list LIKE '%CIRC%' LOOP
+        
+                result.fail_part := standing_penalty.name;
+                result.success := FALSE;
+                done := TRUE;
+                RETURN NEXT result;
+            END LOOP;
+        END IF;
+    
+        IF hold_test.max_holds IS NOT NULL THEN
+            SELECT    INTO hold_count COUNT(*)
+              FROM    action.hold_request
+              WHERE    usr = match_user
+                AND fulfillment_time IS NULL
+                AND cancel_time IS NULL
+                AND CASE WHEN hold_test.include_frozen_holds THEN TRUE ELSE frozen IS FALSE END;
+    
+            IF hold_count >= hold_test.max_holds THEN
+                result.fail_part := 'config.hold_matrix_test.max_holds';
+                result.success := FALSE;
+                done := TRUE;
+                RETURN NEXT result;
+            END IF;
+        END IF;
+    END IF;
+
+    IF item_object.age_protect IS NOT NULL THEN
+        SELECT INTO age_protect_object * FROM config.rule_age_hold_protect WHERE id = item_object.age_protect;
+
+        IF item_object.create_date + age_protect_object.age > NOW() THEN
+            IF hold_test.distance_is_from_owner THEN
+                SELECT INTO hold_transit_prox prox FROM actor.org_unit_proximity WHERE from_org = item_cn_object.owning_lib AND to_org = pickup_ou;
+            ELSE
+                SELECT INTO hold_transit_prox prox FROM actor.org_unit_proximity WHERE from_org = item_object.circ_lib AND to_org = pickup_ou;
+            END IF;
+
+            IF hold_transit_prox > age_protect_object.prox THEN
+                result.fail_part := 'config.rule_age_hold_protect.prox';
+                result.success := FALSE;
+                done := TRUE;
+                RETURN NEXT result;
+            END IF;
+        END IF;
+    END IF;
+
+    IF NOT done THEN
+        RETURN NEXT result;
+    END IF;
+
+    RETURN;
+END;
+$func$ LANGUAGE plpgsql;
+
+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$
+    SELECT * FROM action.hold_request_permit_test( $1, $2, $3, $4, $5, FALSE );
+$func$ LANGUAGE SQL;
+
+CREATE OR REPLACE FUNCTION action.hold_retarget_permit_test( pickup_ou INT, request_ou INT, match_item BIGINT, match_user INT, match_requestor INT ) RETURNS SETOF action.matrix_test_result AS $func$
+    SELECT * FROM action.hold_request_permit_test( $1, $2, $3, $4, $5, TRUE );
+$func$ LANGUAGE SQL;
+
+COMMIT;
+
diff --git a/Open-ILS/src/sql/Pg/upgrade/0429.noop-because-miker-skipped-it.sql b/Open-ILS/src/sql/Pg/upgrade/0429.noop-because-miker-skipped-it.sql
new file mode 100644 (file)
index 0000000..44d294c
--- /dev/null
@@ -0,0 +1 @@
+INSERT INTO config.upgrade_log (version) VALUES ('0429'); -- miker, for shame
diff --git a/Open-ILS/src/sql/Pg/upgrade/0430.schema.strict_ou_test.sql b/Open-ILS/src/sql/Pg/upgrade/0430.schema.strict_ou_test.sql
new file mode 100644 (file)
index 0000000..a423e05
--- /dev/null
@@ -0,0 +1,143 @@
+BEGIN;
+
+INSERT INTO config.upgrade_log (version) VALUES ('0430'); -- miker
+
+ALTER TABLE config.hold_matrix_matchpoint ADD COLUMN strict_ou_match BOOL NOT NULL DEFAULT FALSE;
+
+CREATE OR REPLACE FUNCTION action.find_hold_matrix_matchpoint( pickup_ou INT, request_ou INT, match_item BIGINT, match_user INT, match_requestor INT ) RETURNS INT AS $func$
+DECLARE
+    current_requestor_group    permission.grp_tree%ROWTYPE;
+    requestor_object    actor.usr%ROWTYPE;
+    user_object        actor.usr%ROWTYPE;
+    item_object        asset.copy%ROWTYPE;
+    item_cn_object        asset.call_number%ROWTYPE;
+    rec_descriptor        metabib.rec_descriptor%ROWTYPE;
+    current_mp_weight    FLOAT;
+    matchpoint_weight    FLOAT;
+    tmp_weight        FLOAT;
+    current_mp        config.hold_matrix_matchpoint%ROWTYPE;
+    matchpoint        config.hold_matrix_matchpoint%ROWTYPE;
+BEGIN
+    SELECT INTO user_object * FROM actor.usr WHERE id = match_user;
+    SELECT INTO requestor_object * FROM actor.usr WHERE id = match_requestor;
+    SELECT INTO item_object * FROM asset.copy WHERE id = match_item;
+    SELECT INTO item_cn_object * FROM asset.call_number WHERE id = item_object.call_number;
+    SELECT INTO rec_descriptor r.* FROM metabib.rec_descriptor r WHERE r.record = item_cn_object.record;
+
+    PERFORM * FROM config.internal_flag WHERE name = 'circ.holds.usr_not_requestor' AND enabled;
+
+    IF NOT FOUND THEN
+        SELECT INTO current_requestor_group * FROM permission.grp_tree WHERE id = requestor_object.profile;
+    ELSE
+        SELECT INTO current_requestor_group * FROM permission.grp_tree WHERE id = user_object.profile;
+    END IF;
+
+    LOOP 
+        -- for each potential matchpoint for this ou and group ...
+        FOR current_mp IN
+            SELECT    m.*
+              FROM    config.hold_matrix_matchpoint m
+              WHERE    m.requestor_grp = current_requestor_group.id AND m.active
+              ORDER BY    CASE WHEN m.circ_modifier    IS NOT NULL THEN 16 ELSE 0 END +
+                    CASE WHEN m.juvenile_flag    IS NOT NULL THEN 16 ELSE 0 END +
+                    CASE WHEN m.marc_type        IS NOT NULL THEN 8 ELSE 0 END +
+                    CASE WHEN m.marc_form        IS NOT NULL THEN 4 ELSE 0 END +
+                    CASE WHEN m.marc_vr_format    IS NOT NULL THEN 2 ELSE 0 END +
+                    CASE WHEN m.ref_flag        IS NOT NULL THEN 1 ELSE 0 END DESC LOOP
+
+            current_mp_weight := 5.0;
+
+            IF current_mp.circ_modifier IS NOT NULL THEN
+                CONTINUE WHEN current_mp.circ_modifier <> item_object.circ_modifier OR item_object.circ_modifier IS NULL;
+            END IF;
+
+            IF current_mp.marc_type IS NOT NULL THEN
+                IF item_object.circ_as_type IS NOT NULL THEN
+                    CONTINUE WHEN current_mp.marc_type <> item_object.circ_as_type;
+                ELSE
+                    CONTINUE WHEN current_mp.marc_type <> rec_descriptor.item_type;
+                END IF;
+            END IF;
+
+            IF current_mp.marc_form IS NOT NULL THEN
+                CONTINUE WHEN current_mp.marc_form <> rec_descriptor.item_form;
+            END IF;
+
+            IF current_mp.marc_vr_format IS NOT NULL THEN
+                CONTINUE WHEN current_mp.marc_vr_format <> rec_descriptor.vr_format;
+            END IF;
+
+            IF current_mp.juvenile_flag IS NOT NULL THEN
+                CONTINUE WHEN current_mp.juvenile_flag <> user_object.juvenile;
+            END IF;
+
+            IF current_mp.ref_flag IS NOT NULL THEN
+                CONTINUE WHEN current_mp.ref_flag <> item_object.ref;
+            END IF;
+
+
+            -- caclulate the rule match weight
+            IF current_mp.item_owning_ou IS NOT NULL THEN
+                IF NOT current_mp.strict_ou_match THEN
+                    SELECT INTO tmp_weight 1.0 / (actor.org_unit_proximity(current_mp.item_owning_ou, item_cn_object.owning_lib)::FLOAT + 1.0)::FLOAT;
+                ELSE
+                    tmp_weight := CASE WHEN current_mp.item_owning_ou = item_cn_object.owning_lib THEN 1.0 ELSE 0.0 END;
+                END IF;
+                current_mp_weight := current_mp_weight - tmp_weight;
+            END IF; 
+
+            IF current_mp.item_circ_ou IS NOT NULL THEN
+                IF NOT current_mp.strict_ou_match THEN
+                    SELECT INTO tmp_weight 1.0 / (actor.org_unit_proximity(current_mp.item_circ_ou, item_object.circ_lib)::FLOAT + 1.0)::FLOAT;
+                ELSE
+                    tmp_weight := CASE WHEN current_mp.item_circ_ou = item_object.circ_lib THEN 1.0 ELSE 0.0 END;
+                END IF;
+                current_mp_weight := current_mp_weight - tmp_weight;
+            END IF; 
+
+            IF current_mp.pickup_ou IS NOT NULL THEN
+                IF NOT current_mp.strict_ou_match THEN
+                    SELECT INTO tmp_weight 1.0 / (actor.org_unit_proximity(current_mp.pickup_ou, pickup_ou)::FLOAT + 1.0)::FLOAT;
+                ELSE
+                    tmp_weight := CASE WHEN current_mp.pickup_ou = pickiup_ou THEN 1.0 ELSE 0.0 END;
+                END IF;
+                current_mp_weight := current_mp_weight - tmp_weight;
+            END IF; 
+
+            IF current_mp.request_ou IS NOT NULL THEN
+                IF NOT current_mp.strict_ou_match THEN
+                    SELECT INTO tmp_weight 1.0 / (actor.org_unit_proximity(current_mp.request_ou, request_ou)::FLOAT + 1.0)::FLOAT;
+                ELSE
+                    tmp_weight := CASE WHEN current_mp.request_ou = request_ou THEN 1.0 ELSE 0.0 END;
+                END IF;
+                current_mp_weight := current_mp_weight - tmp_weight;
+            END IF; 
+
+            IF current_mp.user_home_ou IS NOT NULL THEN
+                IF NOT current_mp.strict_ou_match THEN
+                    SELECT INTO tmp_weight 1.0 / (actor.org_unit_proximity(current_mp.user_home_ou, user_object.home_ou)::FLOAT + 1.0)::FLOAT;
+                ELSE
+                    tmp_weight := CASE WHEN current_mp.user_home_ou = user_object.home_ou THEN 1.0 ELSE 0.0 END;
+                END IF;
+                current_mp_weight := current_mp_weight - tmp_weight;
+            END IF; 
+
+            -- set the matchpoint if we found the best one
+            IF matchpoint_weight IS NULL OR matchpoint_weight > current_mp_weight THEN
+                matchpoint = current_mp;
+                matchpoint_weight = current_mp_weight;
+            END IF;
+
+        END LOOP;
+
+        EXIT WHEN current_requestor_group.parent IS NULL OR matchpoint.id IS NOT NULL;
+
+        SELECT INTO current_requestor_group * FROM permission.grp_tree WHERE id = current_requestor_group.parent;
+    END LOOP;
+
+    RETURN matchpoint.id;
+END;
+$func$ LANGUAGE plpgsql;
+
+COMMIT;
+
diff --git a/Open-ILS/src/sql/Pg/upgrade/0431.schema.hold_retarget.sql b/Open-ILS/src/sql/Pg/upgrade/0431.schema.hold_retarget.sql
new file mode 100644 (file)
index 0000000..d0ded3a
--- /dev/null
@@ -0,0 +1,183 @@
+BEGIN;
+
+INSERT INTO config.upgrade_log (version) VALUES ('0431'); -- miker
+
+CREATE OR REPLACE FUNCTION action.hold_request_permit_test( pickup_ou INT, request_ou INT, match_item BIGINT, match_user INT, match_requestor INT, retargetting BOOL ) RETURNS SETOF action.matrix_test_result AS $func$
+DECLARE
+    matchpoint_id        INT;
+    user_object        actor.usr%ROWTYPE;
+    age_protect_object    config.rule_age_hold_protect%ROWTYPE;
+    standing_penalty    config.standing_penalty%ROWTYPE;
+    transit_range_ou_type    actor.org_unit_type%ROWTYPE;
+    transit_source        actor.org_unit%ROWTYPE;
+    item_object        asset.copy%ROWTYPE;
+    ou_skip              actor.org_unit_setting%ROWTYPE;
+    result            action.matrix_test_result;
+    hold_test        config.hold_matrix_matchpoint%ROWTYPE;
+    hold_count        INT;
+    hold_transit_prox    INT;
+    frozen_hold_count    INT;
+    context_org_list    INT[];
+    done            BOOL := FALSE;
+BEGIN
+    SELECT INTO user_object * FROM actor.usr WHERE id = match_user;
+    SELECT INTO context_org_list ARRAY_ACCUM(id) FROM actor.org_unit_full_path( pickup_ou );
+
+    result.success := TRUE;
+
+    -- Fail if we couldn't find a user
+    IF user_object.id IS NULL THEN
+        result.fail_part := 'no_user';
+        result.success := FALSE;
+        done := TRUE;
+        RETURN NEXT result;
+        RETURN;
+    END IF;
+
+    SELECT INTO item_object * FROM asset.copy WHERE id = match_item;
+
+    -- Fail if we couldn't find a copy
+    IF item_object.id IS NULL THEN
+        result.fail_part := 'no_item';
+        result.success := FALSE;
+        done := TRUE;
+        RETURN NEXT result;
+        RETURN;
+    END IF;
+
+    SELECT INTO matchpoint_id action.find_hold_matrix_matchpoint(pickup_ou, request_ou, match_item, match_user, match_requestor);
+    result.matchpoint := matchpoint_id;
+
+    SELECT INTO ou_skip * FROM actor.org_unit_setting WHERE name = 'circ.holds.target_skip_me' AND org_unit = item_object.circ_lib;
+
+    -- Fail if the circ_lib for the item has circ.holds.target_skip_me set to true
+    IF ou_skip.id IS NOT NULL AND ou_skip.value = 'true' THEN
+        result.fail_part := 'circ.holds.target_skip_me';
+        result.success := FALSE;
+        done := TRUE;
+        RETURN NEXT result;
+        RETURN;
+    END IF;
+
+    -- Fail if user is barred
+    IF user_object.barred IS TRUE THEN
+        result.fail_part := 'actor.usr.barred';
+        result.success := FALSE;
+        done := TRUE;
+        RETURN NEXT result;
+        RETURN;
+    END IF;
+
+    -- Fail if we couldn't find any matchpoint (requires a default)
+    IF matchpoint_id IS NULL THEN
+        result.fail_part := 'no_matchpoint';
+        result.success := FALSE;
+        done := TRUE;
+        RETURN NEXT result;
+        RETURN;
+    END IF;
+
+    SELECT INTO hold_test * FROM config.hold_matrix_matchpoint WHERE id = matchpoint_id;
+
+    IF hold_test.holdable IS FALSE THEN
+        result.fail_part := 'config.hold_matrix_test.holdable';
+        result.success := FALSE;
+        done := TRUE;
+        RETURN NEXT result;
+    END IF;
+
+    IF hold_test.transit_range IS NOT NULL THEN
+        SELECT INTO transit_range_ou_type * FROM actor.org_unit_type WHERE id = hold_test.transit_range;
+        IF hold_test.distance_is_from_owner THEN
+            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;
+        ELSE
+            SELECT INTO transit_source * FROM actor.org_unit WHERE id = item_object.circ_lib;
+        END IF;
+
+        PERFORM * FROM actor.org_unit_descendants( transit_source.id, transit_range_ou_type.depth ) WHERE id = pickup_ou;
+
+        IF NOT FOUND THEN
+            result.fail_part := 'transit_range';
+            result.success := FALSE;
+            done := TRUE;
+            RETURN NEXT result;
+        END IF;
+    END IF;
+    FOR standing_penalty IN
+        SELECT  DISTINCT csp.*
+          FROM  actor.usr_standing_penalty usp
+                JOIN config.standing_penalty csp ON (csp.id = usp.standing_penalty)
+          WHERE usr = match_user
+                AND usp.org_unit IN ( SELECT * FROM explode_array(context_org_list) )
+                AND (usp.stop_date IS NULL or usp.stop_date > NOW())
+                AND csp.block_list LIKE '%HOLD%' LOOP
+
+        result.fail_part := standing_penalty.name;
+        result.success := FALSE;
+        done := TRUE;
+        RETURN NEXT result;
+    END LOOP;
+
+    IF hold_test.stop_blocked_user IS TRUE THEN
+        FOR standing_penalty IN
+            SELECT  DISTINCT csp.*
+              FROM  actor.usr_standing_penalty usp
+                    JOIN config.standing_penalty csp ON (csp.id = usp.standing_penalty)
+              WHERE usr = match_user
+                    AND usp.org_unit IN ( SELECT * FROM explode_array(context_org_list) )
+                    AND (usp.stop_date IS NULL or usp.stop_date > NOW())
+                    AND csp.block_list LIKE '%CIRC%' LOOP
+    
+            result.fail_part := standing_penalty.name;
+            result.success := FALSE;
+            done := TRUE;
+            RETURN NEXT result;
+        END LOOP;
+    END IF;
+
+    IF hold_test.max_holds IS NOT NULL AND NOT retargetting THEN
+        SELECT    INTO hold_count COUNT(*)
+          FROM    action.hold_request
+          WHERE    usr = match_user
+            AND fulfillment_time IS NULL
+            AND cancel_time IS NULL
+            AND CASE WHEN hold_test.include_frozen_holds THEN TRUE ELSE frozen IS FALSE END;
+
+        IF hold_count >= hold_test.max_holds THEN
+            result.fail_part := 'config.hold_matrix_test.max_holds';
+            result.success := FALSE;
+            done := TRUE;
+            RETURN NEXT result;
+        END IF;
+    END IF;
+
+    IF item_object.age_protect IS NOT NULL THEN
+        SELECT INTO age_protect_object * FROM config.rule_age_hold_protect WHERE id = item_object.age_protect;
+
+        IF item_object.create_date + age_protect_object.age > NOW() THEN
+            IF hold_test.distance_is_from_owner THEN
+                SELECT INTO hold_transit_prox prox FROM actor.org_unit_proximity WHERE from_org = item_cn_object.owning_lib AND to_org = pickup_ou;
+            ELSE
+                SELECT INTO hold_transit_prox prox FROM actor.org_unit_proximity WHERE from_org = item_object.circ_lib AND to_org = pickup_ou;
+            END IF;
+
+            IF hold_transit_prox > age_protect_object.prox THEN
+                result.fail_part := 'config.rule_age_hold_protect.prox';
+                result.success := FALSE;
+                done := TRUE;
+                RETURN NEXT result;
+            END IF;
+        END IF;
+    END IF;
+
+    IF NOT done THEN
+        RETURN NEXT result;
+    END IF;
+
+    RETURN;
+END;
+$func$ LANGUAGE plpgsql;
+
+COMMIT;
+
diff --git a/Open-ILS/src/sql/Pg/upgrade/0432.schema.config_hard_due_date.sql b/Open-ILS/src/sql/Pg/upgrade/0432.schema.config_hard_due_date.sql
new file mode 100644 (file)
index 0000000..bf13e9f
--- /dev/null
@@ -0,0 +1,16 @@
+BEGIN;
+
+INSERT INTO config.upgrade_log (version) VALUES ('0432'); -- Scott McKellar
+
+ALTER TABLE config.rule_circ_duration
+       ADD COLUMN date_ceiling TIMESTAMPTZ;
+
+CREATE TABLE config.hard_due_date (
+       id              SERIAL      PRIMARY KEY,
+       duration_rule   INT         NOT NULL REFERENCES config.rule_circ_duration (id)
+                                   DEFERRABLE INITIALLY DEFERRED,
+       ceiling_date    TIMESTAMPTZ NOT NULL,
+       active_date     TIMESTAMPTZ NOT NULL
+);
+
+COMMIT;
diff --git a/Open-ILS/src/sql/Pg/upgrade/0433.edi_orders_template.sql b/Open-ILS/src/sql/Pg/upgrade/0433.edi_orders_template.sql
new file mode 100644 (file)
index 0000000..0aec927
--- /dev/null
@@ -0,0 +1,91 @@
+BEGIN;
+
+INSERT INTO config.upgrade_log (version) VALUES ('0433'); -- atz
+
+UPDATE action_trigger.event_definition SET delay='00:00:00', template=$$
+[%- USE date -%]
+[%# start JEDI document 
+  # Vendor specific kludges:
+  # BT      - vendcode goes to NAD/BY *suffix*  w/ 91 qualifier
+  # INGRAM  - vendcode goes to NAD/BY *segment* w/ 91 qualifier (separately)
+  # BRODART - vendcode goes to FTX segment (lineitem level)
+-%]
+[%- 
+IF target.provider.edi_default.vendcode && target.provider.code == 'BRODART';
+    xtra_ftx = target.provider.edi_default.vendcode;
+END;
+-%]
+[%- BLOCK big_block -%]
+{
+   "recipient":"[% target.provider.san %]",
+   "sender":"[% target.ordering_agency.mailing_address.san %]",
+   "body": [{
+     "ORDERS":[ "order", {
+        "po_number":[% target.id %],
+        "date":"[% date.format(date.now, '%Y%m%d') %]",
+        "buyer":[
+            [%   IF   target.provider.edi_default.vendcode && (target.provider.code == 'BT' || target.provider.name.match('(?i)^BAKER & TAYLOR'))  -%]
+                {"id-qualifier": 91, "id":"[% target.ordering_agency.mailing_address.san _ ' ' _ target.provider.edi_default.vendcode %]"}
+            [%- ELSIF target.provider.edi_default.vendcode && target.provider.code == 'INGRAM' -%]
+                {"id":"[% target.ordering_agency.mailing_address.san %]"},
+                {"id-qualifier": 91, "id":"[% target.provider.edi_default.vendcode %]"}
+            [%- ELSE -%]
+                {"id":"[% target.ordering_agency.mailing_address.san %]"}
+            [%- END -%]
+        ],
+        "vendor":[
+            [%- # target.provider.name (target.provider.id) -%]
+            "[% target.provider.san %]",
+            {"id-qualifier": 92, "id":"[% target.provider.id %]"}
+        ],
+        "currency":"[% target.provider.currency_type %]",
+                
+        "items":[
+        [%- FOR li IN target.lineitems %]
+        {
+            "line_index":"[% li.id %]",
+            "identifiers":[   [%-# li.isbns = helpers.get_li_isbns(li.attributes) %]
+            [% FOR isbn IN helpers.get_li_isbns(li.attributes) -%]
+                [% IF isbn.length == 13 -%]
+                {"id-qualifier":"EN","id":"[% isbn %]"},
+                [% ELSE -%]
+                {"id-qualifier":"IB","id":"[% isbn %]"},
+                [%- END %]
+            [% END %]
+                {"id-qualifier":"IN","id":"[% li.id %]"}
+            ],
+            "price":[% li.estimated_unit_price || '0.00' %],
+            "desc":[
+                {"BTI":"[% helpers.get_li_attr('title',     '', li.attributes) %]"},
+                {"BPU":"[% helpers.get_li_attr('publisher', '', li.attributes) %]"},
+                {"BPD":"[% helpers.get_li_attr('pubdate',   '', li.attributes) %]"},
+                {"BPH":"[% helpers.get_li_attr('pagination','', li.attributes) %]"}
+            ],
+            [%- ftx_vals = []; 
+                FOR note IN li.lineitem_notes; 
+                    NEXT UNLESS note.vendor_public == 't'; 
+                    ftx_vals.push(note.value); 
+                END; 
+                IF xtra_ftx;           ftx_vals.unshift(xtra_ftx); END; 
+                IF ftx_vals.size == 0; ftx_vals.unshift('');       END;  # BT needs FTX+LIN for every LI, even if it is an empty one
+            -%]
+
+            "free-text":[ 
+                [% FOR note IN ftx_vals -%] "[% note %]"[% UNLESS loop.last %], [% END %][% END %] 
+            ],            
+            "quantity":[% li.lineitem_details.size %]
+        }[% UNLESS loop.last %],[% END %]
+        [%-# TODO: lineitem details (later) -%]
+        [% END %]
+        ],
+        "line_items":[% target.lineitems.size %]
+     }]  [%# close ORDERS array %]
+   }]    [%# close  body  array %]
+}
+[% END %]
+[% tempo = PROCESS big_block; helpers.escape_json(tempo) %]
+
+$$ 
+WHERE id=23;
+
+COMMIT;
diff --git a/Open-ILS/src/sql/Pg/upgrade/0434.data.merge_template_container_type.sql b/Open-ILS/src/sql/Pg/upgrade/0434.data.merge_template_container_type.sql
new file mode 100644 (file)
index 0000000..1a0fd0f
--- /dev/null
@@ -0,0 +1,9 @@
+
+BEGIN;
+
+INSERT INTO config.upgrade_log (version) VALUES ('0434');
+
+INSERT INTO container.biblio_record_entry_bucket_type (code,label) VALUES ('template_merge','Template Merge Container');
+
+COMMIT;
+
diff --git a/Open-ILS/src/sql/Pg/upgrade/0435.schema.template-add-field.sql b/Open-ILS/src/sql/Pg/upgrade/0435.schema.template-add-field.sql
new file mode 100644 (file)
index 0000000..8ed4344
--- /dev/null
@@ -0,0 +1,78 @@
+
+BEGIN;
+
+INSERT INTO config.upgrade_log (version) VALUES ('0435'); -- miker
+
+CREATE OR REPLACE FUNCTION vandelay.add_field ( target_xml TEXT, source_xml TEXT, field TEXT ) RETURNS TEXT AS $_$
+
+    use MARC::Record;
+    use MARC::File::XML (BinaryEncoding => 'UTF-8');
+    use strict;
+
+    my $target_xml = shift;
+    my $source_xml = shift;
+    my $field_spec = shift;
+
+    my $target_r = MARC::Record->new_from_xml( $target_xml );
+    my $source_r = MARC::Record->new_from_xml( $source_xml );
+
+    return $target_xml unless ($target_r && $source_r);
+
+    my @field_list = split(',', $field_spec);
+
+    my %fields;
+    for my $f (@field_list) {
+        $f =~ s/^\s*//; $f =~ s/\s*$//;
+        if ($f =~ /^(.{3})(\w*)(?:\[([^]]*)\])?$/) {
+            my $field = $1;
+            $field =~ s/\s+//;
+            my $sf = $2;
+            $sf =~ s/\s+//;
+            my $match = $3;
+            $match =~ s/^\s*//; $match =~ s/\s*$//;
+            $fields{$field} = { sf => [ split('', $sf) ] };
+            if ($match) {
+                my ($msf,$mre) = split('~', $match);
+                if (length($msf) > 0 and length($mre) > 0) {
+                    $msf =~ s/^\s*//; $msf =~ s/\s*$//;
+                    $mre =~ s/^\s*//; $mre =~ s/\s*$//;
+                    $fields{$field}{match} = { sf => $msf, re => qr/$mre/ };
+                }
+            }
+        }
+    }
+
+    for my $f ( keys %fields) {
+        if ( @{$fields{$f}{sf}} ) {
+            for my $from_field ($source_r->field( $f )) {
+                my @tos = $target_r->field( $f );
+                if (!@tos) {
+                    my @new_fields = map { $_->clone } $source_r->field( $f );
+                    $target_r->insert_fields_ordered( @new_fields );
+                } else {
+                    for my $to_field (@tos) {
+                        if (exists($fields{$f}{match})) {
+                            next unless (grep { $_ =~ $fields{$f}{match}{re} } $to_field->subfield($fields{$f}{match}{sf}));
+                        }
+                        my @new_sf = map { ($_ => $from_field->subfield($_)) } @{$fields{$f}{sf}};
+                        $to_field->add_subfields( @new_sf );
+                    }
+                }
+            }
+        } else {
+            my @new_fields = map { $_->clone } $source_r->field( $f );
+            $target_r->insert_fields_ordered( @new_fields );
+        }
+    }
+
+    $target_xml = $target_r->as_xml_record;
+    $target_xml =~ s/^<\?.+?\?>$//mo;
+    $target_xml =~ s/\n//sgo;
+    $target_xml =~ s/>\s+</></sgo;
+
+    return $target_xml;
+
+$_$ LANGUAGE PLPERLU;
+
+COMMIT;
+
diff --git a/Open-ILS/src/sql/Pg/upgrade/0436.schema.multiple-rules.sql b/Open-ILS/src/sql/Pg/upgrade/0436.schema.multiple-rules.sql
new file mode 100644 (file)
index 0000000..6d02c40
--- /dev/null
@@ -0,0 +1,50 @@
+BEGIN;
+
+INSERT INTO config.upgrade_log (version) VALUES ('0436'); -- miker
+
+CREATE OR REPLACE FUNCTION vandelay.compile_profile ( incoming_xml TEXT ) RETURNS vandelay.compile_profile AS $_$
+DECLARE
+    output              vandelay.compile_profile%ROWTYPE;
+    profile             vandelay.merge_profile%ROWTYPE;
+    profile_tmpl        TEXT;
+    profile_tmpl_owner  TEXT;
+    add_rule            TEXT := '';
+    strip_rule          TEXT := '';
+    replace_rule        TEXT := '';
+    preserve_rule       TEXT := '';
+
+BEGIN
+
+    profile_tmpl := (oils_xpath('//*[@tag="905"]/*[@code="t"]/text()',incoming_xml))[1];
+    profile_tmpl_owner := (oils_xpath('//*[@tag="905"]/*[@code="o"]/text()',incoming_xml))[1];
+
+    IF profile_tmpl IS NOT NULL AND profile_tmpl <> '' AND profile_tmpl_owner IS NOT NULL AND profile_tmpl_owner <> '' THEN
+        SELECT  p.* INTO profile
+          FROM  vandelay.merge_profile p
+                JOIN actor.org_unit u ON (u.id = p.owner)
+          WHERE p.name = profile_tmpl
+                AND u.shortname = profile_tmpl_owner;
+
+        IF profile.id IS NOT NULL THEN
+            add_rule := COALESCE(profile.add_spec,'');
+            strip_rule := COALESCE(profile.strip_spec,'');
+            replace_rule := COALESCE(profile.replace_spec,'');
+            preserve_rule := COALESCE(profile.preserve_spec,'');
+        END IF;
+    END IF;
+
+    add_rule := add_rule || ',' || COALESCE(ARRAY_TO_STRING(oils_xpath('//*[@tag="905"]/*[@code="a"]/text()',incoming_xml),','),'');
+    strip_rule := strip_rule || ',' || COALESCE(ARRAY_TO_STRING(oils_xpath('//*[@tag="905"]/*[@code="d"]/text()',incoming_xml),','),'');
+    replace_rule := replace_rule || ',' || COALESCE(ARRAY_TO_STRING(oils_xpath('//*[@tag="905"]/*[@code="r"]/text()',incoming_xml),','),'');
+    preserve_rule := preserve_rule || ',' || COALESCE(ARRAY_TO_STRING(oils_xpath('//*[@tag="905"]/*[@code="p"]/text()',incoming_xml),','),'');
+
+    output.add_rule := BTRIM(add_rule,',');
+    output.replace_rule := BTRIM(replace_rule,',');
+    output.strip_rule := BTRIM(strip_rule,',');
+    output.preserve_rule := BTRIM(preserve_rule,',');
+
+    RETURN output;
+END;
+$_$ LANGUAGE PLPGSQL;
+
+COMMIT;
diff --git a/Open-ILS/src/sql/Pg/upgrade/0437.schema.bytea-index-label_sortkey.sql b/Open-ILS/src/sql/Pg/upgrade/0437.schema.bytea-index-label_sortkey.sql
new file mode 100644 (file)
index 0000000..5400166
--- /dev/null
@@ -0,0 +1,9 @@
+
+BEGIN;
+
+INSERT INTO config.upgrade_log (version) VALUES ('0437'); -- miker
+
+DROP INDEX asset.asset_call_number_label_sortkey;
+CREATE INDEX asset_call_number_label_sortkey ON asset.call_number(cast(label_sortkey as bytea));
+
+COMMIT;
diff --git a/Open-ILS/src/sql/Pg/upgrade/0438.schema.bytea-index-label.sql b/Open-ILS/src/sql/Pg/upgrade/0438.schema.bytea-index-label.sql
new file mode 100644 (file)
index 0000000..0ba3194
--- /dev/null
@@ -0,0 +1,9 @@
+
+BEGIN;
+
+INSERT INTO config.upgrade_log (version) VALUES ('0438'); -- miker
+
+DROP INDEX asset.asset_call_number_upper_label_id_owning_lib_idx;
+CREATE INDEX asset_call_number_upper_label_id_owning_lib_idx ON asset.call_number (cast(upper(label) as bytea),id,owning_lib);
+
+COMMIT;
diff --git a/Open-ILS/src/sql/Pg/upgrade/0439.schema.function-bytea-index-label.sql b/Open-ILS/src/sql/Pg/upgrade/0439.schema.function-bytea-index-label.sql
new file mode 100644 (file)
index 0000000..ae850cd
--- /dev/null
@@ -0,0 +1,17 @@
+
+BEGIN;
+
+INSERT INTO config.upgrade_log (version) VALUES ('0439'); -- miker
+
+CREATE OR REPLACE FUNCTION oils_text_as_bytea (TEXT) RETURNS BYTEA AS $_$
+    SELECT CAST(REGEXP_REPLACE($1, $$\\$$, $$\\\\$$, 'g') AS BYTEA);
+$_$ LANGUAGE SQL IMMUTABLE;
+
+
+DROP INDEX asset.asset_call_number_upper_label_id_owning_lib_idx;
+CREATE INDEX asset_call_number_upper_label_id_owning_lib_idx ON asset.call_number (oils_text_as_bytea(label),id,owning_lib);
+
+DROP INDEX asset.asset_call_number_label_sortkey;
+CREATE INDEX asset_call_number_label_sortkey ON asset.call_number(oils_text_as_bytea(label_sortkey));
+
+COMMIT;
index 8013e57..0225518 100755 (executable)
@@ -37,6 +37,7 @@ my $opt_verbose;
 my $opt_hooks;
 my $opt_process_hooks = 0;
 my $opt_granularity   = undef;
+my $opt_gran_only     = undef;
 
 (-f $opt_custom_filter) or undef($opt_custom_filter);   # discard default if no file exists
 
@@ -46,6 +47,7 @@ GetOptions(
     'run-pending'      => \$opt_run_pending,
     'hooks=s'          => \$opt_hooks,
     'granularity=s'    => \$opt_granularity,
+    'granularity-only' => \$opt_gran_only,
     'process-hooks'    => \$opt_process_hooks,
     'debug-stdout'     => \$opt_debug_stdout,
     'custom-filters=s' => \$opt_custom_filter,
@@ -56,6 +58,11 @@ GetOptions(
 
 my $max_sleep = $opt_max_sleep;
 
+#XXX need to figure out why this is required...
+$opt_gran_only = $opt_granularity ? 1 : 0;
+
+$opt_lockfile .= '.' . $opt_granularity if ($opt_granularity && $opt_gran_only);
+
 # typical passive hook filters
 my $hook_handlers = {
 
@@ -109,6 +116,9 @@ $0 : Create and process action/trigger events
     --granularity=<label>
         Run events with {label} granularity setting, or no granularity setting
 
+    --granularity-only
+        Used in combination with --granularity, prevents the running of events with no granularity setting
+
     --debug-stdout
         Print server responses to stdout (as JSON) for debugging
 
@@ -169,10 +179,22 @@ sub process_hooks {
 
 sub run_pending {
     $opt_verbose and print "run_pending: " .
-        ($opt_run_pending ? ($opt_granularity || 'ALL granularity') : 'SKIPPING') . "\n";
+        ($opt_run_pending ?
+            ($opt_granularity ?
+                ( $opt_granularity . (
+                    $opt_gran_only ?
+                        ' ONLY' : 
+                        ' and NON-GRANULAR'
+                    )
+                ) :
+                'NON-GRANULAR'
+            ) :
+            'SKIPPING'
+        ) . "\n";
+
     return unless $opt_run_pending;
     my $ses = OpenSRF::AppSession->create('open-ils.trigger');
-    my $req = $ses->request('open-ils.trigger.event.run_all_pending' => $opt_granularity);
+    my $req = $ses->request('open-ils.trigger.event.run_all_pending' => $opt_granularity => $opt_gran_only);
 
     my $check_lockfile = 1;
     while (my $resp = $req->recv(timeout => $req_timeout)) {
@@ -192,7 +214,7 @@ help() and exit unless ($opt_run_pending or $opt_process_hooks);
 
 # check the lockfile
 if (-e $opt_lockfile) {
-    die "I'm already running with lockfile $opt_lockfile\n" if (!$opt_process_hooks);
+    die "I'm already running with lockfile $opt_lockfile\n" unless ($opt_process_hooks or $opt_granularity);
     # sleeping loop if we're in --process-hooks mode
     while ($max_sleep >= 0 && sleep(1)) {
         last unless ( -e $opt_lockfile ); 
index 3339f4e..9d5f168 100755 (executable)
@@ -140,8 +140,6 @@ print "\ndone\n";
 
 __END__
 
-=pod
-
 =head1 NAME
 
 edi_fetcher.pl - A script for retrieving and processing EDI files from remote accounts.
@@ -159,8 +157,8 @@ the environmental variable FTP_PASSIVE like:
 
 =head1 OPTIONS
 
-    --account=[id]  Target one account, whether or not it is inactive.
-    --inactive      Includes inactive provider accounts (default OFF, forced ON if --account specified)
+  --account=[id]  Target one account, whether or not it is inactive.
+  --inactive      Includes inactive provider accounts (default OFF, forced ON if --account specified)
 
 =head1 ARGUMENTS
 
index 4d1338b..80e4e19 100755 (executable)
@@ -32,6 +32,7 @@ INIT {
 
 my %defaults = (
     'quiet' => 0,
+    'test'  => 0,   # TODO
     'max-batch-size=i' => -1
 );
 
@@ -48,8 +49,8 @@ my $defs = $e->search_action_trigger_event_definition({
 
 $opts->{verbose} = 0 if $opts->{quiet};
 
+print "FTP_PASSIVE is ", ($ENV{FTP_PASSIVE} ? "ON" : "OFF"),  "\n";
 
-# print Dumper($defs);
 print "\nHook '$hook' is used in ", scalar(@$defs), " event definition(s):\n";
 
 $Data::Dumper::Indent = 1;
@@ -99,7 +100,10 @@ foreach my $def (@$defs) {
     if ($opts->{verbose}) {
         # $subq->{'select'}->{'acqedim'} = ['id', 'purchase_order', 'message_type', 'status'];
         my $excluded = $e->json_query($subq);
-        print "Excluded: ", scalar(@$excluded), " purchase order(s):\n", Dumper(\@$excluded), "\n";
+        print "Excluded: ", scalar(@$excluded), " purchase order(s):\n";
+        my $z = 0;
+        print map {sprintf "%7d%s", $_, (++$z % 5) ? '' : "\n"} sort {$a <=> $b} map {$_->{purchase_order}} @$excluded;
+        print "\n";
     }
 
     my $events = $e->json_query($query);
@@ -155,6 +159,11 @@ foreach my $def (@$defs) {
 
         print "\ntarget->provider->edi_default->id: ", $target->provider->edi_default->id, "\n";
         my $logstr2 = sprintf "event %s, PO %s, template_output %s", $_->{id}, $message->purchase_order, $event->template_output->id;
+        if ($opts->{test}) {
+            print "Test mode, skipping translation/send\n";
+            next;
+        }
+
         printf "\nNow calling attempt_translation for $logstr2\n\n";
 
         unless (OpenILS::Application::Acq::EDI->attempt_translation($message, 1)) {
@@ -192,3 +201,41 @@ foreach my $def (@$defs) {
 }
 
 print "\ndone\n";
+
+__END__
+
+=head1 NAME
+
+edi_pusher.pl - A script for generating and sending EDI files to remote accounts.
+
+=head1 DESCRIPTION
+
+This script is expected to be run via crontab, for the purpose of retrieving vendor EDI files.
+
+=head1 OPTIONS
+
+  --max-batch-size=i  Limit the processing to a set number of events.
+
+=head1 TODO
+
+More docs here.
+
+=head1 USAGE
+
+B<FTP_PASSIVE=1> is recommended.  Depending on your vendors' and your own network environments, you may want to set/export
+the environmental variable FTP_PASSIVE like:
+
+    export FTP_PASSIVE=1
+    # or
+    FTP_PASSIVE=1 Open-ILS/src/support-scripts/edi_pusher.pl
+
+=head1 SEE ALSO
+
+    OpenILS::Utils::Cronscript
+    edi_fetcher.pl
+
+=head1 AUTHOR
+
+Joe Atzberger <jatzberger@esilibrary.com>
+
+=cut
index 3740e7f..f5164ba 100755 (executable)
@@ -10,6 +10,7 @@ use OpenSRF::Utils::JSON;
 use OpenSRF::Utils::SettingsClient;
 use OpenILS::Application::AppUtils;
 use OpenILS::Utils::Fieldmapper;
+use OpenILS::Utils::CStoreEditor;
 
 use MARC::Record;
 use MARC::File::XML;
@@ -21,11 +22,12 @@ use Getopt::Long;
 
 my @formats = qw/USMARC UNIMARC XML BRE/;
 
-my ($config,$format,$encoding,$location,$dollarsign,$idl,$help,$holdings,$timeout) = ('/openils/conf/opensrf_core.xml','USMARC','MARC8','','$',0,undef,undef,0);
+my ($config,$format,$encoding,$location,$dollarsign,$idl,$help,$holdings,$timeout,$export_mfhd) = ('/openils/conf/opensrf_core.xml','USMARC','MARC8','','$',0,undef,undef,0,undef);
 
 GetOptions(
         'help'       => \$help,
         'items'      => \$holdings,
+        'mfhd'       => \$export_mfhd,
         'location=s' => \$location,
         'money=s'    => \$dollarsign,
         'config=s'   => \$config,
@@ -40,9 +42,11 @@ print <<"HELP";
 Usage: $0 [options]
  --help or -h       This screen.
  --config or -c     Configuration file [/openils/conf/opensrf_core.xml]
- --format or -f     Output format (USMARC, UNIMARC, XML) [USMARC]
+ --format or -f     Output format (USMARC, UNIMARC, XML, BRE) [USMARC]
  --encoding or -e   Output Encoding (UTF-8, ISO-8859-?, MARC8) [MARC8]
  --items or -i      Include items (holdings) in the output
+ --mfhd             Export serial MFHD records for associated bib records
+                    Not compatible with --format=BRE
  --xml-idl or -x    Location of the IDL XML
  --location or -l   MARC Location Code for holdings from
                     http://www.loc.gov/marc/organizations/orgshome.html
@@ -91,6 +95,8 @@ if (!$idl) {
 Fieldmapper->import(IDL => $idl);
 
 my $ses = OpenSRF::AppSession->create('open-ils.cstore');
+OpenILS::Utils::CStoreEditor::init();
+my $editor = OpenILS::Utils::CStoreEditor->new();
 
 print <<HEADER if ($format eq 'XML');
 <?xml version="1.0" encoding="$encoding"?>
@@ -222,6 +228,29 @@ while ( my $i = <> ) {
         import MARC::File::XML; # reset SAX parser so that one bad record doesn't kill the entire export
     };
 
+    if ($export_mfhd) {
+        my $mfhds = $editor->search_serial_record_entry({record => $i, deleted => 'f'});
+        foreach my $mfhd (@$mfhds) {
+            try {
+                my $r = MARC::Record->new_from_xml( $mfhd->marc, $encoding, $format );
+
+                if (uc($format) eq 'XML') {
+                    my $xml = $r->as_xml_record;
+                    $xml =~ s/^<\?.+?\?>$//mo;
+                    print $xml;
+                } elsif (uc($format) eq 'UNIMARC') {
+                    print $r->as_usmarc;
+                } elsif (uc($format) eq 'USMARC') {
+                    print $r->as_usmarc;
+                }
+            } otherwise {
+                my $e = shift;
+                warn "\n$e\n";
+                import MARC::File::XML; # reset SAX parser so that one bad record doesn't kill the entire export
+            };
+        }
+    }
+
     stats() if (! ($count{bib} % 50 ));
 }
 
diff --git a/Open-ILS/src/templates/password-reset/strings.fr-CA b/Open-ILS/src/templates/password-reset/strings.fr-CA
new file mode 100644 (file)
index 0000000..2dad2ac
--- /dev/null
@@ -0,0 +1,15 @@
+BUTTON_SUBMIT=Envoyer
+REQUEST_TITLE=Formulaire de demande de réinitialisation du mot de passe du réseau de la Bibliothèque
+IDENTIFY_YOURSELF=Entrez votre nom d’utilisateur ou votre code Ã  barres pour indiquer votre compte de bibliothèque et demander la réinitialisation de votre mot de passe.
+REQUEST_SUCCESS=Votre nom d’utilisateur ou votre code Ã  barres a Ã©té présenté pour la réinitialisation de votre mot de passe. S’il existe un compte correspondant assorti d’une adresse Ã©lectronique, vous recevrez bientôt Ã  cette adresse un message contenant les instructions pour réinitialiser votre mot de passe.
+BARCODE_PROMPT=Code Ã  barres :
+USERNAME_PROMPT=Nom d’utilisateur :
+EMAIL_PROMPT=Adresse Ã©lectronique liée au compte :
+NO_SESSION=Impossible de trouver la session de réinitialisation du mot de passe demandée.
+NO_MATCH=Les mots de passe ne concordent pas. Veuillez réessayer.
+NOT_ACTIVE=Cette demande de réinitialisation du mot de passe est inactive. Votre mot de passe n’a pas Ã©té réinitialisé.
+NOT_STRONG=Le mot de passe que vous avez choisi n’est pas assez complexe pour protéger votre compte. Votre mot de passe n’a pas Ã©té réinitialisé.
+SUCCESS=Mot de passe réinitialisé.
+TITLE=Réinitialiser le mot de passe du réseau de la Bibliothèque
+PASSWORD_PROMPT=Nouveau mot de passe :
+PASSWORD_PROMPT2=Entrez de nouveau le mot de passe :
index 6950484..f55863b 100644 (file)
                                                }
 
                                                editor_pane_application_perm.setValue( this.store.getValue( current_group, 'application_perm' ) );
+                                               editor_pane_hold_priority.setValue( this.store.getValue( current_group, 'hold_priority' ) );
 
                                                editor_pane_usergroup.setChecked( this.store.getValue( current_group, 'usergroup' ) == 't' ? true : false );
 ]]>
                                                                </td>
                                                        </tr>
                                                        <tr>
+                                                               <th>&conify.grp_tree.hold_priority.label;</th>
+                                                               <td>
+                                                                       <div
+                                                                         id="editor_pane_hold_priority"
+                                                                         dojoType="dijit.form.NumberSpinner"
+                                                                         jsId="editor_pane_hold_priority"
+                                                                       >
+                                                                               <script type="dojo/connect" event="onChange">
+<![CDATA[
+                                                                                       if (current_group && this.getValue()) {
+                                                                                               group_store.setValue( current_group, "hold_priority", this.getValue() );
+                                                                                       }
+]]>
+                                                                               </script>
+                                                                       </div>
+                                                               </td>
+                                                       </tr>
+                                                       <tr>
                                                                <th>&conify.grp_tree.parent_group.label;</th>
                                                                <td>
                                                                        <div
index 7242a89..8783928 100644 (file)
@@ -40,7 +40,7 @@ if(!dojo._hasResource["MARC.Field"]) {
 
         subfield : function (code) {
             var list = dojo.filter( this.subfields, function (s) {
-                if (s[0] == code) return true; return true;
+                if (s[0] == code) return true; return false;
             });
             if (list.length == 1) return list[0];
             return list;
index d2f6b1d..2473798 100644 (file)
@@ -27,7 +27,7 @@ if(!dojo._hasResource["MARC.Record"]) {
 
         constructor : function(kwargs) {
             this.fields = [];
-            this.leader = '';
+            this.leader = '00000cam a2200205Ka 4500';
 
             if (kwargs.delimiter) this.delimiter = kwargs.delimiter;
             if (kwargs.onLoad) this.onLoad = kwargs.onLoad;
@@ -57,7 +57,11 @@ if(!dojo._hasResource["MARC.Record"]) {
             return list;
         },
 
-        subfield : function (spec, code) { return this.field(spec)[0].subfield(code) },
+        subfield : function (spec, code) {
+            var f = this.field(spec);
+            if (dojo.isArray(f)) f = f[0];
+            return f.subfield(code)
+        },
 
         appendFields : function () {
             var me = this;
@@ -151,7 +155,7 @@ if(!dojo._hasResource["MARC.Record"]) {
 
         fromXmlDocument : function (mxml) {
             var me = this;
-            me.leader = dojox.xml.parser.textContent(dojo.query('leader', mxml)[0]) || '';
+            me.leader = dojox.xml.parser.textContent(dojo.query('leader', mxml)[0]) || '00000cam a2200205Ka 4500';
 
             dojo.forEach( dojo.query('controlfield', mxml), function (cf) {
                 me.fields.push(
@@ -240,7 +244,7 @@ if(!dojo._hasResource["MARC.Record"]) {
                     // skip comment lines
                 } else if (isControlField(current_line)) {
                     if (line_tag(current_line) == 'LDR') {
-                        me.leader = cf_line_data(current_line) || '';
+                        me.leader = cf_line_data(current_line) || '00000cam a2200205Ka 4500';
                     } else {
                         me.fields.push(
                             new MARC.Field({
index 790d16a..c9e9c92 100644 (file)
@@ -36,6 +36,9 @@ if(!dojo._hasResource["openils.BibTemplate"]) {
             this.locale = kwargs.locale || OpenSRF.locale || 'en-US';
             this.nodelay = kwargs.delay == false;
 
+            if (this.xml && this.xml instanceof String)
+                this.xml = dojox.xml.parser.parse(this.xml);
+
             this.mode = 'biblio-record_entry';
             this.default_datatype = 'marcxml-uris';
             if (kwargs.metarecord) {
@@ -56,17 +59,16 @@ if(!dojo._hasResource["openils.BibTemplate"]) {
         },
 
         textContent : function (node) {
-            var content = '';
             if (node) {
-                if(window.ActiveXObject) content = node.text;
-                else content = node.textContent;
+                if (node instanceof HTMLElement) return node.innerText || node.textContent;
+                return dojox.xml.parser.textContent(node);
             }
-            return content;
+            return '';
         },
 
         render : function() {
 
-            var all_slots = dojo.query('*[type^=opac/slot-data]', this.root);
+            var all_slots = dojo.query('*[type^="opac/slot-data"]', this.root);
             var default_datatype = this.default_datatype;
         
             var slots = {};
@@ -99,10 +101,10 @@ if(!dojo._hasResource["openils.BibTemplate"]) {
                                 var item_limit = parseInt(slot.getAttribute('limit'));
                                 var item_offset = parseInt(slot.getAttribute('offset')) || 0;
 
-                                var pre_render_callbacks = dojo.query( '*[type=opac/call-back+pre-render]', slot );
-                                var post_render_callbacks = dojo.query( '*[type=opac/call-back+post-render]', slot );
-                                var pre_query_callbacks = dojo.query( '*[type=opac/call-back+pre-query]', slot );
-                                var post_query_callbacks = dojo.query( '*[type=opac/call-back+post-query]', slot );
+                                var pre_render_callbacks = dojo.query( '*[type="opac/call-back+pre-render"]', slot );
+                                var post_render_callbacks = dojo.query( '*[type="opac/call-back+post-render"]', slot );
+                                var pre_query_callbacks = dojo.query( '*[type="opac/call-back+pre-query"]', slot );
+                                var post_query_callbacks = dojo.query( '*[type="opac/call-back+post-query"]', slot );
 
                                 // Do pre-query stuff
                                 dojo.forEach(pre_query_callbacks, function (cb) {
@@ -119,7 +121,7 @@ if(!dojo._hasResource["openils.BibTemplate"]) {
                                     if (item_list.length) item_list = BT.subsetNL(item_list, item_offset, item_offset + item_limit);
                                 }
 
-                                // Do post-query stuff, only if there's an item list!
+                                // Do post-query stuff
                                 dojo.forEach(post_query_callbacks, function (cb) {
                                     try { (new Function( 'item_list', 'BT', 'slotXML', 'slot', unescape(cb.innerHTML) ))(item_list,BT,bib,slot) } catch (e) {/*meh*/}
                                 });
@@ -139,7 +141,7 @@ if(!dojo._hasResource["openils.BibTemplate"]) {
                                     var template_value_count = 0;
 
                                     dojo.query(
-                                        '*[type=opac/template-value]',
+                                        '*[type="opac/template-value"]',
                                         slot
                                     ).orphan().forEach(function(x) {
                                         var name = x.getAttribute('name');
@@ -154,7 +156,7 @@ if(!dojo._hasResource["openils.BibTemplate"]) {
                                     if (template_value_count > 0) slot.innerHTML = dojo.string.substitute( unescape(slot.innerHTML), template_values );
                                 }
 
-                                var handler_node = dojo.query( '*[type=opac/slot-format]', slot )[0];
+                                var handler_node = dojo.query( '*[type="opac/slot-format"]', slot )[0];
                                 if (handler_node) slot_handler = new Function('item_list', 'BT', 'slotXML', 'slot', 'item', dojox.xml.parser.textContent(handler_node) || handler_node.innerHTML);
                                 else slot_handler = new Function('item_list', 'BT', 'slotXML', 'slot', 'item','return dojox.xml.parser.textContent(item) || item.innerHTML;');
 
index 834c6cd..e9f255b 100644 (file)
@@ -26,11 +26,13 @@ if(!dojo._hasResource["openils.PermaCrud"]) {
         session : null,
         authtoken : null,
         connnected : false,
+        authoritative : false,
 
         constructor : function ( kwargs ) {
             kwargs = kwargs || {};
 
             this.authtoken = kwargs.authtoken;
+            this.authoritative = kwargs.authoritative;
 
             this.session =
                 kwargs.session ||
@@ -66,7 +68,41 @@ if(!dojo._hasResource["openils.PermaCrud"]) {
                 return false;
             }
         },
-        
+
+        _session_request : function ( args /* hash */, commitOnComplete /* set to true, else no */ ) {
+
+            var me = this;
+            var endstyle = 'rollback';
+            var aopts = dojo.mixin({}, args);
+            args = aopts;
+            if (commitOnComplete) endstyle = 'commit';
+
+            if (me.authoritative) {
+                if (!me.connected) me.connect();
+                if (args.timeout && !args.oncomplete && !args.onresponse) { // pure sync call
+                    args.oncomplete = function (r) {
+                        me.session.request('open-ils.pcrud.transaction.' + endstyle, me.auth());
+                        me.session.disconnect();
+                        me.disconnect();
+                    };
+                } else if (args.oncomplete) { // there's an oncomplete, fire that, and then end the transaction
+                    var orig_oncomplete = args.oncomplete;
+                    args.oncomplete = function (r) {
+                        var ret;
+                        try {
+                            ret = orig_oncomplete(r);
+                        } finally {
+                            me.session.request('open-ils.pcrud.transaction.' + endstyle, me.auth());
+                            me.session.disconnect();
+                            me.disconnect();
+                        }
+                        return ret;
+                    };
+                }
+                me.session.request('open-ils.pcrud.transaction.begin', me.auth());
+            }
+            return me.session.request( args );
+        },
 
         retrieve : function ( fm_class /* Fieldmapper class hint */, id /* Fieldmapper object primary key value */,  opts /* Option hash */) {
             if(!opts) opts = {};
@@ -80,7 +116,7 @@ if(!dojo._hasResource["openils.PermaCrud"]) {
             if (!opts.async && !opts.timeout) req_hash.timeout = 10;
 
             var _pcrud = this;
-            var req = this.session.request( req_hash );
+            var req = this._session_request( req_hash );
 
             if (!req.onerror)
                 req.onerror = function (r) { throw js2JSON(r); };
@@ -129,7 +165,7 @@ if(!dojo._hasResource["openils.PermaCrud"]) {
             if (!opts.async && !opts.timeout) req_hash.timeout = 10;
 
             var _pcrud = this;
-            var req = this.session.request( req_hash );
+            var req = this._session_request( req_hash );
 
             if (!req.onerror)
                 req.onerror = function (r) { throw js2JSON(r); };
@@ -175,7 +211,7 @@ if(!dojo._hasResource["openils.PermaCrud"]) {
             if (!opts.async && !opts.timeout) req_hash.timeout = 10;
 
             var _pcrud = this;
-            var req = this.session.request( req_hash );
+            var req = this._session_request( req_hash );
 
             if (!req.onerror)
                 req.onerror = function (r) { throw js2JSON(r); };
index 692097e..00dfdc5 100644 (file)
@@ -16,15 +16,17 @@ if(!dojo._hasResource["openils.XUL"]) {
         if(openils.XUL.isXUL()) {
             try {
                 if(openils.XUL.enableXPConnect()) {
-                               var CacheClass = new Components.Constructor("@mozilla.org/openils_data_cache;1", "nsIOpenILS");
-                               return new CacheClass().wrappedJSObject.OpenILS.prototype.data;
+                    netscape.security.PrivilegeManager.enablePrivilege('UniversalXPConnect');
+                    var CacheClass = new Components.Constructor("@mozilla.org/openils_data_cache;1", "nsIOpenILS");
+                    return new CacheClass().wrappedJSObject.OpenILS.prototype.data;
                 }
             } catch(e) {
                 console.log("Error loading XUL stash: " + e);
+                return { 'error' : e };
             }
         }
 
-        return {};
+        return { 'error' : 'openils.XUL.isXUL() == false' };
     }
 
     openils.XUL.newTab = function(path, tabInfo, options) {
index 6e0f8af..5cdc023 100644 (file)
@@ -1,4 +1,5 @@
 {
+    "DEFAULT_ADDRESS_TYPE" : "MAILING",
     "DELETE_ADDRESS" : "Delete address ${0}?",
     "NEED_ADDRESS" : "An address is required during registration.",
     "DUPE_PATRON_NAME" : "Found ${0} patron(s) with the same name",
index 3ac08e7..25c2175 100644 (file)
  * ---------------------------------------------------------------------------
  */
 
+/*  Example markup:
+
+<div id='facetSidebarContainer' class='hide_me'>
+
+    <div class="side_bar_item" style="margin-top: 10px; font-weight: bold;">
+        <span>&navigate.facetRefine;</span>
+    </div>
+
+    <div
+        dojoType='openils.widget.FacetSidebar'
+        searchBox='facet_box'
+        searchSubmit='search_submit'
+        facetLimit='5'
+        maxValuesPerFacet='10'
+        classOrder='[{"name":"author","facetOrder":["personal","corporate"]},{"name":"subject","facetOrder":["topic"]},"series",{"name":"subject","facetOrder":["name","geographic"]}]'
+    >
+        <script type='dojo/method' event='populate'><![CDATA[
+            var f_sidebar = this;
+            attachEvt("result", "allRecordsReceived", function () {
+                if(!resultFacetKey) return;
+                if (f_sidebar.facetCacheKey) return; // already rendered it
+
+                dojo.removeClass('facetSidebarContainer','hide_me');
+
+                f_sidebar.facetCacheKey = resultFacetKey;
+                f_sidebar.render();
+            });
+        ]]></script>
+    </div>
+</div>
+
+ */
+
+
 if(!dojo._hasResource["openils.widget.FacetSidebar"]) {
 
     dojo._hasResource["openils.widget.FacetSidebar"] = true;
@@ -31,7 +65,8 @@ if(!dojo._hasResource["openils.widget.FacetSidebar"]) {
             facetData : {},
             facetCacheKey : '',
             searchBox : '',
-            classOrder : null,
+            classOrder : null, // Array of cmc.name values, OR array of objects with name and facetOrder properties
+            displayItemLimit : 999, // Number of distinctly described entries (classes or facets), that have values, to display from classOrder
             searchSubmit : '',
             facetLimit : 10,
             maxValuesPerFacet : 100,
@@ -75,21 +110,49 @@ if(!dojo._hasResource["openils.widget.FacetSidebar"]) {
                     classes = [];
                     dojo.forEach(
                         this.classOrder,
-                        function(x) { classes.push({name:x}); }
+                        function(x) {
+                            if (dojo.isObject(x)) classes.push(x);
+                            else classes.push({name:x});
+                        }
                     );
                 }
 
+                var displayedItems = 0;
                 var me = this;
                 dojo.forEach(
                     classes,
                     function (x) {
-                        var possible_facets = dojo.filter(
-                            openils.widget.Searcher._cache.arr.cmf,
-                            function (y) {
-                                if (y.field_class == x.name && facetData[y.id]) return 1;
-                                return 0;
-                            }
-                        );
+                        var possible_facets = [];
+                        if (x.facetOrder) {
+                            dojo.forEach(x.facetOrder, function(fname) {
+                                var maybe_facet = dojo.filter(
+                                    openils.widget.Searcher._cache.arr.cmf,
+                                    function (y) {
+                                        if (y.field_class == x.name && y.name == fname && facetData[y.id]) {
+                                            if (displayedItems < me.displayItemLimit) {
+                                                displayedItems++;
+                                                return 1;
+                                            }
+                                        }
+                                        return 0;
+                                    }
+                                )[0];
+                                if (maybe_facet) possible_facets.push(maybe_facet);
+                            });
+                        } else {
+                            possible_facets = dojo.filter(
+                                openils.widget.Searcher._cache.arr.cmf,
+                                function (y) {
+                                    if (y.field_class == x.name && facetData[y.id]) {
+                                        if (displayedItems < me.displayItemLimit) {
+                                            displayedItems++;
+                                            return 1;
+                                        }
+                                    }
+                                    return 0;
+                                }
+                            );
+                        }
                         if (possible_facets.length > 0) me.addClass( x.name, possible_facets );
                     }
                 );
index 887fe04..7d2d43d 100644 (file)
@@ -48,7 +48,7 @@ if(!dojo._hasResource['openils.widget.ProgressDialog']) {
                 }
                     
                 this.inherited(arguments);
-            },
+            }
         }
     );
 }
index b49b1e3..75cafe7 100644 (file)
@@ -19,6 +19,11 @@ function oilsSetupUser() {
     openils.User.authtoken = null;
     openils.User.workstation = null;
 
+    if(!authtoken && openils.XUL.isXUL()) {
+               stash = openils.XUL.getStash();
+               authtoken = stash.session.key
+       }
+
     if(authtoken) {
         user = new openils.User();
         delete user.sessionCache[authtoken];
@@ -38,6 +43,7 @@ function oilsSetupUser() {
             dojo.addOnLoad(function(){
                 if(openils.XUL.isXUL()) {
                     // let XUL handle the login dialog
+                    dump('getNewSession in base.js\n');
                     openils.XUL.getNewSession( function() { location.href = location.href } );
                 } else {
                     // in web-only mode, use the dojo login dialog
index cff9071..835129b 100644 (file)
@@ -384,14 +384,14 @@ function uEditFetchUserSettings(userId) {
 
     /* fetch any values set for this user */
     userSettings = fieldmapper.standardRequest(
-        ['open-ils.actor', 'open-ils.actor.patron.settings.retrieve'],
+        ['open-ils.actor', 'open-ils.actor.patron.settings.retrieve.authoritative'],
         {params : [openils.User.authtoken, userId, names]});
 }
 
 
 function uEditLoadUser(userId) {
     var patron = fieldmapper.standardRequest(
-        ['open-ils.actor', 'open-ils.actor.user.fleshed.retrieve'],
+        ['open-ils.actor', 'open-ils.actor.user.fleshed.retrieve.authoritative'],
         {params : [openils.User.authtoken, userId]}
     );
     openils.Event.parse_and_raise(patron);
@@ -1213,6 +1213,10 @@ function uEditNewAddr(evt, id, mkLinks) {
             if(row.getAttribute('fmclass')) {
                 var widget = fleshFMRow(row, 'aua', {addr:id});
 
+                // make new addresses a default address type
+                if(id < 0 && row.getAttribute('fmfield') == 'address_type') 
+                    widget.widget.attr('value', localeStrings.DEFAULT_ADDRESS_TYPE); 
+
                 // make new addresses valid by default
                 if(id < 0 && row.getAttribute('fmfield') == 'valid') 
                     widget.widget.attr('value', true); 
index b719ecc..9408b4d 100644 (file)
@@ -176,10 +176,21 @@ function authListInit() {
         dijit.byId('authTerm').attr('value', term);
         displayRecords();
     }
-    dojo.connect(dijit.byId('authTerm'), 'onBlur', function() {
-        dijit.byId('authPage').attr('value', 0);
-        displayRecords();
+
+    dojo.connect(dijit.byId('authAxis'), 'onKeyPress', function(evt) {
+        if (evt.keyCode == dojo.keys.ENTER) {
+            dijit.byId('authPage').attr('value', 0);
+            displayRecords();
+        }
+    }); 
+
+    dojo.connect(dijit.byId('authPage'), 'onKeyPress', function(evt) {
+        if (evt.keyCode == dojo.keys.ENTER) {
+            dijit.byId('authPage').attr('value', 0);
+            displayRecords();
+        }
     });
+
     dojo.connect(dijit.byId('authTerm'), 'onKeyPress', function(evt) {
         if (evt.keyCode == dojo.keys.ENTER) {
             dijit.byId('authPage').attr('value', 0);
@@ -187,6 +198,8 @@ function authListInit() {
         }
     });
 
+    dijit.byId('authTerm').focus();
+
 }
 dojo.addOnLoad(authListInit);
 
@@ -221,6 +234,7 @@ function displayRecords(parms) {
         + '/1' // replace with preceding line if OUs gain some meaning
         + '/' + dijit.byId('authTerm').attr('value')
         + '/' + dijit.byId('authPage').attr('value')
+        + '/' + '20' // 20 results per page
     ;
     dojo.xhrGet({"url":url, "handleAs":"xml", "content":{"format":"marcxml"}, "preventCache": true, "load":displayAuthorities });
 }
index 4ef30e5..aeeacef 100644 (file)
@@ -1447,6 +1447,7 @@ SelfCheckManager.prototype.printFinesReceipt = function(callback) {
  * Logout the patron and return to the login page
  */
 SelfCheckManager.prototype.logoutPatron = function(print) {
+    progressDialog.show(true); // prevent patron from clicking logout link twice
     if(print && this.checkouts.length) {
         this.printSessionReceipt(
             function() {
index d56b91d..1eba4a6 100644 (file)
@@ -16,7 +16,8 @@ var eventDef = null;
 function loadEventDef() { 
     eventDefGranularity.attr('value', null);
     edGrid.overrideEditWidgets.granularity = eventDefGranularity;
-    edGrid.loadAll({order_by:{atevdef : 'hook'}}); 
+    edGrid.overrideEditWidgets.granularity.shove = {"create": ""};
+    edGrid.loadAll({order_by:{atevdef : 'name'}}); 
     openils.widget.Textarea.width = '600px';
     openils.widget.Textarea.height = '600px';
     edGrid.overrideEditWidgetClass.template = 'openils.widget.Textarea';
index 5f3c783..5032b34 100644 (file)
@@ -34,12 +34,14 @@ function init() {
 function filterGrid(org) {
 
     // fetch the locations and order entries
-    var pcrud = new openils.PermaCrud({authtoken : user.authtoken});
-    orders = pcrud.search('acplo', {org : org}, {order_by : {acplo : 'position'}});
-    locations = pcrud.search('acpl', 
-        {owning_lib : fieldmapper.aou.orgNodeTrail(fieldmapper.aou.findOrgUnit(org), true)}, 
-        {order_by : {acpl : 'name'}}
-    ); 
+    if(!orders) {
+        var pcrud = new openils.PermaCrud({authtoken : user.authtoken});
+        orders = pcrud.search('acplo', {org : org}, {order_by : {acplo : 'position'}});
+        locations = pcrud.search('acpl', 
+            {owning_lib : fieldmapper.aou.orgNodeTrail(fieldmapper.aou.findOrgUnit(org), true)}, 
+            {order_by : {acpl : 'name'}}
+        ); 
+    }
 
     // init the DnD environment
     source.selectAll();
@@ -80,30 +82,10 @@ function filterGrid(org) {
 }
 
 function applyChanges() {
-    progressDialog.show(true);
-    if(orders.length) 
-        deleteOrders(createOrders);
-    else
-        createOrders();
-}
-
-function deleteOrders(onload) {
-    // delete the existing order entries in preparation for new ones
-    var pcrud = new openils.PermaCrud({authtoken : user.authtoken});
-    pcrud.eliminate(
-        orders,
-        {
-            async : true,
-            oncomplete : function() {
-                if(onload) onload();
-            }
-        }
-    );
-}
-
-function createOrders() {
+    progressDialog.show();
 
     var newOrders = [];
+    var contextOrg = contextOrgSelector.attr('value');
 
     // pull the locations out of the DnD environment and create order entries for them
     dojo.forEach(
@@ -113,21 +95,27 @@ function createOrders() {
             var o = new fieldmapper.acplo();
             o.position(newOrders.length + 1);
             o.location(item.type[0]); // location.id() is stored in DnD item type
-            o.org(contextOrgSelector.attr('value'));
+            o.org(contextOrg);
             newOrders.push(o);
         }
     );
 
-    // send the order entries off to the server
-    var pcrud = new openils.PermaCrud({authtoken : user.authtoken});
-    pcrud.create(
-        newOrders,
+    fieldmapper.standardRequest(
+        ['open-ils.circ', 'open-ils.circ.copy_location_order.update'],
         {
             async : true,
-            oncomplete : function(r) {
-                progressDialog.hide();
-                filterGrid(contextOrgSelector.attr('value'));
-            }
+            params : [openils.User.authtoken, newOrders],
+            onresponse : function(r) {
+                if(r = openils.Util.readResponse(r)) {
+                    if(r.orders) {
+                        orders = r.order;
+                        progressDialog.hide();
+                        filterGrid(contextOrg);
+                        return;
+                    } 
+                    progressDialog.update(r);
+                }
+            },
         }
     );
 }
index f513b7e..4a74f29 100644 (file)
@@ -1339,7 +1339,7 @@ function vlShowProfileEditor() {
     };
 
     new openils.User().buildPermOrgSelector(
-        '"ADMIN_MERGE_PROFILE', profileContextOrgSelector, null, connect);
+        'ADMIN_MERGE_PROFILE', profileContextOrgSelector, null, connect);
 }
 
 function buildProfileGrid() {
index 038983e..9ed6260 100644 (file)
@@ -536,6 +536,7 @@ function setSessionCookie(ses) {
        if ses != G.user.session, we also force a grab */
 function grabUser(ses, force) {
 
+    _debug("grabUser auth token = " + ses);
        if(!ses && isXUL()) {
                stash = fetchXULStash();
                ses = stash.session.key
@@ -545,6 +546,7 @@ function grabUser(ses, force) {
        if(!ses) {
                ses = cookieManager.read(COOKIE_SES);
                /* https cookies don't show up in http servers.. */
+               _debug("cookie auth token = " + ses);
        }
 
        if(!ses) return false;
@@ -563,6 +565,7 @@ function grabUser(ses, force) {
 
         if(isXUL()) {
             dojo.require('openils.XUL');
+            dump('getNewSession in opac_utils.js\n');
             openils.XUL.getNewSession( 
                 function(success, authtoken) { 
                     if(success) {
diff --git a/Open-ILS/web/opac/extras/circ/alt_holds_print.html b/Open-ILS/web/opac/extras/circ/alt_holds_print.html
new file mode 100644 (file)
index 0000000..849b369
--- /dev/null
@@ -0,0 +1,105 @@
+<html>
+    <head>
+        <title>Printable Pull List</title>
+        <style type="text/css">
+            @import url('/js/dojo/dojo/resources/dojo.css');
+            @import url('/js/dojo/dijit/themes/tundra/tundra.css');
+            @import url('/js/dojo/dojox/widget/Toaster/Toaster.css');
+            @import url('/opac/skin/default/css/layout.css');
+        </style>
+        <style type="text/css">
+            #clear_holds_deck { margin-bottom: 1em; }
+            a { color: blue; text-decoration: underline; }
+            small { font-size: 9pt; }
+            body { font-size: 14pt; }
+            td {
+                padding-right: 1em;
+                padding-bottom: 1em;
+                border-bottom: 1px #999 dashed;
+            }
+            th {
+                text-align: left; font-weight: bold;
+                border-bottom: 1px #000 solid;
+                border-right: 1px #000 solid;
+                padding: 0.5em;
+            }
+        </style>
+        <!-- The OpenSRF API writ JS -->
+        <script language='javascript' src='/opac/common/js/utils.js' type='text/javascript'></script>
+        <script language='javascript' src='/opac/common/js/Cookies.js' type='text/javascript'></script>
+        <script language='javascript' src='/opac/common/js/CGI.js' type='text/javascript'></script>
+        <script language='javascript' src='/opac/common/js/JSON_v1.js' type='text/javascript'></script>
+        <!-- Dojo goodness -->
+        <script type="text/javascript">
+            var djConfig = {parseOnLoad:true,isDebug:false,AutoIDL:['aou','aout','pgt','ahr','acp','acn']};
+            var sort_order = ["acplo.position", "call_number", "request_time"];
+        </script>
+        <script type="text/javascript" src="/js/dojo/dojo/dojo.js"></script>
+        <script type="text/javascript" src="/js/dojo/dojo/openils_dojo.js"></script>
+        <script type="text/javascript" src="/js/dojo/dijit/dijit.js"></script>
+        <script type="text/javascript" src="/js/dojo/openils/AutoIDL.js"></script>
+        <script type="text/javascript" src="/js/dojo/openils/User.js"></script>
+        <script type="text/javascript" src="/js/dojo/openils/Util.js"></script>
+        <script type="text/javascript" src="/opac/extras/circ/alt_holds_print.js"></script>
+        <script type="text/javascript">
+            function my_init() {
+                cgi = new CGI();
+                authtoken = (typeof ses == "function" ? ses() : 0) ||
+                    cgi.param("ses") || dojo.cookie("ses");
+
+                if (cgi.param("do") == "shelf_expired_holds") {
+                    dojo.byId("clear_holds_launcher").onclick = function() {
+                        if (confirm("Are you sure you're ready to clear the expired holds from the shelf?")) { /* XXX i18n */
+                            do_clear_holds(cgi);
+                        }
+                    };
+                    openils.Util.show("clear_holds_deck");
+                } else {
+                    dojo.query("[only='shelf_expired_holds']").forEach(dojo.destroy);
+                    do_pull_list(cgi);
+                }
+            }
+            dojo.addOnLoad(my_init);
+        </script>
+    </head>
+    <body class='tundra'>
+
+        <div style="width: 320px;"
+            dojoType="openils.widget.ProgressDialog"
+            jsId="progress_dialog"></div>
+        <div class="hide_me" id="no_results">No results</div>
+        <div class="hide_me" id="clear_holds_deck">
+            [ <a id="clear_holds_launcher"
+                href="javascript:void(0);">Clear expired holds</a> ]
+            <small><em id="clear_holds_set_label"></em></small>
+        </div>
+<!-- START OF TEMPLATE SECTION -->
+        <table>
+            <thead>
+                <tr>
+                    <th only="shelf_expired_holds">Patron</th>
+                    <th only="shelf_expired_holds">Action</th>
+                    <th>Title</th>
+                    <th>Author</th>
+                    <th>Shelving Location</th>
+                    <th>Call Number</th>
+                    <th>Barcode</th>
+                </tr>
+            </thead>
+            <tbody id='target'>
+            </tbody>
+            <tbody id='template' class='hide_me'>
+                <tr>
+                    <td only="shelf_expired_holds">${usr.display_name}</td>
+                    <td only="shelf_expired_holds">${action}</td>
+                    <td type='opac/slot-data' query='datafield[tag=245]'></td>
+                    <td type='opac/slot-data' query='datafield[tag^=1]' limit='1'> </td>
+                    <td>${current_copy.location.name}</td>
+                    <td>${current_copy.call_number.label}</td>
+                    <td>${current_copy.barcode}</td>
+                </tr>
+            </tbody>
+        </table>
+<!-- END OF TEMPLATE SECTION -->
+    </body>
+</html>
diff --git a/Open-ILS/web/opac/extras/circ/alt_holds_print.js b/Open-ILS/web/opac/extras/circ/alt_holds_print.js
new file mode 100644 (file)
index 0000000..3fd5020
--- /dev/null
@@ -0,0 +1,202 @@
+dojo.require("dojo.cookie");
+dojo.require("dojox.xml.parser");
+dojo.require("openils.BibTemplate");
+dojo.require("openils.widget.ProgressDialog");
+
+var authtoken;
+var cgi;
+
+function do_pull_list() {
+    progress_dialog.show(true);
+
+    var any = false;
+
+    fieldmapper.standardRequest(
+        ['open-ils.circ','open-ils.circ.hold_pull_list.print.stream'],
+        { async : true,
+          params: [
+            authtoken, {
+              org_id     : cgi.param('o'),
+              limit      : cgi.param('limit'),
+              offset     : cgi.param('offset'),
+              chunk_size : cgi.param('chunk_size'),
+              sort       : sort_order
+            }
+          ],
+          onresponse : function (r) {
+            any = true;
+            dojo.forEach( openils.Util.readResponse(r), function (hold_fm) {
+
+                // hashify the hold
+                var hold = hold_fm.toHash(true);
+                hold.usr = hold_fm.usr().toHash(true);
+                hold.usr.card = hold_fm.usr().card().toHash(true);
+                hold.current_copy = hold_fm.current_copy().toHash(true);
+                hold.current_copy.location = hold_fm.current_copy().location().toHash(true);
+                hold.current_copy.call_number = hold_fm.current_copy().call_number().toHash(true);
+                hold.current_copy.call_number.record = hold_fm.current_copy().call_number().record().toHash(true);
+
+                // clone the template's html
+                var tr = dojo.clone(
+                    dojo.query("tr", dojo.byId('template'))[0]
+                );
+                dojo.query("td:not([type])", tr).forEach(
+                    function(td) {
+                        td.innerHTML =
+                            dojo.string.substitute(td.innerHTML, hold);
+                    }
+                );
+
+                new openils.BibTemplate({
+                    root : tr,
+                    xml  : dojox.xml.parser.parse(hold.current_copy.call_number.record.marc),
+                    delay: false
+                });
+
+                dojo.place(tr, "target");
+            });
+          },
+          oncomplete : function () {
+            progress_dialog.hide();
+            if (any)
+                window.print();
+            else
+                alert(dojo.byId("no_results").innerHTML);
+          }
+        }
+    );
+}
+
+function place_by_sortkey(node, container) {
+    /*Don't use a forEach() or anything like that here. too slow.*/
+    var sortkey = dojo.attr(node, "sortkey");
+    for (var i = 0; i < container.childNodes.length; i++) {
+        var rover = container.childNodes[i];
+        if (rover.nodeType != 1) continue;
+        if (dojo.attr(rover, "sortkey") > sortkey) {
+            dojo.place(node, rover, "before");
+            return;
+        }
+    }
+    dojo.place(node, container, "last");
+}
+
+function hashify_fields(fields) {
+    var hold  = {
+        "usr": {},
+        "current_copy": {
+            "barcode": fields.barcode,
+            "call_number": {
+                "label": fields.label,
+                "record": {"marc": fields.marc}
+            },
+            "location": {"name": fields.name}
+        }
+    };
+
+    if (fields.alias) {
+        hold.usr.display_name = fields.alias;
+    } else {
+        hold.usr.display_name = [
+            (fields.family_name ? fields.family_name : ""),
+            (fields.first_given_name ? fields.first_given_name : ""),
+            (fields.second_given_name ? fields.second_given_name : "")
+        ].join(" ");
+    }
+
+    ["first_given_name","second_given_name","family_name","alias"].forEach(
+        function(k) { hold.usr[k] = fields[k]; }
+    );
+
+    return hold;
+}
+
+function do_clear_holds() {
+    progress_dialog.show(true);
+
+    var launcher;
+    fieldmapper.standardRequest(
+        ["open-ils.circ", "open-ils.circ.hold.clear_shelf.process"], {
+            "async": true,
+            "params": [authtoken, cgi.param("o")],
+            "onresponse": function(r) {
+                if (r = openils.Util.readResponse(r)) {
+                    if (r.cache_key) { /* complete */
+                        launcher = dojo.byId("clear_holds_launcher");
+                        launcher.innerHTML = "Re-fetch for Printing"; /* XXX i18n */
+                        launcher.onclick =
+                            function() { do_clear_holds_from_cache(r.cache_key); };
+                        dojo.byId("clear_holds_set_label").innerHTML = r.cache_key;
+                    } else if (r.maximum) {
+                        progress_dialog.update(r);
+                    }
+                }
+            },
+            "oncomplete": function() {
+                progress_dialog.hide();
+                if (launcher) launcher.onclick();
+                else alert(dojo.byId("no_results").innerHTML);
+            }
+        }
+    );
+}
+
+function do_clear_holds_from_cache(cache_key) {
+    progress_dialog.show(true);
+
+    var any = 0;
+    var target = dojo.byId("target");
+    dojo.empty(target);
+    var template = dojo.query("tr", dojo.byId("template"))[0];
+    fieldmapper.standardRequest(
+        ["open-ils.circ",
+            "open-ils.circ.hold.clear_shelf.get_cache"], {
+            "async": true,
+            "params": [authtoken, cache_key, cgi.param("chunk_size")],
+            "onresponse": function(r) {
+                dojo.forEach(
+                    openils.Util.readResponse(r),
+                    function(resp) {
+                        if (resp.maximum) {
+                            progress_dialog.update(resp);
+                            return;
+                        }
+
+                        var hold = hashify_fields(resp.hold_details);
+                        hold.action = resp.action;
+
+                        var tr = dojo.clone(template);
+                        any++;
+
+                        dojo.query("td:not([type])", tr).forEach(
+                            function(td) {
+                                td.innerHTML =
+                                    dojo.string.substitute(td.innerHTML, hold);
+                            }
+                        );
+
+                        new openils.BibTemplate({
+                            "root": tr,
+                            "xml": dojox.xml.parser.parse(
+                                hold.current_copy.call_number.record.marc
+                            ),
+                            "delay": false
+                        });
+
+                        dojo.attr(tr, "sortkey", hold.usr.display_name);
+                        place_by_sortkey(tr, target);
+                    }
+                );
+                progress_dialog.update({"progress": any});
+            },
+            "oncomplete": function() {
+                progress_dialog.hide();
+                if (any)
+                    window.print();
+                else
+                    alert(dojo.byId("no_results").innerHTML);
+            }
+        }
+    );
+}
+
index 8b3648a..6a878e5 100644 (file)
  <!ENTITY conify.grp_tree.group_name.label "Group Name">
  <!ENTITY conify.grp_tree.description.label "Description">
  <!ENTITY conify.grp_tree.permission_interval.label "Permission Interval">
+ <!ENTITY conify.grp_tree.hold_priority.label "Hold Priority">
  <!ENTITY conify.grp_tree.editing_permission.label "Editing Permission">
  <!ENTITY conify.grp_tree.parent_group.label "Parent Group">
  <!ENTITY conify.grp_tree.user_group.label "User Group">
index 398e0e6..9ce45a6 100644 (file)
 <!ENTITY staff.cat.popup.edit.record.window.key "">
 <!ENTITY staff.cat.popup.edit_record.tab "Edit Record (Tab)">
 <!ENTITY staff.cat.popup.edit_record.window "Edit Record (Window)">
+<!ENTITY staff.cat.record_buckets.merge_records.merge_lead "Merge these records? (Select the 'lead' record first)">
+<!ENTITY staff.cat.record_buckets.merge_records.button.label "Merge">
+<!ENTITY staff.cat.record_buckets.merge_records.button.accesskey "M">
+<!ENTITY staff.cat.record_buckets.merge_records.cancel_button.label "Cancel">
+<!ENTITY staff.cat.record_buckets.merge_records.cancel_button.accesskey "C">
+<!ENTITY staff.cat.record_buckets.merge_records.lead "Lead Record?">
+<!ENTITY staff.cat.record_buckets.merge_records.remove_from_consideration "Remove from consideration?">
 <!ENTITY staff.cat.search_advanced "Advanced">
 <!ENTITY staff.cat.search_advanced.key "V">
 <!ENTITY staff.cat.search_all "Keyword">
 <!ENTITY staff.main.menu.cat.edit_user_buckets.label "Manage User Buckets">
 <!ENTITY staff.main.menu.cat.key "a">
 <!ENTITY staff.main.menu.cat.label "Cataloging">
+<!ENTITY staff.main.menu.cat.marc_batch_edit.label "MARC Batch Edit">
+<!ENTITY staff.main.menu.cat.marc_batch_edit.accesskey "E">
 <!ENTITY staff.main.menu.cat.retrieve_last_record.accesskey "L">
 <!ENTITY staff.main.menu.cat.retrieve_last_record.label "Retrieve Last Record">
 <!ENTITY staff.main.menu.cat.search_tcn.accesskey "T">
 <!ENTITY staff.server.admin.index.library_settings "Library Settings Editor">
 <!ENTITY staff.server.admin.index.non_cataloged_types "Non-cataloged Types Editor">
 <!ENTITY staff.server.admin.index.statistical_categories "Statistical Categories Editor">
+<!ENTITY staff.server.admin.index.expired_holds_shelf "Expired Holds Shelf Printable Listing">
 <!ENTITY staff.server.admin.index.hold_pull_list "Pull List for Hold Requests">
 <!ENTITY staff.server.admin.index.testing "(Testing)">
 <!ENTITY staff.server.admin.index.hold_pull_list_classic "Pull List for Hold Requests (Classic)">
 <!ENTITY staff.server.admin.index.external_text_editor.label "External Text Editor Command">
 <!ENTITY staff.server.admin.index.external_text_editor.accesskey "x">
 
-<!ENTITY staff.server.admin.index.booking "Booking">
-<!ENTITY staff.server.admin.index.booking.reservation "Create/Cancel Reservations">
-<!ENTITY staff.server.admin.index.booking.pull_list "Pull List">
-<!ENTITY staff.server.admin.index.booking.capture "Capture">
-<!ENTITY staff.server.admin.index.booking.pickup "Pickup Reservations">
-<!ENTITY staff.server.admin.index.booking.return "Return Reservations">
-
-
 <!ENTITY staff.server.admin.org_settings.title "Evergreen: Library Settings Editor">
 <!-- This will be followed by the user's name -->
 <!ENTITY staff.server.admin.org_settings.greeting "Welcome ">
 <!ENTITY staff.cat.marcedit.validate.accesskey "V">
 <!ENTITY staff.cat.marcedit.save-button.accesskey "d">
 <!ENTITY staff.cat.marcedit.help.label "Help">
-<!ENTITY staff.cat.marcedit.swapEditor.label "Swap Editor Type">
+<!ENTITY staff.cat.marcedit.flatTextEditor.label "Flat-Text Editor">
+<!ENTITY staff.cat.marcedit.flatTextEditor.accesskey "">
 <!ENTITY staff.cat.marcedit.help.accesskey "H">
 <!ENTITY staff.cat.marcedit.caption.label "MARC Record">
 <!ENTITY staff.cat.marcedit.toggleFFE.label "Fixed Fields -- Record type: ">
 <!ENTITY staff.cat.record_buckets_overlay.box.label "Batch:">
 <!ENTITY staff.cat.record_buckets_overlay.sel_opac.label "Show All in Catalog">
 <!ENTITY staff.cat.record_buckets_overlay.transfer_title_holds.label "Transfer Title Holds">
-<!ENTITY staff.cat.record_buckets_overlay.transfer_title_holds.accesskey "Transfer Title Holds">
+<!ENTITY staff.cat.record_buckets_overlay.transfer_title_holds.accesskey "T">
+<!ENTITY staff.cat.record_buckets_overlay.marc_batch_edit.label "MARC Batch Edit">
+<!ENTITY staff.cat.record_buckets_overlay.marc_batch_edit.accesskey "">
 <!ENTITY staff.cat.record_buckets_overlay.del_records.label "Delete All Records">
 <!ENTITY staff.cat.record_buckets_overlay.merge_records.label "Merge All Records">
 <!ENTITY staff.cat.record_buckets_overlay.export_records.label "Export All Records">
 <!ENTITY staff.cat.z3950.menuitem.save_columns.label "Save List Configuration">
 <!ENTITY staff.cat.z3950.marc_view.label "MARC View">
 <!ENTITY staff.cat.z3950.marc_view.accesskey "V">
-<!ENTITY staff.cat.z3950.marc_import_overlay.label "MARC Editor for Overlay">
+<!ENTITY staff.cat.z3950.marc_editor.label "MARC Editor">
+<!ENTITY staff.cat.z3950.marc_editor.accesskey "E">
+<!ENTITY staff.cat.z3950.marc_import_overlay.label "Overlay">
 <!ENTITY staff.cat.z3950.marc_import_overlay.accesskey "O">
-<!ENTITY staff.cat.z3950.result_message.marc_import.label "MARC Editor for Import">
+<!ENTITY staff.cat.z3950.result_message.marc_import.label "Import">
 <!ENTITY staff.cat.z3950.result_message.marc_import.accesskey "I">
 <!ENTITY staff.pat.barcode_entry.retrieve_patron.label "Retrieve Patron">
 <!ENTITY staff.pat.barcode_entry.barcode.label "Barcode:">
 <!ENTITY staff.patron.holds_overlay.print.accesskey "P">
 <!ENTITY staff.patron.holds_overlay.print_full_pull_list.label "Print Full Pull List">
 <!ENTITY staff.patron.holds_overlay.print_full_pull_list.accesskey "u">
+<!ENTITY staff.patron.holds_overlay.print_alt_pull_list.label "Print Full Pull List (Alternate strategy)">
+<!ENTITY staff.patron.holds_overlay.print_alt_pull_list.accesskey "y">
 <!ENTITY staff.patron.holds_overlay.place_hold.label "Place Hold">
 <!ENTITY staff.patron.holds_overlay.place_hold.accesskey "H">
 <!ENTITY staff.patron.holds_overlay.show_cancelled_holds.label "Show Cancelled Holds">
index 510dce6..b18ff47 100644 (file)
                                name="serial_holdings_label"
                                class="result_table_title_cell hide_me"
                                type="opac/slot-data"
-                               query="datafield[tag=901] subfield[code=c]">
+                               query="datafield[tag='901'] subfield[code='c']">
                                <td colspan="2">Issues Held: ${holdingsStatement}
                                        <span class="hide_me" name="holdingsStatement" type="opac/template-value"><![CDATA[
                                                if (fetchOrgSettingDefault(
index da0acf9..4e69f51 100644 (file)
@@ -21,7 +21,7 @@ var djConfig = {parseOnLoad:true,isDebug:false,AutoIDL:[
 'acqligad','acqliuad','acqlipad','acqphsm','acqlilad','acqedi','acqedim','acqdf','acqdfe','acqdfa','acqda','cnal',
 'acqclt','acqclet','acqcl','acqcle','acqscl','acqscle','acqclp','acqclpa','acqlisum','acqft','acqftm','actsce','actscecm',
 'jub','sdist','ssub','sstr','scap','bre','siss','act', 'acpl', 'ccm', 'aiit', 'atevdef', 'ath', 'atreact','atclean','atenv','atevparam','atcol','actsc','cit',
-'atval','crahp','crmf','crrf','crcd','cust','coust','cgf','czs','cbt','csp','brt','brsrc','bra','bram','brav','vaq','vbq','vqar','ccmm','ccmcmtm','citm','cifm','cvrfm']};
+'atval','crahp','crmf','crrf','crcd','cust','coust','cgf','czs','cbt','csp','brt','brsrc','bra','bram','brav','vaq','vbq','vqar','ccmm','ccmcmtm','citm','cifm','cvrfm','chmm']};
         </script>
         <script type="text/javascript" src="[% ctx.media_prefix %]/js/dojo/dojo/dojo.js"></script>
         <script type="text/javascript" src="[% ctx.media_prefix %]/js/dojo/dojo/openils_dojo.js"></script>
index cc4bf1e..bf410e7 100644 (file)
@@ -1,4 +1,5 @@
 [% WRAPPER 'default/base.tt2' %]
+[% ctx.page_title = "Purchase Order" %]
 <script type="text/javascript" src="[% ctx.media_prefix %]/js/ui/default/acq/common/base64.js"></script>
 <script type="text/javascript" src='[% ctx.media_prefix %]/js/ui/default/acq/po/view_po.js'></script>
 <script type="text/javascript" src="[% ctx.media_prefix %]/js/ui/default/acq/po/item_table.js"></script>
index 06a929b..4e9fcbe 100644 (file)
@@ -81,7 +81,7 @@
     </tr>
 
 
-    <tr fmclass='aua' fmfield='address_type' type='addr-template' required='show'/>
+    <tr fmclass='aua' fmfield='address_type' type='addr-template' required='required'/>
     <tr fmclass='aua' fmfield='post_code' type='addr-template' required='required'/>
     <tr fmclass='aua' fmfield='street1' type='addr-template' required='required'/>
     <tr fmclass='aua' fmfield='street2' type='addr-template' required='show'/>
index a65e4f9..fc1bdef 100644 (file)
@@ -6,8 +6,8 @@
 <script type="text/javascript">
     SelfCheckManager.audioConfig = {
         'login-success' : '',
-        'login-failure' : '[% ctx.media_prefix %]/audio/circ/question.wav',
-        'checkout-success' : '[% ctx.media_prefix %]/audio/circ/bonus.wav',
-        'checkout-failure' : '[% ctx.media_prefix %]/audio/circ/question.wav',
+        'login-failure' : '[% ctx.media_prefix %]/audio/question.wav',
+        'checkout-success' : '[% ctx.media_prefix %]/audio/bonus.wav',
+        'checkout-failure' : '[% ctx.media_prefix %]/audio/question.wav',
     }
 </script>
index fcff273..cb74df4 100644 (file)
         <div dojoType="dijit.layout.ContentPane" layoutAlign="client" style='height:600px'>
             <table  jsId="edGrid" 
                     dojoType="openils.widget.AutoGrid" 
-                    fieldOrder="['owner', 'name', 'hook', 'active', 'delay', 'delay_field', 'group_field', 'validator', 'reactor']"
-                    suppressFields="['template', 'cleanup_failure', 'cleanup_success']"
+                    fieldOrder="['owner', 'name', 'hook', 'active', 'delay', 'delay_field', 'group_field', 'reactor', 'validator']"
+                    suppressFields="['usr_field', 'opt_in_setting', 'max_delay', 'template', 'cleanup_failure', 'cleanup_success']"
                     query="{id: '*'}" 
                     fmClass='atevdef'
-                    defaultCellWidth='"auto"'
                     editStyle='pane'
                     showPaginator='true'
                     editOnEnter='true'>
                 <thead>
-                    <tr><th field='name' get='getEventDefNameLink' formatter='formatEventDefNameLink'/></tr>
+                    <tr><th field='name' width='15%' get='getEventDefNameLink' formatter='formatEventDefNameLink'/></tr>
                 </thead>
             </table>
         </div>
index e7024fb..678966f 100644 (file)
@@ -18,7 +18,8 @@
     <table  jsId="hmGrid" 
             autoHeight='true'
             dojoType="openils.widget.AutoGrid" 
-            fieldOrder="['id', 'user_home_ou', 'request_ou', 'pickup_ou', 'item_owning_ou', 'item_circ_ou', 'usr_grp', 'requestor_grp', 'circ_modifier']"
+            fieldOrder="['id', 'strict_ou_match', 'user_home_ou', 'request_ou', 'pickup_ou', 'item_owning_ou', 'item_circ_ou', 'requestor_grp', 'circ_modifier']"
+            suppressFields="['usr_grp']"
             defaultCellWidth='"auto"'
             query="{id: '*'}" 
             fmClass='chmm' 
index 3865cc4..611657c 100644 (file)
@@ -6,7 +6,7 @@
     <div dojoType="dijit.layout.ContentPane" layoutAlign="top" class='oils-header-panel'>
         <div>Import Item Attribute Definitions</div>
         <div>
-            <button dojoType='dijit.form.Button' onClick='itemAttrGrid.showCreateDialog()'>New Definition</button>
+            <button dojoType='dijit.form.Button' onClick='itemAttrGrid.showCreatePane()'>New Definition</button>
             <button dojoType='dijit.form.Button' onClick='itemAttrGrid.deleteSelected()'>Delete Selected</button>
         </div>
     </div>
index 0c51156..20af40b 100644 (file)
@@ -186,6 +186,8 @@ OpenILS.data.prototype = {
                 break;
                 case 'actsc':
                     found = obj.network.simple_request('FM_ACTSC_RETRIEVE_VIA_PCRUD',[ ses(), { 'id' : { '=' : value } }]);
+                    if (typeof found.ilsevent != 'undefined') throw(found);
+                    found = found[0];
                 break;
                 default: return undefined; break;
             }
index ec9bdfa..459a628 100644 (file)
@@ -94,6 +94,9 @@
                             if (ev.explicitOriginalTarget != node) return;
                         } else {
                             target = ev.target;
+                            if (target == window) {
+                                target = window.document.documentElement;
+                            }
                         }
                         var filename = location.pathname.split('/')[ location.pathname.split('/').length - 1 ];
                         var base_key = 'oils_persist_' + String(location.hostname + '_' + filename + '_' + target.getAttribute('id')).replace('/','_','g') + '_' + base_key_suffix;
                             } else if ( attribute_list[j] == 'value' && ['textbox'].indexOf( target.nodeName ) > -1 ) {
                                 value = target.value;
                                 dump('\t' + value + ' <== .' + attribute_list[j] + '\n');
+                            } else if ( attribute_list[j] == 'sizemode' && ['window'].indexOf( target.nodeName ) > -1 ) {
+                                value = window.windowState;
+                                dump('\t' + value + ' <== window.windowState, @' + attribute_list[j] + '\n');
+                            } else if ( attribute_list[j] == 'height' && ['window'].indexOf( target.nodeName ) > -1 ) {
+                                value = window.outerHeight;
+                                dump('\t' + value + ' <== window.outerHeight, @' + attribute_list[j] + '\n');
+                            } else if ( attribute_list[j] == 'width' && ['window'].indexOf( target.nodeName ) > -1 ) {
+                                value = window.outerWidth;
+                                dump('\t' + value + ' <== window.outerWidth, @' + attribute_list[j] + '\n');
                             } else {
                                 dump('\t' + value + ' <== @' + attribute_list[j] + '\n');
                             }
                             prefs.setCharPref( key, value );
-                            // TODO: Need to add logic for window resizing, splitter repositioning, grippy state, etc.
+                            // TODO: Need to add logic for splitter repositioning, grippy state, etc.
+                            // NOTE: oils_persist_peers and oils_persist="width" on those peers can help with the elements adjacent to a splitter
                         }
                         if (target.hasAttribute('oils_persist_peers') && ! ev.cancelable) { // We abuse the .cancelable field on the oils_persist event to prevent looping
                             var peer_list = target.getAttribute('oils_persist_peers').split(' ');
                         } else if ( attribute_list[j] == 'value' && ['textbox'].indexOf( nodes[i].nodeName ) > -1 ) {
                             nodes[i].value = value;
                             dump('\t' + value + ' ==> .' + attribute_list[j] + '\n');
+                        } else if ( attribute_list[j] == 'sizemode' && ['window'].indexOf( nodes[i].nodeName ) > -1 ) {
+                            switch(value) {
+                                case window.STATE_MAXIMIZED:
+                                    window.maximize();
+                                    break;
+                                case window.STATE_MINIMIZED:
+                                    window.minimize();
+                                    break;
+                            };
+                            dump('\t' + value + ' ==> window.windowState, @' + attribute_list[j] + '\n');
+                        } else if ( attribute_list[j] == 'height' && ['window'].indexOf( nodes[i].nodeName ) > -1 ) {
+                            window.outerHeight = value;
+                            dump('\t' + value + ' ==> window.outerHeight, @' + attribute_list[j] + '\n');
+                        } else if ( attribute_list[j] == 'width' && ['window'].indexOf( nodes[i].nodeName ) > -1 ) {
+                            window.outerWidth = value;
+                            dump('\t' + value + ' ==> window.outerWidth, @' + attribute_list[j] + '\n');
                         } else {
                             nodes[i].setAttribute( attribute_list[j], value);
                             dump('\t' + value + ' ==> @' + attribute_list[j] + '\n');
                         false
                     );
                 } else {
+                    var node = nodes[i];
                     var event_types = [];
-                    if (nodes[i].hasAttribute('oils_persist_events')) {
-                        var event_type_list = nodes[i].getAttribute('oils_persist_events').split(' ');
+                    if (node.hasAttribute('oils_persist_events')) {
+                        var event_type_list = node.getAttribute('oils_persist_events').split(' ');
                         for (var j = 0; j < event_type_list.length; j++) {
                             event_types.push( event_type_list[j] );
                         }
                     } else {
-                        if (nodes[i].nodeName == 'textbox') { 
+                        if (node.nodeName == 'textbox') { 
                             event_types.push('change'); 
+                        } else if (node.nodeName == 'window') {
+                            event_types.push('resize'); 
+                            node = window; // xul window is an element of window.document
                         } else {
                             event_types.push('command'); 
                         }
                     }
                     for (var j = 0; j < event_types.length; j++) {
-                        nodes[i].addEventListener(
+                        node.addEventListener(
                             event_types[j],
-                            gen_event_handler(event_types[j],nodes[i]),
+                            gen_event_handler(event_types[j],node),
                             false
                         );
                     }
-                    nodes[i].addEventListener(
+                    node.addEventListener(
                         'oils_persist',
-                        gen_oils_persist_handler( base_key, nodes[i] ),
+                        gen_oils_persist_handler( base_key, node ),
                         false
                     );
                 }
index 4585a07..ca5cb0d 100644 (file)
@@ -115,6 +115,13 @@ auth.session.prototype = {
     'close' : function () { 
         var obj = this;
         obj.error.sdump('D_AUTH','auth.session.close()\n'); 
+        try {
+            netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect");
+            Components.classes["@mozilla.org/cookiemanager;1"]
+                .getService(Components.interfaces.nsICookieManager).removeAll();
+        } catch(E) {
+            dump('Error in auth/session.js, close(): ' + E + '\n');
+        }
         if (obj.key) obj.network.request(
             api.AUTH_DELETE.app,
             api.AUTH_DELETE.method,
index b496f39..d028721 100644 (file)
@@ -497,7 +497,7 @@ function create_mfhd() {
         JSAN.use('util.window'); var win = new util.window();
         win.open(
             xulG.url_prefix(urls.XUL_SERIAL_SELECT_AOU),
-            'sel_bucket_win' + win.window_name_increment(),
+            '_blank',
             'chrome,resizable,modal,centerscreen'
         );
         if (!g.data.create_mfhd_aou) {
@@ -634,7 +634,7 @@ function add_to_bucket() {
     JSAN.use('util.window'); var win = new util.window();
     win.open(
         xulG.url_prefix(urls.XUL_RECORD_BUCKETS_QUICK),
-        'sel_bucket_win' + win.window_name_increment(),
+        '_blank',
         'chrome,resizable,modal,centerscreen',
         {
             record_ids: [ docid ]
index 294b7c5..4b94d7a 100644 (file)
@@ -17,6 +17,7 @@
 
 <window id="offline_win" sizemode="maximized"
     onload="try { my_init(); } catch(E) { alert(E); }"
+    windowtype="eg_offline"
     xmlns:html="http://www.w3.org/1999/xhtml"
     xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
 
index b30fc13..e95db3d 100644 (file)
@@ -351,6 +351,9 @@ var urls = {
     'browser' : '/opac/' + LOCALE + '/skin/default/xml/advanced.xml?nps=1',
     'fieldmapper' : '/opac/common/js/fmall.js',
     'xsl_marc2html' : '/opac/extras/xsl/oilsMARC21slim2HTML.xsl',
+    'ac_jacket_small' : '/opac/extras/ac/jacket/small/',
+    'ac_jacket_large' : '/opac/extras/ac/jacket/large/',
+    'MARC_BATCH_EDIT' : '/opac/extras/merge_template/',
 
     'AUDIO_good' : '/xul/server/skin/media/audio/bonus.wav',
     'AUDIO_bad' : '/xul/server/skin/media/audio/question.wav',
@@ -362,6 +365,7 @@ var urls = {
     'AUTHORITY_MANAGE' : '/eg/cat/authority/list',
     'XUL_AUTH_SIMPLE' : '/xul/server/main/simple_auth.xul',
     'XUL_BIB_BRIEF' : '/xul/server/cat/bib_brief.xul',
+    'XUL_BIB_BRIEF_VERTICAL' : '/xul/server/cat/bib_brief_vertical.xul',
     'XUL_BROWSER' : 'chrome://open_ils_staff_client/content/util/browser.xul',
     'XUL_CHECKIN' : '/xul/server/circ/checkin.xul',
     'XUL_BACKDATE' : '/xul/server/circ/backdate_post_checkin.xul',
index 2a42d3f..7abcd42 100644 (file)
@@ -4,6 +4,9 @@ dump('entering main/main.js\n');
 var xulG;
 var offlineStrings;
 var authStrings;
+var openTabs = new Array();
+var tempWindow = null;
+var tempFocusWindow = null;
 
 function grant_perms(url) {
     var perms = "UniversalXPConnect UniversalPreferencesWrite UniversalBrowserWrite UniversalPreferencesRead UniversalBrowserRead UniversalFileRead";
@@ -71,10 +74,126 @@ function start_js_shell() {
     );
 };
 
+function new_tabs(aTabList, aContinue) {
+    if(aTabList != null) {
+        openTabs = openTabs.concat(aTabList);
+    }
+    if(G.data.session) { // Just add to the list of stuff to open unless we are logged in
+        netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect");
+        var targetwindow = null;
+        var focuswindow = null;
+        var focustab = {'focus' : true};
+        if(aContinue == true && tempWindow.closed == false) {
+            if(tempWindow.g == undefined || tempWindow.g.menu == undefined) {
+                setTimeout(
+                    function() {
+                        new_tabs(null, true);
+                    }, 300);
+                return null;
+            }
+            targetwindow = tempWindow;
+            tempWindow = null;
+            focuswindow = tempFocusWindow;
+            tempFocusWindow = null;
+            focustab = {'nofocus' : true};
+        }
+        else if(tempWindow != null) { // In theory, we are waiting on a setTimeout
+            if(tempWindow.closed == true) // But someone closed our window?
+            {
+                tempWindow = null;
+                tempFocusWindow = null;
+            }
+            else
+            {
+                return null;
+            }
+        }
+        var newTab;
+        var firstURL;
+        var wm = Components.classes["@mozilla.org/appshell/window-mediator;1"].
+            getService(Components.interfaces.nsIWindowMediator);
+            // This may look out of place, but this is so we can continue this loop from down below
+opentabs:
+            while(openTabs.length > 0) {
+            newTab = openTabs.shift();
+            if(newTab == 'new' || newTab == 'init') {
+                if(newTab != 'init' && openTabs.length > 0 && openTabs[0] != 'new') {
+                    firstURL = openTabs.shift();
+                    if(firstURL != 'tab') { // 'new' followed by 'tab' should be equal to 'init' in functionality, this should do that
+                        if(urls[firstURL]) {
+                            firstURL = urls[firstURL];
+                        }
+                        firstURL = '&firstURL=' + window.escape(firstURL);
+                    }
+                    else {
+                        firstURL = '';
+                    }
+                }
+                else {
+                    firstURL = '';
+                }
+                targetwindow = xulG.window.open(urls.XUL_MENU_FRAME
+                    + '?server='+window.escape(G.data.server) + firstURL,
+                    '_blank','chrome,resizable'
+                );
+                targetwindow.xulG = xulG;
+                if (focuswindow == null) {
+                    focuswindow = targetwindow;
+                }
+                tempWindow = targetwindow;
+                tempFocusWindow = focuswindow;
+                setTimeout(
+                    function() {
+                        new_tabs(null, true);
+                    }, 300);
+                return null;
+            }
+            else {
+                if(newTab == 'tab') {
+                    newTab = null;
+                }
+                else if(urls[newTab]) {
+                    newTab = urls[newTab];
+                }
+                if(targetwindow != null) { // Already have a previous target window? Use it first.
+                    if(targetwindow.g.menu.new_tab(newTab,focustab,null)) {
+                        focustab = {'nofocus' : true};
+                        continue;
+                    }
+                }
+                var enumerator = wm.getEnumerator('eg_menu');
+                while(enumerator.hasMoreElements()) {
+                    targetwindow = enumerator.getNext();
+                    if(targetwindow.g.menu.new_tab(newTab,focustab,null)) {
+                        focustab = {'nofocus' : true};
+                        if (focuswindow == null) {
+                            focuswindow = targetwindow;
+                        }
+                        continue opentabs;
+                    }
+                }
+                // No windows found to add the tab to? Make a new one.
+                if(newTab == null) { // Were we making a "default" tab?
+                    openTabs.unshift('init'); // 'init' does that for us!
+                }
+                else {
+                    openTabs.unshift('new',newTab);
+                }
+            }
+        }
+        if(focuswindow != null) {
+            focuswindow.focus();
+        }
+    }
+}
+
 function main_init() {
     dump('entering main_init()\n');
     try {
         clear_the_cache();
+        if("arguments" in window && window.arguments.length > 0 && window.arguments[0].wrappedJSObject != undefined && window.arguments[0].wrappedJSObject.openTabs != undefined) {
+            openTabs = openTabs.concat(window.arguments[0].wrappedJSObject.openTabs);
+        }
 
         // Now we can safely load the strings without the cache getting wiped
         offlineStrings = document.getElementById('offlineStrings');
@@ -353,15 +472,7 @@ function main_init() {
             'command',
             function() {
                 if (G.data.session) {
-                    try {
-                        //G.data_xul.g.open_menu();
-                        netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect");
-                        var mframe = xulG.window.open(( String(urls.XUL_MENU_FRAME).match(/^chrome:/) ? '' : G.data.server ) + urls.XUL_MENU_FRAME
-                            + '?server='+window.escape(G.data.server),
-                            'main'+xulG.window.window_name_increment(),'chrome,resizable'
-                        );
-                        mframe.xulG = xulG; 
-                    } catch(E) { alert(E); }
+                    new_tabs(Array('new'), null, null);
                 } else {
                     alert ( offlineStrings.getString('main.new_window_btn.login_first_warning') );
                 }
index 2ee5c2c..1c9d96f 100644 (file)
@@ -21,7 +21,7 @@
 <window id="main_win" 
     onload="try { main_init(); } catch(E) { alert(E); }"
     onunload="try { G.auth.logoff(); } catch(E) { alert(E); }"
-    title="&staff.auth.title;"
+    title="&staff.auth.title;" persist="width height sizemode"
     width="640" height="480" windowtype="eg_main"
     xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
 
index aa53ed7..c882dcc 100644 (file)
@@ -27,18 +27,6 @@ main.menu = function () {
         },
         false
     );
-
-    if (xulG.pref.getBoolPref('open-ils.disable_accesskeys_on_tabs')) {
-        var tabs = document.getElementById('main_tabs');
-        for (var i = 0; i < tabs.childNodes.length; i++) {
-            tabs.childNodes[i].setAttribute('accesskey','');
-        }
-    }
-
-    if (xulG.pref.getBoolPref('open-ils.enable_join_tabs')) {
-        document.getElementById('join_tabs_menuitem_vertical').hidden = false;
-        document.getElementById('join_tabs_menuitem_horizontal').hidden = false;
-    }
 }
 
 main.menu.prototype = {
@@ -145,29 +133,24 @@ main.menu.prototype = {
             'cmd_new_window' : [
                 ['oncommand'],
                 function() {
-                    obj.data.stash_retrieve();
-                    var mframe = obj.window.open(
-                        obj.url_prefix(urls.XUL_MENU_FRAME)
-                        + '?server='+window.escape(urls.remote),
-                        'main' + obj.window.window_name_increment(),
-                        'chrome,resizable'); 
-                    netscape.security.PrivilegeManager.enablePrivilege('UniversalXPConnect');
-                    mframe.xulG = xulG;
-                    /* This window should get its own objects for these */
-                    delete mframe.xulG['_data'];
+                    var wm = Components.classes["@mozilla.org/appshell/window-mediator;1"].
+                        getService(Components.interfaces.nsIWindowMediator);
+                    wm.getMostRecentWindow('eg_main').new_tabs(Array('new'));
                 }
             ],
             'cmd_new_tab' : [
                 ['oncommand'],
-                function() { obj.new_tab(null,{'focus':true},null); }
-            ],
-            'cmd_join_tabs_vertical' : [
-                ['oncommand'],
-                function() { obj.join_tabs({'orient':'vertical'}); }
-            ],
-            'cmd_join_tabs_horizontal' : [
-                ['oncommand'],
-                function() { obj.join_tabs({'orient':'horizontal'}); }
+                function() {
+                    if (obj.new_tab(null,{'focus':true},null) == false)
+                    {
+                        if(window.confirm(offlineStrings.getString('menu.new_tab.max_tab_dialog')))
+                        {
+                            var wm = Components.classes["@mozilla.org/appshell/window-mediator;1"].
+                                getService(Components.interfaces.nsIWindowMediator);
+                            wm.getMostRecentWindow('eg_main').new_tabs(Array('tab'));
+                        }
+                    }
+                }
             ],
             'cmd_close_tab' : [
                 ['oncommand'],
@@ -1042,6 +1025,18 @@ main.menu.prototype = {
                 }
             ],
 
+            'cmd_marc_batch_edit' : [
+                ['oncommand'],
+                function() {
+                    obj.set_tab(
+                        obj.url_prefix(urls.MARC_BATCH_EDIT),{
+                            'tab_name' : offlineStrings.getString('menu.cmd_marc_batch_edit.tab')
+                        },
+                        {}
+                    );
+                }
+            ],
+
             /* Admin menu */
             'cmd_change_session' : [
                 ['oncommand'],
@@ -1283,7 +1278,6 @@ main.menu.prototype = {
                     }
                 }
             ],
-
         };
 
         JSAN.use('util.controller');
@@ -1292,12 +1286,20 @@ main.menu.prototype = {
         obj.controller.init( { 'window_knows_me_by' : 'g.menu.controller', 'control_map' : cmd_map } );
 
         obj.controller.view.tabbox = window.document.getElementById('main_tabbox');
-        obj.controller.view.tabs = obj.controller.view.tabbox.firstChild;
-        obj.controller.view.panels = obj.controller.view.tabbox.lastChild;
-
-        obj.new_tab(null,{'focus':true},null);
-
-        obj.init_tab_focus_handlers();
+        // Despite what the docs say:
+        // The "tabs" element need not be the first child
+        // The "panels" element need not be the second/last
+        // Nor need they be the only ones there.
+        // Thus, use the IDs for robustness.
+        obj.controller.view.tabs = window.document.getElementById('main_tabs');
+        obj.controller.view.panels = window.document.getElementById('main_panels');
+        obj.controller.view.tabscroller = window.document.getElementById('main_tabs_scrollbox');
+        if(params['firstURL']) {
+            obj.new_tab(params['firstURL'],{'focus':true},null);
+        }
+        else {
+            obj.new_tab(null,{'focus':true},null);
+        }
     },
 
     'spawn_search' : function(s) {
@@ -1306,55 +1308,11 @@ main.menu.prototype = {
         obj.new_patron_tab( {}, { 'doit' : 1, 'query' : js2JSON(s) } );
     },
 
-    'init_tab_focus_handlers' : function() {
-        var obj = this;
-        for (var i = 0; i < obj.controller.view.tabs.childNodes.length; i++) {
-            var tab = obj.controller.view.tabs.childNodes[i];
-            var panel = obj.controller.view.panels.childNodes[i];
-            tab.addEventListener(
-                'command',
-                function(p) {
-                    return function() {
-                        try {
-                            netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect");
-                            if (p
-                                && p.firstChild 
-                                && ( p.firstChild.nodeName == 'iframe' || p.firstChild.nodeName == 'browser' )
-                                && p.firstChild.contentWindow 
-                            ) {
-                                var cw = p.firstChild.contentWindow;
-                                var help_params = {
-                                    'protocol' : cw.location.protocol,
-                                    'hostname' : cw.location.hostname,
-                                    'port' : cw.location.port,
-                                    'pathname' : cw.location.pathname,
-                                    'src' : ''
-                                };
-                                obj.set_help_context(help_params);
-                                if (typeof cw.default_focus == 'function') {
-                                    cw.default_focus();
-                                }
-                            }
-                        } catch(E) {
-                            obj.error.sdump('D_ERROR','init_tab_focus_handler: ' + js2JSON(E));
-                        }
-                    }
-                }(panel),
-                false
-            );
-        }
-    },
-
-    // We keep a reference to content_params fed to tabs, so if we manipulate the DOM (say, via join_tabs),
-    // we can re-inject the content_params into the content if needed.  We have to watch out for memory leaks
-    // doing this.
-    'preserved_content_params' : {},
-
     'close_all_tabs' : function() {
         var obj = this;
         try {
             var count = obj.controller.view.tabs.childNodes.length;
-            for (var i = 0; i < count; i++) obj.close_tab();
+            for (var i = 1; i < count; i++) obj.close_tab();
             setTimeout( function(){ obj.controller.view.tabs.firstChild.focus(); }, 0);
         } catch(E) {
             obj.error.standard_unexpected_error_alert(offlineStrings.getString('menu.close_all_tabs.error'),E);
@@ -1363,154 +1321,101 @@ main.menu.prototype = {
 
     'close_tab' : function (specific_idx) {
         var idx = specific_idx || this.controller.view.tabs.selectedIndex;
-        var tab = this.controller.view.tabs.childNodes[idx];
         var panel = this.controller.view.panels.childNodes[ idx ];
-        while ( panel.lastChild ) panel.removeChild( panel.lastChild );
-        if (idx == 0) {
-            try {
-                this.controller.view.tabs.advanceSelectedTab(+1);
-            } catch(E) {
-                this.error.sdump('D_TAB','failed tabs.advanceSelectedTab(+1):'+js2JSON(E) + '\n');
-                try {
-                    this.controller.view.tabs.advanceSelectedTab(-1);
-                } catch(E) {
-                    this.error.sdump('D_TAB','failed again tabs.advanceSelectedTab(-1):'+
-                        js2JSON(E) + '\n');
-                }
-            }
-        } else {
-            try {
-                this.controller.view.tabs.advanceSelectedTab(-1);
-            } catch(E) {
-                this.error.sdump('D_TAB','failed tabs.advanceSelectedTab(-1):'+js2JSON(E) + '\n');
-                try {
-                    this.controller.view.tabs.advanceSelectedTab(+1);
-                } catch(E) {
-                    this.error.sdump('D_TAB','failed again tabs.advanceSelectedTab(+1):'+
-                        js2JSON(E) + '\n');
-                }
-            }
-
+        this.controller.view.tabs.removeItemAt(idx);
+        this.controller.view.panels.removeChild(panel);
+        if(this.controller.view.tabs.childNodes.length > idx) {
+            this.controller.view.tabbox.selectedIndex = idx;
         }
-        
-        this.error.sdump('D_TAB','\tnew tabbox.selectedIndex = ' + this.controller.view.tabbox.selectedIndex + '\n');
-
-        this.controller.view.tabs.childNodes[ idx ].hidden = true;
-        this.error.sdump('D_TAB','tabs.childNodes[ ' + idx + ' ].hidden = true;\n');
-
-        // Make sure we keep at least one tab open.
-        var tab_flag = true;
-        for (var i = 0; i < this.controller.view.tabs.childNodes.length; i++) {
-            var tab = this.controller.view.tabs.childNodes[i];
-            if (!tab.hidden)
-                tab_flag = false;
+        else {
+            this.controller.view.tabbox.selectedIndex = idx - 1;
         }
-        if (tab_flag) {
-            this.controller.view.tabs.selectedIndex = 0;
+        this.controller.view.tabscroller.ensureElementIsVisible(this.controller.view.tabs.selectedItem);
+        this.update_all_tab_names();
+        // Make sure we keep at least one tab open.
+        if(this.controller.view.tabs.childNodes.length == 1) {
             this.new_tab(); 
         }
     },
-
-    'join_tabs' : function(params) {
-        try {
-            if (!params) { params = {}; }
-            if (!params.orient) { params.orient = 'horizontal'; }
-
-            var left_idx = params.specific_idx || this.controller.view.tabs.selectedIndex;
-            var left_tab = this.controller.view.tabs.childNodes[left_idx];
-            var left_panel = this.controller.view.panels.childNodes[ left_idx ];
-
-            // Find next not-hidden tab
-            var right_idx;
-            for (var i = left_idx + 1; i<this.controller.view.tabs.childNodes.length; i++) {
-                var tab = this.controller.view.tabs.childNodes[i];
-                if (!tab.hidden && !right_idx) {
-                    right_idx = i;
-                }
+    
+    'update_all_tab_names' : function() {
+        var doAccessKeys = !xulG.pref.getBoolPref('open-ils.disable_accesskeys_on_tabs');
+        for(var i = 1; i < this.controller.view.tabs.childNodes.length; ++i) {
+            var tab = this.controller.view.tabs.childNodes[i];
+            tab.curindex = i;
+            tab.label = i + ' ' + tab.origlabel;
+            if(doAccessKeys && offlineStrings.testString('menu.tab' + i + '.accesskey')) {
+                tab.accessKey = offlineStrings.getString('menu.tab' + i + '.accesskey');
             }
-            if (!right_idx) { return; }
-
-            // Grab the content
-            var right_tab = this.controller.view.tabs.childNodes[right_idx];
-            var right_panel = this.controller.view.panels.childNodes[ right_idx ];
-
-            var left_content = left_panel.removeChild( left_panel.firstChild );
-            var right_content = right_panel.removeChild( right_panel.firstChild );
-
-            // Create a wrapper and shuffle the content
-            var box = params.orient == 'vertical' ? document.createElement('vbox') : document.createElement('hbox');
-            box.setAttribute('flex',1);
-            left_panel.appendChild(box);
-            box.appendChild(left_content);
-            var splitter = document.createElement('splitter');
-            splitter.appendChild( document.createElement('grippy') );
-            box.appendChild(splitter);
-            box.appendChild(right_content);
-
-            right_tab.hidden = true;
-            // FIXME: if we really want to combine labels for joined tabs, need to handle the cases where the content dynamically set their tab 
-            // labels with xulG.set_tab_name
-            left_tab.setAttribute('unadornedlabel', left_tab.getAttribute('unadornedlabel') + ' / ' + right_tab.getAttribute('unadornedlabel'));
-            left_tab.setAttribute('label', left_tab.getAttribute('label') + ' / ' + right_tab.getAttribute('unadornedlabel'));
-
-            // Re-apply content params, etc.
-            var left_params = this.preserved_content_params[ left_idx ];
-            var right_params = this.preserved_content_params[ right_idx ];
-            this.preserved_content_params[ left_idx ] = function() {
-                try {
-                    left_params();
-                    right_params();
-                } catch(E) {
-                    alert('Error re-applying content params after join_tabs');
-                }
-            };
-            this.preserved_content_params[ left_idx ]();
-
-        } catch(E) {
-            alert('Error in menu.js with join_tabs(): ' + E);
         }
     },
 
-    'find_free_tab' : function() {
-        var last_not_hidden = -1;
-        for (var i = 0; i<this.controller.view.tabs.childNodes.length; i++) {
-            var tab = this.controller.view.tabs.childNodes[i];
-            if (!tab.hidden)
-                last_not_hidden = i;
+    'new_tab' : function(url,params,content_params) {
+        var obj = this;
+        var max_tabs = 0;
+        try {
+            var max_tabs = xulG.pref.getIntPref('open-ils.window_max_tabs') || max_tabs;
         }
-        if (last_not_hidden == this.controller.view.tabs.childNodes.length - 1)
-            last_not_hidden = -1;
-        // If the one next to last_not_hidden is hidden, we want it.
-        // Basically, we fill in tabs after existing tabs for as 
-        // long as possible.
-        var idx = last_not_hidden + 1;
-        var candidate = this.controller.view.tabs.childNodes[ idx ];
-        if (candidate.hidden)
-            return idx;
-        // Alright, find the first hidden then
-        for (var i = 0; i<this.controller.view.tabs.childNodes.length; i++) {
-            var tab = this.controller.view.tabs.childNodes[i];
-            if (tab.hidden)
-                return i;
+        catch (e) {}
+        if(max_tabs > 0 && this.controller.view.tabs.childNodes.length > max_tabs) return false;
+        var tab = this.w.document.createElement('tab');
+        var panel = this.w.document.createElement('tabpanel');
+        var tabscroller = this.controller.view.tabscroller;
+        this.controller.view.tabs.appendChild(tab);
+        this.controller.view.panels.appendChild(panel);
+        tab.curindex = this.controller.view.tabs.childNodes.length - 1;
+        if(!xulG.pref.getBoolPref('open-ils.disable_accesskeys_on_tabs')) {
+            if(offlineStrings.testString('menu.tab' + tab.curindex + '.accesskey')) {
+                tab.accessKey = offlineStrings.getString('menu.tab' + tab.curindex + '.accesskey');
+            }
         }
-        return -1;
-    },
-
-    'new_tab' : function(url,params,content_params) {
-        var tc = this.find_free_tab();
-        if (tc == -1) { return null; } // 9 tabs max
-        var tab = this.controller.view.tabs.childNodes[ tc ];
-        tab.hidden = false;
+        var tabs = this.controller.view.tabs;
+        tab.addEventListener(
+            'command',
+            function() {
+                try {
+                    tabscroller.ensureElementIsVisible(tab);
+                    netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect");
+                    if (panel
+                        && panel.firstChild 
+                        && ( panel.firstChild.nodeName == 'iframe' || panel.firstChild.nodeName == 'browser' )
+                        && panel.firstChild.contentWindow 
+                    ) {
+                        var cw = panel.firstChild.contentWindow;
+                        var help_params = {
+                            'protocol' : cw.location.protocol,
+                            'hostname' : cw.location.hostname,
+                            'port' : cw.location.port,
+                            'pathname' : cw.location.pathname,
+                            'src' : ''
+                        };
+                        obj.set_help_context(help_params);
+                        if (typeof cw.default_focus == 'function') {
+                            cw.default_focus();
+                        }
+                    }
+                } catch(E) {
+                    obj.error.sdump('D_ERROR','init_tab_focus_handler: ' + js2JSON(E));
+                }
+            }
+            ,
+            false
+        );
         if (!content_params) content_params = {};
         if (!params) params = {};
         if (!params.tab_name) params.tab_name = offlineStrings.getString('menu.new_tab.tab');
         if (!params.nofocus) params.focus = true; /* make focus the default */
         try {
-            if (params.focus) this.controller.view.tabs.selectedIndex = tc;
-            params.index = tc;
+            if (params.focus) {
+                this.controller.view.tabs.selectedItem = tab;
+                tabscroller.ensureElementIsVisible(tab);
+            }
+            params.index = tab.curindex;
             this.set_tab(url,params,content_params);
+            return true;
         } catch(E) {
             this.error.sdump('D_ERROR',E);
+            return false;
         }
     },
 
@@ -1640,8 +1545,19 @@ main.menu.prototype = {
             if (params.src) { help_btn.setAttribute('src', params.src); }
         }
     },
-    'augment_content_params' : function(idx,tab,params,content_params) {
+    'set_tab' : function(url,params,content_params) {
         var obj = this;
+        if (!url) url = '/xul/server/';
+        if (!url.match(/:\/\//) && !url.match(/^data:/)) url = urls.remote + url;
+        if (!params) params = {};
+        if (!content_params) content_params = {};
+        var idx = this.controller.view.tabs.selectedIndex;
+        if (params && typeof params.index != 'undefined') idx = params.index;
+        var tab = this.controller.view.tabs.childNodes[ idx ];
+        if (params.focus) tab.focus();
+        var panel = this.controller.view.panels.childNodes[ idx ];
+        while ( panel.lastChild ) panel.removeChild( panel.lastChild );
+
         content_params.new_tab = function(a,b,c) { return obj.new_tab(a,b,c); };
         content_params.set_tab = function(a,b,c) { return obj.set_tab(a,b,c); };
         content_params.close_tab = function() { return obj.close_tab(); };
@@ -1650,7 +1566,7 @@ main.menu.prototype = {
         content_params.volume_item_creator = function(a) { return obj.volume_item_creator(a); };
         content_params.get_new_session = function(a) { return obj.get_new_session(a); };
         content_params.holdings_maintenance_tab = function(a,b,c) { return obj.holdings_maintenance_tab(a,b,c); };
-        content_params.set_tab_name = function(name) { tab.setAttribute('unadornedlabel',name); tab.setAttribute('label',(idx + 1) + ' ' + name); };
+        content_params.set_tab_name = function(name) { tab.label = tab.curindex + ' ' + name; tab.origlabel = name; };
         content_params.set_help_context = function(params) { return obj.set_help_context(params); };
         content_params.open_chrome_window = function(a,b,c) { return xulG.window.open(a,b,c); };
         content_params.url_prefix = function(url) { return obj.url_prefix(url); };
@@ -1662,24 +1578,6 @@ main.menu.prototype = {
         };
         content_params.chrome_xulG = xulG;
         content_params._data = xulG._data;
-
-        return content_params;
-    },
-    'set_tab' : function(url,params,content_params) {
-        var obj = this;
-        if (!url) url = '/xul/server/';
-        if (!url.match(/:\/\//) && !url.match(/^data:/)) url = urls.remote + url;
-        if (!params) params = {};
-        if (!content_params) content_params = {};
-        var idx = this.controller.view.tabs.selectedIndex;
-        if (obj.preserved_content_params[idx]) { delete obj.preserved_content_params[ idx ]; }
-        if (params && typeof params.index != 'undefined') idx = params.index;
-        var tab = this.controller.view.tabs.childNodes[ idx ];
-        if (params.focus) tab.focus();
-        var panel = this.controller.view.panels.childNodes[ idx ];
-        while ( panel.lastChild ) panel.removeChild( panel.lastChild );
-
-        content_params = obj.augment_content_params(idx,tab,params,content_params);
         if (params && params.tab_name) content_params.set_tab_name( params.tab_name );
         
         var frame;
@@ -1706,9 +1604,6 @@ main.menu.prototype = {
                             'passthru_content_params' : content_params,
                         }
                     );
-                    obj.preserved_content_params[ idx ] = function() {
-                        b.passthru_content_params = content_params;
-                    }
                 } catch(E) {
                     alert(E);
                 }
@@ -1718,40 +1613,37 @@ main.menu.prototype = {
                 panel.appendChild(frame);
                 dump('creating iframe with src = ' + url + '\n');
                 frame.setAttribute('src',url);
-                obj.preserved_content_params[ idx ] = function() {
-                    try {
-                        netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect");
-                        var cw = frame.contentWindow;
-                        if (typeof cw.wrappedJSObject != 'undefined') cw = cw.wrappedJSObject;
-                        cw.IAMXUL = true;
-                        cw.xulG = content_params;
-                        cw.addEventListener(
-                            'load',
-                            function() {
-                                try {
-                                    if (typeof cw.help_context_set_locally == 'undefined') {
-                                        var help_params = {
-                                            'protocol' : cw.location.protocol,
-                                            'hostname' : cw.location.hostname,
-                                            'port' : cw.location.port,
-                                            'pathname' : cw.location.pathname,
-                                            'src' : ''
-                                        };
-                                        obj.set_help_context(help_params);
-                                    } else if (typeof cw.default_focus == 'function') {
-                                        cw.default_focus();
-                                    }
-                                } catch(E) {
-                                    obj.error.sdump('D_ERROR', 'main.menu, set_tab, onload: ' + E);
+                try {
+                    netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect");
+                    var cw = frame.contentWindow;
+                    if (typeof cw.wrappedJSObject != 'undefined') cw = cw.wrappedJSObject;
+                    cw.IAMXUL = true;
+                    cw.xulG = content_params;
+                    cw.addEventListener(
+                        'load',
+                        function() {
+                            try {
+                                if (typeof cw.help_context_set_locally == 'undefined') {
+                                    var help_params = {
+                                        'protocol' : cw.location.protocol,
+                                        'hostname' : cw.location.hostname,
+                                        'port' : cw.location.port,
+                                        'pathname' : cw.location.pathname,
+                                        'src' : ''
+                                    };
+                                    obj.set_help_context(help_params);
+                                } else if (typeof cw.default_focus == 'function') {
+                                    cw.default_focus();
                                 }
-                            },
-                            false
-                        );
-                    } catch(E) {
-                        this.error.sdump('D_ERROR', 'main.menu: ' + E);
-                    }
-                };
-                obj.preserved_content_params[ idx ]();
+                            } catch(E) {
+                                obj.error.sdump('D_ERROR', 'main.menu, set_tab, onload: ' + E);
+                            }
+                        },
+                        false
+                    );
+                } catch(E) {
+                    this.error.sdump('D_ERROR', 'main.menu: ' + E);
+                }
             }
         } catch(E) {
             this.error.sdump('D_ERROR', 'main.menu:2: ' + E);
index e006e46..e87e371 100644 (file)
@@ -31,6 +31,7 @@
     onload="try { my_init(); font_helper(); } catch(E) { alert(E); }"
     orient="vertical" width="1024" height="740"
     sizemode="maximized" persist="width height sizemode" title="&staff.main.menu.title;"
+    windowtype="eg_menu"
     xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
 
     <!-- ///////////////////////////////////////////////////////////////////////////////////////////////////////////// -->
@@ -86,6 +87,7 @@
                 JSAN.use('main.menu'); g.menu = new main.menu();
                 g.menu.init( { 
                     'server' : g.cgi.param('server'),
+                    'firstURL' : g.cgi.param('firstURL'),
                 } );
 
                 JSAN.use('util.window'); g.window = new util.window();
index 4b9db59..ec4d000 100644 (file)
@@ -13,8 +13,6 @@
     <command id="cmd_new_tab" key="new-tab-key" />
     <command id="cmd_close_tab" key="close-tab-key" />
     <command id="cmd_close_all_tabs" key="close-all-tabs-key" />
-    <command id="cmd_join_tabs_vertical" label="&staff.main.menu.file.join_tabs_vertical.label;" accesskey="&staff.main.menu.file.join_tabs_vertical.accesskey;" />
-    <command id="cmd_join_tabs_horizontal" label="&staff.main.menu.file.join_tabs_horizontal.label;" accesskey="&staff.main.menu.file.join_tabs_horizontal.accesskey;" />
     <command id="cmd_shutdown" />
 
     <command id="cmd_edit_copy_buckets" />
@@ -50,6 +48,7 @@
     <command id="cmd_print_list_template_edit" />
     <command id="cmd_z39_50_import" />
     <command id="cmd_create_new_marc_book" />
+    <command id="cmd_marc_batch_edit" label="&staff.main.menu.cat.marc_batch_edit.label;" accesskey="&staff.main.menu.cat.marc_batch_edit.accesskey;"/>
     <command id="cmd_replace_barcode" />
     <command id="cmd_reprint" />
     <command id="cmd_retrieve_last_patron" />
     <menupopup id="main.menu.file.popup">
         <menuitem label="&staff.main.menu.file.new.label;" accesskey="&staff.main.menu.file.new.accesskey;" key="new-window-key" command="cmd_new_window"/>
         <menuitem label="&staff.main.menu.file.new_tab.label;" accesskey="&staff.main.menu.file.new_tab.accesskey;" key="new-tab-key" command="cmd_new_tab"/>
-        <menuitem id="join_tabs_menuitem_horizontal" hidden="true" command="cmd_join_tabs_horizontal"/>
-        <menuitem id="join_tabs_menuitem_vertical" hidden="true" command="cmd_join_tabs_vertical"/>
         <menuseparator />
         <menuitem label="&staff.main.menu.file.close_tab.label;" accesskey="&staff.main.menu.file.close_tab.accesskey;" oldaccesskey="&staff.main.menu.file.close_tab.key;" key="close-tab-key" command="cmd_close_tab"/>
         <menuitem label="&staff.main.menu.tabs.close;" accesskey="&staff.main.menu.tabs.close.accesskey;" key="close-all-tabs-key" command="cmd_close_all_tabs"/>
         <menuitem label="&staff.main.menu.cat.create_marc.label;" accesskey="&staff.main.menu.cat.create_marc.accesskey;" command="cmd_create_marc"/>
         <menuitem label="&staff.main.menu.cat.z39_50_import.label;" accesskey="&staff.main.menu.cat.z39_50_import.accesskey;" command="cmd_z39_50_import"/>
         <menuitem label="&staff.main.menu.cat.vandelay.label;" command="cmd_open_vandelay"/>
+        <menuitem command="cmd_marc_batch_edit"/>
         <menuseparator />
         <menuitem label="&staff.main.menu.replace_barcode.label;" command="cmd_replace_barcode"/>
         <menuitem label="&staff.main.menu.cat.retrieve_last_record.label;" accesskey="&staff.main.menu.cat.retrieve_last_record.accesskey;" command="cmd_retrieve_last_record" key="retrieve_last_record_key"/>
index d1a176e..58bf366 100644 (file)
 <box id="menu_frame_main" flex="1" orient="vertical">
     <toolbox id="main_toolbox"/>
     <tabbox id="main_tabbox" flex="1" eventnode="window" handleCtrlTab="true">
-        <tabs id="main_tabs" closebutton="true" onclosetab="g.menu.close_tab()">
-            <tab id="tab_1" accesskey="&staff.chrome.menu_frame_overlay.tab1.accesskey;" label="&staff.chrome.menu_frame_overlay.tab1.label;" hidden="true" />
-            <tab id="tab_2" accesskey="&staff.chrome.menu_frame_overlay.tab2.accesskey;" label="&staff.chrome.menu_frame_overlay.tab2.label;" hidden="true" />
-            <tab id="tab_3" accesskey="&staff.chrome.menu_frame_overlay.tab3.accesskey;" label="&staff.chrome.menu_frame_overlay.tab3.label;" hidden="true" />
-            <tab id="tab_4" accesskey="&staff.chrome.menu_frame_overlay.tab4.accesskey;" label="&staff.chrome.menu_frame_overlay.tab4.label;" hidden="true" />
-            <tab id="tab_5" accesskey="&staff.chrome.menu_frame_overlay.tab5.accesskey;" label="&staff.chrome.menu_frame_overlay.tab5.label;" hidden="true" />
-            <tab id="tab_6" accesskey="&staff.chrome.menu_frame_overlay.tab6.accesskey;" label="&staff.chrome.menu_frame_overlay.tab6.label;" hidden="true" />
-            <tab id="tab_7" accesskey="&staff.chrome.menu_frame_overlay.tab7.accesskey;" label="&staff.chrome.menu_frame_overlay.tab7.label;" hidden="true" />
-            <tab id="tab_8" accesskey="&staff.chrome.menu_frame_overlay.tab8.accesskey;" label="&staff.chrome.menu_frame_overlay.tab8.label;" hidden="true" />
-            <tab id="tab_9" accesskey="&staff.chrome.menu_frame_overlay.tab9.accesskey;" label="&staff.chrome.menu_frame_overlay.tab9.label;" hidden="true" />
-        </tabs>
+        <hbox>
+            <arrowscrollbox orient="horizontal" id="main_tabs_scrollbox" flex="2">
+                <tabs id="main_tabs">
+                    <tab hidden="true" />
+                </tabs>
+            </arrowscrollbox>
+            <toolbarbutton id="main_tabs_closebutton" class="tabs-closebutton close-button" oncommand="g.menu.close_tab()" />
+        </hbox>
         <tabpanels id="main_panels" flex="1">
-            <tabpanel id="panel_1"><label value="panel_1"/></tabpanel>
-            <tabpanel id="panel_2"><label value="panel_2"/></tabpanel>
-            <tabpanel id="panel_3"><label value="panel_3"/></tabpanel>
-            <tabpanel id="panel_4"><label value="panel_4"/></tabpanel>
-            <tabpanel id="panel_5"><label value="panel_5"/></tabpanel>
-            <tabpanel id="panel_6"><label value="panel_6"/></tabpanel>
-            <tabpanel id="panel_7"><label value="panel_7"/></tabpanel>
-            <tabpanel id="panel_8"><label value="panel_8"/></tabpanel>
-            <tabpanel id="panel_9"><label value="panel_9"/></tabpanel>
+            <tabpanel />
         </tabpanels>
     </tabbox>
     <statusbar>
@@ -83,7 +72,7 @@
         <menu id="main.menu.admin" />
         <menu id="main.menu.help" />
     </menubar>
-    <toolbar id="main_toolbar" hidden="true" class="chromeclass-toolbar">
+    <toolbar id="main_toolbar" hidden="true">
         <toolbarbutton id="tb_checkout" 
             command="cmd_circ_checkout" 
             image="chrome://open_ils_staff_client/skin/media/images/Arrow-rightup-small.png" 
index 8f6ff09..6b3aaad 100644 (file)
@@ -1,19 +1,19 @@
 dump('entering util/deck.js\n');
 
 if (typeof util == 'undefined') util = {};
-util.deck = function (id) {
+util.deck = function (id_or_node) {
 
-    this.node = document.getElementById(id);
+    this.node = typeof id_or_node == 'object' ? id_or_node : document.getElementById(id_or_node);
 
     JSAN.use('util.error'); this.error = new util.error();
 
     if (!this.node) {
-        var error = 'util.deck: Could not find element ' + id;
+        var error = 'util.deck: Could not find element ' + id_or_node;
         this.error.sdump('D_ERROR',error);
         throw(error);
     }
     if (this.node.nodeName != 'deck') {
-        var error = 'util.deck: ' + id + 'is not a deck' + "\nIt's a " + this.node.nodeName;
+        var error = 'util.deck: ' + id_or_node + 'is not a deck' + "\nIt's a " + this.node.nodeName;
         this.error.sdump('D_ERROR',error);
         throw(error);
     }
index 739a244..dc1570a 100644 (file)
@@ -305,12 +305,20 @@ util.list.prototype = {
             }
             /* local file will trump remote file if allowed, so save ourselves an http request if this is the case */
             if (obj.data.hash.aous['url.remote_column_settings'] && ! my_cols ) {
-                var x = new XMLHttpRequest();
-                var url = obj.data.hash.aous['url.remote_column_settings'] + '/tree_columns_for_' + window.escape(id);
-                x.open("GET", url, false);
-                x.send(null);
-                if (x.status == 200) {
-                    my_cols = JSON2js( x.responseText );
+                try {
+                    var x = new XMLHttpRequest();
+                    var url = obj.data.hash.aous['url.remote_column_settings'] + '/tree_columns_for_' + window.escape(id);
+                    x.open("GET", url, false);
+                    x.send(null);
+                    if (x.status == 200) {
+                        my_cols = JSON2js( x.responseText );
+                    }
+                } catch(E) {
+                    // This can happen in the offline interface if you logged in previously and url.remote_column_settings is set.
+                    // 1) You may be really "offline" now
+                    // 2) the URL may just be a path component without a hostname (ie "/xul/column_settings/"), which won't work
+                    // when appended to chrome://open_ils_staff_client/
+                    dump('Error retrieving column settings from ' + url + ': ' + E + '\n');
                 }
             }
 
index 4ed7cb5..5688f11 100644 (file)
@@ -490,7 +490,7 @@ util.network.prototype = {
             if ( 
                 (typeof result.ilsevent != 'undefined') && 
                 (
-                    (override_params.overridable_events.indexOf( result.ilsevent == null ? null : Number(result.ilsevent) ) != -1) ||
+                    (override_params.overridable_events.indexOf( result.ilsevent == null || result.ilsevent == '' ? null : Number(result.ilsevent) ) != -1) ||
                     (override_params.overridable_events.indexOf( result.textcode ) != -1)
                 )
             ) {
@@ -501,7 +501,7 @@ util.network.prototype = {
                     if ( 
                         (result[i].ilsevent != 'undefined') && 
                         (
-                            (override_params.overridable_events.indexOf( result[i].ilsevent == null ? null : Number(result[i].ilsevent) ) != -1) ||
+                            (override_params.overridable_events.indexOf( result[i].ilsevent == null || result.ilsevent == '' ? null : Number(result[i].ilsevent) ) != -1) ||
                             (override_params.overridable_events.indexOf( result[i].textcode ) != -1) 
                         )
                     ) {
index 87e1618..3f9e884 100644 (file)
@@ -16,6 +16,10 @@ util.print = function (context) {
     var prefs = Components.classes['@mozilla.org/preferences-service;1'].getService(Components.interfaces['nsIPrefBranch']);
     var key = 'oils.printer.external.cmd.' + this.context;
     var has_key = prefs.prefHasUserValue(key);
+    if(!has_key && this.context != 'default') {
+        key = 'oils.printer.external.cmd.default';
+        has_key = prefs.prefHasUserValue(key);
+    }
     this.oils_printer_external_cmd = has_key ? prefs.getCharPref(key) : '';
 
     return this;
index 5099a4c..822aa1f 100644 (file)
                     document.getElementById('browser_print').hidden = false;
                 }
 
+                if (xul_param('show_toolbar')) {
+                    document.getElementById('browser_toolbar').hidden = xul_param('show_toolbar');
+                }
+
                 if (xul_param('title')) {
                     try { document.title = xul_param('title'); } catch(E) {}
                     try { window.title = xul_param('title'); } catch(E) {}
     </popupset>
 
     <vbox flex="1">
-        <hbox>
+        <hbox id="browser_toolbar">
             <button id="back" command="cmd_back" disabled="true" hidden="true"/>
             <button id="reload" command="cmd_reload" disabled="false" hidden="false"/>
             <button id="forward" command="cmd_forward" disabled="true" hidden="true"/>
index 90aac64..358519a 100644 (file)
@@ -27,6 +27,7 @@ util.widgets.EXPORT_OK    = [
     'set_text',
     'save_attributes',
     'load_attributes',
+    'find_descendants_by_name'
 ];
 util.widgets.EXPORT_TAGS    = { ':all' : util.widgets.EXPORT_OK };
 
@@ -449,4 +450,10 @@ util.widgets.removeProperty = function(e, c) {
        e.setAttribute('properties', prop_class_string);
 }
 
+util.widgets.find_descendants_by_name = function(top_node,name) {
+    top_node = util.widgets.get(top_node);
+    if (!top_node) { return []; }
+    return top_node.getElementsByAttribute('name',name);
+}
+
 dump('exiting util/widgets.js\n');
index 9d80367..fc1c19a 100644 (file)
@@ -17,18 +17,6 @@ util.window.prototype = {
     // list of documents for debugging.  BROKEN
     'doc_list' : [],    
 
-    // Windows need unique names.  This number helps.
-    'window_name_increment' :  function() {
-        JSAN.use('OpenILS.data'); var data = new OpenILS.data(); data.init({'via':'stash'});
-        if (typeof data.window_name_increment == 'undefined') {
-            data.window_name_increment = 1;
-        } else {
-            data.window_name_increment++;
-        }
-        data.stash('window_name_increment');
-        return data.window_name_increment;
-    },
-
     // This number gets put into the title bar for Top Level menu interface windows
     'appshell_name_increment' : function() {
         JSAN.use('OpenILS.data'); var data = new OpenILS.data(); data.init({'via':'stash'});
@@ -74,7 +62,7 @@ util.window.prototype = {
     'open' : function(url,title,features,my_xulG) {
         netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect");
         var key;
-        if (!title) title = 'anon' + this.window_name_increment();
+        if (!title) title = '_blank';
         if (!features) features = 'chrome';
         this.error.sdump('D_WIN', 'opening ' + url + ', ' + title + ', ' + features + ' from ' + this.win + '\n');
         var data;
index 7fec403..8c2d949 100644 (file)
@@ -115,6 +115,7 @@ menu.cmd_browse_holds_shelf.tab=Holds Shelf
 menu.cmd_browse_hold_pull_list.tab=On Shelf Pull List
 menu.cmd_local_admin.tab=Local Administration
 menu.cmd_open_vandelay.tab=MARC Import/Export
+menu.cmd_marc_batch_edit.tab=MARC Batch Edit
 menu.cmd_open_conify.tab=Server Settings
 menu.cmd_retrieve_last_patron.session.error=No patron visited yet this session.
 menu.cmd_retrieve_last_record.session.error=No record visited yet this session.
@@ -139,6 +140,7 @@ menu.spawn_search.msg=Editor would like to search for: %1$s
 menu.cmd_verify_credentials.tabname=Verify Credentials
 menu.close_all_tabs.error=Error closing all tabs
 menu.new_tab.tab=Tab
+menu.new_tab.max_tab_dialog=Sorry, we can't create any more tabs in this window.\nWould you like to create a new tab in another window?
 main.session_cookie.error=Error setting session cookie: %1$s
 menu.set_tab.error=pause for error
 menu.reset_network_stats=Reset network activity summary?
@@ -267,3 +269,13 @@ printing.nothing_to_reprint=Nothing to re-print
 printing.prompt_for_external_print_cmd=Enter external print command and parameters (use %receipt.txt% or %receipt.html% as the file containing the print data. Those values will be substituted with the proper path.):
 printing.print_strategy_saved=Print strategy (%1$s) for %2$s context saved to file system.
 text_editor.prompt_for_external_cmd=Enter external text editor command and parameters (use %letter.txt% as the file containing the text. This value will be substituted with the proper path.):
+menu.tab1.accesskey=1
+menu.tab2.accesskey=2
+menu.tab3.accesskey=3
+menu.tab4.accesskey=4
+menu.tab5.accesskey=5
+menu.tab6.accesskey=6
+menu.tab7.accesskey=7
+menu.tab8.accesskey=8
+menu.tab9.accesskey=9
+menu.tab10.accesskey=0
diff --git a/Open-ILS/xul/staff_client/components/clh.js b/Open-ILS/xul/staff_client/components/clh.js
new file mode 100644 (file)
index 0000000..9018103
--- /dev/null
@@ -0,0 +1,221 @@
+const nsISupports           = Components.interfaces.nsISupports;\r
+const nsICategoryManager    = Components.interfaces.nsICategoryManager;\r
+const nsIComponentRegistrar = Components.interfaces.nsIComponentRegistrar;\r
+const nsICommandLine        = Components.interfaces.nsICommandLine;\r
+const nsICommandLineHandler = Components.interfaces.nsICommandLineHandler;\r
+const nsIFactory            = Components.interfaces.nsIFactory;\r
+const nsIModule             = Components.interfaces.nsIModule;\r
+const nsIWindowWatcher      = Components.interfaces.nsIWindowWatcher;\r
+\r
+\r
+const XUL_STANDALONE = "chrome://open_ils_staff_client/content/circ/offline.xul";\r
+const XUL_MAIN = "chrome://open_ils_staff_client/content/main/main.xul";\r
+const WINDOW_STANDALONE = "eg_offline"\r
+const WINDOW_MAIN = "eg_main"\r
+\r
+const clh_contractID = "@mozilla.org/commandlinehandler/general-startup;1?type=egcli";\r
+const clh_CID = Components.ID("{7e608198-7355-483a-a85a-20322e4ef91a}");\r
+// category names are sorted alphabetically. Typical command-line handlers use a\r
+// category that begins with the letter "m".\r
+const clh_category = "m-egcli";\r
+\r
+/**\r
+ * Utility functions\r
+ */\r
+\r
+/**\r
+ * Opens a chrome window.\r
+ * @param aChromeURISpec a string specifying the URI of the window to open.\r
+ * @param aArgument an argument to pass to the window (may be null)\r
+ */\r
+function findOrOpenWindow(aWindowType, aChromeURISpec, aName, aArgument)\r
+{\r
+  var wm = Components.classes["@mozilla.org/appshell/window-mediator;1"].\r
+    getService(Components.interfaces.nsIWindowMediator);\r
+  var targetWindow = wm.getMostRecentWindow(aWindowType);\r
+  if (targetWindow != null) {\r
+      if(typeof targetWindow.new_tabs == 'function' && aArgument != null)\r
+      {\r
+          targetWindow.new_tabs(aArgument);\r
+      }\r
+      else {\r
+          targetwindow.focus;\r
+      }\r
+  }\r
+  else {\r
+    var params = null;\r
+    if (aArgument != null && aArgument.length != 0)\r
+    {\r
+        params = { "openTabs" : aArgument };\r
+        params.wrappedJSObject = params;\r
+    }\r
+    var ww = Components.classes["@mozilla.org/embedcomp/window-watcher;1"].\r
+        getService(Components.interfaces.nsIWindowWatcher);\r
+    ww.openWindow(null, aChromeURISpec, aName,\r
+        "chrome,resizable,dialog=no", params);\r
+  }\r
+}\r
\r
+/**\r
+ * The XPCOM component that implements nsICommandLineHandler.\r
+ * It also implements nsIFactory to serve as its own singleton factory.\r
+ */\r
+const myAppHandler = {\r
+  /* nsISupports */\r
+  QueryInterface : function clh_QI(iid)\r
+  {\r
+    if (iid.equals(nsICommandLineHandler) ||\r
+        iid.equals(nsIFactory) ||\r
+        iid.equals(nsISupports))\r
+      return this;\r
+\r
+    throw Components.results.NS_ERROR_NO_INTERFACE;\r
+  },\r
+\r
+  /* nsICommandLineHandler */\r
+\r
+  handle : function clh_handle(cmdLine)\r
+  {\r
+    // Each of these options is used for opening a new tab, either on login or remote send.\r
+    // XULRunner does some sanitize to turn /ilsblah into -ilsblah, for example.\r
+    // In addition to the ones here, -ilslogin, -ilsoffline, and -ilsstandalone\r
+    // are defined below.\r
+\r
+    // With the exception of 'new', 'tab', and 'init', the value is checked for in urls in main.js.\r
+\r
+    // NOTE: The option itself should be all lowercase (we .toLowerCase below)\r
+    var options = {\r
+    '-ilscheckin' : 'XUL_CHECKIN',\r
+    '-ilscheckout' : 'XUL_PATRON_BARCODE_ENTRY',\r
+    '-ilsnew' : 'new', // 'new' is a special keyword for opening a new window\r
+    '-ilstab' : 'tab', // 'tab' is a special keyword for opening a new tab with the default content\r
+    '-ilsnew_default' : 'init', // 'init' is a special keyword for opening a new window with an initial default tab\r
+    };\r
+\r
+    var inParams = new Array();\r
+       var position = 0;\r
+       while (position < cmdLine.length) {\r
+               var arg = cmdLine.getArgument(position).toLowerCase();\r
+               if (options[arg] != undefined) {\r
+                       inParams.push(options[arg]);\r
+                       cmdLine.removeArguments(position,position);\r
+                       continue;\r
+               }\r
+        if (arg == '-ilsurl' && cmdLine.length > position) {\r
+                 inParams.push(cmdLine.getArgument(position + 1));\r
+                 cmdLine.removeArguments(position, position + 1);\r
+                 continue;\r
+               }\r
+               position=position + 1;\r
+       }\r
+\r
+       if (cmdLine.handleFlag("ILSlogin", false) || inParams.length > 0) {\r
+         findOrOpenWindow(WINDOW_MAIN, XUL_MAIN, '_blank', inParams);\r
+         cmdLine.preventDefault = true;\r
+       }\r
+\r
+    if (cmdLine.handleFlag("ILSoffline", false) || cmdLine.handleFlag("ILSstandalone", false)) {\r
+         findOrOpenWindow(WINDOW_STANDALONE, XUL_STANDALONE, 'Offline', null);\r
+      cmdLine.preventDefault = true;\r
+       }\r
+  },\r
+\r
+  // CHANGEME: change the help info as appropriate, but\r
+  // follow the guidelines in nsICommandLineHandler.idl\r
+  // specifically, flag descriptions should start at\r
+  // character 24, and lines should be wrapped at\r
+  // 72 characters with embedded newlines,\r
+  // and finally, the string should end with a newline\r
+  helpInfo : "  -ILScheckin          Open an Evergreen checkin tab\n" +\r
+             "  -ILScheckout         Open an Evergreen checkout tab\n" +\r
+             "  -ILSnew              Open a new Evergreen 'menu' window\n" +\r
+             "  -ILSnew_default      Open a new Evergreen 'menu' window,\n" +\r
+             "                       with a 'default' tab\n" +\r
+             "  -ILStab              Open a 'default' tab alone\n" +\r
+             "  -ILSurl <url>        Open the specified url in an Evergreen tab\n" +\r
+             "  The above six imply -ILSlogin\n" +\r
+             "  -ILSlogin            Open the Evergreen Login window\n" +\r
+             "  -ILSstandalone       Open the Evergreen Standalone interface\n" +\r
+             "  -ILSoffline          Alias for -ILSstandalone\n",\r
+\r
+  /* nsIFactory */\r
+\r
+  createInstance : function clh_CI(outer, iid)\r
+  {\r
+    if (outer != null)\r
+      throw Components.results.NS_ERROR_NO_AGGREGATION;\r
+\r
+    return this.QueryInterface(iid);\r
+  },\r
+\r
+  lockFactory : function clh_lock(lock)\r
+  {\r
+    /* no-op */\r
+  }\r
+};\r
+\r
+/**\r
+ * The XPCOM glue that implements nsIModule\r
+ */\r
+const myAppHandlerModule = {\r
+  /* nsISupports */\r
+  QueryInterface : function mod_QI(iid)\r
+  {\r
+    if (iid.equals(nsIModule) ||\r
+        iid.equals(nsISupports))\r
+      return this;\r
+\r
+    throw Components.results.NS_ERROR_NO_INTERFACE;\r
+  },\r
+\r
+  /* nsIModule */\r
+  getClassObject : function mod_gch(compMgr, cid, iid)\r
+  {\r
+    if (cid.equals(clh_CID))\r
+      return myAppHandler.QueryInterface(iid);\r
+\r
+    throw Components.results.NS_ERROR_NOT_REGISTERED;\r
+  },\r
+\r
+  registerSelf : function mod_regself(compMgr, fileSpec, location, type)\r
+  {\r
+    compMgr.QueryInterface(nsIComponentRegistrar);\r
+\r
+    compMgr.registerFactoryLocation(clh_CID,\r
+                                    "myAppHandler",\r
+                                    clh_contractID,\r
+                                    fileSpec,\r
+                                    location,\r
+                                    type);\r
+\r
+    var catMan = Components.classes["@mozilla.org/categorymanager;1"].\r
+      getService(nsICategoryManager);\r
+    catMan.addCategoryEntry("command-line-handler",\r
+                            clh_category,\r
+                            clh_contractID, true, true);\r
+  },\r
+\r
+  unregisterSelf : function mod_unreg(compMgr, location, type)\r
+  {\r
+    compMgr.QueryInterface(nsIComponentRegistrar);\r
+    compMgr.unregisterFactoryLocation(clh_CID, location);\r
+\r
+    var catMan = Components.classes["@mozilla.org/categorymanager;1"].\r
+      getService(nsICategoryManager);\r
+    catMan.deleteCategoryEntry("command-line-handler", clh_category);\r
+  },\r
+\r
+  canUnload : function (compMgr)\r
+  {\r
+    return true;\r
+  }\r
+};\r
+\r
+/* The NSGetModule function is the magic entry point that XPCOM uses to find what XPCOM objects\r
+ * this component provides\r
+ */\r
+function NSGetModule(comMgr, fileSpec)\r
+{\r
+  return myAppHandlerModule;\r
+}\r
+\r
index 5d50924..9cf3e21 100644 (file)
@@ -18,7 +18,7 @@
 <?xul-overlay href="/xul/server/OpenILS/util_overlay.xul"?>
 
 <window id="main_test_win" 
-    onload="try { my_init(); font_helper(); } catch(E) { alert(E); }"
+    onload="try { my_init(); font_helper(); persist_helper(); } catch(E) { alert(E); }"
     xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
 
     <!-- ///////////////////////////////////////////////////////////////////////////////////////////////////////////// -->
index da35223..f666257 100644 (file)
@@ -18,7 +18,7 @@
 <?xul-overlay href="/xul/server/OpenILS/util_overlay.xul"?>
 
 <window id="example_template_win" 
-    onload="try { my_init(); } catch(E) { alert(E); }"
+    onload="try { my_init(); font_helper(); persist_helper(); } catch(E) { alert(E); }"
     xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
 
     <!-- ///////////////////////////////////////////////////////////////////////////////////////////////////////////// -->
index bece26a..d556207 100644 (file)
@@ -18,7 +18,7 @@
 <?xul-overlay href="/xul/server/OpenILS/util_overlay.xul"?>
 
 <window id="main_test_win" 
-    onload="try { my_init(); font_helper(); } catch(E) { alert(E); }"
+    onload="try { my_init(); font_helper(); persist_helper(); } catch(E) { alert(E); }"
     xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
 
     <!-- ///////////////////////////////////////////////////////////////////////////////////////////////////////////// -->
index 491bf4a..3b968a5 100644 (file)
@@ -18,7 +18,7 @@
 <?xul-overlay href="/xul/server/OpenILS/util_overlay.xul"?>
 
 <window id="font_settings_win" 
-    onload="try { my_init(); } catch(E) { alert(E); }" style="background: white;"
+    onload="try { my_init(); persist_helper(); } catch(E) { alert(E); }" style="background: white;"
     xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
 
     <!-- ///////////////////////////////////////////////////////////////////////////////////////////////////////////// -->
index aa01bc5..60a3327 100644 (file)
             function getBuildId() { return location.href.match(/\/xul\/(.+?)\/server\//)[1]; }
 
             function my_init() {
-                try {
-                    dojo.require("dojo.cookie");
-                    window.xulG.auth = {"session": {"key": dojo.cookie("ses")}};
-                } catch(E) { /* XXX ignorable except for booking links */ }
+                ;
             }
         </script>
         <style type='text/css'>
@@ -50,7 +47,6 @@
                         <th width='25%'>&staff.server.admin.index.workstation_configuration;</th>
                         <th width='25%'>&staff.server.admin.index.library_configuration;</th>
                         <th width='25%'>&staff.server.admin.index.maintenance_reports;</th>
-                        <th width='25%'>&staff.server.admin.index.booking;</th>
                     </tr>
                 </thead>
                 <tbody>
@@ -80,6 +76,9 @@
                             </div>
                         </td><td>
                             <div style='padding: 8px;'>
+                                <a href='javascript:window.xulG.new_tab(window.xulG.url_prefix("/opac/extras/circ/alt_holds_print.html").replace("http","https") + "?do=shelf_expired_holds&amp;chunk_size=25",{"tab_name":"&staff.server.admin.index.expired_holds_shelf;"},{});'>&staff.server.admin.index.expired_holds_shelf;</a>
+                            </div>
+                            <div style='padding: 8px;'>
                                 <a href='javascript:window.xulG.new_tab("/xul/server/patron/holds.xul",{"tab_name":"&staff.server.admin.index.hold_pull_list;"},{});'>&staff.server.admin.index.hold_pull_list;</a> <span style="color: red">&staff.server.admin.index.testing;</span>
                             </div>
                             <div style='padding: 8px;'>
                             <div style='padding: 8px;'>
                                 <a href='javascript:window.xulG.new_tab("/xul/server/admin/transit_list.xul",{"tab_name":"&staff.server.admin.index.transits;"},{});'>&staff.server.admin.index.transit_list;</a>
                             </div>
-                        </td><td>
-                            <div style='padding: 8px;'>
-                                <a href='javascript:window.xulG.new_tab("/eg/booking/reservation",{"tab_name":"&staff.server.admin.index.booking.reservation;","browser":false},window.xulG);'>&staff.server.admin.index.booking.reservation;</a> <span style="color: red">&staff.server.admin.index.testing;</span>
-                            </div>
-                            <div style='padding: 8px;'>
-                                <a href='javascript:window.xulG.new_tab("/eg/booking/pull_list",{"tab_name":"&staff.server.admin.index.booking.pull_list;","browser":false},window.xulG);'>&staff.server.admin.index.booking.pull_list;</a> <span style="color: red">&staff.server.admin.index.testing;</span>
-                            </div>
-                            <div style='padding: 8px;'>
-                                <a href='javascript:window.xulG.new_tab("/eg/booking/capture",{"tab_name":"&staff.server.admin.index.booking.capture;","browser":false},window.xulG);'>&staff.server.admin.index.booking.capture;</a> <span style="color: red">&staff.server.admin.index.testing;</span>
-                            </div>
-                            <div style='padding: 8px;'>
-                                <a href='javascript:window.xulG.new_tab("/eg/booking/pickup",{"tab_name":"&staff.server.admin.index.booking.pickup;","browser":false},window.xulG);'>&staff.server.admin.index.booking.pickup;</a> <span style="color: red">&staff.server.admin.index.testing;</span>
-                            </div>
-                            <div style='padding: 8px;'>
-                                <a href='javascript:window.xulG.new_tab("/eg/booking/return",{"tab_name":"&staff.server.admin.index.booking.return;","browser":false},window.xulG);'>&staff.server.admin.index.booking.return;</a> <span style="color: red">&staff.server.admin.index.testing;</span>
-                            </div>
                         </td>
                     </tr>
                 </tbody>
index dd8b1d5..9e2ae92 100644 (file)
@@ -19,7 +19,7 @@
 <?xul-overlay href="/xul/server/OpenILS/util_overlay.xul"?>
 
 <window id="admin_offline_manage_xacts_win" 
-    onload="try { my_init(); font_helper(); } catch(E) { alert(E); }"
+    onload="try { my_init(); font_helper(); persist_helper(); } catch(E) { alert(E); }"
     xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
 
     <!-- ///////////////////////////////////////////////////////////////////////////////////////////////////////////// -->
@@ -55,7 +55,7 @@
 
     <vbox id="admin_offline_manage_xacts_main" flex="1">
 
-        <groupbox flex="1">
+        <groupbox flex="1" id="before_splitter" oils_persist="height">
             <caption label="&staff.server.admin.offline.xacts.caption;"/>
             <hbox>
                 <button id="refresh" label="&common.refresh;" accesskey="&staff.server.admin.offline.xacts.refresh.accesskey;"/>
@@ -66,8 +66,8 @@
             </hbox>
             <tree id="session_tree" enableColumnDrag="true" seltype="single" flex="1"/>
         </groupbox>
-            <splitter><grippy/></splitter>
-        <deck flex="1" id="deck">
+        <splitter id="splitter" oils_persist="state" oils_persist_peers="before_splitter deck"><grippy/></splitter>
+        <deck flex="1" id="deck" oils_persist="height">
             <label value=" "/>
             <groupbox flex="1">
                 <caption id="status_caption" label="&staff.server.admin.offline.xacts.status.label;"/>
index 5df6609..5d651f6 100644 (file)
@@ -18,7 +18,7 @@
 <?xul-overlay href="/xul/server/OpenILS/util_overlay.xul"?>
 
 <window id="admin_transit_list_win" active="true" 
-    onload="try { my_init(); font_helper(); } catch(E) { alert(E); }"
+    onload="try { my_init(); font_helper(); persist_helper(); } catch(E) { alert(E); }"
     xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
 
     <!-- ///////////////////////////////////////////////////////////////////////////////////////////////////////////// -->
index 8132723..05a01c0 100644 (file)
@@ -33,7 +33,7 @@
     <script type="text/javascript" src="work_log.js"/>
 
     <vbox flex="1">
-        <vbox flex="1">
+        <vbox flex="1" id="before_splitter" oils_persist="height">
             <hbox>
                 <textbox id="desire_number_of_work_log_entries" type="number" oils_persist="value" />
                 <label value="&staff.admin.work_log.list1.header;" class="header1"/>
@@ -44,8 +44,8 @@
             </hbox>
             <tree id="work_action_log" flex="1" enableColumnDrag="true" context="work_log_actions"/>
         </vbox>
-        <splitter><grippy/></splitter>
-        <vbox flex="1">
+        <splitter id="splitter" oils_persist="state hidden" oils_persist_peers="before_splitter after_splitter"><grippy/></splitter>
+        <vbox flex="1" id="after_splitter" oils_persist="height">
             <hbox>
                 <textbox id="desire_number_of_patron_log_entries" type="number" oils_persist="value" />
                 <label value="&staff.admin.work_log.list2.header;" class="header1"/>
index 476ff7e..21c36c8 100644 (file)
@@ -22,7 +22,7 @@ vim: noet:sw=4:ts=4:
 <?xul-overlay href="/xul/server/cat/bib_brief_overlay.xul"?>
 
 <window id="cat_bib_brief_win" 
-    onload="try { my_init(); font_helper(); } catch(E) { alert(E); }"
+    onload="try { my_init(); font_helper(); persist_helper(); } catch(E) { alert(E); }"
     xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
 
     <!-- ///////////////////////////////////////////////////////////////////////////////////////////////////////////// -->
diff --git a/Open-ILS/xul/staff_client/server/cat/bib_brief_overlay_vertical.xul b/Open-ILS/xul/staff_client/server/cat/bib_brief_overlay_vertical.xul
new file mode 100644 (file)
index 0000000..e118349
--- /dev/null
@@ -0,0 +1,62 @@
+<?xml version="1.0"?>
+<!DOCTYPE overlay PUBLIC "" ""[
+    <!--#include virtual="/opac/locale/${locale}/lang.dtd"-->
+]>
+<overlay id="bib_brief_overlay" 
+    xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+
+        <script type="text/javascript" src="/xul/server/cat/bib_brief_overlay.js"/>
+
+        <grid id="bib_brief_grid" flex="0">
+            <columns>
+                <column />
+                <column />
+            </columns>
+            <rows id="bib_brief_grid_rows">
+                <row id="bib_brief_grid_row1" position="1">
+                    <label value="&staff.cat.bib_brief.title.label;" accesskey="&staff.cat.bib_brief.title.accesskey;" control="title" class="emphasis"/>
+                    <textbox id="title" name="title" readonly="true" context="clipboard" class="plain" onfocus="this.select()"/>
+                </row>
+                <row>
+                    <label value="&staff.cat.bib_brief.author.label;" accesskey="&staff.cat.bib_brief.author.accesskey;" control="author" class="emphasis"/>
+                    <textbox id="author" name="author" readonly="true" context="clipboard" class="plain" onfocus="this.select()"/>
+                </row>
+                <row position="2">
+                    <label value="&staff.cat.bib_brief.edition.label;" accesskey="&staff.cat.bib_brief.edition.accesskey;" control="edition" class="emphasis"/>
+                    <textbox id="edition" name="edition" readonly="true" context="clipboard" class="plain" onfocus="this.select()"/>
+                </row>
+                <row>
+                    <label value="&staff.cat.bib_brief.pub_date.label;" accesskey="&staff.cat.bib_brief.pub_date.accesskey;" control="pubdate" class="emphasis"/>
+                    <textbox id="pubdate" name="pubdate" readonly="true" context="clipboard" class="plain" onfocus="this.select()"/>
+                </row>
+                <row>
+                    <label id="bib_call_number_label" value="&staff.cat.bib_brief.call_number.label;" accesskey="&staff.cat.bib_brief.call_number.accesskey;" control="bib_call_number" class="emphasis"/>
+                    <textbox id="bib_call_number" name="bib_call_number" readonly="true" context="clipboard" class="plain" onfocus="this.select()"/>
+                </row>
+                <row id="bib_brief_grid_row3" position="3">
+                    <label value="&staff.cat.bib_brief.title_control_number.label;" accesskey="&staff.cat.bib_brief.title_control_number.accesskey;" control="tcn" class="emphasis"/>
+                    <textbox id="tcn" name="tcn" readonly="true" context="clipboard" class="plain" onfocus="this.select()"/>
+                </row>
+                <row>
+                    <label value="&staff.cat.bib_brief.biblio_record_entry_id.label;" accesskey="&staff.cat.bib_brief.biblio_record_entry_id.accesskey;" control="mvr_doc_id" class="emphasis"/>
+                    <textbox id="mvr_doc_id" name="mvr_doc_id" readonly="true" context="clipboard" class="plain" onfocus="this.select()"/>
+                </row>
+                <row>
+                    <label value="&staff.cat.bib_brief.biblio_record_entry_owner.label;" accesskey="&staff.cat.bib_brief.biblio_record_entry_owner.accesskey;" control="owner" class="emphasis"/>
+                    <textbox id="owner" name="owner" readonly="true" context="clipboard" class="plain" onfocus="this.select()"/>
+                </row>
+                <row>
+                    <label value="&staff.cat.bib_brief.created_by.label;" accesskey="&staff.cat.bib_brief.created_by.accesskey;" control="creator" class="emphasis"/>
+                    <textbox id="creator" name="creator" readonly="true" context="clipboard" class="plain" onfocus="this.select()"/>
+                </row>
+                <row>
+                    <label value="&staff.cat.bib_brief.last_edited_by.label;" accesskey="&staff.cat.bib_brief.last_edited_by.accesskey;" control="editor" class="emphasis"/>
+                    <textbox id="editor" name="editor" readonly="true" context="clipboard" class="plain" onfocus="this.select()"/>
+                </row>
+                <row>
+                    <label value="&staff.cat.bib_brief.last_edited_on.label;" accesskey="&staff.cat.bib_brief.last_edited_on.accesskey;" control="edit_date" class="emphasis"/>
+                    <textbox id="edit_date" name="edit_date" readonly="true" context="clipboard" class="plain" onfocus="this.select()"/>
+                </row>
+            </rows>
+        </grid>
+</overlay>
diff --git a/Open-ILS/xul/staff_client/server/cat/bib_brief_vertical.xul b/Open-ILS/xul/staff_client/server/cat/bib_brief_vertical.xul
new file mode 100644 (file)
index 0000000..15ed851
--- /dev/null
@@ -0,0 +1,47 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Application: Evergreen Staff Client -->
+<!-- Screen: Brief Bib Display -->
+<!--
+vim: noet:sw=4:ts=4:
+-->
+
+<!-- ///////////////////////////////////////////////////////////////////////////////////////////////////////////// -->
+<!-- STYLESHEETS -->
+<?xml-stylesheet href="chrome://global/skin/" type="text/css"?>
+<?xml-stylesheet href="/xul/server/skin/global.css" type="text/css"?>
+
+<!-- ///////////////////////////////////////////////////////////////////////////////////////////////////////////// -->
+<!-- LOCALIZATION -->
+<!DOCTYPE window PUBLIC "" ""[
+    <!--#include virtual="/opac/locale/${locale}/lang.dtd"-->
+]>
+
+<!-- ///////////////////////////////////////////////////////////////////////////////////////////////////////////// -->
+<!-- OVERLAYS -->
+<?xul-overlay href="/xul/server/OpenILS/util_overlay.xul"?>
+<?xul-overlay href="/xul/server/cat/bib_brief_overlay_vertical.xul"?>
+
+<window id="cat_bib_brief_win" 
+    onload="try { my_init(); font_helper(); persist_helper(); } catch(E) { alert(E); }"
+    xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+
+    <!-- ///////////////////////////////////////////////////////////////////////////////////////////////////////////// -->
+    <!-- BEHAVIOR -->
+        <script type="text/javascript">
+        var myPackageDir = 'open_ils_staff_client'; var IAMXUL = true; var g = {};
+    </script>
+        <scripts id="openils_util_scripts"/>
+
+    <script type="text/javascript" src="/xul/server/main/JSAN.js"/>
+    <script type="text/javascript" src="/xul/server/cat/bib_brief.js"/>
+
+    <messagecatalog id="catStrings" src="/xul/server/locale/<!--#echo var='locale'-->/cat.properties"/>
+    <messagecatalog id="circStrings" src="/xul/server/locale/<!--#echo var='locale'-->/circ.properties"/>
+
+    <groupbox id="groupbox" flex="1">
+        <caption id="caption"><label value="&staff.cat.bib_brief.record_summary;"/>(<label value="&staff.cat.bib_brief.view_marc;" class="click_link" onclick="view_marc();"/>)</caption>
+        <grid id="bib_brief_grid" />
+    </groupbox>
+
+</window>
+
diff --git a/Open-ILS/xul/staff_client/server/cat/bibs_abreast.js b/Open-ILS/xul/staff_client/server/cat/bibs_abreast.js
new file mode 100644 (file)
index 0000000..d4b8648
--- /dev/null
@@ -0,0 +1,207 @@
+var error;
+var network;
+var record_ids;
+var lead_record;
+
+function my_init() {
+    try {
+        netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect");
+        if (typeof JSAN == 'undefined') { throw( "The JSAN library object is missing."); }
+        JSAN.errorLevel = "die"; // none, warn, or die
+        JSAN.addRepository('/xul/server/');
+        JSAN.use('util.error'); error = new util.error();
+        error.sdump('D_TRACE','my_init() for bibs_abreast.xul');
+        JSAN.use('util.functional');
+        JSAN.use('util.widgets');
+        JSAN.use('cat.util');
+        JSAN.use('util.network');
+
+        network = new util.network();
+
+        record_ids = xul_param('record_ids') || [];
+        record_ids = util.functional.unique_list_values( record_ids );
+
+        // Merge UI 
+        if (xul_param('merge')) {
+            var x = document.getElementById('merge_bar');
+            x.hidden = false;
+            var y = document.getElementById('merge_button');
+            y.addEventListener('command', merge_records, false);
+            var z = document.getElementById('cancel_button');
+            z.addEventListener('command', function() {
+                x.hidden = true;
+                y.disabled = true;
+                var merge_bars = util.widgets.find_descendants_by_name(document,'merge_bar');
+                for (var i = 0; i < merge_bars.length; i++) { merge_bars[i].hidden = true; }
+            }, false);
+        }
+
+        // Display the records
+        for (var i = 0; i < record_ids.length; i++) {
+            render_bib(record_ids[i]);
+        }
+
+        /*if (typeof window.xulG == 'object' && typeof window.xulG.set_tab_name == 'function') {
+            try { window.xulG.set_tab_name('Test'); } catch(E) { alert(E); }
+        }*/
+
+    } catch(E) {
+        try { error.standard_unexpected_error_alert('main/test.xul',E); } catch(F) { alert(E); }
+    }
+}
+
+function render_bib(record_id) {
+    var main = document.getElementById('main');
+    var template = main.firstChild;
+    var new_node = template.cloneNode(true);
+    main.appendChild(new_node);
+    new_node.hidden = false;
+
+    var splitter_template = template.nextSibling;
+    var splitter = splitter_template.cloneNode(true);
+    main.appendChild(splitter);
+    splitter.hidden = false;
+
+    render_bib_brief(new_node,record_id);
+
+    var xul_deck = util.widgets.find_descendants_by_name(new_node,'bib_deck')[0];
+    var deck = new util.deck(xul_deck);
+
+    // merge UI
+    if (xul_param('merge')) {
+        var merge_bar = util.widgets.find_descendants_by_name(new_node,'merge_bar')[0];
+        merge_bar.hidden = false;
+        var lead_button = util.widgets.find_descendants_by_name(new_node,'lead_button')[0];
+        lead_button.addEventListener('click', function() {
+            lead_record = record_id;
+            dump('record_id = ' + record_id + '\n');
+            document.getElementById('merge_button').disabled = false;
+        }, false);
+    }
+
+    // remove_me button
+    var remove_me = util.widgets.find_descendants_by_name(new_node,'remove_me')[0];
+    remove_me.addEventListener('command', function() {
+        if (lead_record == record_id) {
+            lead_record = undefined;
+            document.getElementById('merge_button').disabled = true;
+        }
+        record_ids = util.functional.filter_list( record_ids, function(o) { return o != record_id; } );
+        main.removeChild(new_node);
+        main.removeChild(splitter);
+        if (main.childNodes.length == 4) {
+            document.getElementById('merge_bar').hidden = true;
+            document.getElementById('merge_button').disabled = true;
+            var merge_bars = util.widgets.find_descendants_by_name(document,'merge_bar');
+            for (var i = 0; i < merge_bars.length; i++) { merge_bars[i].hidden = true; }
+        }
+        if (main.childNodes.length == 2) { xulG.close_tab(); }
+    }, false);
+
+    // radio buttons
+    var view_bib = util.widgets.find_descendants_by_name(new_node,'view_bib')[0];
+    var edit_bib = util.widgets.find_descendants_by_name(new_node,'edit_bib')[0];
+    var holdings = util.widgets.find_descendants_by_name(new_node,'holdings')[0];
+
+    view_bib.addEventListener('command', function() {
+        set_view_pane(deck,record_id);
+    }, false); 
+
+    edit_bib.addEventListener('command', function() {
+        set_edit_pane(deck,record_id);
+    }, false); 
+
+    holdings.addEventListener('command', function() {
+        set_item_pane(deck,record_id);
+    }, false); 
+
+    set_view_pane(deck,record_id);
+
+}
+
+function render_bib_brief(new_node,record_id) {
+    // iframe
+    var bib_brief = util.widgets.find_descendants_by_name(new_node,'bib_brief')[0];
+    bib_brief.setAttribute('src', urls.XUL_BIB_BRIEF_VERTICAL);
+    get_contentWindow(bib_brief).xulG = { 'docid' : record_id };
+}
+
+function set_view_pane(deck,record_id) {
+    deck.set_iframe( urls.XUL_MARC_VIEW, {}, { 'docid' : record_id } );
+}
+
+function set_item_pane(deck,record_id) {
+    var my_xulG = { 'docid' : record_id }; for (var i in xulG) { my_xulG[i] = xulG[i]; }
+    deck.set_iframe( urls.XUL_COPY_VOLUME_BROWSE, {}, my_xulG );
+}
+
+function set_edit_pane(deck,record_id) {
+    var my_xulG = {
+        'record' : { 'url' : '/opac/extras/supercat/retrieve/marcxml/record/' + record_id, "id": record_id, "rtype": "bre" },
+        'fast_add_item' : function(doc_id,cn_label,cp_barcode) {
+            try {
+                return cat.util.fast_item_add(doc_id,cn_label,cp_barcode);
+            } catch(E) {
+                alert('Error in bibs_abreast.js, set_edit_pane, fast_item_add: ' + E);
+            }
+        },
+        'save' : {
+            'label' : document.getElementById('offlineStrings').getString('cat.save_record'),
+            'func' : function (new_marcxml) {
+                try {
+                    var r = network.simple_request('MARC_XML_RECORD_UPDATE', [ ses(), record_id, new_marcxml ]);
+                    if (typeof r.ilsevent != 'undefined') {
+                        throw(r);
+                    } else {
+                        return {
+                            'id' : r.id(),
+                            'oncomplete' : function() {}
+                        };
+                    }
+                } catch(E) {
+                    alert('Error in bibs_abreast.js, set_edit_pane, save: ' + E);
+                }
+            }
+        }
+    };
+    for (var i in xulG) { my_xulG[i] = xulG[i]; }
+    deck.set_iframe( urls.XUL_MARC_EDIT, {}, my_xulG );
+}
+
+function merge_records() {
+    try {
+        var robj = network.simple_request('MERGE_RECORDS',
+            [
+                ses(),
+                lead_record,
+                util.functional.filter_list( record_ids,
+                    function(o) {
+                        return o != lead_record;
+                    }
+                )
+            ]
+        );
+        if (typeof robj.ilsevent != 'undefined') {
+            switch(robj.ilsevent) {
+                case 5000 /* PERM_FAILURE */: break;
+                default: throw(robj);
+            }
+        }
+        if (typeof xulG.on_merge == 'function') {
+            xulG.on_merge(robj);
+        }
+        var opac_url = xulG.url_prefix( urls.opac_rdetail ) + '?r=' + lead_record;
+        var content_params = {
+            'session' : ses(),
+            'authtime' : ses('authtime'),
+            'opac_url' : opac_url,
+        };
+        xulG.set_tab(
+            xulG.url_prefix(urls.XUL_OPAC_WRAPPER),
+            {'tab_name':'Retrieving title...'},
+            content_params
+        );
+    } catch(E) {
+        alert('Error in bibs_abreast.js, merge_records(): ' + E);
+    }
+}
diff --git a/Open-ILS/xul/staff_client/server/cat/bibs_abreast.xul b/Open-ILS/xul/staff_client/server/cat/bibs_abreast.xul
new file mode 100644 (file)
index 0000000..6d7c652
--- /dev/null
@@ -0,0 +1,69 @@
+<?xml version="1.0"?>
+<!-- Application: Evergreen Staff Client -->
+<!-- Screen: Example Template for remote xul -->
+
+<!-- ///////////////////////////////////////////////////////////////////////////////////////////////////////////// -->
+<!-- STYLESHEETS -->
+<?xml-stylesheet href="chrome://global/skin/" type="text/css"?>
+<?xml-stylesheet href="/xul/server/skin/global.css" type="text/css"?>
+
+<!-- ///////////////////////////////////////////////////////////////////////////////////////////////////////////// -->
+<!-- LOCALIZATION -->
+<!DOCTYPE window PUBLIC "" ""[
+    <!--#include virtual="/opac/locale/${locale}/lang.dtd"-->
+]>
+
+<!-- ///////////////////////////////////////////////////////////////////////////////////////////////////////////// -->
+<!-- OVERLAYS -->
+<?xul-overlay href="/xul/server/OpenILS/util_overlay.xul"?>
+
+<window id="main_bibs_abreast" 
+    onload="try { my_init(); font_helper(); persist_helper(); } catch(E) { alert(E); }"
+    xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+
+    <!-- ///////////////////////////////////////////////////////////////////////////////////////////////////////////// -->
+    <!-- BEHAVIOR -->
+    <script type="text/javascript">
+        var myPackageDir = 'open_ils_staff_client'; var IAMXUL = true;
+    </script>
+    <scripts id="openils_util_scripts"/>
+
+    <script type="text/javascript" src="/xul/server/main/JSAN.js"/>
+    <script type="text/javascript" src="bibs_abreast.js"/>
+
+    <hbox id="merge_bar" hidden="true">
+        <description>&staff.cat.record_buckets.merge_records.merge_lead;</description>
+        <button id="merge_button" disabled="true"
+            label="&staff.cat.record_buckets.merge_records.button.label;"
+            accesskey="&staff.cat.record_buckets.merge_records.button.accesskey;"
+        />
+        <button id="cancel_button" 
+            label="&staff.cat.record_buckets.merge_records.cancel_button.label;"
+            accesskey="&staff.cat.record_buckets.merge_records.cancel_button.accesskey;"
+        />
+    </hbox>
+    <hbox id="main" class="my_overflow" flex="1">
+        <vbox name="template" hidden="true" flex="1">
+            <hbox name="merge_bar" hidden="true">
+                <input name="lead_button" type="radio" xmlns="http://www.w3.org/1999/xhtml"></input>
+                &staff.cat.record_buckets.merge_records.lead;
+            </hbox>
+            <iframe name="bib_brief" flex="1" />
+            <hbox>
+                <radiogroup flex="0" orient="horizontal">
+                    <radio name="view_bib" label="View Bib" />
+                    <radio name="edit_bib" label="Edit Bib" />
+                    <radio name="holdings" label="Holdings" />
+                </radiogroup>
+                <spacer flex="1"/>
+                <button name="remove_me"
+                    image="/xul/server/skin/media/images/icon_delete.gif"
+                    label="&staff.cat.record_buckets.merge_records.remove_from_consideration;" />
+            </hbox>
+            <deck name="bib_deck" flex="3" />
+        </vbox>
+        <splitter name="template2" hidden="true"><grippy /></splitter>
+    </hbox>
+
+</window>
+
index f669877..902c52e 100644 (file)
@@ -1251,7 +1251,6 @@ cat.copy_browser.prototype = {
         var obj = this;
         try {
             var org = obj.data.hash.aou[ org_id ];
-            if (obj.data.hash.aout[ org.ou_type() ].depth() == 0 && ! get_bool( obj.data.hash.aout[ org.ou_type() ].can_have_vols() ) ) return;
             obj.funcs.push( function() { 
                 document.getElementById('cmd_refresh_list').setAttribute('disabled','true'); 
                 document.getElementById('cmd_show_libs_with_copies').setAttribute('disabled','true'); 
@@ -1376,10 +1375,8 @@ cat.copy_browser.prototype = {
             }
 
             if (document.getElementById('show_acns').checked) {
-                if (! ( obj.data.hash.aout[ org.ou_type() ].depth() == 0 && ! get_bool( obj.data.hash.aout[ org.ou_type() ].can_have_vols() ) )) {
-                    node.setAttribute('open','true');
-                    obj.funcs.push( function() { obj.on_select_org( org.id() ); } );
-                }
+                node.setAttribute('open','true');
+                obj.funcs.push( function() { obj.on_select_org( org.id() ); } );
             }
 
         } catch(E) {
index f1e5250..2428043 100644 (file)
@@ -22,7 +22,7 @@ vim:noet:sw=4:ts=4:
 <?xul-overlay href="/xul/server/OpenILS/util_overlay.xul"?>
 
 <window id="cat_copy_browser" active="true"
-    onload="try { my_init(); font_helper(); } catch(E) { alert(E); }"
+    onload="try { my_init(); font_helper(); persist_helper(); } catch(E) { alert(E); }"
     xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
 
     <!-- ///////////////////////////////////////////////////////////////////////////////////////////////////////////// -->
index 5eb2992..192c49d 100644 (file)
 </box>
 
 <vbox id="cmvb1" flex="1">
-    <groupbox flex="1">
+    <groupbox flex="1" id="before_splitter" oils_persist="height">
         <caption label="&staff.cat.copy_buckets_overlay.pending_copies;" />
         <hbox id="pending_buckets_top_ui" />
         <tree id="pending_copies_list" flex="1" enableColumnDrag="true"/>
         <hbox id="pending_buckets_bottom_ui" />
     </groupbox>
-    <splitter><grippy /></splitter>
-    <groupbox flex="2">
+    <splitter id="splitter" oils_persist="state hidden" oils_persist_peers="before_splitter after_splitter"><grippy /></splitter>
+    <groupbox flex="2" id="after_splitter" oils_persist="height">
         <caption label="&staff.cat.copy_buckets_overlay.bucket_view;" />
         <hbox id="copy_buckets_top_ui" />
         <hbox id="info_box"/>
index c58a9ef..3fa2ba2 100644 (file)
         </hbox>
 
         <hbox flex="1" style="overflow: scroll">
-            <vbox flex="1">
+            <vbox flex="1" id="before_splitter1" oils_persist="width">
                 <label value="&staff.cat.copy_editor.identification.label;" style="font-weight: bold; font-size: large"/>
                 <vbox id="left_pane" flex="1"/>
             </vbox>
-            <splitter><grippy /></splitter>
-            <vbox flex="1">
+            <splitter id="splitter1" oils_persist="state hidden" oils_persist_peers="before_splitter1 after_splitter1"><grippy /></splitter>
+            <vbox flex="1" id="after_splitter1" oils_persist="width">
                 <button style="font-weight: bold; font-size: normal" label="&staff.cat.copy_editor.identification.location.label;" accesskey="&staff.cat.copy_editor.identification.location.accesskey;" oncommand="document.getElementById('right_pane').firstChild.firstChild.focus();"/>
                 <vbox id="right_pane" flex="1"/>
             </vbox>
-            <splitter><grippy /></splitter>
-            <vbox flex="1">
+            <splitter id="splitter2" oils_persist="state hidden" oils_persist_peers="after_splitter1 after_splitter2"><grippy /></splitter>
+            <vbox flex="1" id="after_splitter2" oils_persist="width">
                 <button style="font-weight: bold; font-size: normal" label="&staff.cat.copy_editor.identification.circulation.label;" accesskey="&staff.cat.copy_editor.identification.circulation.accesskey;" oncommand="document.getElementById('right_pane2').firstChild.firstChild.focus();"/>
                 <vbox id="right_pane2" flex="1"/>
             </vbox>
-            <splitter><grippy /></splitter>
-            <vbox flex="1">
+            <splitter id="splitter3" oils_persist="state hidden" oils_persist_peers="after_splitter2 after_splitter3"><grippy /></splitter>
+            <vbox flex="1" id="after_splitter3" oils_persist="width">
                 <button style="font-weight: bold; font-size: normal" label="&staff.cat.copy_editor.identification.miscellaneous.label;" accesskey="&staff.cat.copy_editor.identification.miscellaneous.accesskey;" oncommand="document.getElementById('right_pane3').firstChild.firstChild.focus();"/>
                 <vbox id="right_pane3" flex="1"/>
             </vbox>
-            <splitter><grippy /></splitter>
-            <vbox flex="1">
+            <splitter id="splitter4" oils_persist="state hidden" oils_persist_peers="after_splitter3 after_splitter4"><grippy /></splitter>
+            <vbox flex="1" id="after_splitter4" oils_persist="width">
                 <button style="font-weight: bold; font-size: normal" label="&staff.cat.copy_editor.identification.statistics.label;" accesskey="&staff.cat.copy_editor.identification.statistics.accesskey;" oncommand="document.getElementById('right_pane4').firstChild.firstChild.focus();"/>
                 <menu label="&staff.cat.copy_editor.stat_cat_lib_filter_menu.label;" id="stat_cat_lib_filter_menu">
                     <menupopup />
index 86dfe78..1da6032 100644 (file)
@@ -22,8 +22,9 @@
 <?xul-overlay href="/xul/server/OpenILS/util_overlay.xul"?>
 
 <window id="copy_notes_win" width="700" height="550"
+    oils_persist="height width sizemode"
     title="&staff.cat.copy_editor.copy_notes.label;"
-    onload="try{ my_init(); font_helper(); } catch(E) { alert(E); }"
+    onload="try{ my_init(); font_helper(); persist_helper(); } catch(E) { alert(E); }"
     xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
 
     <!-- ///////////////////////////////////////////////////////////////////////////////////////////////////////////// -->
index a7823a0..ddc54f3 100644 (file)
@@ -21,7 +21,7 @@
 <?xul-overlay href="/xul/server/OpenILS/util_overlay.xul"?>
 
 <window id="example_template_win" 
-    onload="try { my_init(); font_helper(); } catch(E) { alert(E); }"
+    onload="try { my_init(); font_helper(); persist_helper(); } catch(E) { alert(E); }"
     xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
 
     <!-- ///////////////////////////////////////////////////////////////////////////////////////////////////////////// -->
index daa9ea7..0f6f8a2 100644 (file)
@@ -21,7 +21,7 @@ vim:noet:sw=4:ts=4:
 <?xul-overlay href="/xul/server/OpenILS/util_overlay.xul"?>
 
 <window id="cat_marc_view_win" 
-    onload="try { my_init(); font_helper(); } catch(E) { alert(E); }"
+    onload="try { my_init(); font_helper(); persist_helper(); } catch(E) { alert(E); }"
     xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
 
     <!-- ///////////////////////////////////////////////////////////////////////////////////////////////////////////// -->
index 980ffff..d6b4cc6 100644 (file)
@@ -91,15 +91,22 @@ function wrap_long_fields (node) {
     }
 }
 
-function swap_editors () {
+function set_flat_editor (useFlatText) {
 
     dojo.require('MARC.Record');
 
     var xe = $('xul-editor');
     var te = $('text-editor');
 
-    te.hidden = te.hidden ? false : true;
-    xe.hidden = xe.hidden ? false : true;
+    if (useFlatText) {
+        if (xe.hidden) { return; }
+        te.hidden = false;
+        xe.hidden = true;
+    } else {
+        if (te.hidden) { return; }
+        te.hidden = true;
+        xe.hidden = false;
+    }
 
     if (te.hidden) {
         // get the marcxml from the text box
@@ -168,7 +175,7 @@ function my_init() {
 
         document.getElementById('save-button').setAttribute('label', window.xulG.save.label);
         document.getElementById('save-button').setAttribute('oncommand',
-            'if ($("xul-editor").hidden) swap_editors(); ' +
+            'if ($("xul-editor").hidden) set_flat_editor(false); ' +
             'mangle_005(); ' + 
             'var xml_string = xml_escape_unicode( xml_record.toXMLString() ); ' + 
             'save_attempt( xml_string ); ' +
@@ -2326,7 +2333,7 @@ function browseAuthority (sf_popup, menu_id, target, sf, limit, page) {
     ;
 
     // would be good to carve this out into a separate function
-    dojo.xhrGet({"url":url, "handleAs":"xml", "load": function(records) {
+    dojo.xhrGet({"url":url, "sync": true, "handleAs":"xml", "load": function(records) {
         var create_menu = createMenu({ label: $('catStrings').getString('staff.cat.marcedit.create_authority.label')});
 
         var cm_popup = create_menu.appendChild(
index 83d8a5e..fed7cff 100644 (file)
@@ -24,6 +24,7 @@
     <caption label="&staff.cat.marcedit.options.label;"/>
     <hbox flex="1">
         <checkbox oils_persist="checked" accesskey="&staff.cat.marcedit.stackSubfields.accesskey;" label="&staff.cat.marcedit.stackSubfields.label;" oncommand="stackSubfields(this);" checked="false" id="stackSubfields"/>
+        <checkbox oils_persist="checked" accesskey="&staff.cat.marcedit.flatTextEditor.accesskey;" label="&staff.cat.marcedit.flatTextEditor.label;" oncommand="set_flat_editor(this.checked);" checked="false" id="swapEditor_checkbox"/>
         <checkbox oils_persist="checked" accesskey="&staff.cat.marcedit.fastItemAdd.accesskey;" label="&staff.cat.marcedit.fastItemAdd.label;" oncommand="fastItemAdd_toggle(this);" checked="false" id="fastItemAdd_checkbox"/>
         <hbox id="fastItemAdd_textboxes">
             <label control="fastItemAdd_callnumber" accesskey="&staff.cat.marcedit.fastItemAdd_callnumber.accesskey;" value="&staff.cat.marcedit.fastItemAdd_callnumber.label;" />
@@ -33,7 +34,6 @@
         </hbox>
         <button label="&staff.cat.marcedit.validate.label;" accesskey="&staff.cat.marcedit.validate.accesskey;" oncommand="validateAuthority(this);"/>
         <button id="save-button" accesskey="&staff.cat.marcedit.save-button.accesskey;"/>
-        <button label="&staff.cat.marcedit.swapEditor.label;" oncommand="swap_editors()"/>
         <button label="&staff.cat.marcedit.help.label;" accesskey="&staff.cat.marcedit.help.accesskey;"
             oncommand="alert(
                 $('catStrings').getString('staff.cat.marcedit.help.add_row') + '\n' +
index 814f8af..07a0aad 100644 (file)
@@ -342,6 +342,7 @@ cat.record_buckets.prototype = {
                                     obj.controller.view.cmd_delete_records.setAttribute('disabled','true');
                                     obj.controller.view.cmd_sel_opac.setAttribute('disabled','true');
                                     obj.controller.view.cmd_transfer_title_holds.setAttribute('disabled','true');
+                                    obj.controller.view.cmd_marc_batch_edit.setAttribute('disabled','true');
                                     obj.controller.view.record_buckets_list_actions.disabled = true;
                                     var bucket = obj.network.simple_request(
                                         'BUCKET_FLESH',
@@ -363,6 +364,7 @@ cat.record_buckets.prototype = {
                                         obj.controller.view.cmd_delete_records.setAttribute('disabled','false');
                                         obj.controller.view.cmd_sel_opac.setAttribute('disabled','false');
                                         obj.controller.view.cmd_transfer_title_holds.setAttribute('disabled','false');
+                                        obj.controller.view.cmd_marc_batch_edit.setAttribute('disabled','false');
                                         obj.controller.view.record_buckets_list_actions.disabled = false;
 
                                         var x = document.getElementById('info_box');
@@ -553,6 +555,7 @@ cat.record_buckets.prototype = {
                                 obj.controller.view.cmd_delete_records.setAttribute('disabled','true');
                                 obj.controller.view.cmd_sel_opac.setAttribute('disabled','true');
                                 obj.controller.view.cmd_transfer_title_holds.setAttribute('disabled','true');
+                                obj.controller.view.cmd_marc_batch_edit.setAttribute('disabled','true');
                                 obj.controller.view.record_buckets_list_actions.disabled = true;
                                 obj.controller.render('record_buckets_menulist_placeholder');
                                 setTimeout(
@@ -653,75 +656,24 @@ cat.record_buckets.prototype = {
                                     }
                                 );
 
-                                netscape.security.PrivilegeManager.enablePrivilege('UniversalXPConnect UniversalBrowserWrite');
-                                var top_xml = '<vbox xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" flex="1" >';
-                                top_xml += '<description>' + $("catStrings").getString('staff.cat.record_buckets.merge_records.merge_lead') + '</description>';
-                                top_xml += '<hbox>';
-                                top_xml += '<button id="lead" disabled="true" label="'
-                                        + $("catStrings").getString('staff.cat.record_buckets.merge_records.button.label') + '" name="fancy_submit"/>';
-                                top_xml += '<button label="' + $("catStrings").getString('staff.cat.record_buckets.merge_records.cancel_button.label') +'" accesskey="'
-                                        + $("catStrings").getString('staff.cat.record_buckets.merge_records.cancel_button.accesskey') +'" name="fancy_cancel"/></hbox></vbox>';
-
-                                var xml = '<form xmlns="http://www.w3.org/1999/xhtml">';
-                                xml += '<table><tr valign="top">';
-                                for (var i = 0; i < record_ids.length; i++) {
-                                    xml += '<td><input value="' + $("catStrings").getString('staff.cat.record_buckets.merge_records.lead')
-                                    xml += '" id="record_' + record_ids[i] + '" type="radio" name="lead"';
-                                    xml += ' onclick="' + "try { var x = $('lead'); x.setAttribute('value',";
-                                    xml += record_ids[i] + '); x.disabled = false; } catch(E) { alert(E); }">';
-                                    xml += '</input>' + $("catStrings").getFormattedString('staff.cat.record_buckets.merge_records.lead_record_number',[record_ids[i]]) + '</td>';
-                                }
-                                xml += '</tr><tr valign="top">';
-                                for (var i = 0; i < record_ids.length; i++) {
-                                    xml += '<td nowrap="nowrap"><iframe src="' + urls.XUL_BIB_BRIEF; 
-                                    xml += '?docid=' + record_ids[i] + '"/></td>';
-                                }
-                                xml += '</tr><tr valign="top">';
-                                for (var i = 0; i < record_ids.length; i++) {
-                                    xml += '<td nowrap="nowrap"><iframe style="min-height: 1000px; min-width: 300px;" flex="1" src="' + urls.XUL_MARC_VIEW + '?docid=' + record_ids[i] + ' "/></td>';
-                                }
-                                xml += '</tr></table></form>';
-                                //obj.data.temp_merge_top = top_xml; obj.data.stash('temp_merge_top');
-                                //obj.data.temp_merge_mid = xml; obj.data.stash('temp_merge_mid');
-                                JSAN.use('util.window'); var win = new util.window();
-                                var fancy_prompt_data = win.open(
-                                    urls.XUL_FANCY_PROMPT,
-                                    //+ '?xml_in_stash=temp_merge_mid'
-                                    //+ '&top_xml_in_stash=temp_merge_top'
-                                    //+ '&title=' + window.escape('Record Merging'),
-                                    'fancy_prompt', 'chrome,resizable,modal,width=700,height=500',
-                                    {
-                                        'top_xml' : top_xml, 'xml' : xml, 'title' : $("catStrings").getString('staff.cat.record_buckets.merge_records.fancy_prompt_title')
+                                xulG.new_tab(
+                                    '/xul/server/cat/bibs_abreast.xul',{
+                                        'tab_name' : $("catStrings").getString('staff.cat.record_buckets.merge_records.fancy_prompt_title')
+                                    },{
+                                        'merge' : true,
+                                        'on_merge' : function() {
+                                            obj.render_pending_records(); // FIXME -- need a generic refresh for lists
+                                            setTimeout(
+                                                function() {
+                                                    JSAN.use('util.widgets'); 
+                                                    util.widgets.dispatch('change_bucket',obj.controller.view.bucket_menulist);
+                                                }, 0
+                                            );
+                                        },
+                                        'record_ids':record_ids
                                     }
                                 );
-                                //obj.data.stash_retrieve();
 
-                                if (typeof fancy_prompt_data.fancy_status == 'undefined' || fancy_prompt_data.fancy_status == 'incomplete') {
-                                    alert($("catStrings").getString('staff.cat.record_buckets.merge_records.fancy_prompt.alert'));
-                                    return;
-                                }
-                                var robj = obj.network.simple_request('MERGE_RECORDS', 
-                                    [ 
-                                        ses(), 
-                                        fancy_prompt_data.lead, 
-                                        util.functional.filter_list( record_ids,
-                                            function(o) {
-                                                return o != fancy_prompt_data.lead;
-                                            }
-                                        )
-                                    ]
-                                );
-                                if (typeof robj.ilsevent != 'undefined') {
-                                    throw(robj);
-                                }
-
-                                obj.render_pending_records(); // FIXME -- need a generic refresh for lists
-                                setTimeout(
-                                    function() {
-                                        JSAN.use('util.widgets'); 
-                                        util.widgets.dispatch('change_bucket',obj.controller.view.bucket_menulist);
-                                    }, 0
-                                );
                             } catch(E) {
                                 obj.error.standard_unexpected_error_alert($("catStrings").getString('staff.cat.record_buckets.merge_records.catch.std_unex_error'),E);
                             }
@@ -849,6 +801,25 @@ cat.record_buckets.prototype = {
                             }
                         }
                     ],
+                    'cmd_marc_batch_edit' : [
+                        ['command'],
+                        function() {
+                            try {
+                                var bucket_id = obj.controller.view.bucket_menulist.value;
+                                if (!bucket_id) return;
+                                obj.list2.select_all();
+                                xulG.new_tab(
+                                    urls.MARC_BATCH_EDIT + '?containerid='+bucket_id+'&recordSource=b', 
+                                    {
+                                        'tab_name' : $('offlineStrings').getString('menu.cmd_marc_batch_edit.tab')
+                                    },
+                                    {}
+                                );
+                            } catch(E) {
+                                alert('Error in record_buckets.js, cmd_marc_batch_edit: ' + E);
+                            }
+                        }
+                    ],
                     'cmd_transfer_title_holds' : [
                         ['command'],
                         function() {
index 3f564f6..2ea4cdb 100644 (file)
     <command id="cmd_add_sel_pending_to_record_bucket" />
 
     <command id="cmd_merge_records" disabled="true" />
+    <command id="cmd_marc_batch_edit"
+        label="&staff.cat.record_buckets_overlay.marc_batch_edit.label;" 
+        accesskey="&staff.cat.record_buckets_overlay.marc_batch_edit.accesskey;" 
+        disabled="true" />
     <command id="cmd_transfer_title_holds" 
         label="&staff.cat.record_buckets_overlay.transfer_title_holds.label;" 
         accesskey="&staff.cat.record_buckets_overlay.transfer_title_holds.accesskey;" 
         <button command="cmd_transfer_title_holds" />
         <button command="cmd_delete_records" label="&staff.cat.record_buckets_overlay.del_records.label;"/>
         <button command="cmd_merge_records" label="&staff.cat.record_buckets_overlay.merge_records.label;"/>
+        <button command="cmd_marc_batch_edit" />
         <button id="record_buckets_export_records" label="&staff.cat.record_buckets_overlay.export_records.label;" type="menu" allowevents="true" disabled="true">
             <menupopup id="record_buckets_export_record_types" allowevents="true">
                 <menuitem command="cmd_export_records_usmarc" label="&staff.cat.record_buckets_overlay.menuitem.export_usmarc.label;"/>
index f1d9263..8c32d41 100644 (file)
             w.xulG = { 
                 'url' : 'about:blank',
                 'show_print_button' : 1,
+                'printer_context' : 'label',
                 'alternate_print' : 1,
                 'no_xulG' : 1,
                 'title' : $("catStrings").getString('staff.cat.spine_labels.preview.title'),
                     'url' : 'data:text/html;charset=utf-8,'+window.escape(html),
                     'html_source' : html,
                     'show_print_button' : 1,
+                    'printer_context' : 'label',
                     'no_xulG' : 1
                 }
             );
index d875df4..3839704 100644 (file)
@@ -35,7 +35,7 @@
 
     <vbox id="spine_labels_main" flex="1" class="my_overflow">
         <hbox flex="1" class="my_overflow">
-        <vbox>
+        <vbox id="before_splitter" oils_persist="width">
             <hbox>
                 <button label="&staff.cat.spine_labels.re-generate.label;"
                     accesskey="&staff.cat.spine_labels.re-generate.accesskey;" oncommand="generate()"/>
             </rows></grid>
             <button label="&staff.cat.spine_labels.available_macros.label;" oncommand="show_macros()"/>
         </vbox>
-        <splitter><grippy/></splitter>
-        <vbox id="panel" flex="1" class="my_overflow"/>
+        <splitter id="splitter" oils_persist="state hidden" oils_persist_peers="before_splitter panel"><grippy/></splitter>
+        <vbox id="panel" flex="1" class="my_overflow" oils_persist="width"/>
         </hbox>
     </vbox>
 
index c9f7869..895f51c 100644 (file)
@@ -260,7 +260,7 @@ cat.util.add_copies_to_bucket = function(selection_list) {
     data.stash('cb_temp_copy_ids');
     win.open( 
         xulG.url_prefix(urls.XUL_COPY_BUCKETS_QUICK),
-        'sel_bucket_win' + win.window_name_increment(),
+        '_blank',
         'chrome,resizable,center'
     );
 }
@@ -269,7 +269,7 @@ cat.util.add_titles_to_bucket = function(record_ids) {
     JSAN.use('util.window'); var win = new util.window();
     win.open(
         xulG.url_prefix(urls.XUL_RECORD_BUCKETS_QUICK),
-        'sel_bucket_win' + win.window_name_increment(),
+        '_blank',
         'chrome,resizable,modal,center',
         {
             record_ids: record_ids 
@@ -770,6 +770,7 @@ cat.util.mark_item_as_missing_pieces = function(copy_ids) {
 
         if (r == 0) {
             var count = 0;
+            JSAN.use('cat.util');
             for (var i = 0; i < copies.length; i++) {
                 try {
                     var robj = network.simple_request('MARK_ITEM_MISSING_PIECES',[ses(),copies[i].id()]);
@@ -781,12 +782,7 @@ cat.util.mark_item_as_missing_pieces = function(copy_ids) {
                                 print.simple( robj.payload.slip.template_output().data() );
                             }
                             // Item Note
-                            win.open(
-                                urls.XUL_COPY_NOTES,
-                                $("catStrings").getString("staff.cat.copy_editor.copy_notes"),
-                                'chrome,resizable,modal',
-                                { 'copy_id' : copies[i].id() }
-                            );
+                            cat.util.spawn_copy_editor( { 'copy_ids' : [ copies[i].id() ], 'edit' : 1 } );
                             // Patron Message
                             var my_xulG = win.open(
                                 urls.XUL_NEW_STANDING_PENALTY,
index 78ef047..c9737b8 100644 (file)
@@ -18,7 +18,7 @@
 <?xul-overlay href="/xul/server/OpenILS/util_overlay.xul"?>
 
 <window id="example_template_win" 
-    onload="try { my_init(); font_helper(); } catch(E) { alert(E); }"
+    onload="try { my_init(); font_helper(); persist_helper(); } catch(E) { alert(E); }"
     xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
 
     <!-- ///////////////////////////////////////////////////////////////////////////////////////////////////////////// -->
index 59f0e82..0c50587 100644 (file)
@@ -18,8 +18,8 @@
 <?xul-overlay href="/xul/server/OpenILS/util_overlay.xul"?>
 
 <window id="cat_volume_copy_creator_win" 
-    onload="try { my_init(); font_helper(); } catch(E) { alert(E); }"
-    width="800" height="580"
+    onload="try { my_init(); font_helper(); persist_helper(); } catch(E) { alert(E); }"
+    width="800" height="580" oils_persist="height width sizemode"
     title="&staff.cat.volume_copy_creator.title;"
     xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
 
index 3c7e3ed..1b045bd 100644 (file)
@@ -61,7 +61,19 @@ cat.z3950.prototype = {
                             document.getElementById('sel_clip').setAttribute('disabled', sel.length < 1);
                             var list = util.functional.map_list(
                                 sel,
-                                function(o) { return o.getAttribute('retrieve_id'); }
+                                function(o) {
+                                    if ( $('jacket_image') ) {
+                                        // A side-effect in this map function, mu hahaha
+                                        if (o.getAttribute('isbn')) {
+                                            $('jacket_image').setAttribute('src',urls.ac_jacket_large+o.getAttribute('isbn'));
+                                            $('jacket_image').setAttribute('tooltiptext',urls.ac_jacket_large+o.getAttribute('isbn'));
+                                        } else {
+                                            $('jacket_image').setAttribute('src','');
+                                            $('jacket_image').setAttribute('tooltiptext','');
+                                        }
+                                    }
+                                    return o.getAttribute('retrieve_id');
+                                }
                             );
                             obj.error.sdump('D_TRACE','cat/z3950: selection list = ' + js2JSON(list) );
                             obj.controller.view.marc_import.disabled = false;
@@ -656,6 +668,7 @@ cat.z3950.prototype = {
                                 }
                             }
                         );
+                        n.my_node.setAttribute('isbn', function(a){return a;}(obj.result_set[ obj.number_of_result_sets ].records[j].mvr).isbn());
                         if (!f) { n.my_node.parentNode.focus(); f = n; } 
                     }
                 } else {
@@ -705,119 +718,126 @@ cat.z3950.prototype = {
 
     'spawn_marc_editor' : function(my_marcxml,biblio_source) {
         var obj = this;
-        xulG.new_tab(
-            xulG.url_prefix(urls.XUL_MARC_EDIT), 
-            { 'tab_name' : 'MARC Editor' }, 
-            { 
-                'record' : { 'marc' : my_marcxml, "rtype": "bre" },
-                'fast_add_item' : function(doc_id,cn_label,cp_barcode) {
-                    try {
-                        JSAN.use('cat.util'); return cat.util.fast_item_add(doc_id,cn_label,cp_barcode);
-                    } catch(E) {
-                        alert(E);
-                    }
-                },
-                'save' : {
-                    'label' : 'Import Record',
-                    'func' : function (new_marcxml) {
-                        try {
-                            var r = obj.network.simple_request('MARC_XML_RECORD_IMPORT', [ ses(), new_marcxml, biblio_source ]);
-                            if (typeof r.ilsevent != 'undefined') {
-                                switch(Number(r.ilsevent)) {
-                                    case 1704 /* TCN_EXISTS */ :
-                                        var msg = $("catStrings").getFormattedString('staff.cat.z3950.spawn_marc_editor.same_tcn', [r.payload.tcn]);
-                                        var title = $("catStrings").getString('staff.cat.z3950.spawn_marc_editor.title');
-                                        var btn1 = $("catStrings").getString('staff.cat.z3950.spawn_marc_editor.btn1_overlay');
-                                        var btn2 = typeof r.payload.new_tcn == 'undefined' ? null : $("catStrings").getFormattedString('staff.cat.z3950.spawn_marc_editor.btn2_import', [r.payload.new_tcn]);
-                                        if (btn2) {
-                                            obj.data.init({'via':'stash'});
-                                            var robj = obj.network.simple_request(
-                                                'PERM_CHECK',[
-                                                    ses(),
-                                                    obj.data.list.au[0].id(),
-                                                    obj.data.list.au[0].ws_ou(),
-                                                    [ 'ALLOW_ALT_TCN' ]
-                                                ]
-                                            );
-                                            if (typeof robj.ilsevent != 'undefined') {
-                                                obj.error.standard_unexpected_error_alert($("catStrings").getString('staff.cat.z3950.spawn_marc_editor.permission_error'),E);
-                                            }
-                                            if (robj.length != 0) btn2 = null;
-                                        }
-                                        var btn3 = $("catStrings").getString('staff.cat.z3950.spawn_marc_editor.btn3_cancel_import');
-                                        var p = obj.error.yns_alert(msg,title,btn1,btn2,btn3,$("catStrings").getString('staff.cat.z3950.spawn_marc_editor.confirm_action'));
-                                        obj.error.sdump('D_ERROR','option ' + p + 'chosen');
-                                        switch(p) {
-                                            case 0:
-                                                var r3 = obj.network.simple_request('MARC_XML_RECORD_UPDATE', [ ses(), r.payload.dup_record, new_marcxml, biblio_source ]);
-                                                if (typeof r3.ilsevent != 'undefined') {
-                                                    throw(r3);
-                                                } else {
-                                                    alert($("catStrings").getString('staff.cat.z3950.spawn_marc_editor.successful_overlay'));
-                                                    return {
-                                                        'id' : r3.id(),
-                                                        'on_complete' : function() {
-                                                            try {
-                                                                obj.replace_tab_with_opac(r3.id());
-                                                            } catch(E) {
-                                                                alert(E);
-                                                            }
-                                                        }
-                                                    };
+
+        function save_marc (new_marcxml) {
+            try {
+                var r = obj.network.simple_request('MARC_XML_RECORD_IMPORT', [ ses(), new_marcxml, biblio_source ]);
+                if (typeof r.ilsevent != 'undefined') {
+                    switch(Number(r.ilsevent)) {
+                        case 1704 /* TCN_EXISTS */ :
+                            var msg = $("catStrings").getFormattedString('staff.cat.z3950.spawn_marc_editor.same_tcn', [r.payload.tcn]);
+                            var title = $("catStrings").getString('staff.cat.z3950.spawn_marc_editor.title');
+                            var btn1 = $("catStrings").getString('staff.cat.z3950.spawn_marc_editor.btn1_overlay');
+                            var btn2 = typeof r.payload.new_tcn == 'undefined' ? null : $("catStrings").getFormattedString('staff.cat.z3950.spawn_marc_editor.btn2_import', [r.payload.new_tcn]);
+                            if (btn2) {
+                                obj.data.init({'via':'stash'});
+                                var robj = obj.network.simple_request(
+                                    'PERM_CHECK',[
+                                        ses(),
+                                        obj.data.list.au[0].id(),
+                                        obj.data.list.au[0].ws_ou(),
+                                        [ 'ALLOW_ALT_TCN' ]
+                                    ]
+                                );
+                                if (typeof robj.ilsevent != 'undefined') {
+                                    obj.error.standard_unexpected_error_alert($("catStrings").getString('staff.cat.z3950.spawn_marc_editor.permission_error'),E);
+                                }
+                                if (robj.length != 0) btn2 = null;
+                            }
+                            var btn3 = $("catStrings").getString('staff.cat.z3950.spawn_marc_editor.btn3_cancel_import');
+                            var p = obj.error.yns_alert(msg,title,btn1,btn2,btn3,$("catStrings").getString('staff.cat.z3950.spawn_marc_editor.confirm_action'));
+                            obj.error.sdump('D_ERROR','option ' + p + 'chosen');
+                            switch(p) {
+                                case 0:
+                                    var r3 = obj.network.simple_request('MARC_XML_RECORD_UPDATE', [ ses(), r.payload.dup_record, new_marcxml, biblio_source ]);
+                                    if (typeof r3.ilsevent != 'undefined') {
+                                        throw(r3);
+                                    } else {
+                                        alert($("catStrings").getString('staff.cat.z3950.spawn_marc_editor.successful_overlay'));
+                                        return {
+                                            'id' : r3.id(),
+                                            'on_complete' : function() {
+                                                try {
+                                                    obj.replace_tab_with_opac(r3.id());
+                                                } catch(E) {
+                                                    alert(E);
                                                 }
-                                            break;
-                                            case 1:
-                                                var r2 = obj.network.request(
-                                                    api.MARC_XML_RECORD_IMPORT.app,
-                                                    api.MARC_XML_RECORD_IMPORT.method + '.override',
-                                                    [ ses(), new_marcxml, biblio_source ]
-                                                );
-                                                if (typeof r2.ilsevent != 'undefined') {
-                                                    throw(r2);
-                                                } else {
-                                                    alert($("catStrings").getString('staff.cat.z3950.spawn_marc_editor.successful_import_with_new_tcn'));
-                                                    return {
-                                                        'id' : r2.id(),
-                                                        'on_complete' : function() {
-                                                            try {
-                                                                obj.replace_tab_with_opac(r2.id());
-                                                            } catch(E) {
-                                                                alert(E);
-                                                            }
-                                                        }
-                                                    };
+                                            }
+                                        };
+                                    }
+                                break;
+                                case 1:
+                                    var r2 = obj.network.request(
+                                        api.MARC_XML_RECORD_IMPORT.app,
+                                        api.MARC_XML_RECORD_IMPORT.method + '.override',
+                                        [ ses(), new_marcxml, biblio_source ]
+                                    );
+                                    if (typeof r2.ilsevent != 'undefined') {
+                                        throw(r2);
+                                    } else {
+                                        alert($("catStrings").getString('staff.cat.z3950.spawn_marc_editor.successful_import_with_new_tcn'));
+                                        return {
+                                            'id' : r2.id(),
+                                            'on_complete' : function() {
+                                                try {
+                                                    obj.replace_tab_with_opac(r2.id());
+                                                } catch(E) {
+                                                    alert(E);
                                                 }
-                                            break;
-                                            case 2:
-                                            default:
-                                                alert($("catStrings").getString('staff.cat.z3950.spawn_marc_editor.import_cancelled'));
-                                            break;
-                                        }
-                                    break;
-                                    default:
-                                        throw(r);
-                                    break;
-                                }
-                            } else {
-                                alert($("catStrings").getString('staff.cat.z3950.spawn_marc_editor.successful_import'));
-                                return {
-                                    'id' : r.id(),
-                                    'on_complete' : function() {
-                                        try {
-                                            obj.replace_tab_with_opac(r.id());
-                                        } catch(E) {
-                                            alert(E);
-                                        }
+                                            }
+                                        };
                                     }
-                                };
+                                break;
+                                case 2:
+                                default:
+                                    alert($("catStrings").getString('staff.cat.z3950.spawn_marc_editor.import_cancelled'));
+                                break;
                             }
+                        break;
+                        default:
+                            throw(r);
+                        break;
+                    }
+                } else {
+                    alert($("catStrings").getString('staff.cat.z3950.spawn_marc_editor.successful_import'));
+                    return {
+                        'id' : r.id(),
+                        'on_complete' : function() {
+                            try {
+                                obj.replace_tab_with_opac(r.id());
+                            } catch(E) {
+                                alert(E);
+                            }
+                        }
+                    };
+                }
+            } catch(E) {
+                obj.error.standard_unexpected_error_alert($("catStrings").getString('staff.cat.z3950.spawn_marc_editor.import_error'),E);
+            }
+        };
+
+        if ( $('marc_editor').checked ) {
+            xulG.new_tab(
+                xulG.url_prefix(urls.XUL_MARC_EDIT), 
+                { 'tab_name' : 'MARC Editor' }, 
+                { 
+                    'record' : { 'marc' : my_marcxml, "rtype": "bre" },
+                    'fast_add_item' : function(doc_id,cn_label,cp_barcode) {
+                        try {
+                            JSAN.use('cat.util'); return cat.util.fast_item_add(doc_id,cn_label,cp_barcode);
                         } catch(E) {
-                            obj.error.standard_unexpected_error_alert($("catStrings").getString('staff.cat.z3950.spawn_marc_editor.import_error'),E);
+                            alert(E);
                         }
+                    },
+                    'save' : {
+                        'label' : $("catStrings").getString('staff.cat.z3950.spawn_marc_editor.save_button_label'),
+                        'func' : save_marc
                     }
-                }
-            } 
-        );
+                } 
+            );
+        } else {
+            save_marc(my_marcxml);
+        }
     },
 
     'confirm_overlay' : function(record_ids) {
@@ -865,100 +885,119 @@ cat.z3950.prototype = {
             return;
         }
 
-        xulG.new_tab(
-            xulG.url_prefix(urls.XUL_MARC_EDIT), 
-            { 'tab_name' : $("catStrings").getString('staff.cat.z3950.spawn_marc_editor_for_overlay.tab_name') },
-            { 
-                'record' : { 'marc' : my_marcxml },
-                'fast_add_item' : function(doc_id,cn_label,cp_barcode) {
+        function overlay_marc (new_marcxml) {
+            try {
+                if (! obj.confirm_overlay( [ obj.data.marked_record ] ) ) { return; }
+                var r = obj.network.simple_request('MARC_XML_RECORD_REPLACE', [ ses(), obj.data.marked_record, new_marcxml, biblio_source ]);
+                if (typeof r.ilsevent != 'undefined') {
+                    switch(Number(r.ilsevent)) {
+                        case 1704 /* TCN_EXISTS */ :
+                            var msg = $("catStrings").getFormattedString('staff.cat.z3950.spawn_marc_editor_for_overlay.same_tcn', [r.payload.tcn]);
+                            var title = $("catStrings").getString('staff.cat.z3950.spawn_marc_editor_for_overlay.import_collision');
+                            var btn1 = typeof r.payload.new_tcn == 'undefined' ? null : $("catStrings").getFormattedString('staff.cat.z3950.spawn_marc_editor_for_overlay.btn1_overlay', [r.payload.new_tcn]);
+                            if (btn1) {
+                                var robj = obj.network.simple_request(
+                                    'PERM_CHECK',[
+                                        ses(),
+                                        obj.data.list.au[0].id(),
+                                        obj.data.list.au[0].ws_ou(),
+                                        [ 'ALLOW_ALT_TCN' ]
+                                    ]
+                                );
+                                if (typeof robj.ilsevent != 'undefined') {
+                                    obj.error.standard_unexpected_error_alert($("catStrings").getString('staff.cat.z3950.spawn_marc_editor_for_overlay.permission_error'),E);
+                                }
+                                if (robj.length != 0) btn1 = null;
+                            }
+                            var btn2 = $("catStrings").getString('staff.cat.z3950.spawn_marc_editor_for_overlay.btn2_cancel');
+                            var p = obj.error.yns_alert(msg,title,btn1,btn2,null, $("catStrings").getString('staff.cat.z3950.spawn_marc_editor_for_overlay.confirm_action'));
+                            obj.error.sdump('D_ERROR','option ' + p + 'chosen');
+                            switch(p) {
+                                case 0:
+                                    var r2 = obj.network.request(
+                                        api.MARC_XML_RECORD_REPLACE.app,
+                                        api.MARC_XML_RECORD_REPLACE.method + '.override',
+                                        [ ses(), obj.data.marked_record, new_marcxml, biblio_source ]
+                                    );
+                                    if (typeof r2.ilsevent != 'undefined') {
+                                        throw(r2);
+                                    } else {
+                                        alert($("catStrings").getString('staff.cat.z3950.spawn_marc_editor_for_overlay.successful_overlay_with_new_TCN'));
+                                        return {
+                                            'id' : r2.id(),
+                                            'on_complete' : function() {
+                                                try {
+                                                    obj.replace_tab_with_opac(r2.id());
+                                                } catch(E) {
+                                                    alert(E);
+                                                }
+                                            }
+                                        };
+                                    }
+                                break;
+                                case 1:
+                                default:
+                                    alert($("catStrings").getString('staff.cat.z3950.spawn_marc_editor_for_overlay.cancelled_overlay'));
+                                break;
+                            }
+                        break;
+                        default:
+                            throw(r);
+                        break;
+                    }
+                } else {
+                    alert($("catStrings").getString('staff.cat.z3950.spawn_marc_editor_for_overlay.success_overlay'));
                     try {
-                        JSAN.use('cat.util'); cat.util.fast_item_add(doc_id,cn_label,cp_barcode);
+                        obj.data.marked_record_mvr = null;
+                        obj.data.marked_record = null;
+                        obj.data.stash('marked_record');
+                        obj.data.stash('marked_record_mvr');
+                        obj.controller.view.marc_import_overlay.disabled = true;
+                        if ($("overlay_tcn_indicator")) {
+                            $("overlay_tcn_indicator").setAttribute('value',$("catStrings").getString('staff.cat.z3950.marked_record_for_overlay_indicator.no_record.label'));
+                        }
+                        xulG.set_statusbar(1, $("catStrings").getString('staff.cat.z3950.marked_record_for_overlay_indicator.no_record.label') );
                     } catch(E) {
-                        alert(E);
+                        dump('Error in z3950.js, post-overlay: ' + E + '\n');
                     }
-                },
-                'save' : {
-                    'label' : $("catStrings").getString('staff.cat.z3950.spawn_marc_editor_for_overlay.overlay_record_label'),
-                    'func' : function (new_marcxml) {
-                        try {
-                            if (! obj.confirm_overlay( [ obj.data.marked_record ] ) ) { return; }
-                            var r = obj.network.simple_request('MARC_XML_RECORD_REPLACE', [ ses(), obj.data.marked_record, new_marcxml, biblio_source ]);
-                            if (typeof r.ilsevent != 'undefined') {
-                                switch(Number(r.ilsevent)) {
-                                    case 1704 /* TCN_EXISTS */ :
-                                        var msg = $("catStrings").getFormattedString('staff.cat.z3950.spawn_marc_editor_for_overlay.same_tcn', [r.payload.tcn]);
-                                        var title = $("catStrings").getString('staff.cat.z3950.spawn_marc_editor_for_overlay.import_collision');
-                                        var btn1 = typeof r.payload.new_tcn == 'undefined' ? null : $("catStrings").getFormattedString('staff.cat.z3950.spawn_marc_editor_for_overlay.btn1_overlay', [r.payload.new_tcn]);
-                                        if (btn1) {
-                                            var robj = obj.network.simple_request(
-                                                'PERM_CHECK',[
-                                                    ses(),
-                                                    obj.data.list.au[0].id(),
-                                                    obj.data.list.au[0].ws_ou(),
-                                                    [ 'ALLOW_ALT_TCN' ]
-                                                ]
-                                            );
-                                            if (typeof robj.ilsevent != 'undefined') {
-                                                obj.error.standard_unexpected_error_alert($("catStrings").getString('staff.cat.z3950.spawn_marc_editor_for_overlay.permission_error'),E);
-                                            }
-                                            if (robj.length != 0) btn1 = null;
-                                        }
-                                        var btn2 = $("catStrings").getString('staff.cat.z3950.spawn_marc_editor_for_overlay.btn2_cancel');
-                                        var p = obj.error.yns_alert(msg,title,btn1,btn2,null, $("catStrings").getString('staff.cat.z3950.spawn_marc_editor_for_overlay.confirm_action'));
-                                        obj.error.sdump('D_ERROR','option ' + p + 'chosen');
-                                        switch(p) {
-                                            case 0:
-                                                var r2 = obj.network.request(
-                                                    api.MARC_XML_RECORD_REPLACE.app,
-                                                    api.MARC_XML_RECORD_REPLACE.method + '.override',
-                                                    [ ses(), obj.data.marked_record, new_marcxml, biblio_source ]
-                                                );
-                                                if (typeof r2.ilsevent != 'undefined') {
-                                                    throw(r2);
-                                                } else {
-                                                    alert($("catStrings").getString('staff.cat.z3950.spawn_marc_editor_for_overlay.successful_overlay_with_new_TCN'));
-                                                    return {
-                                                        'id' : r2.id(),
-                                                        'on_complete' : function() {
-                                                            try {
-                                                                obj.replace_tab_with_opac(r2.id());
-                                                            } catch(E) {
-                                                                alert(E);
-                                                            }
-                                                        }
-                                                    };
-                                                }
-                                            break;
-                                            case 1:
-                                            default:
-                                                alert($("catStrings").getString('staff.cat.z3950.spawn_marc_editor_for_overlay.cancelled_overlay'));
-                                            break;
-                                        }
-                                    break;
-                                    default:
-                                        throw(r);
-                                    break;
-                                }
-                            } else {
-                                alert($("catStrings").getString('staff.cat.z3950.spawn_marc_editor_for_overlay.success_overlay'));
-                                return {
-                                    'id' : r.id(),
-                                    'on_complete' : function() {
-                                        try {
-                                            obj.replace_tab_with_opac(r.id());
-                                        } catch(E) {
-                                            alert(E);
-                                        }
-                                    }
-                                };
+                    return {
+                        'id' : r.id(),
+                        'on_complete' : function() {
+                            try {
+                                obj.replace_tab_with_opac(r.id());
+                            } catch(E) {
+                                alert(E);
                             }
+                        }
+                    };
+                }
+            } catch(E) {
+                obj.error.standard_unexpected_error_alert($("catStrings").getString('staff.cat.z3950.spawn_marc_editor_for_overlay.overlay_error'),E);
+            }
+        }
+
+        if ( $('marc_editor').checked ) {
+            xulG.new_tab(
+                xulG.url_prefix(urls.XUL_MARC_EDIT), 
+                { 'tab_name' : $("catStrings").getString('staff.cat.z3950.spawn_marc_editor_for_overlay.tab_name') },
+                { 
+                    'record' : { 'marc' : my_marcxml },
+                    'fast_add_item' : function(doc_id,cn_label,cp_barcode) {
+                        try {
+                            JSAN.use('cat.util'); cat.util.fast_item_add(doc_id,cn_label,cp_barcode);
                         } catch(E) {
-                            obj.error.standard_unexpected_error_alert($("catStrings").getString('staff.cat.z3950.spawn_marc_editor_for_overlay.overlay_error'),E);
+                            alert(E);
                         }
+                    },
+                    'save' : {
+                        'label' : $("catStrings").getString('staff.cat.z3950.spawn_marc_editor_for_overlay.overlay_record_label'),
+                        'func' : overlay_marc
                     }
-                }
-            } 
-        );
+                } 
+            );
+        } else {
+            overlay_marc(my_marcxml);
+        }
     },
 
 
index 3b63172..029dab5 100644 (file)
                         <button id="search" label="&staff.cat.z3950.search.label;" accesskey="&staff.cat.z3950.search.accesskey;" disabled="true"/>
                     </hbox>
                 </groupbox>
-                <splitter id="x_splitter" collapse="after" oils_persist="state hidden"><grippy id="splitter_grippy1"/></splitter>
+                <splitter id="x_splitter" collapse="after" oils_persist="state hidden" oils_persist_peers="x_splitter1 x_splitter2"><grippy id="splitter_grippy1"/></splitter>
                 <groupbox id="x_splitter2" oils_persist="width" flex="1">
                     <caption label="&staff.cat.z3950.service_credentials.label;"/>
                     <grid flex="1">
                     </hbox>
                 </groupbox>
             </hbox>
-            <splitter id="z_splitter" collapse="before" oils_persist="state hidden"><grippy id="splitter_grippy2"/></splitter>
+            <splitter id="z_splitter" collapse="before" oils_persist="state hidden" oils_persist_peers="top_pane z_splitter2"><grippy id="splitter_grippy2"/></splitter>
             <groupbox id="z_splitter2" oils_persist="height" flex="1">
                 <caption label="&staff.cat.z3950.results_caption.label;"/>
                 <hbox>
                     </button>
                     <spacer flex="1"/>
                     <button id="marc_view_btn" command="marc_view" disabled="true"/>
+                    <checkbox id="marc_editor" label="&staff.cat.z3950.marc_editor.label;" accesskey="&staff.cat.z3950.marc_editor.accesskey;" oils_persist="checked" checked="true"/> 
                     <button id="marc_import_overlay" label="&staff.cat.z3950.marc_import_overlay.label;" accesskey="&staff.cat.z3950.marc_import_overlay.accesskey;" disabled="true"/>
                     <button id="marc_import" label="&staff.cat.z3950.result_message.marc_import.label;" accesskey="&staff.cat.z3950.result_message.marc_import.accesskey;" disabled="true"/>
                     <button id="toggle_form_btn" command="toggle_form"/>
                 </hbox>
-                <deck id="deck" flex="1">
-                    <tree id="results" flex="1" enableColumnDrag="true" seltype="single"/>
-                    <iframe id="marc_frame" src="/xul/server/cat/marc_view.html" flex="1"/>
-                </deck>
+                <hbox flex="1">
+                    <image id="jacket_image" oils_persist="width"/>
+                    <splitter id="jacket_splitter" collapse="before" oils_persist="state hidden" oils_persist_peers="jacket_image deck"><grippy id="jacket_splitter_grippy" /></splitter>
+                    <deck id="deck" flex="1" oils_persist="width">
+                        <tree id="results" flex="1" enableColumnDrag="true" seltype="single"/>
+                        <iframe id="marc_frame" src="/xul/server/cat/marc_view.html" flex="1"/>
+                    </deck>
+                </hbox>
             </groupbox>
     </groupbox>
 
index 8128241..13f0214 100644 (file)
@@ -1,6 +1,8 @@
 var error; 
 var network;
 var data;
+var transit_list;
+var hold_list;
 
 function my_init() {
     try {
@@ -39,6 +41,16 @@ function my_init() {
             );
         }
 
+        JSAN.use('circ.util'); 
+        JSAN.use('util.list'); 
+
+        var columns = circ.util.transit_columns({});
+        transit_list = new util.list('transit');
+        transit_list.init( { 'columns' : columns, 'map_row_to_columns' : circ.util.std_map_row_to_columns(), });
+
+        hold_list = new util.list('hold');
+        hold_list.init( { 'columns' : columns, 'map_row_to_columns' : circ.util.std_map_row_to_columns(), });
+
         // timeout so xulG gets a chance to get pushed in
         setTimeout(
             function () { xulG.from_item_details_new = false; load_item(); },
@@ -169,7 +181,11 @@ function load_item() {
             set("copy_circ_lib" , typeof details.copy.circ_lib() == 'object' ? details.copy.circ_lib().shortname() : data.hash.aou[ details.copy.circ_lib() ].shortname()); 
             set_tooltip("copy_circ_lib" , typeof details.copy.circ_lib() == 'object' ? details.copy.circ_lib().name() : data.hash.aou[ details.copy.circ_lib() ].name()); 
             var cm = details.copy.circ_modifier();
-            set("circ_modifier", document.getElementById('commonStrings').getFormattedString('staff.circ_modifier.display',[cm,data.hash.ccm[cm].name(),data.hash.ccm[cm].description()])); 
+            if (typeof data.hash.ccm[cm] != 'undefined') {
+                set("circ_modifier", document.getElementById('commonStrings').getFormattedString('staff.circ_modifier.display',[cm,data.hash.ccm[cm].name(),data.hash.ccm[cm].description()])); 
+            } else {
+                set("circ_modifier","");
+            }
             set("circulate", get_localized_bool( details.copy.circulate() )); 
             set("copy_number", details.copy.copy_number()); 
             set("copy_create_date", util.date.formatted_date( details.copy.create_date(), '%{localized}' )); 
@@ -285,11 +301,9 @@ function load_item() {
         set("hold_transit_copy", '');
 
         if (details.transit) {
-            JSAN.use('circ.util'); var columns = circ.util.transit_columns({});
 
-            JSAN.use('util.list'); var list = new util.list('transit');
-            list.init( { 'columns' : columns, 'map_row_to_columns' : circ.util.std_map_row_to_columns(), });
-            list.append( { 'row' : { 'my' : { 'atc' : details.transit, } } });
+            transit_list.clear();
+            transit_list.append( { 'row' : { 'my' : { 'atc' : details.transit, } } });
 
             var transit_copy_status = typeof details.transit.copy_status() == 'object' ? details.transit.copy_status() : data.hash.ccs[ details.transit.copy_status() ];
                 set("transit_copy_status", transit_copy_status.name() );
@@ -589,9 +603,8 @@ function load_item() {
                 } 
             );
 
-            JSAN.use('util.list'); var list = new util.list('hold');
-            list.init( { 'columns' : columns, 'map_row_to_columns' : circ.util.std_map_row_to_columns(), });
-            list.append( { 'row' : { 'my' : { 'ahr' : better_fleshed_hold_blob.hold, 'acp' : details.copy, 'status' : status_robj, } } });
+            hold_list.clear();
+            hold_list.append( { 'row' : { 'my' : { 'ahr' : better_fleshed_hold_blob.hold, 'acp' : details.copy, 'status' : status_robj, } } });
 
             JSAN.use('patron.util'); 
             var au_obj = patron.util.retrieve_fleshed_au_via_id( ses(), details.hold.usr() );
index b80ce3c..fa5cc66 100644 (file)
@@ -19,7 +19,7 @@
 <?xul-overlay href="/xul/server/cat/bib_brief_overlay.xul"?>
 
 <window id="alt_copy_summary_win" 
-    onload="try { my_init(); font_helper(); } catch(E) { alert(E); }"
+    onload="try { my_init(); font_helper(); persist_helper(); } catch(E) { alert(E); }"
     xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
 
     <!-- ///////////////////////////////////////////////////////////////////////////////////////////////////////////// -->
                     </grid>
                 </tabpanel>
                 <tabpanel orient="vertical"><!-- Hold/Transit -->
-                    <groupbox flex="1" id="holds" style="overflow: none; min-height: 80;">
+                    <groupbox flex="1" id="holds" style="overflow: none; min-height: 80;" oils_persist="height">
                         <caption id="hold_caption" label="&staff.circ.copy_details.hold_caption;"/>
                         <label id="hold_patron_name" class="patronNameLarge"/>
                         <tree id="hold" flex="1" enableColumnDrag="true"/>
                         <spacer FIXME="label and tree get swapped without this"/>
                     </groupbox>
-                    <splitter><grippy/></splitter>
-                    <groupbox flex="1" id="transits" style="overflow: none; min-height: 80;">
+                    <splitter id="splitter" oils_persist="state hidden" oils_persist_peers="holds transits"><grippy/></splitter>
+                    <groupbox flex="1" id="transits" style="overflow: none; min-height: 80;" oils_persist="height">
                         <caption id="transit_caption" label="&staff.circ.copy_details.transit_caption;"/>
                         <tree id="transit" flex="1" enableColumnDrag="true"/>
                     </groupbox>
index 22bc2cc..ef98ce3 100644 (file)
@@ -19,7 +19,7 @@
 <?xul-overlay href="/xul/server/OpenILS/util_overlay.xul"?>
 
 <window id="circ_circ_brief_win" 
-    onload="try { my_init(); font_helper(); } catch(E) { alert(E); }"
+    onload="try { my_init(); font_helper(); persist_helper(); } catch(E) { alert(E); }"
     xmlns:html="http://www.w3.org/1999/xhtml"
     xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
 
index b28d85a..269f81a 100644 (file)
@@ -19,8 +19,8 @@
 <?xul-overlay href="/xul/server/OpenILS/util_overlay.xul"?>
 
 <window id="circ_circ_brief_win" 
-    onload="try { my_init(); font_helper(); } catch(E) { alert(E); }"
-    width="750" height="550"
+    onload="try { my_init(); font_helper(); persist_helper(); } catch(E) { alert(E); }"
+    width="750" height="550" oils_persist="height width sizemode"
     xmlns:html="http://www.w3.org/1999/xhtml"
     xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
 
     </script>
 
     <vbox flex="1" class="my_overflow">
-        <vbox id="top_vbox" flex="1" class="my_overflow"/>
-        <splitter><grippy/></splitter>
-        <vbox id="mid_vbox" flex="1" class="my_overflow"/>
-        <splitter><grippy/></splitter>
-        <groupbox flex="1" id="circs" class="my_overflow">
+        <vbox id="top_vbox" flex="1" class="my_overflow" oils_persist="height"/>
+        <splitter id="splitter1" oils_persist="state hidden" oils_persist_peers="top_vbox mid_vbox"><grippy/></splitter>
+        <vbox id="mid_vbox" flex="1" class="my_overflow" oils_persist="height"/>
+        <splitter id="splitter2" oils_persist="state hidden" oils_persist_peers="mid_vbox circs"><grippy/></splitter>
+        <groupbox flex="1" id="circs" class="my_overflow" oils_persist="height">
             <caption label="&staff.circ.circ_summary.caption;"/>
         </groupbox>
         <hbox>
index d8b42e2..3fcd465 100644 (file)
     <messagecatalog id="circStrings" src="/xul/server/locale/<!--#echo var='locale'-->/circ.properties" />
 
     <vbox flex="1" style="overflow: auto;">
-        <vbox id="top_box" flex="1" style="border: none; overflow: none; min-height: 80;"/>
-        <splitter><grippy/></splitter>
-        <vbox id="item_summary_box" flex="1" style="border: none; overflow: none; min-height: 80;"/>
-        <splitter><grippy/></splitter>
-        <groupbox flex="1" id="holds" style="overflow: none; min-height: 80;">
+        <vbox id="top_box" flex="1" style="border: none; overflow: none; min-height: 80;" oils_persist="height"/>
+        <splitter id="splitter1" oils_persist="state hidden" oils_persist_peers="top_box item_summary_box"><grippy/></splitter>
+        <vbox id="item_summary_box" flex="1" style="border: none; overflow: none; min-height: 80;" oils_persist="height"/>
+        <splitter id="splitter2" oils_persist="state hidden" oils_persist_peers="item_summary_box holds"><grippy/></splitter>
+        <groupbox flex="1" id="holds" style="overflow: none; min-height: 80;" oils_persist="height">
             <caption id="hold_caption" label="&staff.circ.copy_details.hold_caption;"/>
             <label id="patron_name" class="patronNameLarge"/>
             <tree id="hold" flex="1" enableColumnDrag="true"/>
             <spacer FIXME="label and tree get swapped without this"/>
         </groupbox>
-        <splitter><grippy/></splitter>
-        <groupbox flex="1" id="transits" style="overflow: none; min-height: 80;">
+        <splitter id="splitter3" oils_persist="state hidden" oils_persist_peers="holds transits"><grippy/></splitter>
+        <groupbox flex="1" id="transits" style="overflow: none; min-height: 80;" oils_persist="height">
             <caption id="transit_caption" label="&staff.circ.copy_details.transit_caption;"/>
             <tree id="transit" flex="1" enableColumnDrag="true"/>
         </groupbox>
-        <splitter><grippy/></splitter>
-        <groupbox flex="1" id="circs" style="overflow: none; min-height: 80;">
+        <splitter id="splitter4" oils_persist="state hidden" oils_persist_peers="transits circs"><grippy/></splitter>
+        <groupbox flex="1" id="circs" style="overflow: none; min-height: 80;" oils_persist="height">
             <caption id="circ_caption" label="&staff.circ.copy_details.circ_caption;" style="font-weight: bold"/>
             <vbox id="circ_box" flex="1" style="min-height: 80"/>
         </groupbox>
index 5c24a0a..6991013 100644 (file)
@@ -18,7 +18,7 @@
 <?xul-overlay href="/xul/server/OpenILS/util_overlay.xul"?>
 
 <window id="main_test_win" 
-    onload="try { my_init(); font_helper(); } catch(E) { alert(E); }"
+    onload="try { my_init(); font_helper(); persist_helper(); } catch(E) { alert(E); }"
     xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
 
     <!-- ///////////////////////////////////////////////////////////////////////////////////////////////////////////// -->
index e8b20d6..d25552c 100644 (file)
@@ -21,8 +21,8 @@
 <?xul-overlay href="/xul/server/OpenILS/util_overlay.xul"?>
 
 <window id="pre_cat_fields" title="&staff.circ.pre_cat.window.title;"
-    orient="vertical" style="overflow: auto"
-    onload="try{my_init();font_helper(); }catch(E){alert(E);}"
+    orient="vertical" style="overflow: auto" oils_persist="height width sizemode"
+    onload="try{ my_init(); font_helper(); persist_helper(); }catch(E){alert(E);}"
     xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
 
     <!-- ///////////////////////////////////////////////////////////////////////////////////////////////////////////// -->
index 9bb56ff..534b16c 100644 (file)
@@ -18,7 +18,7 @@
 <?xul-overlay href="/xul/server/OpenILS/util_overlay.xul"?>
 
 <window id="print_list_win" 
-    onload="try { my_init(); font_helper(); } catch(E) { alert(E); }"
+    onload="try { my_init(); font_helper(); persist_helper(); } catch(E) { alert(E); }"
     xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
 
     <!-- ///////////////////////////////////////////////////////////////////////////////////////////////////////////// -->
index 3697f5a..0b5b56c 100644 (file)
@@ -2089,6 +2089,15 @@ circ.util.hold_columns = function(modify,params) {
             'editable' : false, 'render' : function(my) { return my.patron_family_name ? my.patron_family_name : ""; }
         },
         {
+            "persist": "hidden width ordinal",
+            "id": "patron_alias",
+            'label' : document.getElementById('circStrings').getString('staff.circ.utils.patron_alias'),
+            'flex' : 1,
+            'primary' : false,
+            'hidden' : true,
+            'editable' : false, 'render' : function(my) { return my.patron_alias ? my.patron_alias : ""; }
+        },
+        {
             'persist' : 'hidden width ordinal',
             'id' : 'patron_first_given_name',
             'label' : document.getElementById('circStrings').getString('staff.circ.utils.patron_first_given_name'),
@@ -2409,6 +2418,8 @@ circ.util.checkin_via_barcode2 = function(session,params,backdate,auto_print,che
             'route_to' : '',
             'route_to_msg' : '',
             'route_to_org_fullname' : '',
+            'destination_shelf' : '',
+            'destination_shelf_msg' : '',
             'courier_code' : '',
             'street1' : '',
             'street2' : '',
@@ -2506,16 +2517,24 @@ circ.util.checkin_via_barcode2 = function(session,params,backdate,auto_print,che
                             if (behind_the_desk_support) {
                                var usr_settings = network.simple_request('FM_AUS_RETRIEVE',[ses(),check.payload.hold.usr()]); 
                                 if (typeof usr_settings['circ.holds_behind_desk'] != 'undefined') {
-                                    print_data.prefer_behind_holds_desk = true;
-                                    check.route_to = document.getElementById('circStrings').getString('staff.circ.route_to.private_hold_shelf');
-                                    print_data.route_to_msg = document.getElementById('circStrings').getFormattedString('staff.circ.utils.route_to.msg', [check.route_to]);
-                                    print_data.route_to = check.route_to;
+                                    if (usr_settings['circ.holds_behind_desk']) {
+                                        print_data.prefer_behind_holds_desk = true;
+                                        check.route_to = document.getElementById('circStrings').getString('staff.circ.route_to.private_hold_shelf');
+                                        print_data.route_to_msg = document.getElementById('circStrings').getFormattedString('staff.circ.utils.route_to.msg', [check.route_to]);
+                                        print_data.route_to = check.route_to;
+                                    } else {
+                                        check.route_to = document.getElementById('circStrings').getString('staff.circ.route_to.public_hold_shelf');
+                                        print_data.route_to_msg = document.getElementById('circStrings').getFormattedString('staff.circ.utils.route_to.msg', [check.route_to]);
+                                        print_data.route_to = check.route_to;
+                                    }
                                 } else {
                                     check.route_to = document.getElementById('circStrings').getString('staff.circ.route_to.public_hold_shelf');
                                     print_data.route_to_msg = document.getElementById('circStrings').getFormattedString('staff.circ.utils.route_to.msg', [check.route_to]);
                                     print_data.route_to = check.route_to;
                                 }
                             }
+                            print_data.destination_shelf_msg = print_data.route_to_msg;
+                            print_data.destination_shelf = print_data.route_to;
                             msg += print_data.route_to_msg;
                             msg += '\n';
                         }
@@ -2891,6 +2910,26 @@ circ.util.checkin_via_barcode2 = function(session,params,backdate,auto_print,che
                 JSAN.use('patron.util');
                 var au_obj = patron.util.retrieve_fleshed_au_via_id( session, check.payload.hold.usr() );
                 print_data.user = au_obj;
+                print_data.user_stat_cat_entries = [];
+                var entries = au_obj.stat_cat_entries();
+                for (var i = 0; i < entries.length; i++) {
+                    var stat_cat = data.hash.my_actsc[ entries[i].stat_cat() ];
+                    if (!stat_cat) {
+                        stat_cat = data.lookup('actsc', entries[i].stat_cat());
+                    }
+                    print_data.user_stat_cat_entries.push( { 
+                        'id' : entries[i].id(),
+                        'stat_cat' : {
+                            'id' : stat_cat.id(),
+                            'name' : stat_cat.name(),
+                            'opac_visible' : stat_cat.opac_visible(),
+                            'owner' : stat_cat.owner(),
+                            'usr_summary' : stat_cat.usr_summary()
+                        },
+                        'stat_cat_entry' : entries[i].stat_cat_entry(),
+                        'target_usr' : entries[i].target_usr() 
+                    } );
+                }
                 msg += '\n';
                 if (au_obj.alias()) {
                     print_data.hold_for_msg = document.getElementById('circStrings').getFormattedString('staff.circ.utils.payload.hold.patron_alias',  [au_obj.alias()]);
@@ -2950,6 +2989,29 @@ circ.util.checkin_via_barcode2 = function(session,params,backdate,auto_print,che
                 print_data.request_date_msg = document.getElementById('circStrings').getFormattedString('staff.circ.utils.payload.hold.request_date', [print_data.request_date]);
                 msg += print_data.request_date_msg;
                 msg += '\n';
+                var destination_shelf = document.getElementById('circStrings').getString('staff.circ.route_to.hold_shelf');
+                print_data.destination_shelf_msg = document.getElementById('circStrings').getFormattedString('staff.circ.utils.route_to.msg', [destination_shelf]);
+                print_data.destination_shelf = destination_shelf;
+                var behind_the_desk_support = String( data.hash.aous['circ.holds.behind_desk_pickup_supported'] ) == 'true';
+                if (behind_the_desk_support) {
+                   var usr_settings = network.simple_request('FM_AUS_RETRIEVE',[ses(),check.payload.hold.usr()]); 
+                    if (typeof usr_settings['circ.holds_behind_desk'] != 'undefined') {
+                        if (usr_settings['circ.holds_behind_desk']) {
+                            print_data.prefer_behind_holds_desk = true;
+                            destination_shelf = document.getElementById('circStrings').getString('staff.circ.route_to.private_hold_shelf');
+                            print_data.destination_shelf_msg = document.getElementById('circStrings').getFormattedString('staff.circ.utils.route_to.msg', [destination_shelf]);
+                            print_data.destination_shelf = destination_shelf;
+                        } else {
+                            destination_shelf = document.getElementById('circStrings').getString('staff.circ.route_to.public_hold_shelf');
+                            print_data.destination_shelf_msg = document.getElementById('circStrings').getFormattedString('staff.circ.utils.route_to.msg', [destination_shelf]);
+                            print_data.destination_shelf = destination_shelf;
+                        }
+                    } else {
+                        destination_shelf = document.getElementById('circStrings').getString('staff.circ.route_to.public_hold_shelf');
+                        print_data.destination_shelf_msg = document.getElementById('circStrings').getFormattedString('staff.circ.utils.route_to.msg', [destination_shelf]);
+                        print_data.destination_shelf = destination_shelf;
+                    }
+                }
             }
             var rv = 0;
             var suppress_popups = data.hash.aous['ui.circ.suppress_checkin_popups'];
index 739d848..68f5f25 100644 (file)
@@ -278,16 +278,7 @@ staff.cat.record_buckets.new_bucket.bucket_prompt=What would you like to name th
 staff.cat.record_buckets.new_bucket.bucket_prompt_title=Bucket Creation
 staff.cat.record_buckets.new_bucket.same_name_alert=You already have a bucket with that name.
 staff.cat.record_buckets.new_bucket.bucket_created=Bucket %1$s created.
-staff.cat.record_buckets.merge_records.merge_lead=Merge these records? (Select the "lead" record first)
-staff.cat.record_buckets.merge_records.button.label=Merge
-staff.cat.record_buckets.merge_records.cancel_button.label=Cancel
-staff.cat.record_buckets.merge_records.cancel_button.accesskey=C
-staff.cat.record_buckets.merge_records.lead_record_number=Lead Record? # %1$s
-staff.cat.record_buckets.merge_records.lead=Lead
 staff.cat.record_buckets.merge_records.fancy_prompt_title=Record Merging
-staff.cat.record_buckets.merge_records.fancy_prompt.alert=Merge Aborted
-staff.cat.record_buckets.merge_records.success=Records were successfully merged.
-staff.cat.record_buckets.merge_records.catch.std_unex_error=Records were not likely merged.
 staff.cat.record_buckets.delete_records.xml1=Delete these records?
 staff.cat.record_buckets.delete_records.button.label=Delete
 staff.cat.record_buckets.delete_records.cancel_button.label=Cancel
@@ -458,6 +449,7 @@ staff.cat.z3950.handle_results.num_of_results=%1$s records found
 staff.cat.z3950.handle_results.result_error=Error retrieving results.
 staff.cat.z3950.handle_results.search_result_error=Failure during search result handling.
 staff.cat.z3950.replace_tab_with_opac.tab_name=Retrieving title...
+staff.cat.z3950.spawn_marc_editor.save_button_label=Import Record
 staff.cat.z3950.spawn_marc_editor.same_tcn=A record with TCN %1$s already exists.\nFIXME: add record summary here
 staff.cat.z3950.spawn_marc_editor.title=Import Collision
 staff.cat.z3950.spawn_marc_editor.btn1_overlay=Overlay
index 005e062..c6f718d 100644 (file)
@@ -330,6 +330,7 @@ staff.circ.utils.title.none=No Title?
 staff.circ.utils.author.none=No Author?
 staff.circ.utils.notify_time=Last Notify Time
 staff.circ.utils.notify_count=Notices
+staff.circ.utils.patron_alias=Patron Alias
 staff.circ.utils.patron_family_name=Patron Last Name
 staff.circ.utils.patron_first_given_name=Patron First Name
 staff.circ.utils.checkin.override=Override Checkin Failure?
index 6ca97b1..5d80e27 100644 (file)
             }
 
             g.open_menu = function() {
-                try {
-                    netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect");
-                    var mframe = xulG.window.open(urls.XUL_MENU_FRAME
-                        + '?server='+window.escape(xulG.url),
-                        'main'+xulG.window.window_name_increment(),'chrome,resizable'
-                    );
-                    delete xulG['_sound']; // This came from util.error but I want menu.js to have its own
-                    mframe.xulG = xulG; // This is the xulG from main.js, with auth, url, and window
-                } catch(E) {
-                    alert(E);
-                }
+                delete xulG['_sound'];
+                netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect");
+                var wm = Components.classes["@mozilla.org/appshell/window-mediator;1"].
+                    getService(Components.interfaces.nsIWindowMediator);
+                var eg_main = wm.getMostRecentWindow('eg_main');
+                eg_main.openTabs.unshift('init');
+                wm.getMostRecentWindow('eg_main').new_tabs(null);
             }
 
             g.data.init();
index e67c9bc..f30a319 100644 (file)
@@ -21,8 +21,8 @@
 <!-- OVERLAYS -->
 <?xul-overlay href="/xul/server/OpenILS/util_overlay.xul"?>
 
-<window id="simple_auth_win" 
-    onload="try { my_init(); font_helper(); } catch(E) { alert(E); }"
+<window id="simple_auth_win" oils_persist="height width sizemode"
+    onload="try { my_init(); font_helper(); persist_helper(); } catch(E) { alert(E); }"
     xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
 
     <!-- ///////////////////////////////////////////////////////////////////////////////////////////////////////////// -->
index 3a6d357..62aed30 100644 (file)
@@ -17,7 +17,7 @@
 <!-- OVERLAYS -->
 <?xul-overlay href="/xul/server/OpenILS/util_overlay.xul"?>
 
-<window id="verify_win" onload="try { verify_init(); font_helper(); } catch(E) { alert(E); }"
+<window id="verify_win" onload="try { verify_init(); font_helper(); persist_helper(); } catch(E) { alert(E); }"
     xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
 
     <!-- ///////////////////////////////////////////////////////////////////////////////////////////////////////////// -->
index 03fee95..0de36de 100644 (file)
@@ -18,7 +18,7 @@
 <?xul-overlay href="/xul/server/OpenILS/util_overlay.xul"?>
 
 <window id="patron_barcode_entry_win" 
-    onload="try { my_init(); font_helper(); } catch(E) { alert(E); }"
+    onload="try { my_init(); font_helper(); persist_helper(); } catch(E) { alert(E); }"
     xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
 
     <!-- ///////////////////////////////////////////////////////////////////////////////////////////////////////////// -->
index 1d4cc14..7366227 100644 (file)
@@ -58,7 +58,7 @@
     <vbox flex="1" class="my_overflow">
         <groupbox orient="vertical" flex="1">
             <caption id="caption" label="&staff.patron.bill_interface.caption.label;"/>
-            <hbox>
+            <hbox id="before_splitter1" oils_persist="height">
                 <grid flex="1">
                     <columns flex="1">
                         <column/>
                                 <label value="&staff.patron.bills_overlay.payment_type.value;" class="emphasis1" accesskey="&staff.patron.bills_overlay.payment_type.accesskey;" control="payment_type"/>
                                 <menulist id="payment_type">
                                     <menupopup id="payment_type_menupopup">
-                                        <menuitem id="payment_type_menuitem1" label="&staff.patron.bills_overlay.cash.label;" value="cash_payment"/>
-                                        <menuitem id="payment_type_menuitem2" label="&staff.patron.bills_overlay.check.label;" value="check_payment"/>
-                                        <menuitem id="payment_type_menuitem3" label="&staff.patron.bills_overlay.credit_card.label;" value="credit_card_payment"/>
+                                        <menuitem id="payment_type_menuitem1" class="hide_patron_cash" label="&staff.patron.bills_overlay.cash.label;" value="cash_payment"/>
+                                        <menuitem id="payment_type_menuitem2" class="hide_patron_check" label="&staff.patron.bills_overlay.check.label;" value="check_payment"/>
+                                        <menuitem id="payment_type_menuitem3" class="hide_patron_credit_card" label="&staff.patron.bills_overlay.credit_card.label;" value="credit_card_payment"/>
                                         <menuitem id="payment_type_menuitem4" class="hide_patron_credit" label="&staff.patron.bills_overlay.patron_credit.label;" value="credit_payment" />
-                                        <menuitem id="payment_type_menuitem5" label="&staff.patron.bills_overlay.word.label;" value="work_payment"/>
-                                        <menuitem id="payment_type_menuitem6" label="&staff.patron.bills_overlay.forgive.label;" value="forgive_payment"/>
-                                        <menuitem id="payment_type_menuitem7" label="&staff.patron.bills_overlay.goods.label;" value="goods_payment"/>
+                                        <menuitem id="payment_type_menuitem5" class="hide_patron_work" label="&staff.patron.bills_overlay.word.label;" value="work_payment"/>
+                                        <menuitem id="payment_type_menuitem6" class="hide_patron_forgive" label="&staff.patron.bills_overlay.forgive.label;" value="forgive_payment"/>
+                                        <menuitem id="payment_type_menuitem7" class="hide_patron_goods" label="&staff.patron.bills_overlay.goods.label;" value="goods_payment"/>
                                     </menupopup>
                                 </menulist>
                             </row>
                     </hbox>
                 </groupbox>
             </hbox>
-            <splitter />
-            <vbox flex="1">
+            <splitter id="splitter1" oils_persist="state hidden" oils_persist_peers="before_splitter1 after_splitter1" />
+            <vbox flex="1" id="after_splitter1" oils_persist="height">
                 <hbox>
                     <button id="bill_patron_btn" label="&staff.patron.bills_overlay.bill_patron.label;" accesskey="&staff.patron.bills_overlay.bill_patron.accesskey;" />
                     <button id="bill_history_btn" label="&staff.patron.bills_overlay.history.label;" accesskey="&staff.patron.bills_overlay.history.accesskey;" />
                     </button>
                 </hbox>
             </vbox>
-            <splitter />
-            <hbox>
+            <splitter id="splitter2" oils_persist="state hidden" oils_persist_peers="after_splitter1 after_splitter2" />
+            <hbox id="after_splitter2" oils_persist="height">
                 <vbox>
                     <hbox>
                         <label value='&staff.patron.bill_interface.voided_this_session.label;' class="emphasis1"/><label id="currently_voided" value="0.00"/>
index 1c78639..2d9656e 100644 (file)
@@ -19,8 +19,8 @@
 <?xul-overlay href="/xul/server/OpenILS/util_overlay.xul"?>
 
 <window id="patron_bill" title="&staff.patron.bill_cc_info.title;"
-    orient="vertical" style="overflow: auto"
-    onload="try{info_init(); font_helper();refresh_fields();}catch(E){alert(E);}"
+    orient="vertical" style="overflow: auto" oils_persist="height width sizemode"
+    onload="try{info_init(); font_helper(); refresh_fields(); persist_helper(); }catch(E){alert(E);}"
     xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
 
     <!-- ///////////////////////////////////////////////////////////////////////////////////////////////////////////// -->
index 668d09c..0bcbb12 100644 (file)
@@ -19,8 +19,8 @@
 <?xul-overlay href="/xul/server/OpenILS/util_overlay.xul"?>
 
 <window id="patron_bill" title="&staff.patron.bill_check_info.title;"
-    orient="vertical" style="overflow: auto"
-    onload="try{info_init(); font_helper();}catch(E){alert(E);}"
+    orient="vertical" style="overflow: auto" oils_persist="height width sizemode"
+    onload="try{info_init(); font_helper(); persist_helper(); }catch(E){alert(E);}"
     xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
 
     <!-- ///////////////////////////////////////////////////////////////////////////////////////////////////////////// -->
index b24189d..a16d4e7 100644 (file)
@@ -20,8 +20,8 @@
 <?xul-overlay href="/xul/server/OpenILS/util_overlay.xul"?>
 <?xul-overlay href="/xul/server/patron/bill_summary_overlay.xul"?>
 
-<window id="bill_details_win" width="700" height="550"
-    onload="try{ my_init(); font_helper(); } catch(E) { alert(E); }"
+<window id="bill_details_win" width="700" height="550" oils_persist="width height sizemode"
+    onload="try{ my_init(); font_helper(); persist_helper(); } catch(E) { alert(E); }"
     xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
 
     <!-- ///////////////////////////////////////////////////////////////////////////////////////////////////////////// -->
     <vbox flex="1" class="my_overflow">
         <label id="patron_name" class="patronNameLarge"/>
 
-        <groupbox orient="vertical" flex="1" id="summary" />
+        <groupbox orient="vertical" flex="1" id="summary" oils_persist="height"/>
 
-        <splitter><grippy/></splitter>
+        <splitter id="splitter1" oils_persist="state hidden" oils_persist_peers="summary copy_summary_vbox"><grippy/></splitter>
 
         <vbox id="copy_summary_vbox" flex="1" />
 
-        <splitter id="copy_summary_splitter"><grippy/></splitter>
+        <splitter id="copy_summary_splitter" oils_persist="state hidden" oils_persist_peers="copy_summary_vbox after_copy_summary_splitter"><grippy/></splitter>
 
-            <groupbox orient="vertical" flex="2">
+            <groupbox id="after_copy_summary_splitter" oils_persist="height" orient="vertical" flex="2">
                 <caption label="&staff.patron.bill_details.bills.label;" style="color: red"/>
                 <tree id="bill_tree" flex="1" enableColumnDrag="true"/>
                 <hbox>
@@ -56,9 +56,9 @@
                 </hbox>
             </groupbox>
 
-            <splitter><grippy/></splitter>
+            <splitter id="splitter2" oils_persist="state hidden" oils_persist_peers="after_copy_summary_splitter after_splitter2"><grippy/></splitter>
 
-            <groupbox orient="vertical" flex="2">
+            <groupbox orient="vertical" flex="2" id="after_splitter2" oils_persist="height">
                 <caption label="&staff.patron.bill_details.payments.label;" style="color: green"/>
                 <tree id="payment_tree" flex="1" enableColumnDrag="true"/>
                 <hbox>
index 0818a9a..d07f71d 100644 (file)
@@ -20,8 +20,8 @@
 <?xul-overlay href="/xul/server/patron/bill_summary_overlay.xul"?>
 
 <window id="patron_bill" title="&staff.patron.bill_wizard.title;"
-    orient="vertical" style="overflow: auto"
-    onload="try { patron_bill_init(); font_helper(); } catch(E) { alert(E); }" width="700" height="550"
+    orient="vertical" style="overflow: auto" oils_persist="width height sizemode"
+    onload="try { patron_bill_init(); font_helper(); persist_helper(); } catch(E) { alert(E); }" width="700" height="550"
     xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
 
     <!-- ///////////////////////////////////////////////////////////////////////////////////////////////////////////// -->
index 5feb719..d37dbb4 100644 (file)
@@ -35,7 +35,7 @@
         <hbox id="left_deck_vbox" flex="1" oils_persist="height"> 
             <deck id="patron_left_deck" oils_persist="height"/>
         </hbox>
-        <splitter id="deck_splitter" collapse="before" oils_persist="state hidden"><grippy id="splitter_grippy"/></splitter>
+        <splitter id="deck_splitter" collapse="before" oils_persist="state hidden" oils_persist_peers="left_deck_vbox right_deck_vbox"><grippy id="splitter_grippy"/></splitter>
         <hbox id="right_deck_vbox" flex="3" oils_persist="height">
             <deck id="patron_right_deck" oils_persist="height"/>
         </hbox>
index 3be2d9f..e94256e 100644 (file)
@@ -35,7 +35,7 @@
         <vbox id="left_deck_vbox" flex="1" oils_persist="width"> 
             <deck id="patron_left_deck" oils_persist="width"/>
         </vbox>
-        <splitter id="deck_splitter" collapse="before" oils_persist="state hidden"><grippy id="splitter_grippy"/></splitter>
+        <splitter id="deck_splitter" collapse="before" oils_persist="state hidden" oils_persist_peers="left_deck_vbox right_deck_vbox"><grippy id="splitter_grippy"/></splitter>
         <vbox id="right_deck_vbox" flex="3" oils_persist="width">
             <deck id="patron_right_deck" oils_persist="width"/>
         </vbox>
index 1dd512f..d27c001 100644 (file)
@@ -18,7 +18,8 @@
 <?xul-overlay href="/xul/server/OpenILS/util_overlay.xul"?>
 
 <window id="edit_penalty_win" 
-    onload="try { edit_penalty_init(); font_helper(); } catch(E) { alert(E); }"
+    onload="try { edit_penalty_init(); font_helper(); persist_helper(); } catch(E) { alert(E); }"
+    oils_persist="height width sizemode"
     xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
     title="&staff.patron_display.edit_penalty_dialog.title;">
 
index fdc8c61..fb3830c 100644 (file)
@@ -18,7 +18,7 @@
 <?xul-overlay href="/xul/server/OpenILS/util_overlay.xul"?>
 
 <window id="hold_cancel_win" 
-    onload="try { hold_cancel_init(); font_helper(); } catch(E) { alert(E); }"
+    onload="try { hold_cancel_init(); font_helper(); persist_helper(); } catch(E) { alert(E); }"
     xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
     title="&staff.hold_list.cancel_hold_dialog.title;">
 
index 4a7b939..a4a4f1b 100644 (file)
@@ -21,6 +21,10 @@ function my_init() {
 
         if (xulG.ahr_id) fetch_and_render_all();
 
+        if (xul_param('when_done')) {
+            xul_param('when_done')();
+        }
+
     } catch(E) {
         try { g.error.standard_unexpected_error_alert('/xul/server/patron/hold_notices.xul',E); } catch(E) { alert('FIXME: ' + js2JSON(E)); }
     }
@@ -121,33 +125,39 @@ function init_list() {
             },
         }
     );
+    dump('hold details init_list done\n');
 }
 
 function a_list_of_one() {
-    g.list.clear();
-    g.list.append(
-        {
-            'row' : {
-                'my' : {
-                    'ahr' : g.ahr,
-                    'status' : g.blob.status,
-                    'acp' : g.blob.copy,
-                    'acn' : g.blob.volume,
-                    'mvr' : g.blob.mvr,
-                    'patron_family_name' : g.blob.patron_last,
-                    'patron_first_given_name' : g.blob.patron_first,
-                    'patron_barcode' : g.blob.patron_barcode,
-                    'total_holds' : g.blob.total_holds,
-                    'queue_position' : g.blob.queue_position,
-                    'potential_copies' : g.blob.potential_copies,
-                    'estimated_wait' : g.blob.estimated_wait,
-                    'ahrn_count' : g.blob.hold.notes().length,
-                    'blob' : g.blob
-                }
-            },
-            'no_auto_select' : true,
-        }
-    );
+    try {
+        g.list.clear();
+        g.list.append(
+            {
+                'row' : {
+                    'my' : {
+                        'ahr' : g.ahr,
+                        'status' : g.blob.status,
+                        'acp' : g.blob.copy,
+                        'acn' : g.blob.volume,
+                        'mvr' : g.blob.mvr,
+                        'patron_family_name' : g.blob.patron_last,
+                        'patron_first_given_name' : g.blob.patron_first,
+                        'patron_barcode' : g.blob.patron_barcode,
+                        'patron_alias' : g.blob.patron_alias,
+                        'total_holds' : g.blob.total_holds,
+                        'queue_position' : g.blob.queue_position,
+                        'potential_copies' : g.blob.potential_copies,
+                        'estimated_wait' : g.blob.estimated_wait,
+                        'ahrn_count' : g.blob.hold.notes().length,
+                        'blob' : g.blob
+                    }
+                },
+                'no_auto_select' : true,
+            }
+        );
+    } catch(E) {
+        alert('Error in hold_details.js, a_list_of_one(): ' + E);
+    }
 }
 
 function retrieve_notifications() {
index 1d3a344..779b798 100644 (file)
@@ -65,7 +65,7 @@
         <vbox id="bib_brief_box" flex="1" style="min-height: 10em;"/>
     </vbox>
 
-    <splitter><grippy/></splitter>
+    <splitter id="splitter1" oils_persist="state hidden" oils_persist_peers="v1 v2"><grippy/></splitter>
 
     <vbox id="v2" flex="1" oils_persist="height">
         <vbox flex="1">
@@ -74,9 +74,9 @@
         </vbox>
     </vbox>
 
-    <splitter><grippy/></splitter>
+    <splitter id="splitter2" oils_persist="state hidden" oils_persist_peers="v2 after_splitter2"><grippy/></splitter>
 
-    <tabbox flex="1">
+    <tabbox flex="1" id="after_splitter2" oils_persist="height">
         <tabs>
             <tab label="&staff.patron.holds.notes_tab.label;" accesskey="&staff.patron.holds.notes_tab.accesskey;" />
             <tab label="&staff.patron.holds.notices_tab.label;" accesskey="&staff.patron.holds.notices_tab.accesskey;" />
index 72c4caa..f6f8116 100644 (file)
@@ -85,6 +85,7 @@ patron.holds.prototype = {
                                     row.my.patron_family_name = blob.patron_last;
                                     row.my.patron_first_given_name = blob.patron_first;
                                     row.my.patron_barcode = blob.patron_barcode;
+                                    row.my.patron_alias = blob.patron_alias;
                                     row.my.total_holds = blob.total_holds;
                                     row.my.queue_position = blob.queue_position;
                                     row.my.potential_copies = blob.potential_copies;
@@ -150,7 +151,10 @@ patron.holds.prototype = {
                         obj.controller.view.cmd_holds_edit_request_date.setAttribute('disabled','false');
                         obj.controller.view.cmd_holds_activate.setAttribute('disabled','false');
                         obj.controller.view.cmd_holds_suspend.setAttribute('disabled','false');
-                        obj.controller.view.cmd_alt_view.setAttribute('disabled','false');
+                        obj.controller.view.cmd_alt_view.setAttribute('rendering_rows','false');
+                        if (obj.controller.view.cmd_alt_view.getAttribute('ready')=='true') {
+                            obj.controller.view.cmd_alt_view.setAttribute('disabled','false');
+                        }
                         obj.controller.view.cmd_holds_retarget.setAttribute('disabled','false');
                         obj.controller.view.cmd_holds_cancel.setAttribute('disabled','false');
                         obj.controller.view.cmd_holds_uncancel.setAttribute('disabled','false');
@@ -173,6 +177,7 @@ patron.holds.prototype = {
                         obj.controller.view.cmd_holds_activate.setAttribute('disabled','true');
                         obj.controller.view.cmd_holds_suspend.setAttribute('disabled','true');
                         obj.controller.view.cmd_alt_view.setAttribute('disabled','true');
+                        obj.controller.view.cmd_alt_view.setAttribute('rendering_rows','true');
                         obj.controller.view.cmd_holds_retarget.setAttribute('disabled','true');
                         obj.controller.view.cmd_holds_cancel.setAttribute('disabled','true');
                         obj.controller.view.cmd_holds_uncancel.setAttribute('disabled','true');
@@ -323,6 +328,43 @@ patron.holds.prototype = {
                             }
                         }
                     ],
+                    'cmd_holds_print_alt' : [
+                        ['command'],
+                        function() {
+                            try {
+                                var content_params = {
+                                    "session": ses(),
+                                    "authtime": ses("authtime"),
+                                    "no_xulG": false,
+                                    "show_nav_buttons": true,
+                                    "show_print_button": false
+                                };
+                                ["url_prefix", "new_tab", "set_tab",
+                                    "close_tab", "new_patron_tab",
+                                    "set_patron_tab", "volume_item_creator",
+                                    "get_new_session",
+                                    "holdings_maintenance_tab", "set_tab_name",
+                                    "open_chrome_window", "url_prefix",
+                                    "network_meter", "page_meter",
+                                    "set_statusbar", "set_help_context"
+                                ].forEach(function(k) {
+                                    content_params[k] = xulG[k];
+                                });
+
+                                var loc = urls.XUL_BROWSER + "?url=" + window.escape(
+                                    xulG.url_prefix("/opac/extras/circ/alt_holds_print.html").replace("http:","https:")
+                                );
+                                xulG.new_tab(
+                                    loc, {
+                                        "tab_name": "Printable Pull List", /* XXX i18n */
+                                        "browser": false
+                                    }, content_params
+                                );
+                            } catch (E) {
+                                g.error.sdump("D_ERROR", E);
+                            }
+                        }
+                    ],
                     'cmd_holds_print' : [
                         ['command'],
                         function() {
@@ -1293,6 +1335,7 @@ patron.holds.prototype = {
         var x_clear_shelf_widgets = document.getElementById('clear_shelf_widgets');
         var x_expired_checkbox = document.getElementById('expired_checkbox');
         var x_print_full_pull_list = document.getElementById('print_full_btn');
+        var x_print_full_pull_list_alt = document.getElementById('print_alt_btn');
         switch(obj.hold_interface_type) {
             case 'shelf':
                 obj.render_lib_menus({'pickup_lib':true});
@@ -1300,10 +1343,12 @@ patron.holds.prototype = {
                 if (x_lib_type_menu) x_lib_type_menu.hidden = false;
                 if (x_lib_menu_placeholder) x_lib_menu_placeholder.hidden = false;
                 if (x_clear_shelf_widgets) x_clear_shelf_widgets.hidden = false;
+                if (x_print_full_pull_list_alt) x_print_full_pull_list_alt.hidden = true;
             break;
             case 'pull' :
                 if (x_fetch_more) x_fetch_more.hidden = false;
                 if (x_print_full_pull_list) x_print_full_pull_list.hidden = false;
+                if (x_print_full_pull_list_alt) x_print_full_pull_list_alt.hidden = false;
                 if (x_lib_type_menu) x_lib_type_menu.hidden = true;
                 if (x_lib_menu_placeholder) x_lib_menu_placeholder.hidden = true;
             break;
@@ -1311,6 +1356,7 @@ patron.holds.prototype = {
                 obj.render_lib_menus({'pickup_lib':true,'request_lib':true});
                 if (x_lib_filter_checkbox) x_lib_filter_checkbox.hidden = false;
                 if (x_lib_type_menu) x_lib_type_menu.hidden = false;
+                if (x_print_full_pull_list_alt) x_print_full_pull_list_alt.hidden = true;
                 if (x_lib_menu_placeholder) x_lib_menu_placeholder.hidden = false;
             break;
             default:
@@ -1319,6 +1365,7 @@ patron.holds.prototype = {
                 if (x_lib_type_menu) x_lib_type_menu.hidden = true;
                 if (x_lib_menu_placeholder) x_lib_menu_placeholder.hidden = true;
                 if (x_show_cancelled_deck) x_show_cancelled_deck.hidden = false;
+                if (x_print_full_pull_list_alt) x_print_full_pull_list_alt.hidden = true;
             break;
         }
         setTimeout( // We do this because render_lib_menus above creates and appends a DOM node, but until this thread exits, it doesn't really happen
@@ -1346,6 +1393,14 @@ patron.holds.prototype = {
             }, 0
         );
 
+        $('cmd_alt_view').setAttribute('disabled','true');
+        xulG.when_done = function() {
+            $('cmd_alt_view').setAttribute('ready','true');
+            if ($('cmd_alt_view').getAttribute('rendering_rows') != 'true') {
+                $('cmd_alt_view').setAttribute('disabled','false');
+            }
+            dump('hold details UI ready\n');
+        }
         netscape.security.PrivilegeManager.enablePrivilege('UniversalXPConnect');
         JSAN.use('util.browser');
         obj.browser = new util.browser();
@@ -1355,7 +1410,7 @@ patron.holds.prototype = {
                 'push_xulG' : true,
                 'alt_print' : false,
                 'browser_id' : 'hold_detail_frame',
-                'passthru_content_params' : xulG,
+                'passthru_content_params' : xulG
             }
         );
 
index 106501c..ce36619 100644 (file)
@@ -19,6 +19,7 @@
         <command id="cmd_csv_to_file" />
         <command id="cmd_holds_print" />
         <command id="cmd_holds_print_full" />
+        <command id="cmd_holds_print_alt" />
         <command id="cmd_show_catalog" />
         <command id="cmd_retrieve_patron" />
         <command id="cmd_holds_edit_desire_mint_condition" />
@@ -42,7 +43,7 @@
             accesskey="&staff.circ.holds.title_transfer.accesskey;" />
         <command id="cmd_search_opac" />
         <command id="save_columns" />
-        <command id="cmd_alt_view" />
+        <command id="cmd_alt_view" disabled="true"/>
         <command id="cmd_cancelled_holds_view" />
         <command id="cmd_uncancelled_holds_view" />
         <command id="cmd_view_expired_onshelf_holds"
 
         <button id="holds_print" label="&staff.patron.holds_overlay.print.label;" command="cmd_holds_print" accesskey="&staff.patron.holds_overlay.print.accesskey;" />
         <button id="print_full_btn" hidden="true" label="&staff.patron.holds_overlay.print_full_pull_list.label;" command="cmd_holds_print_full" accesskey="&staff.patron.holds_overlay.print_full_pull_list.accesskey;" />
+        <button id="print_alt_btn" hidden="true" label="&staff.patron.holds_overlay.print_alt_pull_list.label;" command="cmd_holds_print_alt" accesskey="&staff.patron.holds_overlay.print_alt_pull_list.accesskey;" />
         <spacer flex="1"/>
     </hbox>
 
index 5752eb2..65591fa 100644 (file)
@@ -439,13 +439,19 @@ function link_patron(direction) {
                     break;
             }
 
+            var horizontal_interface = String( g.data.hash.aous['ui.circ.patron_summary.horizontal'] ) == 'true';
             netscape.security.PrivilegeManager.enablePrivilege('UniversalXPConnect UniversalBrowserWrite');
             var top_xml = '<vbox xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" flex="1" style="overflow: auto"><description>' + second_msg + '</description>';
             top_xml += '<hbox><spacer flex="1"/><button label="'+$("patronStrings").getString('staff.patron.info_group.link_patron.move.label')+'"';
             top_xml += ' accesskey="'+$("patronStrings").getString('staff.patron.info_group.link_patron.move.accesskey')+'" name="fancy_submit"/>';
             top_xml += '<button label="'+$("patronStrings").getString('staff.patron.info_group.link_patron.done.label')+'"';
             top_xml += ' accesskey="'+$("patronStrings").getString('staff.patron.info_group.link_patron.done.accesskey')+'" name="fancy_cancel"/></hbox></vbox>';
-            var xml = '<vbox xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" flex="1" style="overflow: vertical"><hbox flex="1">';
+            var xml = '<vbox xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" flex="1" style="overflow: vertical">';
+            if (horizontal_interface) {
+                xml += '<vbox flex="1">';
+            } else {
+                xml += '<hbox flex="1">';
+            }
             /************/
             xml += '<vbox flex="1">';
             xml += '<hbox><spacer flex="1"/>';
@@ -470,7 +476,12 @@ function link_patron(direction) {
             xml += '?show_name=1&amp;id=' + patron_b.id() + '"/>';
             xml += '</vbox>';
             /************/
-            xml += '</hbox></vbox>';
+            if (horizontal_interface) {
+                xml += '</vbox>';
+            } else {
+                xml += '</hbox>';
+            }
+            xml += '</vbox>';
             
             var bot_xml = '<vbox xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" flex="1" style="overflow: auto"><hbox>';
             bot_xml += '</hbox></vbox>';
index abed9b4..1d4211d 100644 (file)
@@ -19,7 +19,7 @@
 <?xul-overlay href="/xul/server/OpenILS/util_overlay.xul"?>
 
 <window id="patron_info_group_win" width="700" height="550" active="true"
-    onload="try{ my_init(); font_helper(); } catch(E) { alert(E); }"
+    onload="try{ my_init(); font_helper(); persist_helper(); } catch(E) { alert(E); }"
     xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
 
     <!-- ///////////////////////////////////////////////////////////////////////////////////////////////////////////// -->
index f0ef3e8..f3e5fed 100644 (file)
@@ -19,7 +19,7 @@
 <?xul-overlay href="/xul/server/OpenILS/util_overlay.xul"?>
 
 <window id="patron_info_win" width="700" height="550"
-    onload="try{ my_init(); font_helper(); } catch(E) { alert(E); }"
+    onload="try{ my_init(); font_helper(); persist_helper(); } catch(E) { alert(E); }"
     xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
 
     <!-- ///////////////////////////////////////////////////////////////////////////////////////////////////////////// -->
index 9a35d31..9dab916 100644 (file)
@@ -19,7 +19,7 @@
 <?xul-overlay href="/xul/server/OpenILS/util_overlay.xul"?>
 
 <window id="patron_info_stat_cats_win" width="700" height="550"
-    onload="try{ my_init(); font_helper(); } catch(E) { alert(E); }"
+    onload="try{ my_init(); font_helper(); persist_helper(); } catch(E) { alert(E); }"
     xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
 
     <!-- ///////////////////////////////////////////////////////////////////////////////////////////////////////////// -->
index 5787a96..2af2ed0 100644 (file)
@@ -19,7 +19,7 @@
 <?xul-overlay href="/xul/server/OpenILS/util_overlay.xul"?>
 
 <window id="patron_info_surveys_win" width="700" height="550"
-    onload="try{ my_init(); font_helper(); } catch(E) { alert(E); }"
+    onload="try{ my_init(); font_helper(); persist_helper(); } catch(E) { alert(E); }"
     xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
 
     <!-- ///////////////////////////////////////////////////////////////////////////////////////////////////////////// -->
index 37e47a1..ddc7c5f 100644 (file)
@@ -20,7 +20,7 @@
 <?xul-overlay href="/xul/server/patron/items_overlay.xul"?>
 
 <window id="items_win" active="true" 
-    onload="try { my_init(); font_helper(); } catch(E) { alert(E); }"
+    onload="try { my_init(); font_helper(); persist_helper(); } catch(E) { alert(E); }"
     xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
 
     <!-- ///////////////////////////////////////////////////////////////////////////////////////////////////////////// -->
index f38ce03..d5f0c55 100644 (file)
@@ -99,7 +99,7 @@
 </box>
 
 <vbox id="cmvb1" flex="1">
-    <groupbox id="cmgb1" flex="1">
+    <groupbox id="cmgb1" flex="1" oils_persist="height">
         <caption label="&staff.patron_navbar.items;" />
         <vbox flex="0">
             <hbox id="items_top_ui" />
             <hbox id="items_bottom_ui" />
         </vbox>
     </groupbox>
-    <splitter><grippy/></splitter>
-    <groupbox flex="1">
+    <splitter id="splitter" oils_persist="state hidden" oils_persist_peers="cmgb1 after_splitter"><grippy/></splitter>
+    <groupbox flex="1" id="after_splitter" oils_persist="height">
         <caption label="&staff.patron_navbar.items.problem_items.caption;" />
         <vbox flex="0">
             <hbox id="items_top_ui2" />
index e576b8d..7e9271b 100644 (file)
@@ -18,7 +18,8 @@
 <?xul-overlay href="/xul/server/OpenILS/util_overlay.xul"?>
 
 <window id="new_penalty_win" 
-    onload="try { new_penalty_init(); font_helper(); } catch(E) { alert(E); }"
+    onload="try { new_penalty_init(); font_helper(); persist_helper(); } catch(E) { alert(E); }"
+    oils_persist="height width sizemode"
     xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
     title="&staff.patron_display.apply_penalty_dialog.title;">
 
index d02a182..6ebb6e1 100644 (file)
@@ -20,7 +20,7 @@
 <?xul-overlay href="/xul/server/patron/search_form_overlay.xul"?>
 
 <window id="patron_search_form_win" 
-    onload="try { my_init(); font_helper(); } catch(E) { alert(E); }"
+    onload="try { my_init(); font_helper(); persist_helper(); } catch(E) { alert(E); }"
     xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
 
     <!-- ///////////////////////////////////////////////////////////////////////////////////////////////////////////// -->
index 7f726d1..1ad8553 100644 (file)
@@ -20,7 +20,7 @@
 <?xul-overlay href="/xul/server/patron/search_form_horiz_overlay.xul"?>
 
 <window id="patron_search_form_win" 
-    onload="try { my_init(); font_helper(); } catch(E) { alert(E); }"
+    onload="try { my_init(); font_helper(); persist_helper(); } catch(E) { alert(E); }"
     xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
 
     <!-- ///////////////////////////////////////////////////////////////////////////////////////////////////////////// -->
index b1a870a..bb31f06 100644 (file)
@@ -20,7 +20,7 @@
 <?xul-overlay href="/xul/server/patron/search_result_overlay.xul"?>
 
 <window id="patron_search_result_win" 
-    onload="try { my_init(); font_helper(); } catch(E) { alert(E); }"
+    onload="try { my_init(); font_helper(); persist_helper(); } catch(E) { alert(E); }"
     xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
 
     <!-- ///////////////////////////////////////////////////////////////////////////////////////////////////////////// -->
index a876e3b..50307d4 100644 (file)
@@ -44,7 +44,7 @@
 
     <!-- ///////////////////////////////////////////////////////////////////////////////////////////////////////////// -->
     <!-- CONTENT -->
-    <groupbox id="penalty_groupbox" flex="1" class="my_overflow">
+    <groupbox id="penalty_groupbox" flex="1" class="my_overflow" oils_persist="height">
         <caption id="penalty_caption" label="&staff.patron_display.penalty.caption;"/>
         <vbox flex="0">
             <hbox flex="1">
@@ -84,8 +84,8 @@
         </vbox>
         <tree id="ausp_list" flex="1" enableColumnDrag="true" context="ausp_actions" />
     </groupbox>
-    <splitter id="list_splitter" collapse="after" oils_persist="state hidden"><grippy id="splitter_grippy"/></splitter>
-    <groupbox id="archived_penalty_groupbox" flex="1" class="my_overflow">
+    <splitter id="list_splitter" collapse="after" oils_persist="state hidden" oils_persist_peers="penalty_groupbox archived_penalty_groupbox"><grippy id="splitter_grippy"/></splitter>
+    <groupbox id="archived_penalty_groupbox" flex="1" class="my_overflow" oils_persist="height">
         <caption id="penalty_caption" label="&staff.patron_display.archived_penalty.caption;"/>
         <vbox flex="0">
             <toolbox flex="1">
index df1671f..c84352a 100644 (file)
@@ -20,7 +20,7 @@
 <?xul-overlay href="/xul/server/OpenILS/util_overlay.xul"?>
 
 <window id="patron_summary_win" 
-    onload="try { font_helper(); my_init(); } catch(E) { alert(E); }" onunload="try { observer.unregister(); } catch(E) { alert(E); }"
+    onload="try { font_helper(); my_init(); persist_helper(); } catch(E) { alert(E); }" onunload="try { observer.unregister(); } catch(E) { alert(E); }"
     xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
 
     <!-- ///////////////////////////////////////////////////////////////////////////////////////////////////////////// -->
index f90bfb9..e029118 100644 (file)
@@ -281,13 +281,26 @@ function uEditMakePhonePw() {
     }
 }
 
-function uEditResetPw(pw) { 
-    if(!pw) pw = uEditMakeRandomPw(patron);    
-    $('ue_password1').value = pw;
-    $('ue_password2').value = pw;
-    $('ue_password1').onchange();
+function uEditResetPw(pw) {
+    if(!pw) {
+        if(uEditUsePhonePw) {
+            if( (pw = patron.day_phone()) ||
+                (pw = patron.evening_phone()) || (pw = patron.other_phone()) ) {
+                    pw = pw.substring(pw.length - 4); // this is iffy
+                    uEditResetPw(pw);
+                        appendClear($('ue_password_plain'), text(pw));
+                        unHideMe($('ue_password_gen'));
+             }
+        } else {
+            pw = uEditMakeRandomPw(patron);
+        }
+        $('ue_password1').value = pw;
+        $('ue_password2').value = pw;
+        $('ue_password1').onchange();
+    }
 }
 
+
 function uEditClone(clone) {
 
     var cloneUser = fetchFleshedUser(clone);
index 6a038ea..591763e 100644 (file)
@@ -295,10 +295,6 @@ function uEditDefineData(patron) {
                 id            : 'ue_day_phone',
                 type        : 'input',
                 regex        :  phoneRegex,
-                onblur      : function() {
-                    if(uEditUsePhonePw)
-                        uEditMakePhonePw();
-                },
                 onpostchange: function(field, newval) {
                     /*  if this is a new patron and we are using the phone number for
                         the password and the staff edits the phone number after entering
@@ -318,11 +314,7 @@ function uEditDefineData(patron) {
             widget    : {
                 id            : 'ue_night_phone',
                 type        : 'input',
-                regex        :  phoneRegex,
-                onblur      : function() {
-                    if(uEditUsePhonePw)
-                        uEditMakePhonePw();
-                }
+                regex        :  phoneRegex
             }
         },
         {
@@ -333,11 +325,7 @@ function uEditDefineData(patron) {
             widget    : {
                 id            : 'ue_other_phone',
                 type        : 'input',
-                regex        :  phoneRegex,
-                onblur      : function() {
-                    if(uEditUsePhonePw)
-                        uEditMakePhonePw();
-                }
+                regex        :  phoneRegex
             }
         },
         {
index 0d1842d..d343a64 100644 (file)
@@ -18,7 +18,7 @@
 <?xul-overlay href="/xul/server/OpenILS/util_overlay.xul"?>
 
 <window id="example_template_win" 
-    onload="try { my_init(); font_helper(); } catch(E) { alert(E); }"
+    onload="try { my_init(); font_helper(); persist_helper(); } catch(E) { alert(E); }"
     xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
 
     <!-- ///////////////////////////////////////////////////////////////////////////////////////////////////////////// -->
index 7e217ac..756ea5d 100644 (file)
@@ -496,7 +496,7 @@ serial.manage_items.prototype = {
             JSAN.use('util.window'); var win = new util.window();
             win.open(
                 xulG.url_prefix(urls.XUL_SERIAL_SELECT_UNIT),
-                'sel_serial_sunit_win_' + win.window_name_increment(),
+                '_blank',
                 'chrome,resizable,modal,centerscreen'
             );
             if (!g.serial_items_sunit_select) {
index c10c436..94a8097 100644 (file)
@@ -82,9 +82,9 @@ vim:noet:sw=4:ts=4:
                 </menu>
             </menubar>
         </hbox>
-        <tree id="item_tree" flex="2" enableColumnDrag="true" context="serial_manage_items_popup"/>
-        <splitter state="open" collapse="after" resizebefore="closest" resizeafter="farthest"/>
-        <hbox align="center">
+        <tree id="item_tree" flex="2" enableColumnDrag="true" context="serial_manage_items_popup" oils_persist="height"/>
+        <splitter state="open" collapse="after" resizebefore="closest" resizeafter="farthest" id="splitter" oils_persist="state hidden" oils_persist_peers="item_tree after_splitter"/>
+        <hbox align="center" id="after_splitter" oils_persist="height">
             <label style="font-weight: bold" value="Showing: "/>
             <label id="serial_workarea_mode_label" value="Recently Received"/>
             <spacer flex="1"/>
index f2212a1..b544763 100644 (file)
@@ -65,7 +65,7 @@ vim:noet:sw=4:ts=4:
                 </popupset>
 
                 <hbox flex="1">
-                    <vbox flex="1">
+                    <vbox flex="1" id="before_splitter" oils_persist="width">
                         <hbox id="serial_sub_lib_menu"/>
                         <hbox>
                             <checkbox id="show_ssubs" label="Show Subs." />
@@ -92,8 +92,8 @@ vim:noet:sw=4:ts=4:
                         </hbox>
                         <tree id="subs_tree" flex="15" enableColumnDrag="true" context="serial_manage_subs_popup"/>
                     </vbox>
-                    <splitter state="open" collapse="before" resizebefore="closest" resizeafter="farthest"/>
-                    <deck id="serial_manage_subs_editor_deck" flex="20">
+                    <splitter state="open" collapse="before" resizebefore="closest" resizeafter="farthest" id="splitter" oils_persist="state hidden" oils_persist_peers="before_splitter serial_manage_subs_editor_deck"/>
+                    <deck id="serial_manage_subs_editor_deck" flex="20" oils_persist="width">
                         <description value="Please select an object to edit"/>
                         <vbox id="serial_ssub_editor_panel" />
                         <vbox id="serial_sdist_editor_panel" />
index 8ec3eaf..2cfa3f7 100644 (file)
@@ -22,8 +22,8 @@
 <!-- OVERLAYS -->
 <?xul-overlay href="/xul/server/OpenILS/util_overlay.xul"?>
 
-<window id="notes_win" width="700" height="550"
-       onload="try{ my_init(); font_helper(); } catch(E) { alert(E); }"
+<window id="notes_win" width="700" height="550" oils_persist="height width sizemode"
+       onload="try{ my_init(); font_helper(); persist_helper(); } catch(E) { alert(E); }"
        xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
 
        <!-- ///////////////////////////////////////////////////////////////////////////////////////////////////////////// -->
index 52956ef..189442b 100644 (file)
         <vbox id="brief_display_box"/>
 
                <hbox flex="1" style="overflow: auto">
-                       <vbox flex="1">
+                       <vbox flex="1" id="before_splitter" oils_persist="height">
                                <label value="Distribution" style="font-weight: bold; font-size: large"/>
                                <vbox id="sdist_editor_left_pane" flex="1"/>
                        </vbox>
-                       <splitter><grippy /></splitter>
-                       <vbox flex="1">
+                       <splitter id="splitter" oils_persist="state hidden" oils_persist_peers="before_splitter after_splitter"><grippy /></splitter>
+                       <vbox flex="1" id="after_splitter" oils_persist="height">
                                <vbox id="sdist_editor_right_pane"/>
                 <groupbox>
                     <caption label="Library Specific Options" />
index 0d9b393..f20d688 100644 (file)
@@ -25,7 +25,7 @@ vim:noet:sw=4:ts=4:
 <?xul-overlay href="/xul/server/serial/manage_subs.xul"?>
 
 <window id="serial_serctrl_main" 
-       onload="try { my_init(); font_helper(); } catch(E) { alert(E); }"
+       onload="try { my_init(); font_helper(); persist_helper(); } catch(E) { alert(E); }"
        xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
 
        <!-- ///////////////////////////////////////////////////////////////////////////////////////////////////////////// -->
index f8be3aa..f94189d 100644 (file)
         <vbox id="brief_display_box"/>
 
                <hbox flex="1" style="overflow: auto">
-                       <vbox flex="1">
+                       <vbox flex="1" id="before_splitter1" oils_persist="width">
                                <label value="Issuance" style="font-weight: bold; font-size: large"/>
                                <vbox id="siss_editor_left_pane" flex="1"/>
                        </vbox>
-                       <splitter><grippy /></splitter>
-                       <vbox flex="1">
+                       <splitter id="splitter1" oils_persist="state hidden" oils_persist_peers="before_splitter1 after_splitter1"><grippy /></splitter>
+                       <vbox flex="1" id="after_splitter1" oils_persist="width">
                                <vbox id="siss_editor_middle_pane"/>
                        </vbox>
-                       <splitter><grippy /></splitter>
-                       <vbox flex="1">
+                       <splitter id="splitter2" oils_persist="state hidden" oils_persist_peers="after_splitter1 after_splitter2"><grippy /></splitter>
+                       <vbox flex="1" id="after_splitter2" oils_persist="width">
                                <vbox id="siss_editor_right_pane"/>
                        </vbox>
                </hbox>
index c4ae7b3..15366f0 100644 (file)
 
        <groupbox flex="1" class="my_overflow">
                <hbox flex="1" style="overflow: auto">
-                       <vbox flex="1">
+                       <vbox flex="1" id="before_splitter1" oils_persist="width">
                                <label value="Item" style="font-weight: bold; font-size: large"/>
                                <vbox id="sitem_editor_left_pane" flex="1"/>
                        </vbox>
-                       <splitter><grippy /></splitter>
-                       <vbox flex="1">
+                       <splitter id="splitter1" oils_persist="state hidden" oils_persist_peers="before_splitter1 after_splitter1"><grippy /></splitter>
+                       <vbox flex="1" id="after_splitter1" oils_persist="width">
                                <label value=" " style="font-weight: bold; font-size: large"/>
                                <vbox id="sitem_editor_middle_pane" flex="1"/>
                        </vbox>
-                       <splitter><grippy /></splitter>
-                       <vbox flex="1">
+                       <splitter id="splitter2" oils_persist="state hidden" oils_persist_peers="after_slitter1 after_splitter2"><grippy /></splitter>
+                       <vbox flex="1" id="after_splitter2" oils_persist="width">
                                <button style="font-weight: bold; font-size: normal" label="Item Dates" accesskey="1" oncommand="document.getElementById('sitem_editor_right_pane').firstChild.firstChild.focus();"/>
                                <vbox id="sitem_editor_right_pane" flex="1"/>
                        </vbox>
index b2ba9aa..4646428 100644 (file)
         <vbox id="brief_display_box"/>
 
                <hbox flex="1" style="overflow: auto">
-                       <vbox flex="1">
+                       <vbox flex="1" id="before_splitter" oils_persist="width">
                                <label value="Subscription" style="font-weight: bold; font-size: large"/>
                                <vbox id="left_pane" flex="1"/>
                        </vbox>
-                       <splitter><grippy /></splitter>
-                       <vbox flex="1">
+                       <splitter id="splitter" oils_persist="state hidden" oils_persist_peers="before_splitter after_splitter"><grippy /></splitter>
+                       <vbox flex="1" id="after_splitter" oils_persist="width">
                                <label value=" " style="font-weight: bold; font-size: large"/>
                                <vbox id="right_pane" flex="1"/>
                        </vbox>
diff --git a/Open-ILS/xul/staff_client/server/skin/custom.js b/Open-ILS/xul/staff_client/server/skin/custom.js
new file mode 100644 (file)
index 0000000..a71d7a0
--- /dev/null
@@ -0,0 +1,9 @@
+/* Settings here override default values from constants.js;for example:
+    urls['AUDIO_good'] = '/xul/server/skin/media/custom/good.wav';
+    urls['opac'] = '/opac/' + LOCALE + '/skin/mylib/xml/advanced.xml?nps=1';
+    urls['opac_rdetail'] = '/opac/' + LOCALE + '/skin/mylib/xml/rdetail.xml';
+    urls['opac_rresult'] = '/opac/' + LOCALE + '/skin/mylib/xml/rresult.xml';
+    urls['browser'] = '/opac/' + LOCALE + '/skin/mylib/xml/advanced.xml?nps=1';
+
+*/
index 1555907..d5baf3a 100644 (file)
@@ -50,9 +50,6 @@ row#row_billing_zip { padding-bottom: 10px; }
 .PATRON_EXCEEDS_FINES label.max_bills_indicator { display: inline; color: purple; }
 .PATRON_EXCEEDS_FINES label#under_bills { color: red; }
 
-.PATRON_AGE_LT_18 .dob { color: purple; }
-.PATRON_AGE_LT_18 label.juvenile_indicator { display: inline; color: purple; }
-
 .PATRON_HAS_INVALID_DOB .dob { color: purple; }
 .PATRON_HAS_INVALID_DOB label.invalid_dob_indicator { display: inline; color: purple }
 
index a4fcb5a..8b275c1 100755 (executable)
@@ -58,10 +58,11 @@ The purpose of this script is to consolidate a lot of the annoying
 and error-prone tasks associated with an upgrade for a developer.
 
 Considerations:
- * Run as opensrf.  
+ * Run as opensrf user
  * opensrf needs sudo 
- * Assumes opensrf has a configured (as in ./configure) both ILS and 
-   OpenSRF as svn or git-svn checkouts
+ * Assumes opensrf has OpenILS and OpenSRF repositories as svn or git-svn 
+   checkouts and both have been configured (as in ./configure) 
+  
 END_OF_USAGE
 }
 
@@ -98,6 +99,7 @@ BASE=~      # default to $HOME (~ doesn't like :- syntax for whatever reason)
 OSRF=${OPT_OSRFDIR:-$BASE/OpenSRF/trunk};
 ILS=${OPT_EGDIR:-$(pwd)};
 XUL="$INSTALL/var/web/xul";
+JSDIR="$INSTALL/lib/javascript";    # only used for FULL install
 
 # ----------------------------------
 # TEST and SANITY CHECK
@@ -150,6 +152,10 @@ if [ -n "$OPT_FULL"  ]; then
     cd $OSRF && make;
     cd $ILS  && make;
     cd $OSRF && sudo make install;
+    if [ -d "$JSDIR" ]; then
+        echo "Copying OpenSRF javascript files into $JSDIR";
+        cp ./src/javascript/* $JSDIR;
+    fi
 fi
 sudo chown -R opensrf:opensrf $INSTALL
 
index 5e3ab12..1d76bd7 100755 (executable)
@@ -60,21 +60,36 @@ VERSION=$(psql -c "select max(version) from config.upgrade_log" -t $PSQL_ACCESS)
 # [ $VERBOSE ] && echo RAW VERSION: $VERSION     # TODO: for verbose mode
 VERSION=$(echo $VERSION | sed -e 's/^ *0*//');    # This is a separate step so we can check $? above.
 [ -z "$VERSION" ] && usage_die "config.upgrade_log missing ANY installed version data!";
-echo "* Last installed version -> $VERSION";
+echo "* Last installed version  ->  $VERSION";
 
 if [ -d ./Open-ILS/src/sql/Pg ] ; then
     cd ./Open-ILS/src/sql/Pg ;
 fi
 [ -d ./upgrade ] || usage_die "No ./upgrade directory found.  Please run from Open-ILS/src/sql/Pg";
 
+MAX=$(ls upgrade/[0-9][0-9][0-9][0-9]* 2>/dev/null | tail -1 | cut -c9-12 );   # could take an optional arg to set this, if we wanted.
+echo "* Last upgrade file found -> $MAX";
+MAX=$(echo $MAX | sed -e 's/^ *0*//');      # remove leading zeroes
+
 declare -a FILES;
+declare -a SKIPPED;
 while true; do
     VERSION=$(($VERSION + 1));
+    [ $VERSION -gt $MAX ] && break;
     PREFIX=$(printf "%0.4d" $VERSION);
     FILE=$(ls upgrade/$PREFIX* 2>/dev/null);
-    [ ! -f "$FILE" ] && break;
-    FILES[${#FILES[@]}]=$FILE;      # "push" onto FILES array
-    echo "* Pending $FILE";
+    if [ -f "$FILE" ] ; then
+        # Note: we only report skipped files once we find the next one.  
+        # Otherwise, we'd report everything from $VERSION+1 to $MAX
+        for skip in ${SKIPPED[@]} ; do
+            echo "* WARNING: Upgrade $skip NOT FOUND.  Skipping it."; 
+        done
+        SKIPPED=();                     # After we reported them, reset array.
+        FILES[${#FILES[@]}]=$FILE;      # "push" onto FILES array
+        # echo "* Pending $FILE";
+    else
+        SKIPPED[${#SKIPPED[@]}]=$PREFIX; # "push" onto SKIPPED array
+    fi
 done;
 
 COUNT=${#FILES[@]};