Alternate parts selection display when placing holds

Users often miss the list of parts on the Place Holds screen, leading to many title-level holds on records where only one or two libraries may have unparted copies.

A new option is available to change this display so that a part is selected via radio buttons instead of the traditional dropdown menu. This display increases the visibility of parts on the Place Holds screen and also forces users to make an explicit choice.

To enable the alternate display, set the option to true in config.tt2.

New config.tt2 setting

Web staff client patron editor

The web staff interface now includes a patron editor/registration form that is written using AngularJS, leading to faster and more responsive patron editing. This feature is currently available in preview mode, but supports the following actions:

  • adding and editing base patron records and addresses
  • setting statistical categories
  • editing secondary groups
  • cloning patron records
  • duplicate detection
  • surveys

Non-active status copy transit message

After copy checkin, if the copy is in transit, display a special message in the transit alert dialog and in the printed transit receipt (optionally, via macro) if the copy is in (or, rather, will be once it arrives at its destination) a non-active copy status.

Upgrade notes

  • To add the new message to the transit slip, add the transit_copy_status_msg MACRO.
  • To remove the new message from the alert dialog, remove the staff.circ.utils.transit.copy_status_message string property from Open-ILS/xul/staff_client/server/locale/LOCALE/
  • For a list of non-active copy statuses, see in the staff client under Admin → Server Administration → Copy Statuses.

Selectively disallow opt-in based on patron’s home library

A new library setting has been added which enables a library to prevent their patrons from being opted in at other libraries.

For example, consider the following org unit hierarchy:

Org Units          Depth
        CONS              0
    |           |
   SYS1        SYS2       1
    |           |
 +--+--+     +--+--+
 |     |     |     |
BR1   BR2   BR3   BR4     2

Suppose that SYS1 wishes to prevent its patrons from being opted in at SYS2. To accomplish this, it sets the value of the "Restrict patron opt-in to home library and related orgs at specified depth" setting to 1, meaning that patrons at SYS1 libraries at or below that depth in the org tree cannot be opted in by libraries outside that part of the org tree. Thus, BR1 patrons can be opted in at BR2, but not at BR3 or BR4.

(This setting is distinct from the "Patron Opt-In Boundary" setting, which merely determines the depth at which Evergreen prompts for the patron to opt in.)

New library setting

  • Restrict patron opt-in to home library and related orgs at specified depth (org.restrict_opt_to_depth)

Standing penalty ignore proximity

Standing penalties now have an ignore_proximity field that takes an integer value. When set, the value of this field represents the proximity from the user’s home organizational unit where this penalty will be ignored for purposes of circulation and holds. Typical values for this field would be 0, 1, or 2 when using a standard hierarchy of Consortium → System → Branch → Sublibrary/Bookmobile. A value of 1 would cause the penalty to be ignored at the user’s home organization unit, its parent and/or immediate child. A value of 2 should cause it to be ignored at the above as well as all sibling organizational units to the user’s home. In all cases, a value of zero causes the penalty to be ignored at the user’s home and to apply at all other organizational units. If the value of this field is left unset (or set to a negative value), the penalty will still take effect everywhere using the normal organizational unit and depth values. If you use a custom hierarchy, you will need to figure out any values greater than 0 on your own.

The ignore_proximity does not affect where penalties are applied. It is used when determining whether or not a penalty blocks an activity at the current organizational unit or the organizational unit that owns the copy involved in the current transaction. For instance, if you set the ignore_proximity to 0 on patron exceeds overdue fines, then the patron will still be able to place holds on and checkout copies owned by their home organizational unit at their home organizational unit. They will not, however, be able to receive copies from other organizational units, nor use other organizational units as a patron.

Patron checkout history stored in a dedicated table

Patron checkout history is now stored in separate, dedicated database table instead of being derived from the main circulation data. This allows us to age/anonymize circulations more aggressively, since they no longer need to stick around in cases where they represent a patron’s opt-in checkout history.

This has a number of patron privacy implications.

  • Minimal metadata is stored in the new patron checkout history table, so once the corresponding circulation is aged, the full set of circulation metadata is no longer linked to a patron’s reading history.

    • It is limited to checkout date, due date, checkin date, and copy data.
  • Staff can no longer report on a patron’s reading history.

    • While it is possible to build aggregate reports on reading history data, it is not possible to report on which user an entry in the history table belongs to. (The usr column is hidden from the reporter).
  • Staff can no longer retrieve a patron’s reading history via API. Only the user that owns the history data can access it.

Upgrade notes

Administrators should verify the CSV export of checkout history works after deploying this change. If local changes were made to the CSV template, the template will not be updated as part of this deployment. The stock template was modified to handle gracefully NULL values for checkin_time.

For example:

-    Returned: [% date.format(helpers.format_date(circ.checkin_time), '%Y-%m-%d') %]
+    Returned: [%
+        date.format(
+            helpers.format_date(circ.checkin_time), '%Y-%m-%d')
+            IF circ.checkin_time;
+    %]