[open-ils-commits] r15312 - in branches/rel_1_6/Open-ILS: examples src/extras src/perlmods/OpenILS src/perlmods/OpenILS/Application src/perlmods/OpenILS/Application/Circ src/perlmods/OpenILS/Application/Storage src/perlmods/OpenILS/Application/Storage/CDBI src/perlmods/OpenILS/Application/Storage/Driver/Pg src/perlmods/OpenILS/Application/Storage/Publisher src/perlmods/OpenILS/Utils src/perlmods/OpenILS/WWW src/sql/Pg src/sql/Pg/upgrade src/support-scripts/test-scripts web/css/skin/default web/js/dojo/openils web/js/dojo/openils/booking/nls web/js/ui/default web/js/ui/default/booking web/opac/locale/en-US web/opac/skin/default/js web/templates/default web/templates/default/booking web/templates/default/conify/global web/templates/default/conify/global/booking xul/staff_client/chrome/content/main xul/staff_client/chrome/content/util xul/staff_client/chrome/locale/en-US xul/staff_client/server/admin xul/staff_client/server/cat xul/staff_client/server/circ xul/staff_client/server/locale/en-US xul/staff_client/server/patron (miker)

svn at svn.open-ils.org svn at svn.open-ils.org
Wed Jan 13 11:35:06 EST 2010


Author: miker
Date: 2010-01-13 11:35:03 -0500 (Wed, 13 Jan 2010)
New Revision: 15312

Added:
   branches/rel_1_6/Open-ILS/src/perlmods/OpenILS/Application/Booking.pm
   branches/rel_1_6/Open-ILS/src/perlmods/OpenILS/Application/Storage/CDBI/booking.pm
   branches/rel_1_6/Open-ILS/src/sql/Pg/095.schema.booking.sql
   branches/rel_1_6/Open-ILS/src/sql/Pg/upgrade/0086.schema.booking-tables.sql
   branches/rel_1_6/Open-ILS/src/sql/Pg/upgrade/0090.schema.booking.bib-base-types.sql
   branches/rel_1_6/Open-ILS/src/sql/Pg/upgrade/0103.schema.booking.max_fine.sql
   branches/rel_1_6/Open-ILS/src/sql/Pg/upgrade/0105.schema.booking-integration.sql
   branches/rel_1_6/Open-ILS/src/sql/Pg/upgrade/0106.booking.admin_permissions.sql
   branches/rel_1_6/Open-ILS/src/sql/Pg/upgrade/0109.data.org-settings-booking_alter_due_date.sql
   branches/rel_1_6/Open-ILS/src/sql/Pg/upgrade/0110.schema.booking_resource_type.elbow_room.sql
   branches/rel_1_6/Open-ILS/src/sql/Pg/upgrade/0122.data.reservation-shelf-status.sql
   branches/rel_1_6/Open-ILS/src/sql/Pg/upgrade/0130.booking.resource_constraint_and_perms.sql
   branches/rel_1_6/Open-ILS/src/sql/Pg/upgrade/119.schema.booking.transits.sql
   branches/rel_1_6/Open-ILS/web/css/skin/default/booking.css
   branches/rel_1_6/Open-ILS/web/js/dojo/openils/booking/
   branches/rel_1_6/Open-ILS/web/js/dojo/openils/booking/nls/capture.js
   branches/rel_1_6/Open-ILS/web/js/dojo/openils/booking/nls/pickup_and_return.js
   branches/rel_1_6/Open-ILS/web/js/dojo/openils/booking/nls/pull_list.js
   branches/rel_1_6/Open-ILS/web/js/ui/default/booking/
   branches/rel_1_6/Open-ILS/web/js/ui/default/booking/capture.js
   branches/rel_1_6/Open-ILS/web/js/ui/default/booking/common.js
   branches/rel_1_6/Open-ILS/web/js/ui/default/booking/pickup.js
   branches/rel_1_6/Open-ILS/web/js/ui/default/booking/populator.js
   branches/rel_1_6/Open-ILS/web/js/ui/default/booking/pull_list.js
   branches/rel_1_6/Open-ILS/web/js/ui/default/booking/return.js
   branches/rel_1_6/Open-ILS/web/templates/default/booking/
   branches/rel_1_6/Open-ILS/web/templates/default/booking/capture.tt2
   branches/rel_1_6/Open-ILS/web/templates/default/booking/pickup.tt2
   branches/rel_1_6/Open-ILS/web/templates/default/booking/pull_list.tt2
   branches/rel_1_6/Open-ILS/web/templates/default/booking/return.tt2
   branches/rel_1_6/Open-ILS/web/templates/default/conify/global/booking/
Modified:
   branches/rel_1_6/Open-ILS/examples/fm_IDL.xml
   branches/rel_1_6/Open-ILS/examples/opensrf.xml.example
   branches/rel_1_6/Open-ILS/examples/opensrf_core.xml.example
   branches/rel_1_6/Open-ILS/src/extras/ils_events.xml
   branches/rel_1_6/Open-ILS/src/perlmods/OpenILS/Application/AppUtils.pm
   branches/rel_1_6/Open-ILS/src/perlmods/OpenILS/Application/Circ.pm
   branches/rel_1_6/Open-ILS/src/perlmods/OpenILS/Application/Circ/Circulate.pm
   branches/rel_1_6/Open-ILS/src/perlmods/OpenILS/Application/Circ/Money.pm
   branches/rel_1_6/Open-ILS/src/perlmods/OpenILS/Application/Collections.pm
   branches/rel_1_6/Open-ILS/src/perlmods/OpenILS/Application/CreditCard.pm
   branches/rel_1_6/Open-ILS/src/perlmods/OpenILS/Application/Storage/CDBI.pm
   branches/rel_1_6/Open-ILS/src/perlmods/OpenILS/Application/Storage/CDBI/action.pm
   branches/rel_1_6/Open-ILS/src/perlmods/OpenILS/Application/Storage/Driver/Pg/dbi.pm
   branches/rel_1_6/Open-ILS/src/perlmods/OpenILS/Application/Storage/Publisher/action.pm
   branches/rel_1_6/Open-ILS/src/perlmods/OpenILS/Application/Storage/Publisher/money.pm
   branches/rel_1_6/Open-ILS/src/perlmods/OpenILS/Const.pm
   branches/rel_1_6/Open-ILS/src/perlmods/OpenILS/Utils/CStoreEditor.pm
   branches/rel_1_6/Open-ILS/src/perlmods/OpenILS/WWW/BadDebt.pm
   branches/rel_1_6/Open-ILS/src/sql/Pg/002.schema.config.sql
   branches/rel_1_6/Open-ILS/src/sql/Pg/100.circ_matrix.sql
   branches/rel_1_6/Open-ILS/src/sql/Pg/500.view.cross-schema.sql
   branches/rel_1_6/Open-ILS/src/sql/Pg/950.data.seed-values.sql
   branches/rel_1_6/Open-ILS/src/sql/Pg/build-db.sh
   branches/rel_1_6/Open-ILS/src/support-scripts/test-scripts/collections.pl
   branches/rel_1_6/Open-ILS/web/js/dojo/openils/booking/nls/reservation.js
   branches/rel_1_6/Open-ILS/web/js/ui/default/booking/reservation.js
   branches/rel_1_6/Open-ILS/web/opac/locale/en-US/lang.dtd
   branches/rel_1_6/Open-ILS/web/opac/skin/default/js/myopac.js
   branches/rel_1_6/Open-ILS/web/templates/default/booking/reservation.tt2
   branches/rel_1_6/Open-ILS/web/templates/default/conify/global/booking/resource.tt2
   branches/rel_1_6/Open-ILS/xul/staff_client/chrome/content/main/constants.js
   branches/rel_1_6/Open-ILS/xul/staff_client/chrome/content/main/menu.js
   branches/rel_1_6/Open-ILS/xul/staff_client/chrome/content/main/menu_frame_menus.xul
   branches/rel_1_6/Open-ILS/xul/staff_client/chrome/content/main/menu_frame_overlay.xul
   branches/rel_1_6/Open-ILS/xul/staff_client/chrome/content/util/functional.js
   branches/rel_1_6/Open-ILS/xul/staff_client/chrome/locale/en-US/offline.properties
   branches/rel_1_6/Open-ILS/xul/staff_client/server/admin/index.xhtml
   branches/rel_1_6/Open-ILS/xul/staff_client/server/cat/copy_browser.js
   branches/rel_1_6/Open-ILS/xul/staff_client/server/cat/copy_browser.xul
   branches/rel_1_6/Open-ILS/xul/staff_client/server/cat/util.js
   branches/rel_1_6/Open-ILS/xul/staff_client/server/circ/copy_status.js
   branches/rel_1_6/Open-ILS/xul/staff_client/server/circ/copy_status.xul
   branches/rel_1_6/Open-ILS/xul/staff_client/server/circ/copy_status_overlay.xul
   branches/rel_1_6/Open-ILS/xul/staff_client/server/locale/en-US/cat.properties
   branches/rel_1_6/Open-ILS/xul/staff_client/server/locale/en-US/patron.properties
   branches/rel_1_6/Open-ILS/xul/staff_client/server/patron/bills.js
   branches/rel_1_6/Open-ILS/xul/staff_client/server/patron/display.js
   branches/rel_1_6/Open-ILS/xul/staff_client/server/patron/display.xul
   branches/rel_1_6/Open-ILS/xul/staff_client/server/patron/display_horiz.xul
   branches/rel_1_6/Open-ILS/xul/staff_client/server/patron/display_horiz_overlay.xul
   branches/rel_1_6/Open-ILS/xul/staff_client/server/patron/display_overlay.xul
Log:
Turn away!  Avert your eyes!

Herein lies the initial 1.6 backport of changes* implementing the booking
module for Evergreen.  1.6.1, we look forward to you!

*) Changesets included: 14921, 14925, 15019, 15068, 15071, 15072, 15076, 15077, 15092, 15096, 15099, 15100, 15103, 15104, 15108, 15110, 15113, 15128, 15133, 15161, 15164, 15188, 15207, 15211, 15215, 15223, 15224, 15228, 15236, 15241, 15247, 15264, 15285, 15288, 15289, 15309



Modified: branches/rel_1_6/Open-ILS/examples/fm_IDL.xml
===================================================================
--- branches/rel_1_6/Open-ILS/examples/fm_IDL.xml	2010-01-13 16:30:42 UTC (rev 15311)
+++ branches/rel_1_6/Open-ILS/examples/fm_IDL.xml	2010-01-13 16:35:03 UTC (rev 15312)
@@ -1649,6 +1649,7 @@
 			<field reporter:label="Open Billable Transactions" name="open_billable_transactions_summary" oils_persist:virtual="true" reporter:datatype="link"/>
 			<field reporter:label="Checkins" name="checkins" oils_persist:virtual="true" reporter:datatype="link"/>
 			<field reporter:label="Circulations Performed as Staff" name="performed_circulations" oils_persist:virtual="true" reporter:datatype="link"/>
+			<field reporter:label="Reservations" name="reservations" oils_persist:virtual="true" reporter:datatype="link"/>
 		</fields>
 		<links>
 			<link field="demographic" reltype="might_have" key="id" map="" class="rud"/>
@@ -1678,6 +1679,7 @@
 			<link reporter:label="Check-ins Performed as Staff" field="checkins" reltype="has_many" key="checkin_staff" map="" class="circ"/>
 			<link field="cards" reltype="has_many" key="usr" map="" class="ac"/>
 			<link reporter:label="Circulations Performed as Staff" field="performed_circulations" reltype="has_many" key="circ_staff" map="" class="circ"/>
+			<link field="reservations" reltype="has_many" key="usr" map="" class="bresv"/>
 		</links>
 	</class>
 	<class id="aous" controller="open-ils.cstore" oils_obj:fieldmapper="actor::org_unit_setting" oils_persist:tablename="actor.org_unit_setting" reporter:label="Organizational Unit Setting">
@@ -2208,6 +2210,221 @@
 			<link field="copy_bib_record" reltype="has_a" key="id" map="" class="bre"/>
 		</links>
 	</class>
+
+	<class id="brt" controller="open-ils.cstore open-ils.pcrud" oils_obj:fieldmapper="booking::resource_type" oils_persist:tablename="booking.resource_type" reporter:label="Resource Type">
+		<fields oils_persist:primary="id" oils_persist:sequence="booking.resource_type_id_seq">
+			<field reporter:label="Resource Type ID" name="id" reporter:datatype="id"/>
+			<field reporter:label="Resource Type Name" name="name" reporter:datatype="text"/>
+			<field reporter:label="Fine Interval" name="fine_interval" reporter:datatype="interval"/>
+			<field reporter:label="Fine Amount" name="fine_amount" reporter:datatype="money"/>
+			<field reporter:label="Max Fine Amount" name="max_fine" reporter:datatype="money"/>
+			<field reporter:label="Owning Library" name="owner" reporter:datatype="org_unit"/>
+			<field reporter:label="Catalog Item" name="catalog_item" reporter:datatype="bool"/>
+			<field reporter:label="Bibliographic Record" name="record" reporter:datatype="link"/>
+			<field reporter:label="Transferable" name="transferable" reporter:datatype="bool"/>
+			<field reporter:label="Inter-booking and Inter-circulation Interval" name="elbow_room" reporter:datatype="interval"/>
+			<field reporter:label="Resources" name="resources" oils_persist:virtual="true" reporter:datatype="link"/>
+			<field reporter:label="Resource Attributes" name="resource_attrs" oils_persist:virtual="true" reporter:datatype="link"/>
+			<field reporter:label="Target Resource Types" name="tgt_rsrc_types" oils_persist:virtual="true" reporter:datatype="link"/>
+		</fields>
+		<links>
+			<link field="owner" reltype="has_a" key="id" map="" class="aou"/>
+			<link field="record" reltype="has_a" key="id" map="" class="bre"/>
+			<link field="resources" reltype="has_many" key="type" map="" class="brsrc"/>
+			<link field="resource_attrs" reltype="has_many" key="type" map="" class="bra"/>
+			<link field="tgt_rsrc_types" reltype="has_many" key="type" map="" class="bresv"/>
+		</links>
+		<permacrud xmlns="http://open-ils.org/spec/opensrf/IDL/permacrud/v1">
+			<actions>
+				<create permission="ADMIN_BOOKING_RESOURCE_TYPE" global_required='true'/>
+				<retrieve />
+				<update permission="ADMIN_BOOKING_RESOURCE_TYPE" global_required='true'/>
+				<delete permission="ADMIN_BOOKING_RESOURCE_TYPE" global_required='true'/>
+			</actions>
+		</permacrud>
+	</class>
+
+	<class id="brsrc" controller="open-ils.cstore open-ils.pcrud" oils_obj:fieldmapper="booking::resource" oils_persist:tablename="booking.resource" reporter:label="Resource">
+		<fields oils_persist:primary="id" oils_persist:sequence="booking.resource_id_seq">
+			<field reporter:label="Resource ID" name="id" reporter:datatype="id"/>
+			<field reporter:label="Owning Library" name="owner" reporter:datatype="org_unit"/>
+			<field reporter:label="Resource Type" name="type" reporter:datatype="link"/>
+			<field reporter:label="Overbook" name="overbook" reporter:datatype="bool"/>
+			<field reporter:label="Barcode" name="barcode" reporter:datatype="text"/>
+			<field reporter:label="Is Deposit Required" name="deposit" reporter:datatype="bool"/>
+			<field reporter:label="Deposit Amount" name="deposit_amount" reporter:datatype="money"/>
+			<field reporter:label="User Fee" name="user_fee" reporter:datatype="money"/>
+			<field reporter:label="Resource Attribute Maps" name="attr_maps" oils_persist:virtual="true" reporter:datatype="link"/>
+			<field reporter:label="Reservation Target Resources" name="tgt_rsrcs" oils_persist:virtual="true" reporter:datatype="link"/>
+			<field reporter:label="Reservation Current Resources" name="curr_rsrcs" oils_persist:virtual="true" reporter:datatype="link"/>
+			<field reporter:label="Catalog Item" name="catalog_item" oils_persist:virtual="true" reporter:datatype="link"/>
+		</fields>
+		<links>
+			<link field="owner" reltype="has_a" key="id" map="" class="aou"/>
+			<link field="type" reltype="has_a" key="id" map="" class="brt"/>
+			<link field="attr_maps" reltype="has_many" key="resource" map="" class="bram"/>
+			<link field="tgt_rsrcs" reltype="has_many" key="targeted_resource" map="" class="bresv"/>
+			<link field="curr_rsrcs" reltype="has_many" key="current_resource" map="" class="bresv"/>
+		</links>
+		<permacrud xmlns="http://open-ils.org/spec/opensrf/IDL/permacrud/v1">
+			<actions>
+				<create permission="ADMIN_BOOKING_RESOURCE" global_required='true'/>
+				<retrieve />
+				<update permission="ADMIN_BOOKING_RESOURCE" global_required='true'/>
+				<delete permission="ADMIN_BOOKING_RESOURCE" global_required='true'/>
+			</actions>
+		</permacrud>
+	</class>
+	
+	<class id="bra" controller="open-ils.cstore open-ils.pcrud" oils_obj:fieldmapper="booking::resource_attr" oils_persist:tablename="booking.resource_attr" reporter:label="Resource Attribute">
+		<fields oils_persist:primary="id" oils_persist:sequence="booking.resource_attr_id_seq">
+			<field reporter:label="Resource Attribute ID" name="id" reporter:datatype="id"/>
+			<field reporter:label="Owning Library" name="owner" reporter:datatype="org_unit"/>
+			<field reporter:label="Resource Attribute Name" name="name" reporter:datatype="text"/>
+			<field reporter:label="Resource Type" name="resource_type" reporter:datatype="link"/>
+			<field reporter:label="Is Required" name="required" reporter:datatype="bool"/>
+			<field reporter:label="Valid Values" name="valid_values" oils_persist:virtual="true" reporter:datatype="link"/>
+			<field reporter:label="Resource Attribute Maps" name="attr_maps" oils_persist:virtual="true" reporter:datatype="link"/>
+		</fields>
+		<links>
+			<link field="owner" reltype="has_a" key="id" map="" class="aou"/>
+			<link field="resource_type" reltype="has_a" key="id" map="" class="brt"/>
+			<link field="valid_values" reltype="has_many" key="attr" map="" class="brav"/>
+			<link field="attr_maps" reltype="has_many" key="attr" map="" class="bram"/>
+		</links>
+		<permacrud xmlns="http://open-ils.org/spec/opensrf/IDL/permacrud/v1">
+			<actions>
+				<create permission="ADMIN_BOOKING_RESOURCE_ATTR" global_required='true'/>
+				<retrieve />
+				<update permission="ADMIN_BOOKING_RESOURCE_ATTR" global_required='true'/>
+				<delete permission="ADMIN_BOOKING_RESOURCE_ATTR" global_required='true'/>
+			</actions>
+		</permacrud>
+	</class>
+	
+	<class id="brav" controller="open-ils.cstore open-ils.pcrud" oils_obj:fieldmapper="booking::resource_attr_value" oils_persist:tablename="booking.resource_attr_value" reporter:label="Resource Attribute Value">
+		<fields oils_persist:primary="id" oils_persist:sequence="booking.resource_attr_value_id_seq">
+			<field reporter:label="Resource Attribute Value ID" name="id" reporter:datatype="id"/>
+			<field reporter:label="Owning Library" name="owner" reporter:datatype="org_unit"/>
+			<field reporter:label="Resource Attribute" name="attr" reporter:datatype="link"/>
+			<field reporter:label="Valid Value" name="valid_value" reporter:datatype="text"/>
+			<field reporter:label="Resource Attribute Maps" name="attr_maps" oils_persist:virtual="true" reporter:datatype="link"/>
+			<field reporter:label="Resource Attribute Value Maps" name="attr_val_maps" oils_persist:virtual="true" reporter:datatype="link"/>
+		</fields>
+		<links>
+			<link field="owner" reltype="has_a" key="id" map="" class="aou"/>
+			<link field="attr" reltype="has_a" key="id" map="" class="bra"/>
+			<link field="attr_maps" reltype="has_many" key="id" map="" class="bram"/>
+			<link field="attr_val_maps" reltype="has_many" key="attr_value" map="" class="bravm"/>
+		</links>
+		<permacrud xmlns="http://open-ils.org/spec/opensrf/IDL/permacrud/v1">
+			<actions>
+				<create permission="ADMIN_BOOKING_RESOURCE_ATTR_VALUE" global_required='true'/>
+				<retrieve />
+				<update permission="ADMIN_BOOKING_RESOURCE_ATTR_VALUE" global_required='true'/>
+				<delete permission="ADMIN_BOOKING_RESOURCE_ATTR_VALUE" global_required='true'/>
+			</actions>
+		</permacrud>
+	</class>
+	
+	<class id="bram" controller="open-ils.cstore open-ils.pcrud" oils_obj:fieldmapper="booking::resource_attr_map" oils_persist:tablename="booking.resource_attr_map" reporter:label="Resource Attribute Map">
+		<fields oils_persist:primary="id" oils_persist:sequence="booking.resource_attr_map_id_seq">
+			<field reporter:label="Resource Attribute Map ID" name="id" reporter:datatype="id"/>
+			<field reporter:label="Resource" name="resource" reporter:datatype="link"/>
+			<field reporter:label="Resource Attribute" name="resource_attr" reporter:datatype="link"/>
+			<field reporter:label="Attribute Value" name="value" reporter:datatype="link"/>
+		</fields>
+		<links>
+			<link field="resource" reltype="has_a" key="id" map="" class="brsrc"/>
+			<link field="resource_attr" reltype="has_a" key="id" map="" class="bra"/>
+			<link field="value" reltype="has_a" key="id" map="" class="brav"/>
+		</links>
+		<permacrud xmlns="http://open-ils.org/spec/opensrf/IDL/permacrud/v1">
+			<actions>
+				<create permission="ADMIN_BOOKING_RESOURCE_ATTR_MAP" global_required='true'/>
+				<retrieve />
+				<update permission="ADMIN_BOOKING_RESOURCE_ATTR_MAP" global_required='true'/>
+				<delete permission="ADMIN_BOOKING_RESOURCE_ATTR_MAP" global_required='true'/>
+			</actions>
+		</permacrud>
+	</class>
+	
+	<class id="bresv" controller="open-ils.cstore open-ils.pcrud" oils_obj:fieldmapper="booking::reservation" oils_persist:tablename="booking.reservation" reporter:label="Reservation">
+		<fields oils_persist:primary="id" oils_persist:sequence="money.billable_xact_id_seq">
+			<field reporter:label="Transaction ID" name="id" reporter:datatype="id" />
+			<field reporter:label="User" name="usr" reporter:datatype="link"/>
+			<field reporter:label="Transaction Finish Date/Time" name="xact_finish" reporter:datatype="timestamp"/>
+			<field reporter:label="Transaction Start Date/Time" name="xact_start" reporter:datatype="timestamp"/>
+			<field reporter:label="Unrecovered Debt" name="unrecovered" reporter:datatype="bool"/>
+			<field reporter:label="Billing Line Items" name="billings" oils_persist:virtual="true" reporter:datatype="link"/>
+			<field reporter:label="Payment Line Items" name="payments" oils_persist:virtual="true" reporter:datatype="link"/>
+			<field reporter:label="Billing Totals" name="billing_total" oils_persist:virtual="true" reporter:datatype="money"/>
+			<field reporter:label="Payment Totals" name="payment_total" oils_persist:virtual="true" reporter:datatype="money"/>
+			<field reporter:label="Payment Summary" name="summary" oils_persist:virtual="true" reporter:datatype="link"/>
+			<field reporter:label="Request Time" name="request_time" reporter:datatype="timestamp"/>
+			<field reporter:label="Start Time" name="start_time" reporter:datatype="timestamp"/>
+			<field reporter:label="End Time" name="end_time" reporter:datatype="timestamp"/>
+			<field reporter:label="Capture Time" name="capture_time" reporter:datatype="timestamp"/>
+			<field reporter:label="Cancel Time" name="cancel_time" reporter:datatype="timestamp"/>
+			<field reporter:label="Pickup Time" name="pickup_time" reporter:datatype="timestamp"/>
+			<field reporter:label="Return Time" name="return_time" reporter:datatype="timestamp"/>
+			<field reporter:label="Booking Interval" name="booking_interval" reporter:datatype="interval"/>
+			<field reporter:label="Fine Interval" name="fine_interval" reporter:datatype="interval"/>
+			<field reporter:label="Fine Amount" name="fine_amount" reporter:datatype="money"/>
+			<field reporter:label="Max Fine Amount" name="max_fine" reporter:datatype="money"/>
+			<field reporter:label="Target Resource Type" name="target_resource_type" reporter:datatype="link"/>
+			<field reporter:label="Target Resource" name="target_resource" reporter:datatype="link"/>
+			<field reporter:label="Current Resource" name="current_resource" reporter:datatype="link"/>
+			<field reporter:label="Request Library" name="request_lib" reporter:datatype="link"/>
+			<field reporter:label="Pickup Library" name="pickup_lib" reporter:datatype="link"/>
+			<field reporter:label="Capture Staff" name="capture_staff" reporter:datatype="link"/>
+			<field reporter:label="Attribute Value Maps" name="attr_val_maps" oils_persist:virtual="true" reporter:datatype="link"/>
+		</fields>
+		<links>
+			<link field="usr" reltype="has_a" key="id" map="" class="au"/>
+			<link field="payments" reltype="has_many" key="xact" map="" class="mp"/>
+			<link field="billings" reltype="has_many" key="xact" map="" class="mb"/>
+			<link field="billing_total" reltype="might_have" key="xact" map="" class="rxbt"/>
+			<link field="payment_total" reltype="might_have" key="xact" map="" class="rxpt"/>
+			<link field="summary" reltype="might_have" key="id" map="" class="mbts"/>
+			<link field="target_resource_type" reltype="has_a" key="id" map="" class="brt"/>
+			<link field="target_resource" reltype="has_a" key="id" map="" class="brsrc"/>
+			<link field="current_resource" reltype="has_a" key="id" map="" class="brsrc"/>
+			<link field="request_lib" reltype="has_a" key="id" map="" class="aou"/>
+			<link field="pickup_lib" reltype="might_have" key="id" map="" class="aou"/>
+			<link field="capture_staff" reltype="might_have" key="id" map="" class="au"/>
+			<link field="attr_val_maps" reltype="has_many" key="reservation" map="" class="bravm"/>
+		</links>
+		<permacrud xmlns="http://open-ils.org/spec/opensrf/IDL/permacrud/v1">
+			<actions>
+				<create permission="ADMIN_BOOKING_RESERVATION" global_required='true'/>
+				<retrieve />
+				<update permission="ADMIN_BOOKING_RESERVATION" global_required='true'/>
+				<delete permission="ADMIN_BOOKING_RESERVATION" global_required='true'/>
+			</actions>
+		</permacrud>
+	</class>
+	
+	<class id="bravm" controller="open-ils.cstore open-ils.pcrud" oils_obj:fieldmapper="booking::reservation_attr_value_map" oils_persist:tablename="booking.reservation_attr_value_map" reporter:label="Reservation Attribute Value Map">
+		<fields oils_persist:primary="id" oils_persist:sequence="booking.reservation_attr_value_map_id_seq">
+			<field reporter:label="Reservation Attribute Value Map" name="id" reporter:datatype="id"/>
+			<field reporter:label="Reservation" name="reservation" reporter:datatype="link"/>
+			<field reporter:label="Attribute Map" name="attr_value" reporter:datatype="link"/>
+		</fields>
+		<links>
+			<link field="reservation" reltype="has_a" key="id" map="" class="bresv"/>
+			<link field="attr_value" reltype="has_a" key="id" map="" class="brav"/>
+		</links>
+		<permacrud xmlns="http://open-ils.org/spec/opensrf/IDL/permacrud/v1">
+			<actions>
+				<create permission="ADMIN_BOOKING_RESERVATION_ATTR_MAP" global_required='true'/>
+				<retrieve />
+				<update permission="ADMIN_BOOKING_RESERVATION_ATTR_MAP" global_required='true'/>
+				<delete permission="ADMIN_BOOKING_RESERVATION_ATTR_MAP" global_required='true'/>
+			</actions>
+		</permacrud>
+	</class>
+
 	<class id="ccnbi" controller="open-ils.cstore" oils_obj:fieldmapper="container::call_number_bucket_item" oils_persist:tablename="container.call_number_bucket_item" reporter:label="Call Number Bucket Item">
 		<fields oils_persist:primary="id" oils_persist:sequence="container.call_number_bucket_item_id_seq">
 			<field name="bucket" reporter:datatype="link"/>
@@ -2676,6 +2893,13 @@
 			<field reporter:label="Addresses" name="addresses" oils_persist:virtual="true" reporter:datatype="link"/>
 			<field reporter:label="Checkins" name="checkins" oils_persist:virtual="true" reporter:datatype="link"/>
 			<field reporter:label="Workstations" name="workstations" oils_persist:virtual="true" reporter:datatype="link"/>
+			<field reporter:label="Reservation Requests" name="resv_requests" oils_persist:virtual="true" reporter:datatype="link"/>
+			<field reporter:label="Reservation Pickups" name="resv_pickups" oils_persist:virtual="true" reporter:datatype="link"/>
+			<field reporter:label="Resource Types" name="rsrc_types" oils_persist:virtual="true" reporter:datatype="link"/>
+			<field reporter:label="Resources" name="resources" oils_persist:virtual="true" reporter:datatype="link"/>
+			<field reporter:label="Resource Attributes" name="rsrc_attrs" oils_persist:virtual="true" reporter:datatype="link"/>
+			<field reporter:label="Attribute Values" name="attr_vals" oils_persist:virtual="true" reporter:datatype="link"/>
+
 		</fields>
 		<links>
 			<link field="billing_address" reltype="has_a" key="id" map="" class="aoa"/>
@@ -2694,6 +2918,12 @@
 			<link field="workstations" reltype="has_many" key="owning_lib" map="" class="aws"/>
 			<link field="distribution_formulas" reltype="has_many" key="owner" map="" class="acqdf"/>
 			<link field="distribution_formula_entries" reltype="has_many" key="owning_lib" map="" class="acqdfe"/>
+			<link field="resv_requests" reltype="has_many" key="request_lib" map="" class="bresv"/>
+			<link field="resv_pickups" reltype="has_many" key="pickup_lib" map="" class="bresv"/>
+			<link field="rsrc_types" reltype="has_many" key="owner" map="" class="brt"/>
+			<link field="resources" reltype="has_many" key="owner" map="" class="brsrc"/>
+			<link field="rsrc_attrs" reltype="has_many" key="owner" map="" class="bra"/>
+			<link field="attr_vals" reltype="has_many" key="owner" map="" class="brav"/>
 		</links>
         <permacrud xmlns="http://open-ils.org/spec/opensrf/IDL/permacrud/v1">
             <actions>
@@ -3616,6 +3846,41 @@
 	</class>
 
 
+	<class id="artc" controller="open-ils.cstore open-ils.pcrud" oils_obj:fieldmapper="action::reservation_transit_copy" oils_persist:tablename="action.reservation_transit_copy" reporter:core="true" reporter:label="Reservation Transit">
+		<fields oils_persist:primary="id" oils_persist:sequence="action.transit_copy_id_seq">
+			<field reporter:label="Copy Status at Transit" name="copy_status" reporter:datatype="link"/>
+			<field reporter:label="Destination Library" name="dest" reporter:datatype="org_unit"/>
+			<field reporter:label="Receive Date/Time" name="dest_recv_time" reporter:datatype="timestamp"/>
+			<field reporter:label="Reservation requiring Transit" name="reservation" reporter:datatype="link"/>
+			<field reporter:label="Transit ID" name="id" reporter:datatype="id" />
+			<field reporter:label="Is Persistent?" name="persistant_transfer" reporter:datatype="bool"/>
+			<field reporter:label="Previous Stop" name="prev_hop" reporter:datatype="link"/>
+			<field reporter:label="Sending Library" name="source" reporter:datatype="org_unit"/>
+			<field reporter:label="Send Date/Time" name="source_send_time" reporter:datatype="timestamp"/>
+			<field reporter:label="Transited Copy" name="target_copy" reporter:datatype="link"/>
+			<field reporter:label="Base Transit" name="transit_copy" oils_persist:virtual="true" reporter:datatype="link"/>
+			<field reporter:label="Prev Destination Library" name="prev_dest" reporter:datatype="org_unit"/>
+		</fields>
+		<links>
+			<link field="transit_copy" reltype="might_have" key="id" map="" class="atc"/>
+			<link field="target_copy" reltype="has_a" key="id" map="" class="brsrc"/>
+			<link field="source" reltype="has_a" key="id" map="" class="aou"/>
+			<link field="copy_status" reltype="has_a" key="id" map="" class="ccs"/>
+			<link field="dest" reltype="has_a" key="id" map="" class="aou"/>
+			<link field="prev_dest" reltype="has_a" key="id" map="" class="aou"/>
+			<link field="reservation" reltype="has_a" key="id" map="" class="bresv"/>
+		</links>
+        <permacrud xmlns="http://open-ils.org/spec/opensrf/IDL/permacrud/v1">
+            <actions>
+                <create permission="TRANSIT_COPY">
+                    <context link="target_copy" field="owner"/>
+                </create>
+                <retrieve/>
+                <update permission="UPDATE_TRANSIT" context_field="dest source"/>
+                <delete permission="DELETE_TRANSIT" context_field="dest source"/>
+            </actions>
+        </permacrud>
+	</class>
 	<class id="ahtc" controller="open-ils.cstore open-ils.pcrud" oils_obj:fieldmapper="action::hold_transit_copy" oils_persist:tablename="action.hold_transit_copy" reporter:core="true" reporter:label="Hold Transit">
 		<fields oils_persist:primary="id" oils_persist:sequence="action.transit_copy_id_seq">
 			<field reporter:label="Copy Status at Transit" name="copy_status" reporter:datatype="link"/>

Modified: branches/rel_1_6/Open-ILS/examples/opensrf.xml.example
===================================================================
--- branches/rel_1_6/Open-ILS/examples/opensrf.xml.example	2010-01-13 16:30:42 UTC (rev 15311)
+++ branches/rel_1_6/Open-ILS/examples/opensrf.xml.example	2010-01-13 16:35:03 UTC (rev 15312)
@@ -430,6 +430,28 @@
                 </unix_config>
             </open-ils.actor>
 
+            <open-ils.booking>
+                <keepalive>5</keepalive>
+                <stateless>1</stateless>
+                <language>perl</language>
+                <implementation>OpenILS::Application::Booking</implementation>
+                <max_requests>199</max_requests>
+                <unix_config>
+                    <unix_sock>open-ils.booking_unix.sock</unix_sock>
+                    <unix_pid>open-ils.booking_unix.pid</unix_pid>
+                    <max_requests>1000</max_requests>
+                    <unix_log>open-ils.booking_unix.log</unix_log>
+                    <min_children>1</min_children>
+                    <max_children>15</max_children>
+                    <min_spare_children>1</min_spare_children>
+                    <max_spare_children>5</max_spare_children>
+                </unix_config>
+                <app_settings>
+                    <marctemplates>
+                        <K_book>LOCALSTATEDIR/templates/marc/k_book.xml</K_book>
+                    </marctemplates>
+                </app_settings>
+            </open-ils.booking>
 
             <open-ils.cat>
                 <keepalive>5</keepalive>
@@ -946,6 +968,7 @@
                 <appname>opensrf.math</appname> 
                 <appname>opensrf.dbmath</appname> 
                 <appname>open-ils.acq</appname> 
+                <appname>open-ils.booking</appname>
                 <appname>open-ils.cat</appname> 
                 <appname>open-ils.supercat</appname> 
                 <appname>open-ils.search</appname> 

Modified: branches/rel_1_6/Open-ILS/examples/opensrf_core.xml.example
===================================================================
--- branches/rel_1_6/Open-ILS/examples/opensrf_core.xml.example	2010-01-13 16:30:42 UTC (rev 15311)
+++ branches/rel_1_6/Open-ILS/examples/opensrf_core.xml.example	2010-01-13 16:35:03 UTC (rev 15312)
@@ -22,6 +22,7 @@
           <service>open-ils.actor</service>
           <service>open-ils.acq</service>
           <service>open-ils.auth</service>
+          <service>open-ils.booking</service>
           <service>open-ils.cat</service>
           <service>open-ils.circ</service>
           <service>open-ils.collections</service>

Modified: branches/rel_1_6/Open-ILS/src/extras/ils_events.xml
===================================================================
--- branches/rel_1_6/Open-ILS/src/extras/ils_events.xml	2010-01-13 16:30:42 UTC (rev 15311)
+++ branches/rel_1_6/Open-ILS/src/extras/ils_events.xml	2010-01-13 16:35:03 UTC (rev 15312)
@@ -814,6 +814,18 @@
 	<event code='7019' textcode='HOLD_CAPTURE_DELAYED'>
 		<desc xml:lang="en-US">Hold capture was delayed for this item</desc>
 	</event>
+	<event code='7020' textcode='COPY_RESERVED'>
+		<desc xml:lang="en-US">Item reserved for booking request</desc>
+	</event>
+	<event code='7021' textcode='RESERVATION_NOT_FOUND'>
+		<desc xml:lang="en-US">Booking reservation not found</desc>
+	</event>
+	<event code='7022' textcode='RESERVATION_CAPTURE_FAILED'>
+		<desc xml:lang="en-US">Booking reservation capture failed</desc>
+	</event>
+	<event code='7023' textcode='RESERVATION_BAD_PARAMS'>
+		<desc xml:lang="en-US">Provided parameters describe unacceptable reservation.</desc>
+	</event>
 
 
 

Modified: branches/rel_1_6/Open-ILS/src/perlmods/OpenILS/Application/AppUtils.pm
===================================================================
--- branches/rel_1_6/Open-ILS/src/perlmods/OpenILS/Application/AppUtils.pm	2010-01-13 16:30:42 UTC (rev 15311)
+++ branches/rel_1_6/Open-ILS/src/perlmods/OpenILS/Application/AppUtils.pm	2010-01-13 16:35:03 UTC (rev 15312)
@@ -811,6 +811,64 @@
 		payload => ($payload) ? $payload : undef ); 
 }
 
+sub fetch_booking_reservation {
+	my( $self, $id ) = @_;
+	my( $res, $evt );
+
+	$res = $self->simplereq(
+		'open-ils.cstore', 
+		'open-ils.cstore.direct.booking.reservation.retrieve', $id
+	);
+
+	# simplereq doesn't know how to flesh so ...
+	if ($res) {
+		$res->usr(
+			$self->simplereq(
+				'open-ils.cstore', 
+				'open-ils.cstore.direct.actor.user.retrieve', $res->usr
+			)
+		);
+
+		$res->target_resource_type(
+			$self->simplereq(
+				'open-ils.cstore', 
+				'open-ils.cstore.direct.booking.resource_type.retrieve', $res->target_resource_type
+			)
+		);
+
+		if ($res->current_resource) {
+			$res->current_resource(
+				$self->simplereq(
+					'open-ils.cstore', 
+					'open-ils.cstore.direct.booking.resource.retrieve', $res->current_resource
+				)
+			);
+
+			if ($self->is_true( $res->target_resource_type->catalog_item )) {
+				$res->current_resource->catalog_item( $self->fetch_copy_by_barcode( $res->current_resource->barcode ) );
+			}
+		}
+
+		if ($res->target_resource) {
+			$res->target_resource(
+				$self->simplereq(
+					'open-ils.cstore', 
+					'open-ils.cstore.direct.booking.resource.retrieve', $res->target_resource
+				)
+			);
+
+			if ($self->is_true( $res->target_resource_type->catalog_item )) {
+				$res->target_resource->catalog_item( $self->fetch_copy_by_barcode( $res->target_resource->barcode ) );
+			}
+		}
+
+	} else {
+		$evt = OpenILS::Event->new('RESERVATION_NOT_FOUND');
+	}
+
+	return ($res, $evt);
+}
+
 sub fetch_circ_duration_by_name {
 	my( $self, $name ) = @_;
 	my( $dur, $evt );
@@ -951,6 +1009,16 @@
 	return $copy;
 }
 
+sub unflesh_reservation {
+	my( $self, $reservation ) = @_;
+	return undef unless $reservation;
+	$reservation->usr( $reservation->usr->id ) if ref($reservation->usr);
+	$reservation->target_resource_type( $reservation->target_resource_type->id ) if ref($reservation->target_resource_type);
+	$reservation->target_resource( $reservation->target_resource->id ) if ref($reservation->target_resource);
+	$reservation->current_resource( $reservation->current_resource->id ) if ref($reservation->current_resource);
+	return $reservation;
+}
+
 # un-fleshes a copy and updates it in the DB
 # returns a DB_UPDATE_FAILED event on error
 # returns undef on success
@@ -979,6 +1047,29 @@
 	return undef;
 }
 
+sub update_reservation {
+	my( $self, %params ) = @_;
+
+	my $reservation	= $params{reservation}	|| die "update_reservation(): reservation required";
+	my $editor		= $params{editor} || die "update_reservation(): copy editor required";
+	my $session		= $params{session};
+
+	$logger->debug("Updating copy in the database: " . $reservation->id);
+
+	$self->unflesh_reservation($reservation);
+
+	my $s;
+	my $meth = 'open-ils.cstore.direct.booking.reservation.update';
+
+	$s = $session->request( $meth, $reservation )->gather(1) if $session;
+	$s = $self->cstorereq( $meth, $reservation ) unless $session;
+
+	$logger->debug("Update of copy ".$reservation->id." returned: $s");
+
+	return $self->DB_UPDATE_FAILED($reservation) unless $s;
+	return undef;
+}
+
 sub fetch_billable_xact {
 	my( $self, $id ) = @_;
 	my($xact, $evt);

Copied: branches/rel_1_6/Open-ILS/src/perlmods/OpenILS/Application/Booking.pm (from rev 15077, trunk/Open-ILS/src/perlmods/OpenILS/Application/Booking.pm)
===================================================================
--- branches/rel_1_6/Open-ILS/src/perlmods/OpenILS/Application/Booking.pm	                        (rev 0)
+++ branches/rel_1_6/Open-ILS/src/perlmods/OpenILS/Application/Booking.pm	2010-01-13 16:35:03 UTC (rev 15312)
@@ -0,0 +1,1148 @@
+package OpenILS::Application::Booking;
+
+use strict;
+use warnings;
+
+use POSIX qw/strftime/;
+use OpenILS::Application;
+use base qw/OpenILS::Application/;
+
+use OpenILS::Utils::CStoreEditor qw/:funcs/;
+use OpenILS::Utils::Fieldmapper;
+use OpenILS::Application::AppUtils;
+my $U = "OpenILS::Application::AppUtils";
+
+use OpenSRF::Utils::Logger qw/$logger/;
+
+sub prepare_new_brt {
+    my ($record_id, $owning_lib, $mvr) = @_;
+
+    my $brt = new Fieldmapper::booking::resource_type;
+    $brt->isnew(1);
+    $brt->name($mvr->title);
+    $brt->record($record_id);
+    $brt->catalog_item('t');
+    $brt->transferable('t');
+    $brt->owner($owning_lib);
+
+    return $brt;
+}
+
+sub get_existing_brt {
+    my ($e, $record_id, $owning_lib, $mvr) = @_;
+    my $results = $e->search_booking_resource_type(
+        {name => $mvr->title, owner => $owning_lib, record => $record_id}
+    );
+
+    return $results->[0] if scalar(@$results) > 0;
+    return undef;
+}
+
+sub get_mvr {
+    return $U->simplereq(
+        'open-ils.search',
+        'open-ils.search.biblio.record.mods_slim.retrieve.authoritative',
+        shift # record id
+    );
+}
+
+sub get_unique_owning_libs {
+    my %hash = ();
+    $hash{$_->call_number->owning_lib} = 1 foreach (@_);    # @_ are copies
+    return keys %hash;
+}
+
+sub fetch_copies_by_ids {
+    my ($e, $copy_ids) = @_;
+    my $results = $e->search_asset_copy([
+        {id => $copy_ids},
+        {flesh => 1, flesh_fields => {acp => ['call_number']}}
+    ]);
+    return $results if ref($results) eq 'ARRAY';
+    return [];
+}
+
+sub get_single_record_id {
+    my $record_id = undef;
+    foreach (@_) {  # @_ are copies
+        return undef if
+            (defined $record_id && $record_id != $_->call_number->record);
+        $record_id = $_->call_number->record;
+    }
+    return $record_id;
+}
+
+# This function generates the correct json_query clause for determining
+# whether two given ranges overlap.  Each range is composed of a start
+# and an end point.  All four points should be the same type (could be int,
+# date, time, timestamp, or perhaps other types).
+#
+# The first range (or the first two points) should be specified as
+# literal values.  The second range (or the last two points) should be
+# specified as the names of columns, the values of which in a given row
+# will constitute the second range in the comparison.
+#
+# ALSO: PostgreSQL includes an OVERLAPS operator which provides the same
+# functionality in a much more concise way, but json_query does not (yet).
+sub json_query_ranges_overlap {
+    +{ '-or' => [
+        { '-and' => [{$_[2] => {'>=', $_[0]}}, {$_[2] => {'<',  $_[1]}}]},
+        { '-and' => [{$_[3] => {'>',  $_[0]}}, {$_[3] => {'<',  $_[1]}}]},
+        { '-and' => { $_[3] => {'>',  $_[0]},   $_[2] => {'<=', $_[0]}}},
+        { '-and' => { $_[3] => {'>',  $_[1]},   $_[2] => {'<',  $_[1]}}},
+    ]};
+}
+
+sub create_brt_and_brsrc {
+    my ($self, $conn, $authtoken, $copy_ids) = @_;
+    my (@created_brt, @created_brsrc);
+    my %brt_table = ();
+
+    my $e = new_editor(xact => 1, authtoken => $authtoken);
+    return $e->die_event unless $e->checkauth;
+
+    my @copies = @{fetch_copies_by_ids($e, $copy_ids)};
+    my $record_id = get_single_record_id(@copies) or return $e->die_event;
+    my $mvr = get_mvr($record_id) or return $e->die_event;
+
+    foreach (get_unique_owning_libs(@copies)) {
+        $brt_table{$_} = get_existing_brt($e, $record_id, $_, $mvr) ||
+            prepare_new_brt($record_id, $_, $mvr);
+    }
+
+    while (my ($owning_lib, $brt) = each %brt_table) {
+        my $pre_existing = 1;
+        if ($brt->isnew) {
+            if ($e->allowed('ADMIN_BOOKING_RESOURCE_TYPE', $owning_lib)) {
+                $pre_existing = 0;
+                return $e->die_event unless (
+                    #    v-- Important: assignment modifies original hash
+                    $brt = $e->create_booking_resource_type($brt)
+                );
+            }
+        }
+        push @created_brt, [$brt->id, $brt->record, $pre_existing];
+    }
+
+    foreach (@copies) {
+        if ($e->allowed(
+            'ADMIN_BOOKING_RESOURCE', $_->call_number->owning_lib
+        )) {
+            # This block needs to disregard any cstore failures and just
+            # return what results it can.
+            my $brsrc = new Fieldmapper::booking::resource;
+            $brsrc->isnew(1);
+            $brsrc->type($brt_table{$_->call_number->owning_lib}->id);
+            $brsrc->owner($_->call_number->owning_lib);
+            $brsrc->barcode($_->barcode);
+
+            $e->set_savepoint("alpha");
+            my $pre_existing = 0;
+            my $usable_result = undef;
+            if (!($usable_result = $e->create_booking_resource($brsrc))) {
+                $e->rollback_savepoint("alpha");
+                if (($usable_result = $e->search_booking_resource(
+                    +{ map { ($_, $brsrc->$_()) } qw/type owner barcode/ }
+                ))) {
+                    $usable_result = $usable_result->[0];
+                    $pre_existing = 1;
+                } else {
+                    # So we failed to create a booking resource for this copy.
+                    # For now, let's just keep going.  If the calling app wants
+                    # to consider this an error, it can notice the absence
+                    # of a booking resource for the copy in the returned
+                    # results.
+                    $logger->warn(
+                        "Couldn't create or find brsrc for acp #" .  $_->id
+                    );
+                }
+            } else {
+                $e->release_savepoint("alpha");
+            }
+
+            if ($usable_result) {
+                push @created_brsrc,
+                    [$usable_result->id, $_->id, $pre_existing];
+            }
+        }
+    }
+
+    $e->commit and
+        return {brt => \@created_brt, brsrc => \@created_brsrc} or
+        return $e->die_event;
+}
+__PACKAGE__->register_method(
+    method   => "create_brt_and_brsrc",
+    api_name => "open-ils.booking.resources.create_from_copies",
+    signature => {
+        params => [
+            {type => 'string', desc => 'Authentication token'},
+            {type => 'array', desc => 'Copy IDs'},
+        ],
+        return => { desc => "A two-element hash. The 'brt' element " .
+            "is a list of created booking resource types described by " .
+            "3-tuples (id, copy id, was pre-existing).  The 'brsrc' " .
+            "element is a similar list of created booking resources " .
+            "described by (id, record id, was pre-existing) 3-tuples."}
+    }
+);
+
+
+sub create_bresv {
+    my ($self, $client, $authtoken,
+        $target_user_barcode, $datetime_range, $pickup_lib,
+        $brt, $brsrc_list, $attr_values) = @_;
+
+    $brsrc_list = [ undef ] if not defined $brsrc_list;
+    return undef if scalar(@$brsrc_list) < 1; # Empty list not ok.
+
+    my $e = new_editor(xact => 1, authtoken => $authtoken);
+    return $e->die_event unless $e->checkauth;
+    return $e->die_event unless $e->allowed("ADMIN_BOOKING_RESERVATION");
+
+    my $usr = $U->fetch_user_by_barcode($target_user_barcode);
+    return $usr if ref($usr) eq 'HASH' and exists($usr->{"ilsevent"});
+
+    my $results = [];
+    foreach my $brsrc (@$brsrc_list) {
+        my $bresv = new Fieldmapper::booking::reservation;
+        $bresv->usr($usr->id);
+        $bresv->request_lib($e->requestor->ws_ou);
+        $bresv->pickup_lib($pickup_lib);
+        $bresv->start_time($datetime_range->[0]);
+        $bresv->end_time($datetime_range->[1]);
+
+        # A little sanity checking: don't agree to put a reservation on a
+        # brsrc and a brt when they don't match.  In fact, bomb out of
+        # this transaction entirely.
+        if ($brsrc) {
+            my $brsrc_itself = $e->retrieve_booking_resource([
+                $brsrc, {
+                    "flesh" => 1,
+                    "flesh_fields" => {"brsrc" => ["type"]}
+                }
+            ]);
+
+            if (not $brsrc_itself) {
+                my $ev = new OpenILS::Event(
+                    "RESERVATION_BAD_PARAMS",
+                    desc => "brsrc $brsrc doesn't exist"
+                );
+                $e->disconnect;
+                return $ev;
+            }
+            elsif ($brsrc_itself->type->id != $brt) {
+                my $ev = new OpenILS::Event(
+                    "RESERVATION_BAD_PARAMS",
+                    desc => "brsrc $brsrc doesn't match given brt $brt"
+                );
+                $e->disconnect;
+                return $ev;
+            }
+
+            # Also bail if the user is trying to create a reservation at
+            # a pickup lib to which our resource won't go.
+            if (
+                $brsrc_itself->owner != $pickup_lib and
+                    not $brsrc_itself->type->transferable
+            ) {
+                my $ev = new OpenILS::Event(
+                    "RESERVATION_BAD_PARAMS",
+                    desc => "brsrc $brsrc doesn't belong to $pickup_lib and " .
+                        "is not transferable"
+                );
+                $e->disconnect;
+                return $ev;
+            }
+        }
+        $bresv->target_resource($brsrc);    # undef is ok here
+        $bresv->target_resource_type($brt);
+
+        ($bresv = $e->create_booking_reservation($bresv)) or
+            return $e->die_event;
+
+        # We could/should do some sanity checking on this too: namely, on
+        # whether the attribute values given actually apply to the relevant
+        # brt.  Not seeing any grievous side effects of not checking, though.
+        my @bravm = ();
+        foreach my $value (@$attr_values) {
+            my $bravm = new Fieldmapper::booking::reservation_attr_value_map;
+            $bravm->reservation($bresv->id);
+            $bravm->attr_value($value);
+            $bravm = $e->create_booking_reservation_attr_value_map($bravm) or
+                return $e->die_event;
+            push @bravm, $bravm;
+        }
+        push @$results, {
+            "bresv" => $bresv->id,
+            "bravm" => \@bravm,
+        };
+    }
+
+    $e->commit or return $e->die_event;
+
+    # Targeting must be tacked on _after_ committing the transaction where the
+    # reservations are actually created.
+    foreach (@$results) {
+        $_->{"targeting"} = $U->storagereq(
+            "open-ils.storage.booking.reservation.resource_targeter",
+            $_->{"bresv"}
+        )->[0];
+    }
+    return $results;
+}
+__PACKAGE__->register_method(
+    method   => "create_bresv",
+    api_name => "open-ils.booking.reservations.create",
+    signature => {
+        params => [
+            {type => 'string', desc => 'Authentication token'},
+            {type => 'string', desc => 'Barcode of user for whom to reserve'},
+            {type => 'array', desc => 'Two elements: start and end timestamp'},
+            {type => 'int', desc => 'Desired reservation pickup lib'},
+            {type => 'int', desc => 'Booking resource type'},
+            {type => 'list', desc => 'Booking resource (undef ok; empty not ok)'},
+            {type => 'array', desc => 'Attribute values selected'},
+        ],
+        return => { desc => "A hash containing the new bresv and a list " .
+            "of new bravm"}
+    }
+);
+
+
+sub resource_list_by_attrs {
+    my $self = shift;
+    my $client = shift;
+    my $auth = shift; # Keep as argument, though not used just now.
+    my $filters = shift;
+
+    return undef unless ($filters->{type} || $filters->{attribute_values});
+
+    my $query = {
+        "select"   => {brsrc => ["id"]},
+        "from"     => {brsrc => {"brt" => {}}},
+        "where"    => {},
+        "distinct" => 1
+    };
+
+    $query->{where} = {"-and" => []};
+    if ($filters->{type}) {
+        push @{$query->{where}->{"-and"}}, {"type" => $filters->{type}};
+    }
+
+    if ($filters->{pickup_lib}) {
+        push @{$query->{where}->{"-and"}},
+            {"-or" => [
+                {"owner" => $filters->{pickup_lib}},
+                {"+brt" => {"transferable" => "t"}}
+            ]};
+    }
+
+    if ($filters->{attribute_values}) {
+
+        $query->{from}->{brsrc}->{bram} = { field => 'resource' };
+
+        $filters->{attribute_values} = [$filters->{attribute_values}]
+            if (!ref($filters->{attribute_values}));
+
+        $query->{having}->{'+bram'}->{value}->{'@>'} = {
+            transform => 'array_accum',
+            value => '$_' . $$ . '${' .
+                join(',', @{$filters->{attribute_values}}) .
+                '}$_' . $$ . '$'
+        };
+    }
+
+    if ($filters->{available}) {
+        # If only one timestamp has been provided, make it into a range.
+        if (!ref($filters->{available})) {
+            $filters->{available} = [($filters->{available}) x 2];
+        }
+
+        push @{$query->{where}->{"-and"}}, {
+            "-or" => [
+                {"overbook" => "t"},
+                {"-not-exists" => {
+                    "select" => {"bresv" => ["id"]},
+                    "from" => "bresv",
+                    "where" => {"-and" => [
+                        json_query_ranges_overlap(
+                            $filters->{available}->[0],
+                            $filters->{available}->[1],
+                            "start_time",
+                            "end_time"
+                        ),
+                        {"cancel_time" => undef},
+                        {"current_resource" => {"=" => {"+brsrc" => "id"}}}
+                    ]},
+                }}
+            ]
+        };
+    }
+    if ($filters->{booked}) {
+        # If only one timestamp has been provided, make it into a range.
+        if (!ref($filters->{booked})) {
+            $filters->{booked} = [($filters->{booked}) x 2];
+        }
+
+        push @{$query->{where}->{"-and"}}, {
+            "-exists" => {
+                "select" => {"bresv" => ["id"]},
+                "from" => "bresv",
+                "where" => {"-and" => [
+                    json_query_ranges_overlap(
+                        $filters->{booked}->[0],
+                        $filters->{booked}->[1],
+                        "start_time",
+                        "end_time"
+                    ),
+                    {"cancel_time" => undef},
+                    {"current_resource" => { "=" => {"+brsrc" => "id"}}}
+                ]},
+            }
+        };
+        # I think that the "booked" case could be done with a JOIN instead of
+        # an EXISTS, but I'm leaving it this way for symmetry with the
+        # "available" case for now.  The available case cannot be done with a
+        # join.
+    }
+
+    my $cstore = OpenSRF::AppSession->connect('open-ils.cstore');
+    my $rows = $cstore->request( 'open-ils.cstore.json_query.atomic', $query )->gather(1);
+    $cstore->disconnect;
+
+    return @$rows ? [map { $_->{id} } @$rows] : [];
+}
+__PACKAGE__->register_method(
+    method   => "resource_list_by_attrs",
+    api_name => "open-ils.booking.resources.filtered_id_list",
+    argc     => 3,
+    signature=> {
+        params => [
+            {type => 'string', desc => 'Authentication token (unused for now,' .
+               ' but at least pass undef here)'},
+            {type => 'object', desc => 'Filter object: see notes for details'},
+            {type => 'bool', desc => 'Return whole objects instead of IDs?'}
+        ],
+        return => { desc => "An array of brsrc ids matching the requested filters." },
+    },
+    notes    => <<'NOTES'
+
+The filter object parameter can contain the following keys:
+ * type             => The id of a booking resource type (brt)
+ * attribute_values => The ids of booking resource type attribute values that the resource must have assigned to it (brav)
+ * available        => Either:
+                        A timestamp during which the resources are not reserved.  If the resource is overbookable, this is ignored.
+                        A range of two timestamps which do not overlap any reservations for the resources.  If the resource is overbookable, this is ignored.
+ * booked           => Either:
+                        A timestamp during which the resources are reserved.
+                        A range of two timestamps which overlap a reservation of the resources.
+
+Note that at least one of 'type' or 'attribute_values' is required.
+
+NOTES
+);
+
+
+sub reservation_list_by_filters {
+    my $self = shift;
+    my $client = shift;
+    my $auth = shift;
+    my $filters = shift;
+    my $whole_obj = shift;
+
+    return undef unless ($filters->{user} || $filters->{user_barcode} || $filters->{resource} || $filters->{type} || $filters->{attribute_values});
+
+    my $e = new_editor(authtoken=>$auth);
+    return $e->event unless $e->checkauth;
+    return $e->event unless $e->allowed('VIEW_TRANSACTION');
+
+    my $query = {
+        'select'   => { bresv => [ 'id', 'start_time' ] },
+        'from'     => { bresv => {} },
+        'where'    => {},
+        'order_by' => [{ class => bresv => field => start_time => direction => 'asc' }],
+        'distinct' => 1
+    };
+
+    if ($filters->{fields}) {
+        $query->{where} = $filters->{fields};
+    }
+
+
+    if ($filters->{user}) {
+        $query->{where}->{usr} = $filters->{user};
+    }
+    elsif ($filters->{user_barcode}) {  # just one of user and user_barcode
+        my $usr = $U->fetch_user_by_barcode($filters->{user_barcode});
+        return $usr if ref($usr) eq 'HASH' and exists($usr->{"ilsevent"});
+        $query->{where}->{usr} = $usr->id;
+    }
+
+
+    if ($filters->{type}) {
+        $query->{where}->{target_resource_type} = $filters->{type};
+    }
+
+    if ($filters->{resource}) {
+        $query->{where}->{target_resource} = $filters->{resource};
+    }
+
+    if ($filters->{attribute_values}) {
+
+        $query->{from}->{bresv}->{bravm} = { field => 'reservation' };
+
+        $filters->{attribute_values} = [$filters->{attribute_values}]
+            if (!ref($filters->{attribute_values}));
+
+        $query->{having}->{'+bravm'}->{attr_value}->{'@>'} = {
+            transform => 'array_accum',
+            value => '$_' . $$ . '${' .
+                join(',', @{$filters->{attribute_values}}) .
+                '}$_' . $$ . '$'
+        };
+    }
+
+    if ($filters->{search_start} || $filters->{search_end}) {
+        $query->{where}->{'-or'} = {};
+
+        $query->{where}->{'-or'}->{start_time} = { 'between' => [ $filters->{search_start}, $filters->{search_end} ] }
+                if ($filters->{search_start});
+
+        $query->{where}->{'-or'}->{end_time} = { 'between' => [ $filters->{search_start}, $filters->{search_end} ] }
+                if ($filters->{search_end});
+    }
+
+    my $cstore = OpenSRF::AppSession->connect('open-ils.cstore');
+    my $ids = [ map { $_->{id} } @{
+        $cstore->request(
+            'open-ils.cstore.json_query.atomic', $query
+        )->gather(1)
+    } ];
+    $cstore->disconnect;
+
+    if (not $whole_obj or @$ids < 1) {
+        $e->disconnect;
+        return $ids;
+    }
+
+    my $bresv_list = $e->search_booking_reservation([
+        {"id" => $ids},
+        {"flesh" => 1,
+            "flesh_fields" => {
+                "bresv" =>
+                    [qw/target_resource current_resource target_resource_type/]
+            }
+        }]
+    );
+    $e->disconnect;
+    return $bresv_list ? $bresv_list : [];
+}
+__PACKAGE__->register_method(
+    method   => "reservation_list_by_filters",
+    api_name => "open-ils.booking.reservations.filtered_id_list",
+    argc     => 2,
+    signature=> {
+        params => [
+            {type => 'string', desc => 'Authentication token'},
+            {type => 'object', desc => 'Filter object -- see notes for details'}
+        ],
+        return => { desc => "An array of bresv ids matching the requested filters." },
+    },
+    notes    => <<'NOTES'
+
+The filter object parameter can contain the following keys:
+ * user             => The id of a user that has requested a bookable item -- filters on bresv.usr
+ * barcode          => The barcode of a user that has requested a bookable item
+ * type             => The id of a booking resource type (brt) -- filters on bresv.target_resource_type
+ * resource         => The id of a booking resource (brsrc) -- filters on bresv.target_resource
+ * attribute_values => The ids of booking resource type attribute values that the resource must have assigned to it (brav)
+ * search_start     => If search_end is not specified, booking interval (start_time to end_time) must contain this timestamp.
+ * search_end       => If search_start is not specified, booking interval (start_time to end_time) must contain this timestamp.
+ * fields           => An object containing any combination of bresv search filters in standard cstore/pcrud search format.
+
+Note that at least one of 'user', 'type', 'resource' or 'attribute_values' is required.  If both search_start and search_end are specified,
+then the result includes any reservations that overlap with that time range.  Any filter fields supplied in 'fields' are overridden
+by the top-level filters ('user', 'type', 'resource').
+
+NOTES
+);
+
+
+sub naive_ts_string {strftime("%F %T", localtime($_[0] || time));}
+sub naive_start_of_day {strftime("%F", localtime($_[0] || time))." 00:00:00";}
+
+# Return a list of bresv or an ilsevent on failure.
+sub get_uncaptured_bresv_for_brsrc {
+    my ($e, $o) = @_; # o's keys (all optional): owning_lib, barcode, range
+
+    my $from_clause = {
+        "bresv" => {
+            "brsrc" => {"field" => "id", "fkey" => "current_resource"}
+        }
+    };
+
+    my $query = {
+        "select" => {
+            "bresv" => [
+                "current_resource",
+                {
+                    "column" => "start_time",
+                    "transform" => "min",
+                    "aggregate" => 1
+                }
+            ]
+        },
+        "from" => $from_clause,
+        "where" => {
+            "-and" => [
+                {"current_resource" => {"!=" => undef}},
+                {"capture_time" => undef},
+                {"cancel_time" => undef},
+                {"return_time" => undef},
+                {"pickup_time" => undef}
+            ]
+        }
+    };
+    if ($o->{"owning_lib"}) {
+        push @{$query->{"where"}->{"-and"}},
+            {"+brsrc" => {"owner" => $o->{"owning_lib"}}};
+    }
+    if ($o->{"range"}) {
+        push @{$query->{"where"}->{"-and"}},
+            json_query_ranges_overlap(
+                $o->{"range"}->[0], $o->{"range"}->[1],
+                "start_time", "end_time"
+            );
+    }
+    if ($o->{"barcode"}) {
+        push @{$query->{"where"}->{"-and"}},
+            {"+brsrc" => {"barcode" => $o->{"barcode"}}};
+    }
+
+    my $rows = $e->json_query($query);
+    my $current_resource_bresv_map = {};
+    if (@$rows) {
+        my $id_query = {
+            "select" => {"bresv" => ["id"]},
+            "from" => $from_clause,
+            "where" => {
+                "-and" => [
+                    {"current_resource" => "PLACEHOLDER"},
+                    {"start_time" => "PLACEHOLDER"},
+                ]
+            }
+        };
+        if ($o->{"owning_lib"}) {
+            push @{$id_query->{"where"}->{"-and"}},
+                {"+brsrc" => {"owner" => $o->{"owning_lib"}}};
+        }
+
+        foreach (@$rows) {
+            $id_query->{"where"}->{"-and"}->[0]->{"current_resource"} =
+                $_->{"current_resource"};
+            $id_query->{"where"}->{"-and"}->[1]->{"start_time"} =
+                $_->{"start_time"};
+
+            my $results = $e->json_query($id_query);
+            if ($results && @$results) {
+                $current_resource_bresv_map->{$_->{"current_resource"}} =
+                    [map { $_->{"id"} } @$results];
+            }
+        }
+    }
+    return $current_resource_bresv_map;
+}
+
+sub get_pull_list {
+    my ($self, $client, $auth, $range, $interval_secs, $owning_lib) = @_;
+
+    my $e = new_editor(xact => 1, authtoken => $auth);
+    return $e->die_event unless $e->checkauth;
+    return $e->die_event unless $e->allowed("RETRIEVE_RESERVATION_PULL_LIST");
+    return $e->die_event unless (
+        ref($range) eq "ARRAY" or
+        ($interval_secs = int($interval_secs)) > 0
+    );
+
+    $owning_lib = $e->requestor->ws_ou if not $owning_lib;
+    $range = [ naive_ts_string(time), naive_ts_string(time + $interval_secs) ]
+        if not $range;
+
+    my $uncaptured = get_uncaptured_bresv_for_brsrc(
+        $e, {"range" => $range, "owning_lib" => $owning_lib}
+    );
+
+    if (keys(%$uncaptured)) {
+        my @all_bresv_ids = map { @{$_} } values %$uncaptured;
+        my %bresv_lookup = (
+            map { $_->id => $_ } @{
+                $e->search_booking_reservation([{"id" => [@all_bresv_ids]}, {
+                    flesh => 1,
+                    flesh_fields => { bresv => [
+                        "usr", "target_resource_type", "current_resource"
+                    ]}
+                }])
+            }
+        );
+        $e->disconnect;
+        return [ map {
+            my $key = $_;
+            my $one = $bresv_lookup{$uncaptured->{$key}->[0]};
+            my $result = {
+                "current_resource" => $one->current_resource,
+                "target_resource_type" => $one->target_resource_type,
+                "reservations" => [
+                    map { $bresv_lookup{$_} } @{$uncaptured->{$key}}
+                ]
+            };
+            foreach (@{$result->{"reservations"}}) {    # deflesh
+                $_->current_resource($_->current_resource->id);
+                $_->target_resource_type($_->target_resource_type->id);
+            }
+            $result;
+        } keys %$uncaptured ];
+    } else {
+        $e->disconnect;
+        return [];
+    }
+}
+__PACKAGE__->register_method(
+    method   => "get_pull_list",
+    api_name => "open-ils.booking.reservations.get_pull_list",
+    argc     => 4,
+    signature=> {
+        params => [
+            {type => "string", desc => "Authentication token"},
+            {type => "array", desc =>
+                "range: Date/time range for reservations (opt)"},
+            {type => "int", desc =>
+                "interval: Seconds from now (instead of range)"},
+            {type => "number", desc => "(Optional) Owning library"}
+        ],
+        return => { desc => "An array of hashes, each containing key/value " .
+            "pairs describing resource, resource type, and a list of " .
+            "reservations that claim the given resource." }
+    }
+);
+
+
+sub get_copy_fleshed_just_right {
+    my ($self, $client, $auth, $barcode) = @_;
+
+    return undef if not defined $barcode;
+    return {} if ref($barcode) eq "ARRAY" and not @$barcode;
+
+    my $e = new_editor(authtoken => $auth);
+    my $results = $e->search_asset_copy([
+        {"barcode" => $barcode},
+        {
+            "flesh" => 1,
+            "flesh_fields" => {"acp" => [qw/call_number location/]}
+        }
+    ]);
+
+    if (ref($results) eq "ARRAY") {
+        $e->disconnect;
+        return $results->[0] unless ref $barcode;
+        return +{ map { $_->barcode => $_ } @$results };
+    } else {
+        return $e->die_event;
+    }
+}
+__PACKAGE__->register_method(
+    method   => "get_copy_fleshed_just_right",
+    api_name => "open-ils.booking.asset.get_copy_fleshed_just_right",
+    argc     => 2,
+    signature=> {
+        params => [
+            {type => "string", desc => "Authentication token"},
+            {type => "mixed", desc => "One barcode or an array of them"},
+        ],
+        return => { desc =>
+            "A copy, or a hash of copies keyed by barcode if an array of " .
+            "barcodes was given"
+        }
+    }
+);
+
+
+sub best_bresv_candidate {
+    my ($e, $id_list) = @_;
+
+    # This will almost always be the case.
+    return $id_list->[0] if @$id_list == 1;
+
+    my @here = ();
+    my $this_ou = $e->requestor->ws_ou;
+    my $results = $e->json_query({
+        "select" => {"brsrc" => ["pickup_lib"], "bresv" => ["id"]},
+        "from" => {
+            "bresv" => {
+                "brsrc" => {"field" => "id", "fkey" => "current_resource"}
+            }
+        },
+        "where" => {
+            {"+bresv" => {"id" => $id_list}}
+        }
+    });
+
+    foreach (@$results) {
+        push @here, $_->{"id"} if $_->{"pickup_lib"} == $this_ou;
+    }
+
+    if (@here > 0) {
+        return pop @here if @here == 1;
+        return (sort @here)[0];
+    } else {
+        return (sort @$id_list)[0];
+    }
+}
+
+
+sub capture_resource_for_reservation {
+    my ($self, $client, $auth, $barcode) = @_;
+
+    my $e = new_editor(xact => 1, authtoken => $auth);
+    return $e->die_event unless $e->checkauth;
+    return $e->die_event unless $e->allowed("CAPTURE_RESERVATION");
+
+    my $uncaptured = get_uncaptured_bresv_for_brsrc(
+        $e, {"barcode" => $barcode}
+    );
+    $e->disconnect;
+
+    if (keys %$uncaptured) {
+        # Note this will only capture one reservation at a time, even in
+        # cases with overbooking (multiple "soonest" bresv's on a resource).
+        my $key = (sort(keys %$uncaptured))[0];
+        return capture_reservation(
+            $self, $client, $auth, best_bresv_candidate($e, $uncaptured->{$key})
+        );
+    } else {
+        return new OpenILS::Event(
+            "RESERVATION_NOT_FOUND",
+            desc => "No capturable reservation found pertaining " .
+                "to a resource with barcode $barcode",
+            payload => {fail_cause => 'no-reservation', captured => 0}
+        );
+    }
+}
+__PACKAGE__->register_method(
+    method   => "capture_resource_for_reservation",
+    api_name => "open-ils.booking.resources.capture_for_reservation",
+    argc     => 3,
+    signature=> {
+        params => [
+            {type => "string", desc => "Authentication token"},
+            {type => "string", desc => "Barcode of booked & targeted resource"},
+            {type => "int", desc => "Pickup library (default to client ws_ou)"},
+        ],
+        return => { desc => "An OpenILS event describing the capture outcome" }
+    }
+);
+
+
+sub capture_reservation {
+    my ($self, $client, $auth, $res_id) = @_;
+
+    my $e = new_editor(xact => 1, authtoken => $auth);
+    return $e->event unless $e->checkauth;
+    return $e->event unless $e->allowed('CAPTURE_RESERVATION');
+    my $here = $e->requestor->ws_ou;
+
+    my $reservation = $e->retrieve_booking_reservation([
+        $res_id, {
+            flesh => 2,
+            flesh_fields => {"bresv" => ["usr"], "au" => ["card"]}
+        }
+    ]);
+    return OpenILS::Event->new('RESERVATION_NOT_FOUND') unless $reservation;
+
+    return OpenILS::Event->new('RESERVATION_CAPTURE_FAILED', payload => { captured => 0, fail_cause => 'no-resource' })
+        if (!$reservation->current_resource); # no resource
+
+    return OpenILS::Event->new('RESERVATION_CAPTURE_FAILED', payload => { captured => 0, fail_cause => 'cancelled' })
+        if ($reservation->cancel_time); # canceled
+
+    my $resource = $e->retrieve_booking_resource( $reservation->current_resource );
+    my $type = $e->retrieve_booking_resource_type( $resource->type );
+
+    $reservation->capture_staff( $e->requestor->id );
+    $reservation->capture_time( 'now' );
+
+    my $reservation_id = undef;
+    return $e->event unless ( $e->update_booking_reservation( $reservation ) and $reservation_id = $e->data );
+
+    $reservation->id($reservation_id);
+
+    my $ret = { captured => 1, reservation => $reservation };
+
+    if ($here <> $reservation->pickup_lib) {
+        return OpenILS::Event->new('RESERVATION_CAPTURE_FAILED', payload => { captured => 0, fail_cause => 'not-transferable' })
+            if (!$U->is_true($type->transferable)); # non-transferable resource
+
+        # need to transit the item ... is it already in transit?
+        my $transit = $e->search_action_reservation_transit_copy( { reservation => $res_id, dest_recv_time => undef } )->[0];
+
+        if (!$transit) { # not yet in transit
+            $transit = new Fieldmapper::action::reservation_transit_copy;
+
+            $transit->reservation($reservation->id);
+            $transit->target_copy($resource->id);
+            $transit->copy_status(15);
+            $transit->source_send_time('now');
+            $transit->source($here);
+            $transit->dest($reservation->pickup_lib);
+
+            $e->create_action_reservation_transit_copy( $transit );
+
+            if ($U->is_true($type->catalog_item)) {
+                my $copy = $e->search_asset_copy( { barcode => $resource->barcode, deleted => 'f' } )->[0];
+
+                if ($copy) {
+                    return new OpenILS::Event(
+                        "OPEN_CIRCULATION_EXISTS",
+                        payload => { captured => 0, copy => $copy }
+                    ) if $copy->status == 1;
+                    $copy->status(6);
+                    $e->update_asset_copy( $copy );
+                    $$ret{catalog_item} = $copy; # $e->data is just id (int)
+                }
+            }
+        }
+
+        $$ret{transit} = $transit;
+    } elsif ($U->is_true($type->catalog_item)) {
+        my $copy = $e->search_asset_copy( { barcode => $resource->barcode, deleted => 'f' } )->[0];
+
+        if ($copy) {
+            return OpenILS::Event->new('OPEN_CIRCULATION_EXISTS', payload => { captured => 0, copy => $copy }) if ($copy->status == 1);
+            $copy->status(15);
+            $e->update_asset_copy( $copy );
+            $$ret{catalog_item} = $copy; # $e->data is just id (int)
+        }
+    }
+
+    $e->commit;
+
+    return OpenILS::Event->new('SUCCESS', payload => $ret);
+}
+__PACKAGE__->register_method(
+    method   => "capture_reservation",
+    api_name => "open-ils.booking.reservations.capture",
+    argc     => 2,
+    signature=> {
+        params => [
+            {type => 'string', desc => 'Authentication token'},
+            {type => 'mixed', desc =>
+                'Reservation ID (number) or array of resource barcodes'}
+        ],
+        return => { desc => "An OpenILS Event object describing the outcome of the capture, with relevant payload." },
+    }
+);
+
+
+sub cancel_reservation {
+    my ($self, $client, $auth, $id_list) = @_;
+
+    my $e = new_editor(xact => 1, authtoken => $auth);
+    return $e->die_event unless $e->checkauth;
+    # Should the following permission really be checked as relates to each
+    # individual reservation's request_lib?  Hrmm...
+    return $e->die_event unless $e->allowed("ADMIN_BOOKING_RESERVATION");
+
+    my $bresv_list = $e->search_booking_reservation([
+        {"id" => $id_list},
+        {"flesh" => 1, "flesh_fields" => {"bresv" => [
+            "current_resource", "target_resource_type"
+        ]}}
+    ]);
+    return $e->die_event if not $bresv_list;
+
+    my $circ = OpenSRF::AppSession->connect("open-ils.circ") or
+        return $e->die_event;
+    my @results = ();
+    foreach my $bresv (@$bresv_list) {
+        if (
+            $bresv->target_resource_type->catalog_item == "t" &&
+            $bresv->current_resource
+        ) {
+            $logger->info("result of no-op checkin (upon cxl bresv) is " .
+                $circ->request(
+                    "open-ils.circ.checkin", $auth,
+                    {"barcode" => $bresv->current_resource->barcode,
+                        "noop" => 1}
+                )->gather(1)->{"textcode"});
+        }
+        $bresv->cancel_time("now");
+        $e->update_booking_reservation($bresv) or do {
+            $circ->disconnect;
+            return $e->die_event;
+        };
+
+        push @results, $bresv->id;
+    }
+
+    $e->commit;
+    $circ->disconnect;
+
+    return \@results;
+}
+__PACKAGE__->register_method(
+    method   => "cancel_reservation",
+    api_name => "open-ils.booking.reservations.cancel",
+    argc     => 2,
+    signature=> {
+        params => [
+            {type => "string", desc => "Authentication token"},
+            {type => "array", desc => "List of reservation IDs"}
+        ],
+        return => { desc => "A list of canceled reservation IDs" },
+    }
+);
+
+
+sub get_captured_reservations {
+    my ($self, $client, $auth, $barcode, $which) = @_;
+
+    my $e = new_editor(xact => 1, authtoken => $auth);
+    return $e->die_event unless $e->checkauth;
+    return $e->die_event unless $e->allowed("VIEW_USER");
+    return $e->die_event unless $e->allowed("ADMIN_BOOKING_RESERVATION");
+
+    # fetch the patron for our uses in any case...
+    my $patron = $U->fetch_user_by_barcode($barcode);
+    return $patron if ref($patron) eq "HASH" and exists $patron->{"ilsevent"};
+
+    my $bresv_flesh = {
+        "flesh" => 1,
+        "flesh_fields" => {"bresv" => [
+            qw/target_resource_type current_resource/
+        ]}
+    };
+
+    my $dispatch = {
+        "patron" => sub {
+            return $patron;
+        },
+        "ready" => sub {
+            return $e->search_booking_reservation([
+                {
+                    "usr" => $patron->id,
+                    "capture_time" => {"!=" => undef},
+                    "pickup_time" => undef,
+                    "start_time" => {">=" => naive_start_of_day()},
+                    "cancel_time" => undef
+                },
+                $bresv_flesh
+            ]) or $e->die_event;
+        },
+        "out" => sub {
+            return $e->search_booking_reservation([
+                {
+                    "usr" => $patron->id,
+                    "pickup_time" => {"!=" => undef},
+                    "return_time" => undef,
+                    "cancel_time" => undef
+                },
+                $bresv_flesh
+            ]) or $e->die_event;
+        },
+        "in" => sub {
+            return $e->search_booking_reservation([
+                {
+                    "usr" => $patron->id,
+                    "return_time" => {">=" => naive_start_of_day()},
+                    "cancel_time" => undef
+                },
+                $bresv_flesh
+            ]) or $e->die_event;
+        }
+    };
+
+    my $result = {};
+    foreach (@$which) {
+        my $f = $dispatch->{$_};
+        if ($f) {
+            my $r = &{$f}();
+            return $r if (ref($r) eq "HASH" and exists $r->{"ilsevent"});
+            $result->{$_} = $r;
+        }
+    }
+
+    return $result;
+}
+__PACKAGE__->register_method(
+    method   => "get_captured_reservations",
+    api_name => "open-ils.booking.reservations.get_captured",
+    argc     => 3,
+    signature=> {
+        params => [
+            {type => "string", desc => "Authentication token"},
+            {type => "string", desc => "Patron barcode"},
+            {type => "array", desc => "Parts wanted (patron, ready, out, in?)"}
+        ],
+        return => { desc => "A hash of parts." } # XXX describe more fully
+    }
+);
+
+
+sub get_bresv_by_returnable_resource_barcode {
+    my ($self, $client, $auth, $barcode) = @_;
+
+    my $e = new_editor(xact => 1, authtoken => $auth);
+    return $e->die_event unless $e->checkauth;
+    return $e->die_event unless $e->allowed("VIEW_USER");
+    return $e->die_event unless $e->allowed("ADMIN_BOOKING_RESERVATION");
+
+    my $rows = $e->json_query({
+        "select" => {"bresv" => ["id"]},
+        "from" => {
+            "bresv" => {
+                "brsrc" => {"field" => "id", "fkey" => "current_resource"}
+            }
+        },
+        "where" => {
+            "+brsrc" => {"barcode" => $barcode},
+            "-and" => {
+                "pickup_time" => {"!=" => undef},
+                "cancel_time" => undef,
+                "return_time" => undef
+            }
+        }
+    }) or return $e->die_event;
+
+    if (@$rows < 1) {
+        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([
+            $id, {
+                "flesh" => 2,
+                "flesh_fields" => {
+                    "bresv" => [qw/usr target_resource_type current_resource/],
+                    "au" => ["card"]
+                }
+            }
+        ]) or $e->die_event;
+    }
+}
+
+__PACKAGE__->register_method(
+    method   => "get_bresv_by_returnable_resource_barcode",
+    api_name => "open-ils.booking.reservations.by_returnable_resource_barcode",
+    argc     => 2,
+    signature=> {
+        params => [
+            {type => "string", desc => "Authentication token"},
+            {type => "string", desc => "Resource barcode"},
+        ],
+        return => { desc => "A fleshed bresv or an ilsevent on error" }
+    }
+);
+
+
+1;

Modified: branches/rel_1_6/Open-ILS/src/perlmods/OpenILS/Application/Circ/Circulate.pm
===================================================================
--- branches/rel_1_6/Open-ILS/src/perlmods/OpenILS/Application/Circ/Circulate.pm	2010-01-13 16:30:42 UTC (rev 15311)
+++ branches/rel_1_6/Open-ILS/src/perlmods/OpenILS/Application/Circ/Circulate.pm	2010-01-13 16:35:03 UTC (rev 15312)
@@ -2,10 +2,12 @@
 use strict; use warnings;
 use base 'OpenILS::Application';
 use OpenSRF::EX qw(:try);
+use OpenSRF::AppSession;
 use OpenSRF::Utils::SettingsClient;
 use OpenSRF::Utils::Logger qw(:logger);
 use OpenILS::Const qw/:const/;
 use OpenILS::Application::AppUtils;
+use DateTime;
 my $U = "OpenILS::Application::AppUtils";
 
 my %scripts;
@@ -148,6 +150,13 @@
 
 __PACKAGE__->register_method(
     method  => "run_method",
+    api_name    => "open-ils.circ.reservation.pickup");
+__PACKAGE__->register_method(
+    method  => "run_method",
+    api_name    => "open-ils.circ.reservation.return");
+
+__PACKAGE__->register_method(
+    method  => "run_method",
     api_name    => "open-ils.circ.checkout.inspect",
     desc => q/
         Returns the circ matrix test result and, on success, the rule set and matrix test object
@@ -167,9 +176,98 @@
     return circ_events($circulator) if $circulator->bail_out;
 
     # --------------------------------------------------------------------------
+    # First, check for a booking transit, as the barcode may not be a copy
+    # barcode, but a resource barcode, and nothing else in here will work
+    # --------------------------------------------------------------------------
+
+    if ((my $bc = $circulator->copy_barcode) && $api !~ /checkout|inspect/) { # do we have a barcode?
+        my $resources = $circulator->editor->search_booking_resource( { barcode => $bc } ); # any resources by this barcode?
+        if (@$resources) { # yes!
+
+            my $res_id_list = [ map { $_->id } @$resources ];
+            my $transit = $circulator->editor->search_action_reservation_transit_copy(
+                [
+                    { target_copy => $res_id_list, dest => $circulator->circ_lib },
+                    { order_by => { artc => 'source_send_time' }, limit => 1 }
+                ]
+            )->[0]; # Any transit for this barcode?
+
+            if ($transit) { # yes! unwrap it.
+
+                my $reservation = $circulator->editor->retrieve_booking_reservation( $transit->reservation );
+                my $res_type = $circulator->editor->retrieve_booking_resource_type( $reservation->target_resource_type );
+
+                if ($U->is_true($res_type->catalog_item)) { # is there a copy to be had here?
+                    if (my $copy = $circulator->editor->search_asset_copy({ barcode => $bc, deleted => 'f' })->[0]) { # got a copy
+                        $copy->status( $transit->copy_status );
+                        $copy->editor($circulator->editor->requestor->id);
+                        $copy->edit_date('now');
+                        $circulator->editor->update_asset_copy( $copy );
+                    }
+                }
+
+                $transit->dest_recv_time('now');
+                $circulator->editor->update_action_reservation_transit_copy( $transit );
+
+                $circulator->editor->commit;
+
+                #XXX need to return here, with info about the resource/copy and the "put it on the booking shelf" message
+
+            } else { # no transit, look for an upcoming reservation to capture for
+
+                my $reservation = $circulator->editor->search_booking_reservation(
+                    [
+                        { current_resource => $res_id_list,
+                          pickup_lib => $circulator->circ_lib,
+                          cancel_time => undef,
+                          capture_time => undef
+                        },
+                        { order_by => { bresv => 'start_time' }, limit => 1 }
+                    ]
+                )->[0];
+
+                if ($reservation) { # we have a reservation for which we could capture this resource.  wheee!
+                    my $res_type = $circulator->editor->retrieve_booking_resource_type( $reservation->target_resource_type );
+                    my $elbow_room = $res_type->elbow_room ||
+                        $U->ou_ancestor_setting_value( $circulator->circ_lib, 'circ.booking_reservation.default_elbow_room', $circulator->editor );
+                
+                    if ($elbow_room) {
+                        $reservation = $circulator->editor->search_booking_reservation(
+                            [
+                                { id => $reservation->id, start_time => { '<=' => DateTime->now->add( seconds => interval_to_seconds($elbow_room) )->strftime('%FT%T%z') } },
+                                { order_by => { bresv => 'start_time' }, limit => 1 }
+                            ]
+                        )->[0];
+                    }
+
+                    if ($reservation) { # no elbow room specified, or we still have a reservation within the elbow_room time
+                        my $b_ses = OpenSRF::AppSession->create('open-ils.booking');
+                        my $result = $b_ses->request(
+                            'open-ils.booking.reservations.capture',
+                            $auth => $reservation->id
+                        )->gather(1);
+
+                        if (ref($result) && $result->{ilsevent} == 0) { # captured!
+                            #XXX what to return here???
+                            return $result; # the booking capture success
+                        } else {
+                            #XXX how to fail???  Probably, just move on.
+                        }
+                    }
+                }
+            }
+        }
+    }
+            
+    
+
+    # --------------------------------------------------------------------------
     # Go ahead and load the script runner to make sure we have all 
     # of the objects we need
     # --------------------------------------------------------------------------
+    $circulator->is_res_checkin($circulator->is_checkin(1)) if $api =~ /reservation.return/;
+    $circulator->is_res_checkout(1) if $api =~ /reservation.pickup/;
+
     $circulator->is_renewal(1) if $api =~ /renew/;
     $circulator->is_checkin(1) if $api =~ /checkin/;
 
@@ -180,7 +278,7 @@
         $circulator->circ_permit_copy($scripts{circ_permit_copy});      
         $circulator->circ_duration($scripts{circ_duration});             
         $circulator->circ_permit_renew($scripts{circ_permit_renew});
-    } else {
+    } elsif (not $circulator->is_res_checkin) { # mk_env cannot work w/ reservation.return
         $circulator->mk_env();
     }
     return circ_events($circulator) if $circulator->bail_out;
@@ -203,6 +301,9 @@
             $circulator->do_checkout();
         }
 
+    } elsif( $circulator->is_res_checkout ) {
+        $circulator->do_reservation_pickup();
+
     } elsif( $api =~ /inspect/ ) {
         my $data = $circulator->do_inspect();
         $circulator->editor->rollback;
@@ -212,6 +313,9 @@
         $circulator->is_checkout(1);
         $circulator->do_checkout();
 
+    } elsif( $circulator->is_res_checkin ) {
+        $circulator->do_reservation_return();
+        $circulator->do_checkin() if ($circulator->copy());
     } elsif( $api =~ /checkin/ ) {
         $circulator->do_checkin();
 
@@ -335,6 +439,7 @@
     notify_hold
     remote_hold
     backdate
+    reservation
     copy
     copy_id
     copy_barcode
@@ -346,10 +451,12 @@
     title
     is_renewal
     is_checkout
+    is_res_checkout
     is_noncat
     is_precat
     request_precat
     is_checkin
+    is_res_checkin
     noncat_type
     editor
     events
@@ -1163,9 +1270,12 @@
     $self->build_checkout_circ_object();
     return if $self->bail_out;
 
-    $self->apply_modified_due_date();
+    my $modify_to_start = $self->booking_adjusted_due_date();
     return if $self->bail_out;
 
+    $self->apply_modified_due_date($modify_to_start);
+    return if $self->bail_out;
+
     return $self->bail_on_events($self->editor->event)
         unless $self->editor->create_action_circulation($self->circ);
 
@@ -1258,7 +1368,29 @@
     $copy->circ_lib($circ_lib) if $circ_lib;
 }
 
+sub update_reservation {
+    my $self = shift;
+    my $reservation = $self->reservation;
 
+    my $usr = $reservation->usr;
+    my $target_rt = $reservation->target_resource_type;
+    my $target_r = $reservation->target_resource;
+    my $current_r = $reservation->current_resource;
+
+    $reservation->usr($usr->id) if ref $usr;
+    $reservation->target_resource_type($target_rt->id) if ref $target_rt;
+    $reservation->target_resource($target_r->id) if ref $target_r;
+    $reservation->current_resource($current_r->id) if ref $current_r;
+
+    return $self->bail_on_events($self->editor->event)
+        unless $self->editor->update_booking_reservation($self->reservation);
+
+    my $evt;
+    ($reservation, $evt) = $U->fetch_booking_reservation($reservation->id);
+    $self->reservation($reservation);
+}
+
+
 sub bail_on_events {
     my( $self, @evts ) = @_;
     $self->push_events(@evts);
@@ -1340,6 +1472,7 @@
 
 sub run_checkout_scripts {
     my $self = shift;
+    my $nobail = shift;
 
     my $evt;
     my $runner = $self->script_runner;
@@ -1374,13 +1507,13 @@
 
         unless($duration) {
             ($duration, $evt) = $U->fetch_circ_duration_by_name($duration_name);
-            return $self->bail_on_events($evt) if $evt;
+            return $self->bail_on_events($evt) if ($evt && !$nobail);
         
             ($recurring, $evt) = $U->fetch_recurring_fine_by_name($recurring_name);
-            return $self->bail_on_events($evt) if $evt;
+            return $self->bail_on_events($evt) if ($evt && !$nobail);
         
             ($max_fine, $evt) = $U->fetch_max_fine_by_name($max_fine_name);
-            return $self->bail_on_events($evt) if $evt;
+            return $self->bail_on_events($evt) if ($evt && !$nobail);
         }
 
     } else {
@@ -1462,9 +1595,151 @@
     $self->circ($circ);
 }
 
+sub do_reservation_pickup {
+    my $self = shift;
 
+    $self->log_me("do_reservation_pickup()");
+
+    $self->reservation->pickup_time('now');
+
+    if (
+        $self->reservation->current_resource &&
+        $self->reservation->current_resource->catalog_item
+    ) {
+        $self->copy( $self->reservation->current_resource->catalog_item );
+        $self->patron( $self->reservation->usr );
+        $self->run_checkout_scripts(1);
+
+        my $duration   = $self->duration_rule;
+        my $max        = $self->max_fine_rule;
+        my $recurring  = $self->recurring_fines_rule;
+
+        if ($duration && $max && $recurring) {
+            my $policy = $self->get_circ_policy($duration, $recurring, $max);
+
+            my $dname = $duration->name;
+            my $mname = $max->name;
+            my $rname = $recurring->name;
+
+            $logger->debug("circulator: building reservation ".
+                "with duration=$dname, maxfine=$mname, recurring=$rname");
+
+            $self->reservation->fine_amount($policy->{recurring_fine});
+            $self->reservation->max_fine($policy->{max_fine});
+            $self->reservation->fine_interval($recurring->recurrence_interval);
+        }
+
+        $self->copy->status(OILS_COPY_STATUS_CHECKED_OUT);
+        $self->update_copy();
+
+    } else {
+        $self->reservation->fine_amount($self->reservation->fine_amount);
+        $self->reservation->max_fine($self->reservation->max_fine);
+        $self->reservation->fine_interval($self->reservation->fine_interval);
+    }
+
+    $self->update_reservation();
+}
+
+sub do_reservation_return {
+    my $self = shift;
+    my $request = shift;
+
+    $self->log_me("do_reservation_return()");
+
+    my ($reservation, $evt) = $U->fetch_booking_reservation($self->reservation);
+    return $self->bail_on_events($evt) if $evt;
+
+    $self->reservation( $reservation );
+    $self->generate_fines(1);
+    $self->reservation->return_time('now');
+    $self->update_reservation();
+
+    if ( $self->reservation->current_resource && $self->reservation->current_resource->catalog_item ) {
+        $self->copy( $self->reservation->current_resource->catalog_item );
+    }
+}
+
+sub booking_adjusted_due_date {
+    my $self = shift;
+    my $circ = $self->circ;
+    my $copy = $self->copy;
+
+
+    my $changed;
+
+    if( $self->due_date ) {
+
+        return $self->bail_on_events($self->editor->event)
+            unless $self->editor->allowed('CIRC_OVERRIDE_DUE_DATE', $self->circ_lib);
+
+       $circ->due_date(clense_ISO8601($self->due_date));
+
+    } else {
+
+        return unless $copy and $circ->due_date;
+    }
+
+    my $booking_items = $self->editor->search_booking_resource( { barcode => $copy->barcode } );
+    if (@$booking_items) {
+        my $booking_item = $booking_items->[0];
+        my $resource_type = $self->editor->retrieve_booking_resource_type( $booking_item->type );
+
+        my $stop_circ_setting = $U->ou_ancestor_setting_value( $self->circ_lib, 'circ.booking_reservation.stop_circ', $self->editor );
+        my $shorten_circ_setting = $resource_type->elbow_room ||
+            $U->ou_ancestor_setting_value( $self->circ_lib, 'circ.booking_reservation.default_elbow_room', $self->editor ) ||
+            '0 seconds';
+
+        my $booking_ses = OpenSRF::AppSession->create( 'open-ils.booking' );
+        my $bookings = $booking_ses->request(
+            'open-ils.booking.reservations.filtered_id_list', $self->editor->authtoken,
+            { resource => $booking_item->id, search_start => 'now', search_end => $circ->due_date }
+        )->gather(1);
+        $booking_ses->disconnect;
+        
+        my $dt_parser = DateTime::Format::ISO8601->new;
+        my $due_date = $dt_parser->parse_datetime( clense_ISO8601($circ->due_date) );
+
+        for my $bid (@$bookings) {
+
+            my $booking = $self->editor->retrieve_booking_reservation( $bid );
+
+            my $booking_start = $dt_parser->parse_datetime( clense_ISO8601($booking->start_time) );
+            my $booking_end = $dt_parser->parse_datetime( clense_ISO8601($booking->end_time) );
+
+            return $self->bail_on_events( OpenILS::Event->new('COPY_RESERVED') )
+                if ($booking_start < DateTime->now);
+
+
+            if ($U->is_true($stop_circ_setting)) {
+                $self->bail_on_events( OpenILS::Event->new('COPY_RESERVED') ); 
+            } else {
+                $due_date = $booking_start->subtract( seconds => interval_to_seconds($shorten_circ_setting) );
+                $self->bail_on_events( OpenILS::Event->new('COPY_RESERVED') ) if ($due_date < DateTime->now); 
+            }
+            
+            # We set the circ duration here only to affect the logic that will
+            # later (in a DB trigger) mangle the time part of the due date to
+            # 11:59pm. Having any circ duration that is not a whole number of
+            # days is enough to prevent the "correction."
+            my $new_circ_duration = $due_date->epoch - time;
+            $new_circ_duration++ if $new_circ_duration % 86400 == 0;
+            $circ->duration("$new_circ_duration seconds");
+
+            $circ->due_date(clense_ISO8601($due_date->strftime('%FT%T%z')));
+            $changed = 1;
+        }
+
+        return $self->bail_on_events($self->editor->event)
+            unless $self->editor->allowed('CIRC_OVERRIDE_DUE_DATE', $self->circ_lib);
+    }
+
+    return $changed;
+}
+
 sub apply_modified_due_date {
     my $self = shift;
+    my $shift_earlier = shift;
     my $circ = $self->circ;
     my $copy = $self->copy;
 
@@ -1499,7 +1774,11 @@
 
             # XXX make the behavior more dynamic
             # for now, we just push the due date to after the close date
-            $circ->due_date($dateinfo->{end});
+            if ($shift_earlier) {
+                $circ->due_date($dateinfo->{start});
+            } else {
+                $circ->due_date($dateinfo->{end});
+            }
       }
    }
 }
@@ -1721,7 +2000,7 @@
    # this copy can fulfill a hold or needs to be routed to a different location
    # ------------------------------------------------------------------------------
 
-    unless($self->noop) { # no-op checkins to not capture holds or put items into transit
+    if(!$self->noop) { # /not/ a no-op checkin, capture for hold or put item into transit
 
         my $needed_for_hold = (!$self->remote_hold and $self->attempt_checkin_hold_capture());
         return if $self->bail_out;
@@ -2110,6 +2389,53 @@
 }
 
 
+# ------------------------------------------------------------------
+# Sets the shelf_time and shelf_expire_time for a newly shelved hold
+# ------------------------------------------------------------------
+sub put_hold_on_shelf {
+    my($self, $hold) = @_;
+
+    $hold->shelf_time('now');
+
+    my $shelf_expire = $U->ou_ancestor_setting_value(
+        $self->circ_lib, 'circ.holds.default_shelf_expire_interval', $self->editor);
+
+    if($shelf_expire) {
+        my $seconds = OpenSRF::Utils->interval_to_seconds($shelf_expire);
+        my $expire_time = DateTime->now->add(seconds => $seconds);
+        $hold->shelf_expire_time($expire_time->strftime('%FT%T%z'));
+    }
+
+    return undef;
+}
+
+
+
+sub generate_fines {
+   my $self = shift;
+   my $reservation = shift;
+   my $evt;
+   my $obt;
+
+   my $id = $reservation ? $self->reservation->id : $self->circ->id;
+
+   my $st = OpenSRF::AppSession->connect('open-ils.storage');
+
+   $st->request(
+      'open-ils.storage.action.circulation.overdue.generate_fines',
+      undef,
+      $id
+   )->wait_complete;
+
+   $st->disconnect;
+
+   # refresh the circ in case the fine generator set the stop_fines field
+   $self->reservation($self->editor->retrieve_booking_reservation($id)) if $reservation;
+   $self->circ($self->editor->retrieve_action_circulation($id)) if !$reservation;
+
+   return undef;
+}
+
 sub checkin_handle_circ {
    my $self = shift;
    my $circ = $self->circ;
@@ -2292,6 +2618,7 @@
             $status == OILS_COPY_STATUS_ON_HOLDS_SHELF  ||
             $status == OILS_COPY_STATUS_IN_TRANSIT  ||
             $status == OILS_COPY_STATUS_CATALOGING  ||
+            $status == OILS_COPY_STATUS_ON_RESV_SHELF  ||
             $status == OILS_COPY_STATUS_RESHELVING );
 
    return OpenILS::Event->new('COPY_STATUS_LOST', payload => $copy )

Modified: branches/rel_1_6/Open-ILS/src/perlmods/OpenILS/Application/Circ/Money.pm
===================================================================
--- branches/rel_1_6/Open-ILS/src/perlmods/OpenILS/Application/Circ/Money.pm	2010-01-13 16:30:42 UTC (rev 15311)
+++ branches/rel_1_6/Open-ILS/src/perlmods/OpenILS/Application/Circ/Money.pm	2010-01-13 16:35:03 UTC (rev 15312)
@@ -270,10 +270,24 @@
 
 
 __PACKAGE__->register_method(
-	method => 'fetch_grocery',
-	api_name => 'open-ils.circ.money.grocery.retrieve'
+    method => 'fetch_reservation',
+    api_name => 'open-ils.circ.booking.reservation.retrieve'
 );
+sub fetch_reservation {
+    my( $self, $conn, $auth, $id ) = @_;
+    my $e = new_editor(authtoken=>$auth);
+    return $e->event unless $e->checkauth;
+    return $e->event unless $e->allowed('VIEW_TRANSACTION'); # eh.. basically the same permission
+    my $g = $e->retrieve_booking_reservation($id)
+        or return $e->event;
+    return $g;
+}
 
+__PACKAGE__->register_method(
+    method => 'fetch_grocery',
+    api_name => 'open-ils.circ.money.grocery.retrieve'
+);
+
 sub fetch_grocery {
 	my( $self, $conn, $auth, $id ) = @_;
 	my $e = new_editor(authtoken=>$auth);

Modified: branches/rel_1_6/Open-ILS/src/perlmods/OpenILS/Application/Circ.pm
===================================================================
--- branches/rel_1_6/Open-ILS/src/perlmods/OpenILS/Application/Circ.pm	2010-01-13 16:30:42 UTC (rev 15311)
+++ branches/rel_1_6/Open-ILS/src/perlmods/OpenILS/Application/Circ.pm	2010-01-13 16:35:03 UTC (rev 15312)
@@ -1105,8 +1105,146 @@
 }
 
 
+# XXX
+# XXX !!! HERE through line 1245 !!!
+# XXX Backported from trunk aprox 15310, may not be supported or required by 1.6
+# XXX
 
+__PACKAGE__->register_method(
+	method	=> "user_payments_list",
+	api_name	=> "open-ils.circ.user_payments.filtered.batch",
+    stream => 1,
+	signature => {
+        desc => q/Returns a fleshed, date-limited set of all payments a user
+                has made.  By default, ordered by payment date.  Optionally
+                ordered by other columns in the top-level "mp" object/,
+        params => [
+            {desc => 'Authentication token', type => 'string'},
+            {desc => 'User ID', type => 'number'},
+            {desc => 'Order by column(s), optional.  Array of "mp" class columns', type => 'array'}
+        ],
+        return => {desc => q/List of "mp" objects, fleshed with the billable transaction 
+            and the related fully-realized payment object (e.g money.cash_payment)/}
+    }
+);
 
+sub user_payments_list {
+    my($self, $conn, $auth, $user_id, $start_date, $end_date, $order_by) = @_;
+
+    my $e = new_editor(authtoken => $auth);
+    return $e->event unless $e->checkauth;
+
+    my $user = $e->retrieve_actor_user($user_id) or return $e->event;
+    return $e->event unless $e->allowed('VIEW_CIRCULATIONS', $user->home_ou);
+
+    $order_by ||= ['payment_ts'];
+
+    # all payments by user, between start_date and end_date
+    my $payments = $e->json_query({
+        select => {mp => ['id']}, 
+        from => {
+            mp => {
+                mbt => {
+                    fkey => 'xact', field => 'id'}
+            }
+        }, 
+        where => {
+            '+mbt' => {usr => $user_id}, 
+            '+mp' => {payment_ts => {between => [$start_date, $end_date]}}
+        },
+        order_by => {mp => $order_by}
+    });
+
+    for my $payment_id (@$payments) {
+        my $payment = $e->retrieve_money_payment([
+            $payment_id->{id}, 
+            {   
+                flesh => 2,
+                flesh_fields => {
+                    mp => [
+                        'xact',
+                        'cash_payment',
+                        'credit_card_payment',
+                        'credit_payment',
+                        'check_payment',
+                        'work_payment',
+                        'forgive_payment',
+                        'goods_payment'
+                    ],
+                    mbt => [
+                        'circulation', 
+                        'grocery',
+                        'reservation'
+                    ]
+                }
+            }
+        ]);
+        $conn->respond($payment);
+    }
+
+    return undef;
+}
+
+
+__PACKAGE__->register_method(
+	method	=> "retrieve_circ_chain",
+	api_name	=> "open-ils.circ.renewal_chain.retrieve_by_circ",
+    stream => 1,
+	signature => {
+        desc => q/Given a circulation, this returns all circulation objects
+                that are part of the same chain of renewals./,
+        params => [
+            {desc => 'Authentication token', type => 'string'},
+            {desc => 'Circ ID', type => 'number'},
+        ],
+        return => {desc => q/List of circ objects, orderd by oldest circ first/}
+    }
+);
+
+__PACKAGE__->register_method(
+	method	=> "retrieve_circ_chain",
+	api_name	=> "open-ils.circ.renewal_chain.retrieve_by_circ.summary",
+	signature => {
+        desc => q/Given a circulation, this returns all circulation objects
+                that are part of the same chain of renewals./,
+        params => [
+            {desc => 'Authentication token', type => 'string'},
+            {desc => 'Circ ID', type => 'number'},
+        ],
+        return => {desc => q/List of circ objects, orderd by oldest circ first/}
+    }
+);
+
+sub retrieve_circ_chain {
+    my($self, $conn, $auth, $circ_id) = @_;
+
+    my $e = new_editor(authtoken => $auth);
+    return $e->event unless $e->checkauth;
+	return $e->event unless $e->allowed('VIEW_CIRCULATIONS');
+
+    if($self->api_name =~ /summary/) {
+        my $sum = $e->json_query({from => ['action.summarize_circ_chain', $circ_id]})->[0];
+        return undef unless $sum;
+        my $obj = Fieldmapper::action::circ_chain_summary->new;
+        $obj->$_($sum->{$_}) for keys %$sum;
+        return $obj;
+
+    } else {
+
+        my $chain = $e->json_query({from => ['action.circ_chain', $circ_id]});
+
+        for my $circ_info (@$chain) {
+            my $circ = Fieldmapper::action::circulation->new;
+            $circ->$_($circ_info->{$_}) for keys %$circ_info;
+            $conn->respond($circ);
+        }
+    }
+
+    return undef;
+}
+
+
+
 # {"select":{"acp":["id"],"circ":[{"aggregate":true,"transform":"count","alias":"count","column":"id"}]},"from":{"acp":{"circ":{"field":"target_copy","fkey":"id","type":"left"},"acn"{"field":"id","fkey":"call_number"}}},"where":{"+acn":{"record":200057}}
 
 

Modified: branches/rel_1_6/Open-ILS/src/perlmods/OpenILS/Application/Collections.pm
===================================================================
--- branches/rel_1_6/Open-ILS/src/perlmods/OpenILS/Application/Collections.pm	2010-01-13 16:30:42 UTC (rev 15311)
+++ branches/rel_1_6/Open-ILS/src/perlmods/OpenILS/Application/Collections.pm	2010-01-13 16:35:03 UTC (rev 15312)
@@ -621,7 +621,9 @@
 			circulations	=> 
 				fetch_circ_xacts($e, $uid, $org, $start_date, $end_date),
 			grocery			=> 
-				fetch_grocery_xacts($e, $uid, $org, $start_date, $end_date)
+				fetch_grocery_xacts($e, $uid, $org, $start_date, $end_date),
+			reservations	=> 
+				fetch_reservation_xacts($e, $uid, $org, $start_date, $end_date)
 		};
 
 		# for each transaction, flesh the workstatoin on any attached payment
@@ -629,6 +631,7 @@
 		# not just a generic payment object
 		for my $xact ( 
 			@{$blob->{transactions}->{circulations}}, 
+			@{$blob->{transactions}->{reservations}}, 
 			@{$blob->{transactions}->{grocery}} ) {
 
 			my $ps;
@@ -778,8 +781,55 @@
 	return \@data;
 }
 
+sub fetch_reservation_xacts {
+	my $e				= shift;
+	my $uid			= shift;
+	my $org			= shift;
+	my $start_date = shift;
+	my $end_date	= shift;
 
+	my @xacts;
+	$U->walk_org_tree( $org, 
+		sub {
+			my $n = shift;
+			$logger->debug("collect: searching for open grocery xacts at " . $n->shortname);
+			push( @xacts, 
+				@{
+					$e->search_booking_reservation(
+						{
+							usr					=> $uid, 
+							pickup_lib      	=> $n->id,
+						}, 
+						{idlist => 1}
+					)
+				}
+			);
+		}
+	);
 
+	my @data;
+	my $active_ids = fetch_active($e, \@xacts, $start_date, $end_date);
+
+	for my $id (@$active_ids) {
+		push( @data, 
+			$e->retrieve_booking_reservation(
+				[
+					$id,
+					{
+						flesh => 1,
+						flesh_fields => { 
+							bresv => [ "billings", "payments", "pickup_lib" ] }
+					}
+				]
+			)
+		);
+	}
+
+	return \@data;
+}
+
+
+
 # --------------------------------------------------------------
 # Given a list of xact id's, this returns a list of id's that
 # had any activity within the given time span

Modified: branches/rel_1_6/Open-ILS/src/perlmods/OpenILS/Application/CreditCard.pm
===================================================================
--- branches/rel_1_6/Open-ILS/src/perlmods/OpenILS/Application/CreditCard.pm	2010-01-13 16:30:42 UTC (rev 15311)
+++ branches/rel_1_6/Open-ILS/src/perlmods/OpenILS/Application/CreditCard.pm	2010-01-13 16:35:03 UTC (rev 15312)
@@ -291,9 +291,12 @@
             my $circ = $e->retrieve_action_circulation($xact->id) or return $e->event;
             next unless grep { $_ == $circ->circ_lib } @credit_orgs;
 
-        } else {
+        } elsif ($xact->xact_type eq 'grocery') {
             my $bill = $e->retrieve_money_grocery($xact->id) or return $e->event;
             next unless grep { $_ == $bill->billing_location } @credit_orgs;
+        } elsif ($xact->xact_type eq 'reservation') {
+            my $bill = $e->retrieve_booking_reservation($xact->id) or return $e->event;
+            next unless grep { $_ == $bill->pickup_lib } @credit_orgs;
         }
         $sum += $xact->balance_owed();
     }

Modified: branches/rel_1_6/Open-ILS/src/perlmods/OpenILS/Application/Storage/CDBI/action.pm
===================================================================
--- branches/rel_1_6/Open-ILS/src/perlmods/OpenILS/Application/Storage/CDBI/action.pm	2010-01-13 16:30:42 UTC (rev 15311)
+++ branches/rel_1_6/Open-ILS/src/perlmods/OpenILS/Application/Storage/CDBI/action.pm	2010-01-13 16:35:03 UTC (rev 15312)
@@ -127,6 +127,16 @@
 
 #-------------------------------------------------------------------------------
 
+package action::reservation_transit_copy;
+use base qw/action/;
+__PACKAGE__->table('action_reservation_transit_copy');
+__PACKAGE__->columns(Primary => 'id');
+__PACKAGE__->columns(Essential => qw/source dest persistant_transfer target_copy
+				     source_send_time dest_recv_time prev_hop prev_dest
+				     copy_status reservation/);
+
+#-------------------------------------------------------------------------------
+
 package action::transit_copy;
 use base qw/action/;
 __PACKAGE__->table('action_transit_copy');

Copied: branches/rel_1_6/Open-ILS/src/perlmods/OpenILS/Application/Storage/CDBI/booking.pm (from rev 15071, trunk/Open-ILS/src/perlmods/OpenILS/Application/Storage/CDBI/booking.pm)
===================================================================
--- branches/rel_1_6/Open-ILS/src/perlmods/OpenILS/Application/Storage/CDBI/booking.pm	                        (rev 0)
+++ branches/rel_1_6/Open-ILS/src/perlmods/OpenILS/Application/Storage/CDBI/booking.pm	2010-01-13 16:35:03 UTC (rev 15312)
@@ -0,0 +1,57 @@
+package OpenILS::Application::Storage::CDBI::booking;
+our $VERSION = 1;
+
+#-------------------------------------------------------------------------------
+package booking;
+use base qw/OpenILS::Application::Storage::CDBI/;
+#-------------------------------------------------------------------------------
+
+package booking::resource_type;
+use base qw/booking/;
+__PACKAGE__->table('booking_resource_type');
+__PACKAGE__->columns(Primary => 'id');
+__PACKAGE__->columns(Essential => qw/name fine_interval fine_amount
+                     max_fine owner catalog_item record transferable elbow_room/);
+
+#-------------------------------------------------------------------------------
+
+package booking::resource;
+use base qw/booking/;
+__PACKAGE__->table('booking_resource');
+__PACKAGE__->columns(Primary => 'id');
+__PACKAGE__->columns(Essential => qw/owner type overbook barcode deposit
+                     deposit_amount user_fee/);
+
+#-------------------------------------------------------------------------------
+
+package booking::reservation;
+use base qw/booking/;
+__PACKAGE__->table('booking_reservation');
+__PACKAGE__->columns(Primary => 'id');
+__PACKAGE__->columns(Essential => qw/xact_start usr current_resource
+				     fine_amount max_fine fine_interval xact_finish 
+				     capture_staff pickup_lib request_time start_time end_time
+                     capture_time cancel_time pickup_time return_time
+                     booking_interval target_resource_type target_resource
+                     current_resource request_lib/);
+
+#-------------------------------------------------------------------------------
+
+package booking::resource_attr_map;
+use base qw/booking/;
+__PACKAGE__->table('booking_resource_attr_map');
+__PACKAGE__->columns(Primary => 'id');
+__PACKAGE__->columns(Essential => qw/resource resource_attr value/);
+
+#-------------------------------------------------------------------------------
+
+package booking::reservation_attr_value_map;
+use base qw/booking/;
+__PACKAGE__->table('booking_reservation_attr_value_map');
+__PACKAGE__->columns(Primary => 'id');
+__PACKAGE__->columns(Essential => qw/reservation attr_value/);
+
+#-------------------------------------------------------------------------------
+
+1;
+

Modified: branches/rel_1_6/Open-ILS/src/perlmods/OpenILS/Application/Storage/CDBI.pm
===================================================================
--- branches/rel_1_6/Open-ILS/src/perlmods/OpenILS/Application/Storage/CDBI.pm	2010-01-13 16:30:42 UTC (rev 15311)
+++ branches/rel_1_6/Open-ILS/src/perlmods/OpenILS/Application/Storage/CDBI.pm	2010-01-13 16:35:03 UTC (rev 15312)
@@ -5,6 +5,7 @@
 
 use OpenILS::Application::Storage::CDBI::actor;
 use OpenILS::Application::Storage::CDBI::action;
+use OpenILS::Application::Storage::CDBI::booking;
 use OpenILS::Application::Storage::CDBI::asset;
 use OpenILS::Application::Storage::CDBI::authority;
 use OpenILS::Application::Storage::CDBI::biblio;
@@ -555,6 +556,15 @@
 
 	action::circulation->has_a( usr => 'actor::user' );
 	actor::user->has_many( circulations => 'action::circulation' => 'usr' );
+
+	booking::resource_attr_map->has_a( resource => 'booking::resource' );
+
+	booking::resource->has_a( owner => 'actor::org_unit' );
+	booking::resource->has_a( type => 'booking::resource_type' );
+	booking::resource_type->has_a( owner => 'actor::org_unit' );
+
+	booking::reservation->has_a( usr => 'actor::user' );
+	actor::user->has_many( reservations => 'booking::reservation' => 'usr' );
 	
 	action::circulation->has_a( circ_staff => 'actor::user' );
 	actor::user->has_many( performed_circulations => 'action::circulation' => 'circ_staff' );
@@ -565,6 +575,8 @@
 	action::circulation->has_a( target_copy => 'asset::copy' );
 	asset::copy->has_many( circulations => 'action::circulation' => 'target_copy' );
 
+	booking::reservation->has_a( pickup_lib => 'actor::org_unit' );
+
 	action::circulation->has_a( circ_lib => 'actor::org_unit' );
 	actor::org_unit->has_many( circulations => 'action::circulation' => 'circ_lib' );
 	
@@ -621,9 +633,11 @@
 	action::circulation->has_many( billings => 'money::billing' => 'xact' );
 	action::circulation->has_many( payments => 'money::payment' => 'xact' );
 	#action::circulation->might_have( billable_transaction => 'money::billable_transaction' );
-
 	#action::open_circulation->might_have( circulation => 'action::circulation' );
 
+	booking::reservation->has_many( billings => 'money::billing' => 'xact' );
+	booking::reservation->has_many( payments => 'money::payment' => 'xact' );
+
 	action::in_house_use->has_a( org_unit => 'actor::org_unit' );
 	action::in_house_use->has_a( staff => 'actor::user' );
 	action::in_house_use->has_a( item => 'asset::copy' );

Modified: branches/rel_1_6/Open-ILS/src/perlmods/OpenILS/Application/Storage/Driver/Pg/dbi.pm
===================================================================
--- branches/rel_1_6/Open-ILS/src/perlmods/OpenILS/Application/Storage/Driver/Pg/dbi.pm	2010-01-13 16:30:42 UTC (rev 15311)
+++ branches/rel_1_6/Open-ILS/src/perlmods/OpenILS/Application/Storage/Driver/Pg/dbi.pm	2010-01-13 16:35:03 UTC (rev 15312)
@@ -161,6 +161,36 @@
 	action::circulation->sequence( 'money.billable_xact_id_seq' );
 
 	#---------------------------------------------------------------------
+	package booking::resource_type;
+	
+	booking::resource_type->table( 'booking.resource_type' );
+	booking::resource_type->sequence( 'booking.resource_type_id_seq' );
+
+	#---------------------------------------------------------------------
+	package booking::resource;
+	
+	booking::resource->table( 'booking.resource' );
+	booking::resource->sequence( 'booking.resource_id_seq' );
+
+	#---------------------------------------------------------------------
+	package booking::reservation;
+	
+	booking::reservation->table( 'booking.reservation' );
+	booking::reservation->sequence( 'money.billable_xact_id_seq' );
+
+	#---------------------------------------------------------------------
+	package booking::reservation_attr_value_map;
+	
+	booking::reservation_attr_value_map->table( 'booking.reservation_attr_value_map' );
+	booking::reservation_attr_value_map->sequence( 'booking.reservation_attr_value_map_id_seq' );
+
+	#---------------------------------------------------------------------
+	package booking::resource_attr_map;
+	
+	booking::resource_attr_map->table( 'booking.resource_attr_map' );
+	booking::resource_attr_map->sequence( 'booking.resource_attr_map_id_seq' );
+
+	#---------------------------------------------------------------------
 	package action::non_cat_in_house_use;
 	
 	action::non_cat_in_house_use->table( 'action.non_cat_in_house_use' );
@@ -653,6 +683,13 @@
 
 	#-------------------------------------------------------------------------------
 
+	package action::reservation_transit_copy;
+
+	action::reservation_transit_copy->sequence( 'action.transit_copy_id_seq' );
+	action::reservation_transit_copy->table('action.reservation_transit_copy');
+
+	#-------------------------------------------------------------------------------
+
 	package action::transit_copy;
 
 	action::transit_copy->sequence( 'action.transit_copy_id_seq' );

Modified: branches/rel_1_6/Open-ILS/src/perlmods/OpenILS/Application/Storage/Publisher/action.pm
===================================================================
--- branches/rel_1_6/Open-ILS/src/perlmods/OpenILS/Application/Storage/Publisher/action.pm	2010-01-13 16:30:42 UTC (rev 15311)
+++ branches/rel_1_6/Open-ILS/src/perlmods/OpenILS/Application/Storage/Publisher/action.pm	2010-01-13 16:35:03 UTC (rev 15312)
@@ -114,8 +114,24 @@
 	my $sth = action::circulation->db_Main->prepare_cached($sql);
 	$sth->execute($upper_interval);
 
-	return ( map { action::circulation->construct($_) } $sth->fetchall_hash );
+	my @circs = map { action::circulation->construct($_) } $sth->fetchall_hash;
 
+	$c_t = booking::reservation->table;
+	$sql = <<"	SQL";
+		SELECT	*
+		  FROM	$c_t
+		  WHERE	return_time IS NULL
+		  	AND end_time < ( CURRENT_TIMESTAMP $grace)
+            AND fine_interval IS NOT NULL
+            AND cancel_time IS NULL
+	SQL
+
+	$sth = action::circulation->db_Main->prepare_cached($sql);
+	$sth->execute();
+
+    push @circs, map { booking::reservation->construct($_) } $sth->fetchall_hash;
+
+    return @circs;
 }
 
 sub complete_reshelving {
@@ -597,7 +613,9 @@
 
 	my @circs;
 	if ($circ) {
-		push @circs, action::circulation->search_where( { id => $circ, stop_fines => undef } );
+		push @circs,
+            action::circulation->search_where( { id => $circ, stop_fines => undef } ),
+            booking::reservation->search_where( { id => $circ, return_time => undef, cancel_time => undef } );
 	} else {
 		push @circs, overdue_circs($grace);
 	}
@@ -606,7 +624,22 @@
 
 	my $penalty = OpenSRF::AppSession->create('open-ils.penalty');
 	for my $c (@circs) {
+
+        my $ctype = ref($c);
+        $ctype =~ s/^.*([^:]+)$/$1/o;
 	
+        my $due_date_method = 'due_date';
+        my $target_copy_method = 'target_copy';
+        my $circ_lib_method = 'circ_lib';
+        my $recurring_fine_method = 'recurring_fine';
+        if ($ctype eq 'reservation') {
+            $due_date_method = 'end_time';
+            $target_copy_method = 'current_resource';
+            $circ_lib_method = 'pickup_lib';
+            $recurring_fine_method = 'fine_amount';
+            next unless ($c->fine_interval);
+        }
+
 		try {
 			if ($self->method_lookup('open-ils.storage.transaction.current')->run) {
 				$log->debug("Cleaning up after previous transaction\n");
@@ -616,7 +649,7 @@
 			$log->info("Processing circ ".$c->id."...\n");
 
 
-			my $due_dt = $parser->parse_datetime( clense_ISO8601( $c->due_date ) );
+			my $due_dt = $parser->parse_datetime( clense_ISO8601( $c->$due_date_method ) );
 	
 			my $due = $due_dt->epoch;
 			my $now = time;
@@ -636,15 +669,15 @@
 			}
 	
 			$client->respond(
-				"ARG! Overdue circulation ".$c->id.
-				" for item ".$c->target_copy.
+				"ARG! Overdue $ctype ".$c->id.
+				" for item ".$c->$target_copy_method.
 				" (user ".$c->usr.").\n".
 				"\tItem was due on or before: ".localtime($due)."\n");
 	
 			my @fines = money::billing->search_where(
 				{ xact => $c->id,
 				  btype => 1,
-				  billing_ts => { '>' => $c->due_date } },
+				  billing_ts => { '>' => $c->$due_date_method } },
 				{ order_by => 'billing_ts DESC'}
 			);
 
@@ -666,7 +699,7 @@
 				$last_fine = $due;
 
 				if (0) {
-					if (my $h = $hoo{$c->circ_lib}) { 
+					if (my $h = $hoo{$c->$circ_lib_method}) { 
 
 						$log->info( "Circ lib has an hours-of-operation entry" );
 						# find the day after the due date...
@@ -714,17 +747,17 @@
 	
 			$client->respond( "\t$pending_fine_count pending fine(s)\n" );
 
-			my $recuring_fine = int($c->recuring_fine * 100);
+			my $recurring_fine = int($c->$recurring_fine_method * 100);
 			my $max_fine = int($c->max_fine * 100);
 
 			my ($latest_billing_ts, $latest_amount) = ('',0);
 			for (my $bill = 1; $bill <= $pending_fine_count; $bill++) {
 	
 				if ($current_fine_total >= $max_fine) {
-					$c->update({stop_fines => 'MAXFINES', stop_fines_time => 'now'});
+					$c->update({stop_fines => 'MAXFINES', stop_fines_time => 'now'}) if ($ctype eq 'circulation');
 					$client->respond(
 						"\tMaximum fine level of ".$c->max_fine.
-						" reached for this circulation.\n".
+						" reached for this $ctype.\n".
 						"\tNo more fines will be generated.\n" );
 					last;
 				}
@@ -735,7 +768,7 @@
 				my $dow_open = "dow_${dow}_open";
 				my $dow_close = "dow_${dow}_close";
 
-				if (my $h = $hoo{$c->circ_lib}) {
+				if (my $h = $hoo{$c->$circ_lib_method}) {
 					next if ( $h->$dow_open eq '00:00:00' and $h->$dow_close eq '00:00:00');
 				}
 
@@ -743,7 +776,7 @@
 				my @cl = actor::org_unit::closed_date->search_where(
 						{ close_start	=> { '<=' => $timestamptz },
 						  close_end	=> { '>=' => $timestamptz },
-						  org_unit	=> $c->circ_lib }
+						  org_unit	=> $c->$circ_lib_method }
 				);
 				next if (@cl);
 	
@@ -772,7 +805,7 @@
 
                 # Caluclate penalties inline
 				OpenILS::Utils::Penalty->calculate_penalties(
-					undef, $c->usr->to_fieldmapper->id.'', $c->circ_lib->to_fieldmapper->id.'');
+					undef, $c->usr->to_fieldmapper->id.'', $c->$circ_lib_method->to_fieldmapper->id.'');
 
 			} else {
 
@@ -783,7 +816,7 @@
 				$penalty->request(
 				    'open-ils.penalty.patron_penalty.calculate',
 				    { patronid	=> ''.$c->usr,
-				    context_org	=> ''.$c->circ_lib,
+				    context_org	=> ''.$c->$circ_lib_method,
 				    update	=> 1,
 				    background	=> 1,
 				    }
@@ -792,8 +825,8 @@
 
 		} catch Error with {
 			my $e = shift;
-			$client->respond( "Error processing overdue circulation [".$c->id."]:\n\n$e\n" );
-			$log->error("Error processing overdue circulation [".$c->id."]:\n$e\n");
+			$client->respond( "Error processing overdue $ctype [".$c->id."]:\n\n$e\n" );
+			$log->error("Error processing overdue $ctype [".$c->id."]:\n$e\n");
 			$self->method_lookup('open-ils.storage.transaction.rollback')->run;
 			throw $e if ($e =~ /IS NOT CONNECTED TO THE NETWORK/o);
 		};
@@ -1129,6 +1162,206 @@
 	method		=> 'new_hold_copy_targeter',
 );
 
+sub reservation_targeter {
+	my $self = shift;
+	my $client = shift;
+	my $one_reservation = shift;
+
+	local $OpenILS::Application::Storage::WRITE = 1;
+
+	my $reservations;
+
+	try {
+		if ($one_reservation) {
+			$self->method_lookup('open-ils.storage.transaction.begin')->run( $client );
+			$reservations = [ booking::reservation->search_where( { id => $one_reservation, capture_time => undef, cancel_time => undef } ) ];
+		} else {
+
+			# find all the reservations needing targeting
+			$reservations = [
+                booking::reservation->search_where(
+					{ current_resource => undef,
+					  cancel_time => undef,
+					  start_time => { '>' => 'now' }
+                    },
+                    { order_by => 'start_time' }
+                )
+            ];
+		}
+	} catch Error with {
+		my $e = shift;
+		die "Could not retrieve reservation requests:\n\n$e\n";
+	};
+
+	my @successes = ();
+	for my $bresv (@$reservations) {
+		try {
+			#start a transaction if needed
+			if ($self->method_lookup('open-ils.storage.transaction.current')->run) {
+				$log->debug("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 );
+			$log->info("Processing reservation ".$bresv->id."...\n");
+
+			#first, re-fetch the hold, to make sure it's not captured already
+			$bresv->remove_from_object_index();
+			$bresv = booking::reservation->retrieve( $bresv->id );
+
+			die "OK\n" if (!$bresv or $bresv->capture_time or $bresv->cancel_time);
+
+			my $end_time = $parser->parse_datetime( clense_ISO8601( $bresv->end_time ) );
+			if (DateTime->compare($end_time, DateTime->now) < 0) {
+
+				# cancel cause = un-targeted expiration
+				$bresv->update( { cancel_time => 'now' } ); 
+				$self->method_lookup('open-ils.storage.transaction.commit')->run;
+
+				# tell A/T the reservation was cancelled
+				my $fm_bresv = $bresv->to_fieldmapper;
+				my $ses = OpenSRF::AppSession->create('open-ils.trigger');
+				$ses->request('open-ils.trigger.event.autocreate', 
+					'booking.reservation.cancel.expire_no_target', $fm_bresv, $fm_bresv->pickup_lib);
+
+				die "OK\n";
+			}
+
+			my $possible_resources;
+
+			# find all the potential resources
+			if (!$bresv->target_resource) {
+				my $filter = { type => $bresv->target_resource_type };
+				my $attr_maps = [ booking::reservation_attr_value_map->search( reservation => $bresv->id) ];
+
+				$filter->{attribute_values} = [ map { $_->attr_value } @$attr_maps ] if (@$attr_maps);
+
+				$filter->{available} = [$bresv->start_time, $bresv->end_time];
+				my $ses = OpenSRF::AppSession->create('open-ils.booking');
+				$possible_resources = $ses->request('open-ils.booking.resources.filtered_id_list', undef, $filter)->gather(1);
+			} else {
+				$possible_resources = $bresv->target_resource;
+			}
+
+            my $all_resources = [ booking::resource->search( id => $possible_resources ) ];
+			@$all_resources = grep { isTrue($_->type->transferable) || $_->owner.'' eq $bresv->pickup_lib.'' } @$all_resources;
+
+
+            my @good_resources = ();
+            for my $res (@$all_resources) {
+                unless (isTrue($res->type->catalog_item)) {
+                    push @good_resources, $res;
+                    next;
+                }
+
+                my $copy = [ asset::copy->search( deleted => f, barcode => $res->barcode )]->[0];
+
+                unless ($copy) {
+                    push @good_resources, $res;
+                    next;
+                }
+
+                if ($copy->status->id == 0 || $copy->status->id == 7) {
+                    push @good_resources, $res;
+                    next;
+                }
+
+                if ($copy->status->id == 1) {
+                    my $circs = action::circulation->search_where(
+                        {target_copy => $copy->id, checkin_time => undef },
+                        { order_by => 'id DESC' }
+                    );
+
+                    if (@$circs) {
+                        my $due_date = $circs->[0]->due_date;
+			            $due_date = $parser->parse_datetime( clense_ISO8601( $due_date ) );
+			            my $start_time = $parser->parse_datetime( clense_ISO8601( $bresv->start_time ) );
+                        next if (DateTime->compare($start_time, $due_date) < 0);
+                        push @good_resources, $res;
+                    }
+
+                    next;
+                }
+
+                push @good_resources, $res if (isTrue($copy->status->holdable));
+            }
+
+			# let 'em know we're still working
+			$client->status( new OpenSRF::DomainObject::oilsContinueStatus );
+			
+			# if we have no copies ...
+			if (!@good_resources) {
+				$log->info("\tNo resources available for targeting at all!\n");
+				push @successes, { reservation => $bresv->id, eligible_copies => 0, error => 'NO_COPIES' };
+
+				$self->method_lookup('open-ils.storage.transaction.commit')->run;
+				die "OK\n";
+			}
+
+			$log->debug("\t".scalar(@good_resources)." resources available for targeting...");
+
+			my $prox_list = [];
+			$$prox_list[0] =
+			[
+				grep {
+					$_->owner == $bresv->pickup_lib
+				} @good_resources
+			];
+
+			$all_resources = [grep {$_->owner != $bresv->pickup_lib } @good_resources];
+			# $all_copies is now a list of copies not at the pickup library
+
+			my $best = shift @good_resources;
+			$client->status( new OpenSRF::DomainObject::oilsContinueStatus );
+
+			if (!$best) {
+				$log->debug("\tNothing at the pickup lib, looking elsewhere among ".scalar(@$all_resources)." resources");
+
+				$prox_list =
+                    map  { $_->[1] }
+                    sort { $a->[0] <> $b->[0] }
+                    map  {
+                        [   actor::org_unit_proximity->search_where(
+                                { from_org => $bresv->pickup_lib.'', to_org => $_=>owner.'' }
+                            )->[0]->prox,
+                            $_
+                        ]
+                    } @$all_resources;
+
+				$client->status( new OpenSRF::DomainObject::oilsContinueStatus );
+
+				$best = shift @$prox_list
+			}
+
+			if ($best) {
+				$bresv->update( { current_resource => ''.$best->id } );
+				$log->debug("\tUpdating reservation [".$bresv->id."] with new 'current_resource' [".$best->id."] for reservation fulfillment.");
+			}
+
+			$self->method_lookup('open-ils.storage.transaction.commit')->run;
+			$log->info("\tProcessing of bresv ".$bresv->id." complete.");
+
+			push @successes,
+				{ reservation => $bresv->id,
+				  current_resource => ($best ? $best->id : undef) };
+
+		} otherwise {
+			my $e = shift;
+			if ($e !~ /^OK/o) {
+				$log->error("Processing of bresv failed:  $e");
+				$self->method_lookup('open-ils.storage.transaction.rollback')->run;
+				throw $e if ($e =~ /IS NOT CONNECTED TO THE NETWORK/o);
+			}
+		};
+	}
+
+	return \@successes;
+}
+__PACKAGE__->register_method(
+	api_name	=> 'open-ils.storage.booking.reservation.resource_targeter',
+	api_level	=> 1,
+	method		=> 'reservation_targeter',
+);
+
 my $locations;
 my $statuses;
 my %cache = (titles => {}, cns => {});

Modified: branches/rel_1_6/Open-ILS/src/perlmods/OpenILS/Application/Storage/Publisher/money.pm
===================================================================
--- branches/rel_1_6/Open-ILS/src/perlmods/OpenILS/Application/Storage/Publisher/money.pm	2010-01-13 16:30:42 UTC (rev 15311)
+++ branches/rel_1_6/Open-ILS/src/perlmods/OpenILS/Application/Storage/Publisher/money.pm	2010-01-13 16:35:03 UTC (rev 15312)
@@ -51,8 +51,13 @@
                 $s->balance_owed( sprintf('%0.2f', (($to) - ($tp)) / 100) );
 		#$log->debug( "balance of ".$x->id." == ".$s->balance_owed, DEBUG );
 
-                $s->xact_type( 'grocery' ) if (money::grocery->retrieve($x->id));
-                $s->xact_type( 'circulation' ) if (action::circulation->retrieve($x->id));
+                if (action::circulation->retrieve($x->id)) {
+                    $s->xact_type( 'circulation' );
+                } elsif (money::grocery->retrieve($x->id)) {
+                    $s->xact_type( 'grocery' );
+                } elsif (booking::reservation->retrieve($x->id)) {
+                    $s->xact_type( 'reservation' );
+                }
 
                 push @mbts, $s;
         }
@@ -117,6 +122,7 @@
 	my $mb = money::billing->table;
 	my $circ = action::circulation->table;
 	my $mg = money::grocery->table;
+	my $res = booking::reservation->table;
 	my $descendants = "actor.org_unit_descendants((select id from actor.org_unit where shortname = ?))";
 
 	my $SQL = <<"	SQL";
@@ -156,6 +162,23 @@
                 and b.billing_ts < current_timestamp - ? * '1 day'::interval
                 and not b.voided
           group by 1,2
+
+                  union all
+
+         select
+                x.id,
+                x.usr,
+                MAX(b.billing_ts) as last_billing,
+                SUM(b.amount) AS total_billing
+          from  booking.reservation x
+                left join money.collections_tracker c ON (c.usr = x.usr AND c.location = ?)
+                join money.billing b on (b.xact = x.id)
+          where x.xact_finish is null
+                and c.id is null
+                and x.pickup_lib in (XX)
+                and b.billing_ts < current_timestamp - ? * '1 day'::interval
+                and not b.voided
+          group by 1,2
         ) full_list
         left join money.payment p on (full_list.id = p.xact)
   group by 1
@@ -236,6 +259,20 @@
                 and b.billing_ts between ? and ?
                 and not b.voided
           group by 1,2
+
+                  union all
+
+         select
+                x.id,
+                x.usr,
+                SUM(b.amount) AS total_billing
+          from  booking.reservation x
+                join money.billing b on (b.xact = x.id)
+          where x.xact_finish is null
+                and x.pickup_lib in (XX)
+                and b.billing_ts between ? and ?
+                and not b.voided
+          group by 1,2
         ) full_list
         left join money.payment p on (full_list.id = p.xact)
   group by 1
@@ -292,6 +329,42 @@
                 SELECT  lt.usr,
                         NULL::TIMESTAMPTZ AS last_pertinent_billing,
                         NULL::TIMESTAMPTZ AS last_pertinent_payment
+                  FROM  booking.reservation lt
+                        JOIN money.collections_tracker cl ON (lt.usr = cl.usr)
+                        JOIN money.billing bl ON (lt.id = bl.xact)
+                  WHERE cl.location = ?
+                        AND lt.pickup_lib IN (XX)
+                        AND bl.void_time BETWEEN ? AND ?
+                  GROUP BY 1
+
+                                UNION ALL
+                SELECT  lt.usr,
+                        MAX(bl.billing_ts) AS last_pertinent_billing,
+                        NULL::TIMESTAMPTZ AS last_pertinent_payment
+                  FROM  booking.reservation lt
+                        JOIN money.collections_tracker cl ON (lt.usr = cl.usr)
+                        JOIN money.billing bl ON (lt.id = bl.xact)
+                  WHERE cl.location = ?
+                        AND lt.pickup_lib IN (XX)
+                        AND bl.billing_ts BETWEEN ? AND ?
+                  GROUP BY 1
+
+                                UNION ALL
+                SELECT  lt.usr,
+                        NULL::TIMESTAMPTZ AS last_pertinent_billing,
+                        MAX(pm.payment_ts) AS last_pertinent_payment
+                  FROM  booking.reservation lt
+                        JOIN money.collections_tracker cl ON (lt.usr = cl.usr)
+                        JOIN money.payment pm ON (lt.id = pm.xact)
+                  WHERE cl.location = ?
+                        AND lt.pickup_lib IN (XX)
+                        AND pm.payment_ts BETWEEN ? AND ?
+                  GROUP BY 1
+
+                                UNION ALL
+                 SELECT  lt.usr,
+                        NULL::TIMESTAMPTZ AS last_pertinent_billing,
+                        NULL::TIMESTAMPTZ AS last_pertinent_payment
                   FROM  money.grocery lt
                         JOIN money.collections_tracker cl ON (lt.usr = cl.usr)
                         JOIN money.billing bl ON (lt.id = bl.xact)
@@ -389,12 +462,20 @@
 
 		my $sth = money::collections_tracker->db_Main->prepare($real_sql);
 		$sth->execute(
+            # reservation queries
 			$org->id, $startdate, $enddate,
 			$org->id, $startdate, $enddate,
 			$org->id, $startdate, $enddate,
+
+            # grocery queries
 			$org->id, $startdate, $enddate,
 			$org->id, $startdate, $enddate,
 			$org->id, $startdate, $enddate,
+
+            # circ queries
+			$org->id, $startdate, $enddate,
+			$org->id, $startdate, $enddate,
+			$org->id, $startdate, $enddate,
 			$org->id, $startdate, $enddate
 		);
 

Modified: branches/rel_1_6/Open-ILS/src/perlmods/OpenILS/Const.pm
===================================================================
--- branches/rel_1_6/Open-ILS/src/perlmods/OpenILS/Const.pm	2010-01-13 16:30:42 UTC (rev 15311)
+++ branches/rel_1_6/Open-ILS/src/perlmods/OpenILS/Const.pm	2010-01-13 16:35:03 UTC (rev 15312)
@@ -41,6 +41,7 @@
 econst OILS_COPY_STATUS_RESERVES      => 12;
 econst OILS_COPY_STATUS_DISCARD       => 13;
 econst OILS_COPY_STATUS_DAMAGED       => 14;
+econst OILS_COPY_STATUS_ON_RESV_SHELF => 15;
 
 
 # ---------------------------------------------------------------------

Modified: branches/rel_1_6/Open-ILS/src/perlmods/OpenILS/Utils/CStoreEditor.pm
===================================================================
--- branches/rel_1_6/Open-ILS/src/perlmods/OpenILS/Utils/CStoreEditor.pm	2010-01-13 16:30:42 UTC (rev 15311)
+++ branches/rel_1_6/Open-ILS/src/perlmods/OpenILS/Utils/CStoreEditor.pm	2010-01-13 16:35:03 UTC (rev 15312)
@@ -258,7 +258,7 @@
     my $name = shift || 'savepoint';
     return unless $self->{session} and $self->{xact_id};
 	$self->log(I, "setting savepoint '$name'");
-	my $stat = $self->request($self->app.".savepoint.set")
+	my $stat = $self->request($self->app.".savepoint.set", $name)
 	    or $self->log(E, "error setting savepoint '$name'");
     return $stat;
 }
@@ -268,7 +268,7 @@
     my $name = shift || 'savepoint';
     return unless $self->{session} and $self->{xact_id};
 	$self->log(I, "releasing savepoint '$name'");
-	my $stat = $self->request($self->app.".savepoint.release")
+	my $stat = $self->request($self->app.".savepoint.release", $name)
         or $self->log(E, "error releasing savepoint '$name'");
     return $stat;
 }
@@ -278,7 +278,7 @@
     my $name = shift || 'savepoint';
     return unless $self->{session} and $self->{xact_id};
 	$self->log(I, "rollback savepoint '$name'");
-	my $stat = $self->request($self->app.".savepoint.rollback")
+	my $stat = $self->request($self->app.".savepoint.rollback", $name)
         or $self->log(E, "error rolling back savepoint '$name'");
     return $stat;
 }

Modified: branches/rel_1_6/Open-ILS/src/perlmods/OpenILS/WWW/BadDebt.pm
===================================================================
--- branches/rel_1_6/Open-ILS/src/perlmods/OpenILS/WWW/BadDebt.pm	2010-01-13 16:30:42 UTC (rev 15311)
+++ branches/rel_1_6/Open-ILS/src/perlmods/OpenILS/WWW/BadDebt.pm	2010-01-13 16:35:03 UTC (rev 15312)
@@ -97,9 +97,17 @@
             my $s = $cstore->request('open-ils.cstore.direct.money.billable_xact_summary.retrieve' => $xact)->gather(1);
             my $u = $cstore->request('open-ils.cstore.direct.actor.usr.retrieve' => $s->usr)->gather(1);
             my $c = $cstore->request('open-ils.cstore.direct.actor.card.retrieve' => $u->card)->gather(1);
-            my $w = $s->xact_type eq 'circulation' ? 
-                $cstore->request('open-ils.cstore.direct.action.circulation.retrieve' => $xact)->gather(1)->circ_lib :
-                $cstore->request('open-ils.cstore.direct.money.grocery.retrieve' => $xact)->gather(1)->billing_location;
+            my $w;
+
+            if ($s->xact_type eq 'circulation') {
+                $w = $cstore->request('open-ils.cstore.direct.action.circulation.retrieve' => $xact)->gather(1)->circ_lib :
+            } elsif ($s->xact_type eq 'grocery') {
+                $w = $cstore->request('open-ils.cstore.direct.money.grocery.retrieve' => $xact)->gather(1)->billing_location;
+            } elsif ($s->xact_type eq 'reservation') {
+                $w = $cstore->request('open-ils.cstore.direct.booking.reservation.retrieve' => $xact)->gather(1)->pickup_lib;
+            } else {
+                die;
+            }
     
             my $failures = $actor->request('open-ils.actor.user.perm.check', $auth_ses, $user->id, $w, ['MARK_BAD_DEBT'])->gather(1);
     

Modified: branches/rel_1_6/Open-ILS/src/sql/Pg/002.schema.config.sql
===================================================================
--- branches/rel_1_6/Open-ILS/src/sql/Pg/002.schema.config.sql	2010-01-13 16:30:42 UTC (rev 15311)
+++ branches/rel_1_6/Open-ILS/src/sql/Pg/002.schema.config.sql	2010-01-13 16:35:03 UTC (rev 15312)
@@ -51,7 +51,7 @@
     install_date    TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW()
 );
 
-INSERT INTO config.upgrade_log (version) VALUES ('0131'); -- dbs
+INSERT INTO config.upgrade_log (version) VALUES ('0130'); -- senator
 
 CREATE TABLE config.bib_source (
 	id		SERIAL	PRIMARY KEY,

Copied: branches/rel_1_6/Open-ILS/src/sql/Pg/095.schema.booking.sql (from rev 14921, trunk/Open-ILS/src/sql/Pg/095.schema.booking.sql)
===================================================================
--- branches/rel_1_6/Open-ILS/src/sql/Pg/095.schema.booking.sql	                        (rev 0)
+++ branches/rel_1_6/Open-ILS/src/sql/Pg/095.schema.booking.sql	2010-01-13 16:35:03 UTC (rev 15312)
@@ -0,0 +1,168 @@
+/*
+ * Copyright (C) 2009  Equinox Software, Inc.
+ * Scott McKellar <scott at esilibrary.com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ */
+
+BEGIN;
+
+DROP SCHEMA IF EXISTS booking CASCADE;
+
+CREATE SCHEMA booking;
+
+CREATE TABLE booking.resource_type (
+	id             SERIAL          PRIMARY KEY,
+	name           TEXT            NOT NULL,
+	elbow_room     INTERVAL,
+	fine_interval  INTERVAL,
+	fine_amount    DECIMAL(8,2)    NOT NULL DEFAULT 0,
+	max_fine       DECIMAL(8,2),
+	owner          INT             NOT NULL
+	                               REFERENCES actor.org_unit( id )
+	                               DEFERRABLE INITIALLY DEFERRED,
+	catalog_item   BOOLEAN         NOT NULL DEFAULT FALSE,
+	transferable   BOOLEAN         NOT NULL DEFAULT FALSE,
+    record         INT             REFERENCES biblio.record_entry (id)
+                                   DEFERRABLE INITIALLY DEFERRED,
+	CONSTRAINT brt_name_once_per_owner UNIQUE(owner, name, record)
+);
+
+CREATE TABLE booking.resource (
+	id             SERIAL           PRIMARY KEY,
+	owner          INT              NOT NULL
+	                                REFERENCES actor.org_unit(id)
+	                                DEFERRABLE INITIALLY DEFERRED,
+	type           INT              NOT NULL
+	                                REFERENCES booking.resource_type(id)
+	                                DEFERRABLE INITIALLY DEFERRED,
+	overbook       BOOLEAN          NOT NULL DEFAULT FALSE,
+	barcode        TEXT             NOT NULL,
+	deposit        BOOLEAN          NOT NULL DEFAULT FALSE,
+	deposit_amount DECIMAL(8,2)     NOT NULL DEFAULT 0.00,
+	user_fee       DECIMAL(8,2)     NOT NULL DEFAULT 0.00,
+	CONSTRAINT br_unique UNIQUE(owner, barcode)
+);
+
+-- For non-catalog items: hijack barcode for name/description
+
+CREATE TABLE booking.resource_attr (
+	id              SERIAL          PRIMARY KEY,
+	owner           INT             NOT NULL
+	                                REFERENCES actor.org_unit(id)
+	                                DEFERRABLE INITIALLY DEFERRED,
+	name            TEXT            NOT NULL,
+	resource_type   INT             NOT NULL
+	                                REFERENCES booking.resource_type(id)
+	                                ON DELETE CASCADE
+	                                DEFERRABLE INITIALLY DEFERRED,
+	required        BOOLEAN         NOT NULL DEFAULT FALSE,
+	CONSTRAINT bra_name_once_per_type UNIQUE(resource_type, name)
+);
+
+CREATE TABLE booking.resource_attr_value (
+	id               SERIAL         PRIMARY KEY,
+	owner            INT            NOT NULL
+	                                REFERENCES actor.org_unit(id)
+	                                DEFERRABLE INITIALLY DEFERRED,
+	attr             INT            NOT NULL
+	                                REFERENCES booking.resource_attr(id)
+	                                DEFERRABLE INITIALLY DEFERRED,
+	valid_value      TEXT           NOT NULL,
+	CONSTRAINT brav_logical_key UNIQUE(owner, attr, valid_value)
+);
+
+-- Do we still need a name column?
+
+
+CREATE TABLE booking.resource_attr_map (
+	id               SERIAL         PRIMARY KEY,
+	resource         INT            NOT NULL
+	                                REFERENCES booking.resource(id)
+	                                ON DELETE CASCADE
+	                                DEFERRABLE INITIALLY DEFERRED,
+	resource_attr    INT            NOT NULL
+	                                REFERENCES booking.resource_attr(id)
+	                                ON DELETE CASCADE
+	                                DEFERRABLE INITIALLY DEFERRED,
+	value            INT            NOT NULL
+	                                REFERENCES booking.resource_attr_value(id)
+	                                DEFERRABLE INITIALLY DEFERRED,
+	CONSTRAINT bram_one_value_per_attr UNIQUE(resource, resource_attr)
+);
+
+CREATE TABLE booking.reservation (
+	request_time     TIMESTAMPTZ   NOT NULL DEFAULT now(),
+	start_time       TIMESTAMPTZ,
+	end_time         TIMESTAMPTZ,
+	capture_time     TIMESTAMPTZ,
+	cancel_time      TIMESTAMPTZ,
+	pickup_time      TIMESTAMPTZ,
+	return_time      TIMESTAMPTZ,
+	booking_interval INTERVAL,
+	fine_interval    INTERVAL,
+	fine_amount      DECIMAL(8,2),
+	max_fine         DECIMAL(8,2),
+	target_resource_type  INT       NOT NULL
+	                                REFERENCES booking.resource_type(id)
+	                                ON DELETE CASCADE
+	                                DEFERRABLE INITIALLY DEFERRED,
+	target_resource  INT            REFERENCES booking.resource(id)
+	                                ON DELETE CASCADE
+	                                DEFERRABLE INITIALLY DEFERRED,
+	current_resource INT            REFERENCES booking.resource(id)
+	                                ON DELETE CASCADE
+	                                DEFERRABLE INITIALLY DEFERRED,
+	request_lib      INT            NOT NULL
+	                                REFERENCES actor.org_unit(id)
+	                                DEFERRABLE INITIALLY DEFERRED,
+	pickup_lib       INT            REFERENCES actor.org_unit(id)
+	                                DEFERRABLE INITIALLY DEFERRED,
+	capture_staff    INT            REFERENCES actor.usr(id)
+	                                DEFERRABLE INITIALLY DEFERRED
+) INHERITS (money.billable_xact);
+
+ALTER TABLE booking.reservation ADD PRIMARY KEY (id);
+
+ALTER TABLE booking.reservation
+	ADD CONSTRAINT booking_reservation_usr_fkey
+	FOREIGN KEY (usr) REFERENCES actor.usr (id)
+	DEFERRABLE INITIALLY DEFERRED;
+
+CREATE TRIGGER mat_summary_create_tgr AFTER INSERT ON booking.reservation FOR EACH ROW EXECUTE PROCEDURE money.mat_summary_create ('reservation');
+CREATE TRIGGER mat_summary_change_tgr AFTER UPDATE ON booking.reservation FOR EACH ROW EXECUTE PROCEDURE money.mat_summary_update ();
+CREATE TRIGGER mat_summary_remove_tgr AFTER DELETE ON booking.reservation FOR EACH ROW EXECUTE PROCEDURE money.mat_summary_delete ();
+
+
+CREATE TABLE booking.reservation_attr_value_map (
+	id               SERIAL         PRIMARY KEY,
+	reservation      INT            NOT NULL
+	                                REFERENCES booking.reservation(id)
+	                                ON DELETE CASCADE
+	                                DEFERRABLE INITIALLY DEFERRED,
+	attr_value       INT            NOT NULL
+	                                REFERENCES booking.resource_attr_value(id)
+	                                ON DELETE CASCADE
+	                                DEFERRABLE INITIALLY DEFERRED,
+	CONSTRAINT bravm_logical_key UNIQUE(reservation, attr_value)
+);
+
+CREATE TABLE action.reservation_transit_copy (
+    reservation    INT REFERENCES booking.reservation (id) ON DELETE SET NULL DEFERRABLE INITIALLY DEFERRED
+) INHERITS (action.transit_copy);
+ALTER TABLE action.reservation_transit_copy ADD PRIMARY KEY (id);
+ALTER TABLE action.reservation_transit_copy ADD CONSTRAINT artc_tc_fkey FOREIGN KEY (target_copy) REFERENCES booking.resource (id) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED;
+CREATE INDEX active_reservation_transit_dest_idx ON "action".reservation_transit_copy (dest);
+CREATE INDEX active_reservation_transit_source_idx ON "action".reservation_transit_copy (source);
+CREATE INDEX active_reservation_transit_cp_idx ON "action".reservation_transit_copy (target_copy);
+
+COMMIT;

Modified: branches/rel_1_6/Open-ILS/src/sql/Pg/100.circ_matrix.sql
===================================================================
--- branches/rel_1_6/Open-ILS/src/sql/Pg/100.circ_matrix.sql	2010-01-13 16:30:42 UTC (rev 15311)
+++ branches/rel_1_6/Open-ILS/src/sql/Pg/100.circ_matrix.sql	2010-01-13 16:35:03 UTC (rev 15312)
@@ -431,6 +431,12 @@
         SELECT  SUM(f.balance_owed) INTO current_fines
           FROM  money.materialized_billable_xact_summary f
                 JOIN (
+                    SELECT  r.id
+                      FROM  booking.reservation r
+                            JOIN  actor.org_unit_full_path( max_fines.org_unit ) fp ON (r.pickup_lib = fp.id)
+                      WHERE usr = match_user
+                            AND xact_finish IS NULL
+                                UNION ALL
                     SELECT  g.id
                       FROM  money.grocery g
                             JOIN  actor.org_unit_full_path( max_fines.org_unit ) fp ON (g.billing_location = fp.id)
@@ -611,6 +617,12 @@
         SELECT  SUM(f.balance_owed) INTO current_fines
           FROM  money.materialized_billable_xact_summary f
                 JOIN (
+                    SELECT  r.id
+                      FROM  booking.reservation r
+                            JOIN  actor.org_unit_full_path( max_fines.org_unit ) fp ON (r.pickup_lib = fp.id)
+                      WHERE usr = match_user
+                            AND xact_finish IS NULL
+                                UNION ALL
                     SELECT  g.id
                       FROM  money.grocery g
                             JOIN  actor.org_unit_full_path( max_fines.org_unit ) fp ON (g.billing_location = fp.id)

Modified: branches/rel_1_6/Open-ILS/src/sql/Pg/500.view.cross-schema.sql
===================================================================
--- branches/rel_1_6/Open-ILS/src/sql/Pg/500.view.cross-schema.sql	2010-01-13 16:30:42 UTC (rev 15311)
+++ branches/rel_1_6/Open-ILS/src/sql/Pg/500.view.cross-schema.sql	2010-01-13 16:35:03 UTC (rev 15312)
@@ -20,7 +20,7 @@
 CREATE OR REPLACE VIEW money.open_billable_xact_summary AS
 	SELECT	xact.id AS id,
 		xact.usr AS usr,
-		COALESCE(circ.circ_lib,groc.billing_location) AS billing_location,
+		COALESCE(circ.circ_lib,groc.billing_location,res.pickup_lib) AS billing_location,
 		xact.xact_start AS xact_start,
 		xact.xact_finish AS xact_finish,
 		SUM(credit.amount) AS total_paid,
@@ -37,6 +37,7 @@
 	  	JOIN pg_class p ON (xact.tableoid = p.oid)
 		LEFT JOIN "action".circulation circ ON (circ.id = xact.id)
 		LEFT JOIN money.grocery groc ON (groc.id = xact.id)
+		LEFT JOIN booking.reservation res ON (groc.id = xact.id)
 	  	LEFT JOIN (
 			SELECT	billing.xact,
 				billing.voided,

Modified: branches/rel_1_6/Open-ILS/src/sql/Pg/950.data.seed-values.sql
===================================================================
--- branches/rel_1_6/Open-ILS/src/sql/Pg/950.data.seed-values.sql	2010-01-13 16:30:42 UTC (rev 15311)
+++ branches/rel_1_6/Open-ILS/src/sql/Pg/950.data.seed-values.sql	2010-01-13 16:35:03 UTC (rev 15312)
@@ -126,6 +126,7 @@
 INSERT INTO config.copy_status (id,name,opac_visible) VALUES (12,oils_i18n_gettext(12, 'Reserves', 'ccs', 'name'),'t');
 INSERT INTO config.copy_status (id,name) VALUES (13,oils_i18n_gettext(13, 'Discard/Weed', 'ccs', 'name'));
 INSERT INTO config.copy_status (id,name) VALUES (14,oils_i18n_gettext(14, 'Damaged', 'ccs', 'name'));
+INSERT INTO config.copy_status (id,name) VALUES (15,oils_i18n_gettext(15, 'On reservation shelf', 'ccs', 'name'));
 
 SELECT SETVAL('config.copy_status_id_seq'::TEXT, 100);
 
@@ -1231,6 +1232,17 @@
     (201, 'DELETE_MFHD_RECORD', oils_i18n_gettext(201, 'Allows a user to delete an MFHD record', 'ppl', 'description')),
     (202, 'ADMIN_ACQ_FUND', oils_i18n_gettext(202, 'Allow a user to create/view/update/delete a fund', 'ppl', 'description')),
     (203, 'group_application.user.staff.acq_admin', oils_i18n_gettext(203, 'Allows a user to add/remove/edit users in the "Acquisitions Administrators" group', 'ppl', 'description'))
+    (351, 'HOLD_LOCAL_AVAIL_OVERRIDE', oils_i18n_gettext(351, 'Allow a user to place a hold despite the availability of a local copy', 'ppl', 'description')),
+    (352, 'ADMIN_BOOKING_RESOURCE', oils_i18n_gettext(352, 'Enables the user to create/update/delete booking resources', 'ppl', 'description')),
+    (353, 'ADMIN_BOOKING_RESOURCE_TYPE', oils_i18n_gettext(353, 'Enables the user to create/update/delete booking resource types', 'ppl', 'description')),
+    (354, 'ADMIN_BOOKING_RESOURCE_ATTR', oils_i18n_gettext(354, 'Enables the user to create/update/delete booking resource attributes', 'ppl', 'description')),
+    (355, 'ADMIN_BOOKING_RESOURCE_ATTR_MAP', oils_i18n_gettext(355, 'Enables the user to create/update/delete booking resource attribute maps', 'ppl', 'description')),
+    (356, 'ADMIN_BOOKING_RESOURCE_ATTR_VALUE', oils_i18n_gettext(356, 'Enables the user to create/update/delete booking resource attribute values', 'ppl', 'description')),
+    (357, 'ADMIN_BOOKING_RESERVATION', oils_i18n_gettext(357, 'Enables the user to create/update/delete booking reservations', 'ppl', 'description')),
+    (358, 'ADMIN_BOOKING_RESERVATION_ATTR_VALUE_MAP', oils_i18n_gettext(358, 'Enables the user to create/update/delete booking reservation attribute value maps', 'ppl', 'description')),
+    (359, 'HOLD_ITEM_CHECKED_OUT.override', oils_i18n_gettext(359, 'Allows a user to place a hold on an item that they already have checked out', 'ppl', 'description')),
+    (360, 'RETRIEVE_RESERVATION_PULL_LIST', oils_i18n_gettext(360, 'Allows a user to retrieve a booking reservation pull list', 'ppl', 'description')),
+    (361, 'CAPTURE_RESERVATION', oils_i18n_gettext(361, 'Allows a user to capture booking reservations', 'ppl', 'description'))
 ;
 
 SELECT SETVAL('permission.perm_list_id_seq'::TEXT, (SELECT MAX(id) FROM permission.perm_list));
@@ -1379,8 +1391,8 @@
 INSERT INTO permission.perm_list (code) VALUES ('UPDATE_ORG_UNIT_SETTING.cat.bib.alert_on_empty');
 INSERT INTO permission.perm_list (code) VALUES ('UPDATE_ORG_UNIT_SETTING.patron.password.use_phone');
 
+SELECT SETVAL('permission.perm_list_id_seq'::TEXT, 1000);
 
-
 INSERT INTO permission.grp_tree (id, name, parent, description, perm_interval, usergroup, application_perm) VALUES
 	(1, oils_i18n_gettext(1, 'Users', 'pgt', 'name'), NULL, NULL, '3 years', FALSE, 'group_application.user');
 INSERT INTO permission.grp_tree (id, name, parent, description, perm_interval, usergroup, application_perm) VALUES

Modified: branches/rel_1_6/Open-ILS/src/sql/Pg/build-db.sh
===================================================================
--- branches/rel_1_6/Open-ILS/src/sql/Pg/build-db.sh	2010-01-13 16:30:42 UTC (rev 15311)
+++ branches/rel_1_6/Open-ILS/src/sql/Pg/build-db.sh	2010-01-13 16:35:03 UTC (rev 15312)
@@ -89,15 +89,18 @@
 
   005.schema.actors.sql
   006.schema.permissions.sql
+  008.schema.query.sql
   010.schema.biblio.sql
   011.schema.authority.sql
   012.schema.vandelay.sql
+  015.schema.staging.sql
   020.schema.functions.sql
   030.schema.metabib.sql
   040.schema.asset.sql
   070.schema.container.sql
   080.schema.money.sql
   090.schema.action.sql
+  095.schema.booking.sql
   
   100.circ_matrix.sql
   110.hold_matrix.sql

Copied: branches/rel_1_6/Open-ILS/src/sql/Pg/upgrade/0086.schema.booking-tables.sql (from rev 14921, trunk/Open-ILS/src/sql/Pg/upgrade/0086.schema.booking-tables.sql)
===================================================================
--- branches/rel_1_6/Open-ILS/src/sql/Pg/upgrade/0086.schema.booking-tables.sql	                        (rev 0)
+++ branches/rel_1_6/Open-ILS/src/sql/Pg/upgrade/0086.schema.booking-tables.sql	2010-01-13 16:35:03 UTC (rev 15312)
@@ -0,0 +1,135 @@
+BEGIN;
+
+INSERT INTO config.upgrade_log (version) VALUES ('0086');
+
+DROP SCHEMA IF EXISTS booking CASCADE;
+
+CREATE SCHEMA booking;
+
+CREATE TABLE booking.resource_type (
+	id             SERIAL          PRIMARY KEY,
+	name           TEXT            NOT NULL,
+	fine_interval  INTERVAL,
+	fine_amount    DECIMAL(8,2)    NOT NULL DEFAULT 0,
+	owner          INT             NOT NULL
+	                               REFERENCES actor.org_unit( id )
+	                               DEFERRABLE INITIALLY DEFERRED,
+	catalog_item   BOOLEAN         NOT NULL DEFAULT FALSE,
+	transferable   BOOLEAN         NOT NULL DEFAULT FALSE,
+	CONSTRAINT brt_name_once_per_owner UNIQUE(owner, name)
+);
+
+CREATE TABLE booking.resource (
+	id             SERIAL           PRIMARY KEY,
+	owner          INT              NOT NULL
+	                                REFERENCES actor.org_unit(id)
+	                                DEFERRABLE INITIALLY DEFERRED,
+	type           INT              NOT NULL
+	                                REFERENCES booking.resource_type(id)
+	                                DEFERRABLE INITIALLY DEFERRED,
+	overbook       BOOLEAN          NOT NULL DEFAULT FALSE,
+	barcode        TEXT             NOT NULL,
+	deposit        BOOLEAN          NOT NULL DEFAULT FALSE,
+	deposit_amount DECIMAL(8,2)     NOT NULL DEFAULT 0.00,
+	user_fee       DECIMAL(8,2)     NOT NULL DEFAULT 0.00,
+	CONSTRAINT br_unique UNIQUE(owner, type, barcode)
+);
+
+-- For non-catalog items: hijack barcode for name/description
+
+CREATE TABLE booking.resource_attr (
+	id              SERIAL          PRIMARY KEY,
+	owner           INT             NOT NULL
+	                                REFERENCES actor.org_unit(id)
+	                                DEFERRABLE INITIALLY DEFERRED,
+	name            TEXT            NOT NULL,
+	resource_type   INT             NOT NULL
+	                                REFERENCES booking.resource_type(id)
+	                                ON DELETE CASCADE
+	                                DEFERRABLE INITIALLY DEFERRED,
+	required        BOOLEAN         NOT NULL DEFAULT FALSE,
+	CONSTRAINT bra_name_once_per_type UNIQUE(resource_type, name)
+);
+
+CREATE TABLE booking.resource_attr_value (
+	id               SERIAL         PRIMARY KEY,
+	owner            INT            NOT NULL
+	                                REFERENCES actor.org_unit(id)
+	                                DEFERRABLE INITIALLY DEFERRED,
+	attr             INT            NOT NULL
+	                                REFERENCES booking.resource_attr(id)
+	                                DEFERRABLE INITIALLY DEFERRED,
+	valid_value      TEXT           NOT NULL,
+	CONSTRAINT brav_logical_key UNIQUE(owner, attr, valid_value)
+);
+
+-- Do we still need a name column?
+
+
+CREATE TABLE booking.resource_attr_map (
+	id               SERIAL         PRIMARY KEY,
+	resource         INT            NOT NULL
+	                                REFERENCES booking.resource(id)
+	                                ON DELETE CASCADE
+	                                DEFERRABLE INITIALLY DEFERRED,
+	resource_attr    INT            NOT NULL
+	                                REFERENCES booking.resource_attr(id)
+	                                ON DELETE CASCADE
+	                                DEFERRABLE INITIALLY DEFERRED,
+	value            INT            NOT NULL
+	                                REFERENCES booking.resource_attr_value(id)
+	                                DEFERRABLE INITIALLY DEFERRED,
+	CONSTRAINT bram_one_value_per_attr UNIQUE(resource, resource_attr)
+);
+
+CREATE TABLE booking.reservation (
+	request_time     TIMESTAMPTZ   NOT NULL DEFAULT now(),
+	start_time       TIMESTAMPTZ,
+	end_time         TIMESTAMPTZ,
+	capture_time     TIMESTAMPTZ,
+	cancel_time      TIMESTAMPTZ,
+	pickup_time      TIMESTAMPTZ,
+	return_time      TIMESTAMPTZ,
+	booking_interval INTERVAL,
+	fine_interval    INTERVAL,
+	fine_amount      DECIMAL(8,2),
+	target_resource_type  INT       NOT NULL
+	                                REFERENCES booking.resource_type(id)
+	                                ON DELETE CASCADE
+	                                DEFERRABLE INITIALLY DEFERRED,
+	target_resource  INT            REFERENCES booking.resource(id)
+	                                ON DELETE CASCADE
+	                                DEFERRABLE INITIALLY DEFERRED,
+	current_resource INT            REFERENCES booking.resource(id)
+	                                ON DELETE CASCADE
+	                                DEFERRABLE INITIALLY DEFERRED,
+	request_lib      INT            NOT NULL
+	                                REFERENCES actor.org_unit(id)
+	                                DEFERRABLE INITIALLY DEFERRED,
+	pickup_lib       INT            REFERENCES actor.org_unit(id)
+	                                DEFERRABLE INITIALLY DEFERRED,
+	capture_staff    INT            REFERENCES actor.usr(id)
+	                                DEFERRABLE INITIALLY DEFERRED
+) INHERITS (money.billable_xact);
+
+ALTER TABLE booking.reservation ADD PRIMARY KEY (id);
+
+ALTER TABLE booking.reservation
+	ADD CONSTRAINT booking_reservation_usr_fkey
+	FOREIGN KEY (usr) REFERENCES actor.usr (id)
+	DEFERRABLE INITIALLY DEFERRED;
+
+CREATE TABLE booking.reservation_attr_value_map (
+	id               SERIAL         PRIMARY KEY,
+	reservation      INT            NOT NULL
+	                                REFERENCES booking.reservation(id)
+	                                ON DELETE CASCADE
+	                                DEFERRABLE INITIALLY DEFERRED,
+	attr_value       INT            NOT NULL
+	                                REFERENCES booking.resource_attr_value(id)
+	                                ON DELETE CASCADE
+	                                DEFERRABLE INITIALLY DEFERRED,
+	CONSTRAINT bravm_logical_key UNIQUE(reservation, attr_value)
+);
+
+COMMIT;

Copied: branches/rel_1_6/Open-ILS/src/sql/Pg/upgrade/0090.schema.booking.bib-base-types.sql (from rev 15019, trunk/Open-ILS/src/sql/Pg/upgrade/0090.schema.booking.bib-base-types.sql)
===================================================================
--- branches/rel_1_6/Open-ILS/src/sql/Pg/upgrade/0090.schema.booking.bib-base-types.sql	                        (rev 0)
+++ branches/rel_1_6/Open-ILS/src/sql/Pg/upgrade/0090.schema.booking.bib-base-types.sql	2010-01-13 16:35:03 UTC (rev 15312)
@@ -0,0 +1,9 @@
+BEGIN;
+
+INSERT INTO config.upgrade_log (version) VALUES ('0090'); -- miker
+
+ALTER TABLE booking.resource_type DROP CONSTRAINT brt_name_once_per_owner;
+ALTER TABLE booking.resource_type ADD COLUMN record BIGINT REFERENCES biblio.record_entry (id) DEFERRABLE INITIALLY DEFERRED;
+ALTER TABLE booking.resource_type ADD CONSTRAINT brt_name_or_record_once_per_owner UNIQUE(owner, name, record);
+
+COMMIT;

Copied: branches/rel_1_6/Open-ILS/src/sql/Pg/upgrade/0103.schema.booking.max_fine.sql (from rev 15068, trunk/Open-ILS/src/sql/Pg/upgrade/0103.schema.booking.max_fine.sql)
===================================================================
--- branches/rel_1_6/Open-ILS/src/sql/Pg/upgrade/0103.schema.booking.max_fine.sql	                        (rev 0)
+++ branches/rel_1_6/Open-ILS/src/sql/Pg/upgrade/0103.schema.booking.max_fine.sql	2010-01-13 16:35:03 UTC (rev 15312)
@@ -0,0 +1,8 @@
+BEGIN;
+
+INSERT INTO config.upgrade_log (version) VALUES ('0103'); -- miker
+
+ALTER TABLE booking.resource_type ADD COLUMN max_fine NUMERIC(8,2);
+ALTER TABLE booking.reservation ADD COLUMN max_fine NUMERIC(8,2);
+
+COMMIT;

Copied: branches/rel_1_6/Open-ILS/src/sql/Pg/upgrade/0105.schema.booking-integration.sql (from rev 15071, trunk/Open-ILS/src/sql/Pg/upgrade/0105.schema.booking-integration.sql)
===================================================================
--- branches/rel_1_6/Open-ILS/src/sql/Pg/upgrade/0105.schema.booking-integration.sql	                        (rev 0)
+++ branches/rel_1_6/Open-ILS/src/sql/Pg/upgrade/0105.schema.booking-integration.sql	2010-01-13 16:35:03 UTC (rev 15312)
@@ -0,0 +1,339 @@
+BEGIN;
+
+INSERT INTO config.upgrade_log (version) VALUES ('0105'); -- miker
+
+CREATE TRIGGER mat_summary_create_tgr AFTER INSERT ON booking.reservation FOR EACH ROW EXECUTE PROCEDURE money.mat_summary_create ('reservation');
+CREATE TRIGGER mat_summary_change_tgr AFTER UPDATE ON booking.reservation FOR EACH ROW EXECUTE PROCEDURE money.mat_summary_update ();
+CREATE TRIGGER mat_summary_remove_tgr AFTER DELETE ON booking.reservation FOR EACH ROW EXECUTE PROCEDURE money.mat_summary_delete ();
+
+CREATE OR REPLACE VIEW money.open_billable_xact_summary AS
+    SELECT  xact.id AS id,
+        xact.usr AS usr,
+        COALESCE(circ.circ_lib,groc.billing_location,res.pickup_lib) AS billing_location,
+        xact.xact_start AS xact_start,
+        xact.xact_finish AS xact_finish,
+        SUM(credit.amount) AS total_paid,
+        MAX(credit.payment_ts) AS last_payment_ts,
+        LAST(credit.note) AS last_payment_note,
+        LAST(credit.payment_type) AS last_payment_type,
+        SUM(debit.amount) AS total_owed,
+        MAX(debit.billing_ts) AS last_billing_ts,
+        LAST(debit.note) AS last_billing_note,
+        LAST(debit.billing_type) AS last_billing_type,
+        COALESCE(SUM(debit.amount),0) - COALESCE(SUM(credit.amount),0) AS balance_owed,
+        p.relname AS xact_type
+      FROM  money.billable_xact xact
+        JOIN pg_class p ON (xact.tableoid = p.oid)
+        LEFT JOIN "action".circulation circ ON (circ.id = xact.id)
+        LEFT JOIN money.grocery groc ON (groc.id = xact.id)
+        LEFT JOIN booking.reservation res ON (groc.id = xact.id)
+        LEFT JOIN (
+            SELECT  billing.xact,
+                billing.voided,
+                sum(billing.amount) AS amount,
+                max(billing.billing_ts) AS billing_ts,
+                last(billing.note) AS note,
+                last(billing.billing_type) AS billing_type
+              FROM  money.billing
+              WHERE billing.voided IS FALSE
+              GROUP BY billing.xact, billing.voided
+        ) debit ON (xact.id = debit.xact AND debit.voided IS FALSE)
+        LEFT JOIN (
+            SELECT  payment_view.xact,
+                payment_view.voided,
+                sum(payment_view.amount) AS amount,
+                max(payment_view.payment_ts) AS payment_ts,
+                last(payment_view.note) AS note,
+                last(payment_view.payment_type) AS payment_type
+              FROM  money.payment_view
+              WHERE payment_view.voided IS FALSE
+              GROUP BY payment_view.xact, payment_view.voided
+        ) credit ON (xact.id = credit.xact AND credit.voided IS FALSE)
+      WHERE xact.xact_finish IS NULL
+      GROUP BY 1,2,3,4,5,15
+      ORDER BY MAX(debit.billing_ts), MAX(credit.payment_ts);
+
+CREATE OR REPLACE FUNCTION actor.calculate_system_penalties( match_user INT, context_org INT ) RETURNS SETOF actor.usr_standing_penalty AS $func$
+DECLARE
+    user_object         actor.usr%ROWTYPE;
+    new_sp_row          actor.usr_standing_penalty%ROWTYPE;
+    existing_sp_row     actor.usr_standing_penalty%ROWTYPE;
+    collections_fines   permission.grp_penalty_threshold%ROWTYPE;
+    max_fines           permission.grp_penalty_threshold%ROWTYPE;
+    max_overdue         permission.grp_penalty_threshold%ROWTYPE;
+    max_items_out       permission.grp_penalty_threshold%ROWTYPE;
+    tmp_grp             INT;
+    items_overdue       INT;
+    items_out           INT;
+    context_org_list    INT[];
+    current_fines        NUMERIC(8,2) := 0.0;
+    tmp_fines            NUMERIC(8,2);
+    tmp_groc            RECORD;
+    tmp_circ            RECORD;
+    tmp_org             actor.org_unit%ROWTYPE;
+BEGIN
+    SELECT INTO user_object * FROM actor.usr WHERE id = match_user;
+
+    -- Max fines
+    SELECT INTO tmp_org * FROM actor.org_unit WHERE id = context_org;
+
+    -- Fail if the user has a high fine balance
+    LOOP
+        tmp_grp := user_object.profile;
+        LOOP
+            SELECT * INTO max_fines FROM permission.grp_penalty_threshold WHERE grp = tmp_grp AND penalty = 1 AND org_unit = tmp_org.id;
+
+            IF max_fines.threshold IS NULL THEN
+                SELECT parent INTO tmp_grp FROM permission.grp_tree WHERE id = tmp_grp;
+            ELSE
+                EXIT;
+            END IF;
+
+            IF tmp_grp IS NULL THEN
+                EXIT;
+            END IF;
+        END LOOP;
+
+        IF max_fines.threshold IS NOT NULL OR tmp_org.parent_ou IS NULL THEN
+            EXIT;
+        END IF;
+
+        SELECT * INTO tmp_org FROM actor.org_unit WHERE id = tmp_org.parent_ou;
+
+    END LOOP;
+
+    IF max_fines.threshold IS NOT NULL THEN
+
+        FOR existing_sp_row IN
+                SELECT  *
+                  FROM  actor.usr_standing_penalty
+                  WHERE usr = match_user
+                        AND org_unit = max_fines.org_unit
+                        AND (stop_date IS NULL or stop_date > NOW())
+                        AND standing_penalty = 1
+                LOOP
+            RETURN NEXT existing_sp_row;
+        END LOOP;
+
+        SELECT  SUM(f.balance_owed) INTO current_fines
+          FROM  money.materialized_billable_xact_summary f
+                JOIN (
+                    SELECT  r.id
+                      FROM  booking.reservation r
+                            JOIN  actor.org_unit_full_path( max_fines.org_unit ) fp ON (r.pickup_lib = fp.id)
+                      WHERE usr = match_user
+                            AND xact_finish IS NULL
+                                UNION ALL
+                    SELECT  g.id
+                      FROM  money.grocery g
+                            JOIN  actor.org_unit_full_path( max_fines.org_unit ) fp ON (g.billing_location = fp.id)
+                      WHERE usr = match_user
+                            AND xact_finish IS NULL
+                                UNION ALL
+                    SELECT  circ.id
+                      FROM  action.circulation circ
+                            JOIN  actor.org_unit_full_path( max_fines.org_unit ) fp ON (circ.circ_lib = fp.id)
+                      WHERE usr = match_user
+                            AND xact_finish IS NULL ) l USING (id);
+
+        IF current_fines >= max_fines.threshold THEN
+            new_sp_row.usr := match_user;
+            new_sp_row.org_unit := max_fines.org_unit;
+            new_sp_row.standing_penalty := 1;
+            RETURN NEXT new_sp_row;
+        END IF;
+    END IF;
+
+    -- Start over for max overdue
+    SELECT INTO tmp_org * FROM actor.org_unit WHERE id = context_org;
+
+    -- Fail if the user has too many overdue items
+    LOOP
+        tmp_grp := user_object.profile;
+        LOOP
+
+            SELECT * INTO max_overdue FROM permission.grp_penalty_threshold WHERE grp = tmp_grp AND penalty = 2 AND org_unit = tmp_org.id;
+
+            IF max_overdue.threshold IS NULL THEN
+                SELECT parent INTO tmp_grp FROM permission.grp_tree WHERE id = tmp_grp;
+            ELSE
+                EXIT;
+            END IF;
+
+            IF tmp_grp IS NULL THEN
+                EXIT;
+            END IF;
+        END LOOP;
+
+        IF max_overdue.threshold IS NOT NULL OR tmp_org.parent_ou IS NULL THEN
+            EXIT;
+        END IF;
+
+        SELECT INTO tmp_org * FROM actor.org_unit WHERE id = tmp_org.parent_ou;
+
+    END LOOP;
+
+    IF max_overdue.threshold IS NOT NULL THEN
+
+        FOR existing_sp_row IN
+                SELECT  *
+                  FROM  actor.usr_standing_penalty
+                  WHERE usr = match_user
+                        AND org_unit = max_overdue.org_unit
+                        AND (stop_date IS NULL or stop_date > NOW())
+                        AND standing_penalty = 2
+                LOOP
+            RETURN NEXT existing_sp_row;
+        END LOOP;
+
+        SELECT  INTO items_overdue COUNT(*)
+          FROM  action.circulation circ
+                JOIN  actor.org_unit_full_path( max_overdue.org_unit ) fp ON (circ.circ_lib = fp.id)
+          WHERE circ.usr = match_user
+            AND circ.checkin_time IS NULL
+            AND circ.due_date < NOW()
+            AND (circ.stop_fines = 'MAXFINES' OR circ.stop_fines IS NULL);
+
+        IF items_overdue >= max_overdue.threshold::INT THEN
+            new_sp_row.usr := match_user;
+            new_sp_row.org_unit := max_overdue.org_unit;
+            new_sp_row.standing_penalty := 2;
+            RETURN NEXT new_sp_row;
+        END IF;
+    END IF;
+
+    -- Start over for max out
+    SELECT INTO tmp_org * FROM actor.org_unit WHERE id = context_org;
+
+    -- Fail if the user has too many checked out items
+    LOOP
+        tmp_grp := user_object.profile;
+        LOOP
+            SELECT * INTO max_items_out FROM permission.grp_penalty_threshold WHERE grp = tmp_grp AND penalty = 3 AND org_unit = tmp_org.id;
+
+            IF max_items_out.threshold IS NULL THEN
+                SELECT parent INTO tmp_grp FROM permission.grp_tree WHERE id = tmp_grp;
+            ELSE
+                EXIT;
+            END IF;
+
+            IF tmp_grp IS NULL THEN
+                EXIT;
+            END IF;
+        END LOOP;
+
+        IF max_items_out.threshold IS NOT NULL OR tmp_org.parent_ou IS NULL THEN
+            EXIT;
+        END IF;
+
+        SELECT INTO tmp_org * FROM actor.org_unit WHERE id = tmp_org.parent_ou;
+
+    END LOOP;
+
+
+    -- Fail if the user has too many items checked out
+    IF max_items_out.threshold IS NOT NULL THEN
+
+        FOR existing_sp_row IN
+                SELECT  *
+                  FROM  actor.usr_standing_penalty
+                  WHERE usr = match_user
+                        AND org_unit = max_items_out.org_unit
+                        AND (stop_date IS NULL or stop_date > NOW())
+                        AND standing_penalty = 3
+                LOOP
+            RETURN NEXT existing_sp_row;
+        END LOOP;
+
+        SELECT  INTO items_out COUNT(*)
+          FROM  action.circulation circ
+                JOIN  actor.org_unit_full_path( max_items_out.org_unit ) fp ON (circ.circ_lib = fp.id)
+          WHERE circ.usr = match_user
+                AND circ.checkin_time IS NULL
+                AND (circ.stop_fines IN ('MAXFINES','LONGOVERDUE') OR circ.stop_fines IS NULL);
+
+           IF items_out >= max_items_out.threshold::INT THEN
+            new_sp_row.usr := match_user;
+            new_sp_row.org_unit := max_items_out.org_unit;
+            new_sp_row.standing_penalty := 3;
+            RETURN NEXT new_sp_row;
+           END IF;
+    END IF;
+
+    -- Start over for collections warning
+    SELECT INTO tmp_org * FROM actor.org_unit WHERE id = context_org;
+
+    -- Fail if the user has a collections-level fine balance
+    LOOP
+        tmp_grp := user_object.profile;
+        LOOP
+            SELECT * INTO max_fines FROM permission.grp_penalty_threshold WHERE grp = tmp_grp AND penalty = 4 AND org_unit = tmp_org.id;
+
+            IF max_fines.threshold IS NULL THEN
+                SELECT parent INTO tmp_grp FROM permission.grp_tree WHERE id = tmp_grp;
+            ELSE
+                EXIT;
+            END IF;
+
+            IF tmp_grp IS NULL THEN
+                EXIT;
+            END IF;
+        END LOOP;
+
+        IF max_fines.threshold IS NOT NULL OR tmp_org.parent_ou IS NULL THEN
+            EXIT;
+        END IF;
+
+        SELECT * INTO tmp_org FROM actor.org_unit WHERE id = tmp_org.parent_ou;
+
+    END LOOP;
+
+    IF max_fines.threshold IS NOT NULL THEN
+
+        FOR existing_sp_row IN
+                SELECT  *
+                  FROM  actor.usr_standing_penalty
+                  WHERE usr = match_user
+                        AND org_unit = max_fines.org_unit
+                        AND (stop_date IS NULL or stop_date > NOW())
+                        AND standing_penalty = 4
+                LOOP
+            RETURN NEXT existing_sp_row;
+        END LOOP;
+
+        SELECT  SUM(f.balance_owed) INTO current_fines
+          FROM  money.materialized_billable_xact_summary f
+                JOIN (
+                    SELECT  r.id
+                      FROM  booking.reservation r
+                            JOIN  actor.org_unit_full_path( max_fines.org_unit ) fp ON (r.pickup_lib = fp.id)
+                      WHERE usr = match_user
+                            AND xact_finish IS NULL
+                                UNION ALL
+                    SELECT  g.id
+                      FROM  money.grocery g
+                            JOIN  actor.org_unit_full_path( max_fines.org_unit ) fp ON (g.billing_location = fp.id)
+                      WHERE usr = match_user
+                            AND xact_finish IS NULL
+                                UNION ALL
+                    SELECT  circ.id
+                      FROM  action.circulation circ
+                            JOIN  actor.org_unit_full_path( max_fines.org_unit ) fp ON (circ.circ_lib = fp.id)
+                      WHERE usr = match_user
+                            AND xact_finish IS NULL ) l USING (id);
+
+        IF current_fines >= max_fines.threshold THEN
+            new_sp_row.usr := match_user;
+            new_sp_row.org_unit := max_fines.org_unit;
+            new_sp_row.standing_penalty := 4;
+            RETURN NEXT new_sp_row;
+        END IF;
+    END IF;
+
+
+    RETURN;
+END;
+$func$ LANGUAGE plpgsql;
+
+COMMIT;
+

Copied: branches/rel_1_6/Open-ILS/src/sql/Pg/upgrade/0106.booking.admin_permissions.sql (from rev 15077, trunk/Open-ILS/src/sql/Pg/upgrade/0106.booking.admin_permissions.sql)
===================================================================
--- branches/rel_1_6/Open-ILS/src/sql/Pg/upgrade/0106.booking.admin_permissions.sql	                        (rev 0)
+++ branches/rel_1_6/Open-ILS/src/sql/Pg/upgrade/0106.booking.admin_permissions.sql	2010-01-13 16:35:03 UTC (rev 15312)
@@ -0,0 +1,15 @@
+BEGIN;
+
+INSERT INTO config.upgrade_log (version) VALUES ('0106'); -- senator
+
+INSERT INTO permission.perm_list (id, code, description) VALUES
+    (352, 'ADMIN_BOOKING_RESOURCE', oils_i18n_gettext(352, 'Enables the user to create/update/delete booking resources', 'ppl', 'description')),
+    (353, 'ADMIN_BOOKING_RESOURCE_TYPE', oils_i18n_gettext(353, 'Enables the user to create/update/delete booking resource types', 'ppl', 'description')),
+    (354, 'ADMIN_BOOKING_RESOURCE_ATTR', oils_i18n_gettext(354, 'Enables the user to create/update/delete booking resource attributes', 'ppl', 'description')),
+    (355, 'ADMIN_BOOKING_RESOURCE_ATTR_MAP', oils_i18n_gettext(355, 'Enables the user to create/update/delete booking resource attribute maps', 'ppl', 'description')),
+    (356, 'ADMIN_BOOKING_RESOURCE_ATTR_VALUE', oils_i18n_gettext(356, 'Enables the user to create/update/delete booking resource attribute values', 'ppl', 'description')),
+    (357, 'ADMIN_BOOKING_RESERVATION', oils_i18n_gettext(357, 'Enables the user to create/update/delete booking reservations', 'ppl', 'description')),
+    (358, 'ADMIN_BOOKING_RESERVATION_ATTR_VALUE_MAP', oils_i18n_gettext(358, 'Enables the user to create/update/delete booking reservation attribute value maps', 'ppl', 'description'))
+;
+
+COMMIT;

Copied: branches/rel_1_6/Open-ILS/src/sql/Pg/upgrade/0109.data.org-settings-booking_alter_due_date.sql (from rev 15104, trunk/Open-ILS/src/sql/Pg/upgrade/0109.data.org-settings-booking_alter_due_date.sql)
===================================================================
--- branches/rel_1_6/Open-ILS/src/sql/Pg/upgrade/0109.data.org-settings-booking_alter_due_date.sql	                        (rev 0)
+++ branches/rel_1_6/Open-ILS/src/sql/Pg/upgrade/0109.data.org-settings-booking_alter_due_date.sql	2010-01-13 16:35:03 UTC (rev 15312)
@@ -0,0 +1,20 @@
+BEGIN;
+
+INSERT INTO config.upgrade_log (version) VALUES ('0109'); --miker
+
+INSERT INTO config.org_unit_setting_type (name, label, description, datatype) VALUES (
+    'circ.booking_reservation.stop_circ',
+    'Disallow circulation of items when they are on booking reserve and that reserve overlaps with the checkout period',
+    'When true, items on booking reserve during the proposed checkout period will not be allowed to circulate unless overridden with the COPY_RESERVED.override permission.',
+    'bool'
+);
+
+INSERT INTO config.org_unit_setting_type (name, label, description, datatype) VALUES (
+    'circ.booking_reservation.default_elbow_room',
+    'Default amount of time by which a circulation should be shortened to allow for booking reservation delivery',
+    'When an item is on booking reserve, and that reservation overlaps with the proposed checkout period, and circulations have not been strictly disallowed on reserved items, Evergreen will attempt to adjust the due date of the circulation for this about of time before the beginning of the reservation period.  If this is not possible because the due date would end up in the past, the circulation is disallowed.',
+    'interval'
+);
+
+COMMIT;
+

Copied: branches/rel_1_6/Open-ILS/src/sql/Pg/upgrade/0110.schema.booking_resource_type.elbow_room.sql (from rev 15104, trunk/Open-ILS/src/sql/Pg/upgrade/0110.schema.booking_resource_type.elbow_room.sql)
===================================================================
--- branches/rel_1_6/Open-ILS/src/sql/Pg/upgrade/0110.schema.booking_resource_type.elbow_room.sql	                        (rev 0)
+++ branches/rel_1_6/Open-ILS/src/sql/Pg/upgrade/0110.schema.booking_resource_type.elbow_room.sql	2010-01-13 16:35:03 UTC (rev 15312)
@@ -0,0 +1,8 @@
+BEGIN;
+
+INSERT INTO config.upgrade_log (version) VALUES ('0110'); --miker
+
+ALTER TABLE booking.resource_type ADD COLUMN elbow_room INTERVAL;
+
+COMMIT;
+

Copied: branches/rel_1_6/Open-ILS/src/sql/Pg/upgrade/0122.data.reservation-shelf-status.sql (from rev 15215, trunk/Open-ILS/src/sql/Pg/upgrade/0122.data.reservation-shelf-status.sql)
===================================================================
--- branches/rel_1_6/Open-ILS/src/sql/Pg/upgrade/0122.data.reservation-shelf-status.sql	                        (rev 0)
+++ branches/rel_1_6/Open-ILS/src/sql/Pg/upgrade/0122.data.reservation-shelf-status.sql	2010-01-13 16:35:03 UTC (rev 15312)
@@ -0,0 +1,7 @@
+BEGIN;
+
+INSERT INTO config.upgrade_log (version) VALUES ('0122'); -- miker
+
+INSERT INTO config.copy_status (id,name) VALUES (15,oils_i18n_gettext(15, 'On reservation shelf', 'ccs', 'name'));
+
+COMMIT;

Copied: branches/rel_1_6/Open-ILS/src/sql/Pg/upgrade/0130.booking.resource_constraint_and_perms.sql (from rev 15264, trunk/Open-ILS/src/sql/Pg/upgrade/0130.booking.resource_constraint_and_perms.sql)
===================================================================
--- branches/rel_1_6/Open-ILS/src/sql/Pg/upgrade/0130.booking.resource_constraint_and_perms.sql	                        (rev 0)
+++ branches/rel_1_6/Open-ILS/src/sql/Pg/upgrade/0130.booking.resource_constraint_and_perms.sql	2010-01-13 16:35:03 UTC (rev 15312)
@@ -0,0 +1,12 @@
+BEGIN;
+
+INSERT INTO config.upgrade_log (version) VALUES ('0130'); -- senator
+
+ALTER TABLE booking.resource DROP CONSTRAINT br_unique;
+ALTER TABLE booking.resource ADD CONSTRAINT br_unique UNIQUE (owner, barcode);
+
+INSERT into permission.perm_list VALUES
+    (360, 'RETRIEVE_RESERVATION_PULL_LIST', oils_i18n_gettext(360, 'Allows a user to retrieve a booking reservation pull list', 'ppl', 'description')),
+    (361, 'CAPTURE_RESERVATION', oils_i18n_gettext(361, 'Allows a user to capture booking reservations', 'ppl', 'description')) ; 
+
+COMMIT;

Copied: branches/rel_1_6/Open-ILS/src/sql/Pg/upgrade/119.schema.booking.transits.sql (from rev 15188, trunk/Open-ILS/src/sql/Pg/upgrade/119.schema.booking.transits.sql)
===================================================================
--- branches/rel_1_6/Open-ILS/src/sql/Pg/upgrade/119.schema.booking.transits.sql	                        (rev 0)
+++ branches/rel_1_6/Open-ILS/src/sql/Pg/upgrade/119.schema.booking.transits.sql	2010-01-13 16:35:03 UTC (rev 15312)
@@ -0,0 +1,15 @@
+BEGIN;
+
+INSERT INTO config.upgrade_log (version) VALUES ('0119'); -- miker
+
+CREATE TABLE action.reservation_transit_copy (
+    reservation    INT REFERENCES booking.reservation (id) ON DELETE SET NULL DEFERRABLE INITIALLY DEFERRED
+) INHERITS (action.transit_copy);
+ALTER TABLE action.reservation_transit_copy ADD PRIMARY KEY (id);
+ALTER TABLE action.reservation_transit_copy ADD CONSTRAINT artc_tc_fkey FOREIGN KEY (target_copy) REFERENCES booking.resource (id) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED;
+CREATE INDEX active_reservation_transit_dest_idx ON "action".reservation_transit_copy (dest);
+CREATE INDEX active_reservation_transit_source_idx ON "action".reservation_transit_copy (source);
+CREATE INDEX active_reservation_transit_cp_idx ON "action".reservation_transit_copy (target_copy);
+
+COMMIT;
+

Modified: branches/rel_1_6/Open-ILS/src/support-scripts/test-scripts/collections.pl
===================================================================
--- branches/rel_1_6/Open-ILS/src/support-scripts/test-scripts/collections.pl	2010-01-13 16:30:42 UTC (rev 15311)
+++ branches/rel_1_6/Open-ILS/src/support-scripts/test-scripts/collections.pl	2010-01-13 16:35:03 UTC (rev 15312)
@@ -80,6 +80,7 @@
 	my $user		= $xact_data->{usr}->{__data__};
 	my $circs	= $xact_data->{transactions}->{circulations};
 	my $grocery = $xact_data->{transactions}->{grocery};
+	my $reservations = $xact_data->{transactions}->{reservations};
 
 
 	# --------------------------------------------------------------------
@@ -96,7 +97,7 @@
 			$a->{post_code}) . "\n";
 	}
 
-	print_xact_details($_->{__data__}) for (@$circs, @$grocery);
+	print_xact_details($_->{__data__}) for (@$circs, @$grocery, @$reservations);
 
 	print "\n" . '-'x60 . "\n";
 }

Copied: branches/rel_1_6/Open-ILS/web/css/skin/default/booking.css (from rev 15207, trunk/Open-ILS/web/css/skin/default/booking.css)
===================================================================
--- branches/rel_1_6/Open-ILS/web/css/skin/default/booking.css	                        (rev 0)
+++ branches/rel_1_6/Open-ILS/web/css/skin/default/booking.css	2010-01-13 16:35:03 UTC (rev 15312)
@@ -0,0 +1,89 @@
+div#brsrc_available_outer {
+    width: 50%;
+    float: left;
+    border-right: 1px solid #999999;
+}
+div#bra_and_brav {
+}
+div#reserve_right_side {
+    float: right;
+    width: 49%;
+    padding-left: 4px;
+}
+div#reserve_under {
+    clear: both;
+}
+div#reserve_datetime_start {
+    padding-bottom: 6px;
+}
+div#reserve_datetime_end {
+    padding-bottom: 6px;
+    border-bottom: 1px solid #999999;
+}
+label.bra {
+    font-style: italic;
+    padding-right: 12px;
+}
+h1.booking, h2.booking, h3.booking {
+    margin: 0;
+    padding-top: 0;
+    padding-bottom: 8px;
+}
+h1.booking { font-size: 16pt; font-weight: bold; }
+select#brsrc_list {
+    width: 90%;
+}
+label.reserve_datetime {
+    font-style: italic;
+    margin-bottom: 2px;
+}
+id#patron_barcode {
+    width: 150px;
+}
+div.nice_vertical_padding {
+    padding-top: 6px;
+    padding-bottom: 6px;
+}
+span.two_buttons {
+    text-align: center;
+}
+option.forced_unavailable {
+    background-color: #ffcccc;
+    color: #990000;
+    font-weight: bold;
+    font-style: italic;
+}
+input#arbitrary_resource { margin-left: 8px; margin-right: 8px; }
+div#or { font-size: 12pt; font-weight: bold; }
+input#interval_in_days { width: 75px; }
+table#the_table thead tr th {
+    vertical-align: top;
+    background-color: #dddddd;
+    color: #000000;
+    font-weight: bold;
+    padding: 0 6px 0 6px;
+    border-left: 1px #cccccc solid;
+    border-right: 1px #333333 solid;
+}
+tbody#the_table_body td {
+    vertical-align: top;
+    padding: 2px;
+    border-top: 1px #cccccc solid;
+    border-left: 1px #cccccc solid;
+    border-bottom: 1px #333333 solid;
+    border-right: 1px #333333 solid;
+}
+.capture_failure { color: #cc0000; }
+.capture_success { color: #00cc00; }
+ul { list-style-type: square; }
+.capture_info { font-size: 12pt; font-weight: bold; margin-bottom: 4px; }
+.transit_notice {
+    font-size: 12pt; font-weight: bold; color: #ff6666;
+    margin-bottom: 4px; margin-top: 4px;
+}
+span#result_display { margin-left: 12px; }
+div#contains_misc_controls { text-align:right; }
+div#patron_info { font-size: 12pt; font-weight: bold; }
+div#no_ready_bresv, div#no_out_bresv, div#no_in_bresv {
+    font-style: italic;
+}

Copied: branches/rel_1_6/Open-ILS/web/js/dojo/openils/booking (from rev 15207, trunk/Open-ILS/web/js/dojo/openils/booking)

Copied: branches/rel_1_6/Open-ILS/web/js/dojo/openils/booking/nls/capture.js (from rev 15264, trunk/Open-ILS/web/js/dojo/openils/booking/nls/capture.js)
===================================================================
--- branches/rel_1_6/Open-ILS/web/js/dojo/openils/booking/nls/capture.js	                        (rev 0)
+++ branches/rel_1_6/Open-ILS/web/js/dojo/openils/booking/nls/capture.js	2010-01-13 16:35:03 UTC (rev 15312)
@@ -0,0 +1,24 @@
+{
+    'FAILURE': "Capture failed",
+    'SUCCESS': "Capture succeeded",
+    'UNKNOWN_PROBLEM': "An unknown problem occurred during capture attempt.",
+    'CAPTURED_NOTHING': "Didn't capture anything.",
+    'NO_PAYLOAD':
+        "We did not receive further information from the server about this" +
+        "attempt to capture.",
+    'HERES_WHAT_WE_KNOW':
+        "The following information is available about the failed capture:",
+    'CAPTURE_INFO': "Capture Information",
+    'CAPTURE_BRESV_DATES': "Reservation time:",
+    'CAPTURE_BRESV_BRSRC': "Resource barcode:",
+    'CAPTURE_BRESV_PICKUP_LIB': "Pickup library:",
+    'CAPTURE_BRESV_PATRON_BARCODE': "Patron barcode:",
+    'CAPTURE_CAUSES_TRANSIT': "This item is now in transit!",
+    'CAPTURE_TRANSIT_SOURCE': "From:",
+    'CAPTURE_TRANSIT_DEST': "To:",
+
+    'AUTO_capture_heading': "Capture Reserved Resources",
+    'AUTO_resource_barcode': "Enter barcode:",
+    'AUTO_pickup_lib_selector': "Pickup library:",
+    'AUTO_ATTR_VALUE_capture': "Capture"
+}

Copied: branches/rel_1_6/Open-ILS/web/js/dojo/openils/booking/nls/pickup_and_return.js (from rev 15285, trunk/Open-ILS/web/js/dojo/openils/booking/nls/pickup_and_return.js)
===================================================================
--- branches/rel_1_6/Open-ILS/web/js/dojo/openils/booking/nls/pickup_and_return.js	                        (rev 0)
+++ branches/rel_1_6/Open-ILS/web/js/dojo/openils/booking/nls/pickup_and_return.js	2010-01-13 16:35:03 UTC (rev 15312)
@@ -0,0 +1,38 @@
+{
+    'NO_PATRON_BARCODE': "Please enter a patron barcode.",
+    'RESERVATIONS_NO_RESPONSE':
+        "No response from server when asking for reservations.",
+    'RESERVATIONS_ERROR':
+        "Error communicating with server (asking for reservations):",
+    'PICKUP_NO_RESPONSE': "No response from server when attempting pickup.",
+    'PICKUP_ERROR': "Error communicating with server (attempting pickup):",
+    'RETURN_NO_RESPONSE': "No response from server when attempting return.",
+    'RETURN_ERROR': "Error communicating with server (attempting return):",
+    'RETURN_SUCCESS': "Return successful.",
+    'SELECT_SOMETHING': "You have not selected any reservations.",
+    'NO_SUCH_RETURNABLE_RESOURCE': "No such returnable resource.",
+    'RETURNABLE_RESOURCE_ERROR': "Error looking up returnable resource:",
+    'NOTICE_CHANGE_OF_PATRON':
+        "Note that the resource scanned was out on reservation to different\n" +
+        "patron than the last resource you scanned.  If this is not\n" +
+        "expected, stop to examine outstanding reservations for your patron\n" +
+        "or on the resource.",
+
+    'AUTO_h1': "Reservations Pickup",
+    'AUTO_return_h1': "Reservations Return",
+    'AUTO_patron_barcode': "Enter patron barcode:",
+    'AUTO_barcode_type': "Return by barcode of",
+    'AUTO_in_bresv': "Patron has returned these resources today:",
+    'AUTO_ready_bresv': "Patron has these reservations ready for pickup:",
+    'AUTO_out_bresv': "Patron currently has these reservations out:",
+    'AUTO_no_ready_bresv':
+        "Patron has no reservations ready for pickup at this time.",
+    'AUTO_no_out_bresv': "Patron has no more reservations out at this time.",
+    'AUTO_no_in_bresv': "Patron has not returned any resources today.",
+    'AUTO_patron': "Patron",
+    'AUTO_resource': "Resource",
+    'AUTO_ATTR_VALUE_go': "Go",
+    'AUTO_ATTR_VALUE_reset': "Clear / New Patron",
+    'AUTO_ATTR_VALUE_pickup': "Pick up",
+    'AUTO_ATTR_VALUE_return': "Return"
+}

Copied: branches/rel_1_6/Open-ILS/web/js/dojo/openils/booking/nls/pull_list.js (from rev 15247, trunk/Open-ILS/web/js/dojo/openils/booking/nls/pull_list.js)
===================================================================
--- branches/rel_1_6/Open-ILS/web/js/dojo/openils/booking/nls/pull_list.js	                        (rev 0)
+++ branches/rel_1_6/Open-ILS/web/js/dojo/openils/booking/nls/pull_list.js	2010-01-13 16:35:03 UTC (rev 15312)
@@ -0,0 +1,20 @@
+{
+    'PULL_LIST_NO_RESPONSE': "No response from server trying to get pull list!",
+    'PULL_LIST_ERROR': "Error trying to fetch pull list: ",
+    'COPY_LOOKUP_NO_RESPONSE': "No response looking up copies by barcode",
+    'COPY_LOOKUP_ERROR': "Error looking up copies by barcode: ",
+    'COPY_MISSING': "Unexpected error: No information for copy: ",
+
+    'AUTO_no_results': "No results",
+    'AUTO_owning_lib_selector': "See pull list for library:",
+    'AUTO_pull_list_title': "Booking Pull List",
+    'AUTO_interval_in_days': "Generate list for this many days hence: ",
+    'AUTO_ATTR_VALUE_fetch': "Fetch",
+    'AUTO_th_title_or_name': "Title or name",
+    'AUTO_th_barcode': "Barcode",
+    'AUTO_th_call_number': "Call number",
+    'AUTO_th_copy_location': "Copy location",
+    'AUTO_th_copy_number': "Copy number",
+    'AUTO_th_resv_details': "Reservation details",
+    'AUTO_ATTR_VALUE_print': "Print",
+}

Modified: branches/rel_1_6/Open-ILS/web/js/dojo/openils/booking/nls/reservation.js
===================================================================
--- trunk/Open-ILS/web/js/dojo/openils/booking/nls/reservation.js	2009-12-21 16:21:48 UTC (rev 15207)
+++ branches/rel_1_6/Open-ILS/web/js/dojo/openils/booking/nls/reservation.js	2010-01-13 16:35:03 UTC (rev 15312)
@@ -5,14 +5,16 @@
     'SELECT_A_BRSRC_THEN': "Select a resource from the big list above.",
     'CREATE_BRESV_LOCAL_ERROR': "Exception trying to create reservation: ",
     'CREATE_BRESV_SERVER_ERROR': "Server error trying to create reservation: ",
-    'CREATE_BRESV_SERVER_NO_RESPONSE': "No response from server after trying " +
-        "to create reservation: ",
+    'CREATE_BRESV_SERVER_NO_RESPONSE':
+        "No response from server after trying to create reservation.",
     /* FIXME: Users aren't likely to be able to do anything with the following
      * message.  Figure out a way to do something more helpful.
      */
     'CREATE_BRESV_OK_MISSING_TARGET': function(n, m) {
         return "Created " + n + " reservation(s), but " + m + " of these " +
-            "couldn't target any resources.";
+            "couldn't target any resources.\n\n" +
+            "This means that it won't be possible to fulfill some of these\n" +
+            "reservations until a suitable resource becomes available.";
     },
     'CREATE_BRESV_OK': function(n) {
         return "Created " + n + " reservation" + (n == 1 ? "" : "s") + ".";
@@ -20,25 +22,42 @@
     'WHERES_THE_BARCODE': "Enter a patron's barcode to make a reservation.",
     'ACTOR_CARD_NOT_FOUND': "Patron barcode not found. Please try again.",
     'GET_BRESV_LIST_ERR': "Error while retrieving reservation list: ",
-    'GET_BRESV_LIST_NO_RESULT': "No results from server " +
-        "retrieving reservation list.",
+    'GET_BRESV_LIST_NO_RESULT':
+        "No results from server retrieving reservation list.",
     'OUTSTANDING_BRESV': "Outstanding reservations for patron",
     'UNTARGETED': "None targeted",
-    'GET_PATRON_NO_RESULT': "No server response after attempting to " +
-        "look up patron by barcode.",
+    'GET_PATRON_NO_RESULT':
+        "No server response after attempting to look up patron by barcode.",
     'HERE_ARE_EXISTING_BRESV': "Existing reservations for",
+    'NO_EXISTING_BRESV': "This user has no existing reservations at this time.",
+    'NO_USABLE_BRSRC':
+        "No reservable resources.  Adjust start and end time\n" +
+        "until a resource is available for reservation.",
     'CXL_BRESV_SUCCESS': function(n) {
         return ("Canceled " + n + " reservation" + (n == 1 ? "" : "s") + ".");
     },
-    'CXL_BRESV_FAILURE': "Error canceling reservations.",
-    'CXL_BRESV_SELECT_SOMETHING': "You have not selected any reservations to " +
-        "cancel.",
+    'CXL_BRESV_FAILURE': "Error canceling reservations; server silent.",
+    'CXL_BRESV_FAILURE2': "Error canceling reservations:\n",
+    'CXL_BRESV_SELECT_SOMETHING':
+        "You have not selected any reservations to cancel.",
+    'NEED_EXACTLY_ONE_BRT_PASSED_IN':
+        "Can't book multiple resource types at once",
+    'COULD_NOT_RETRIEVE_BRT_PASSED_IN':
+        "Error retrieving booking resource type",
+    'INVALID_TS_RANGE':
+        "You must choose a valid start and end time for the reservation.",
+    'BRSRC_NOT_FOUND': "Could not locate that resource.",
+    'BRSRC_RETRIVE_ERROR': "Error retrieving resource: ",
+    'ON_FLY_NO_RESPONSE':
+        "No response from server attempting to make item a bookable resource.",
+    'ON_FLY_ERROR':
+        "Error attempting to make item a bookable resource:",
     'ANY': "ANY",
 
     'AUTO_choose_a_brt': "Choose a Bookable Resource Type",
     'AUTO_i_need_this_resource': "I need this resource...",
-    'AUTO_starting_at': "Starting at",
-    'AUTO_ending_at': "and ending at",
+    'AUTO_starting_at': "Between",
+    'AUTO_ending_at': "and",
     'AUTO_with_these_attr': "With these attributes:",
     'AUTO_patron_barcode': "Reserve to patron barcode:",
     'AUTO_ATTR_VALUE_next': "Next",
@@ -49,5 +68,15 @@
     'AUTO_bresv_grid_type': "Type",
     'AUTO_bresv_grid_resource': "Resource",
     'AUTO_bresv_grid_start_time': "Start time",
-    'AUTO_bresv_grid_end_time': "End time"
+    'AUTO_bresv_grid_end_time': "End time",
+    'AUTO_brt_noncat_only': "Show only non-cataloged bookable resource types",
+    'AUTO_arbitrary_resource':
+        "Enter the barcode of a cataloged, bookable resource:",
+    'AUTO_explain_bookable':
+        "To reserve an item that is not yet registered as a bookable " +
+        "resource, find it in the catalog or under <em>Display Item</em>, and "+
+        "select <em>Make Item Bookable</em> or <em>Book Item Now</em> there.",
+    'AUTO_pickup_lib_selector':
+        "Choose the pickup library for this reservation:",
+    'AUTO_or': '- Or -'
 }

Copied: branches/rel_1_6/Open-ILS/web/js/ui/default/booking (from rev 15207, trunk/Open-ILS/web/js/ui/default/booking)

Copied: branches/rel_1_6/Open-ILS/web/js/ui/default/booking/capture.js (from rev 15264, trunk/Open-ILS/web/js/ui/default/booking/capture.js)
===================================================================
--- branches/rel_1_6/Open-ILS/web/js/ui/default/booking/capture.js	                        (rev 0)
+++ branches/rel_1_6/Open-ILS/web/js/ui/default/booking/capture.js	2010-01-13 16:35:03 UTC (rev 15312)
@@ -0,0 +1,174 @@
+dojo.require("openils.User");
+dojo.require("openils.widget.OrgUnitFilteringSelect");
+dojo.requireLocalization("openils.booking", "capture");
+
+const CAPTURE_FAILURE = 0;
+const CAPTURE_SUCCESS = 1;
+const CAPTURE_UNKNOWN = 2;
+
+var localeStrings = dojo.i18n.getLocalization("openils.booking", "capture");
+
+function CaptureDisplay(element) { this.element = element; }
+CaptureDisplay.prototype.no_payload = function() {
+    this.element.appendChild(document.createTextNode(localeStrings.NO_PAYLOAD));
+};
+CaptureDisplay.prototype.dump = function(payload) {
+    var div = document.createElement("div");
+    div.appendChild(document.createTextNode(localeStrings.HERES_WHAT_WE_KNOW));
+    this.element.appendChild(div);
+
+    var ul = document.createElement("ul");
+    for (var k in payload) {
+        var li = document.createElement("li");
+        li.appendChild(document.createTextNode(k + ": " + payload[k]));
+        ul.appendChild(li);
+    }
+    this.element.appendChild(ul);
+};
+CaptureDisplay.prototype.generate_transit_display = function(payload) {
+    var super_div = document.createElement("div");
+    var div;
+
+    div = document.createElement("div");
+    div.appendChild(document.createTextNode(
+        localeStrings.CAPTURE_CAUSES_TRANSIT
+    ));
+    div.setAttribute("class", "transit_notice");
+    super_div.appendChild(div);
+
+    div = document.createElement("div");
+    div.appendChild(document.createTextNode(
+        localeStrings.CAPTURE_TRANSIT_SOURCE + " " +
+        fieldmapper.aou.findOrgUnit(payload.transit.source()).shortname()
+    ));
+    super_div.appendChild(div);
+
+    div = document.createElement("div");
+    div.appendChild(document.createTextNode(
+        localeStrings.CAPTURE_TRANSIT_DEST + " " +
+        fieldmapper.aou.findOrgUnit(payload.transit.dest()).shortname()
+    ));
+    super_div.appendChild(div);
+
+    return super_div;
+};
+CaptureDisplay.prototype.display_with_transit_info = function(payload) {
+    var div;
+
+    div = document.createElement("div");
+    div.appendChild(document.createTextNode(localeStrings.CAPTURE_INFO));
+    div.setAttribute("class", "capture_info");
+    this.element.appendChild(div);
+
+    if (payload.catalog_item) {
+        div = document.createElement("div");
+        div.appendChild(document.createTextNode(
+            localeStrings.CAPTURE_BRESV_BRSRC + " " +
+            payload.catalog_item.barcode()
+        ));
+        this.element.appendChild(div);
+    }
+
+    div = document.createElement("div");
+    div.appendChild(document.createTextNode(
+        localeStrings.CAPTURE_BRESV_DATES + " " +
+        humanize_timestamp_string(payload.reservation.start_time()) + " - " +
+        humanize_timestamp_string(payload.reservation.end_time())
+    ));
+    this.element.appendChild(div);
+
+    div = document.createElement("div");
+    div.appendChild(document.createTextNode(
+        localeStrings.CAPTURE_BRESV_PICKUP_LIB + " " +
+        fieldmapper.aou.findOrgUnit(
+            payload.reservation.pickup_lib()
+        ).shortname()
+    ));
+    this.element.appendChild(div);
+
+    div = document.createElement("div");
+    div.appendChild(document.createTextNode(
+        localeStrings.CAPTURE_BRESV_PATRON_BARCODE + " " +
+        payload.reservation.usr().card().barcode()
+    ));
+    this.element.appendChild(div);
+
+    if (payload.transit) {
+        this.element.appendChild(this.generate_transit_display(payload));
+    }
+};
+CaptureDisplay.prototype.clear = function() { this.element.innerHTML = ""; };
+CaptureDisplay.prototype.load = function(payload) {
+    try {
+        this.element.appendChild(document.createElement("hr"));
+        if (!payload) {
+            this.no_payload();
+        } else if (!payload.fail_cause && payload.captured) {
+            this.display_with_transit_info(payload);
+        } else {
+            this.dump(payload);
+        }
+    } catch (E) {
+        alert(E); /* XXX */
+    }
+};
+
+var capture_display;
+var last_result;
+
+function clear_for_next() {
+    if (last_result == CAPTURE_SUCCESS) {
+        last_result = undefined;
+        document.getElementById("result_display").innerHTML = "";
+        document.getElementById("resource_barcode").value = "";
+    }
+}
+
+function capture() {
+    var barcode = document.getElementById("resource_barcode").value;
+    var result = fieldmapper.standardRequest(
+        [
+            "open-ils.booking",
+            "open-ils.booking.resources.capture_for_reservation"
+        ],
+        [xulG.auth.session.key, barcode]
+    );
+
+    if (result && result.ilsevent !== undefined) {
+        if (result.payload && result.payload.captured > 0) {
+            capture_display.load(result.payload);
+            return CAPTURE_SUCCESS;
+        } else {
+            capture_display.load(result.payload);
+            alert(my_ils_error(localeStrings.CAPTURED_NOTHING, result));
+            return CAPTURE_FAILURE;
+        }
+    } else {
+        return CAPTURE_UNKNOWN;
+    }
+}
+
+function attempt_capture() {
+    var rd = document.getElementById("result_display");
+    capture_display.clear();
+    switch(last_result = capture()) {
+        case CAPTURE_FAILURE:
+            rd.setAttribute("class", "capture_failure");
+            rd.innerHTML = localeStrings.FAILURE;
+            break;
+        case CAPTURE_SUCCESS:
+            rd.setAttribute("class", "capture_success");
+            rd.innerHTML = localeStrings.SUCCESS;
+            break;
+        default:
+            alert(localeStrings.UNKNOWN_PROBLEM);
+            break;
+    }
+}
+
+function my_init() {
+    init_auto_l10n(document.getElementById("auto_l10n_start_here"));
+    capture_display = new CaptureDisplay(
+        document.getElementById("capture_display")
+    );
+}

Copied: branches/rel_1_6/Open-ILS/web/js/ui/default/booking/common.js (from rev 15247, trunk/Open-ILS/web/js/ui/default/booking/common.js)
===================================================================
--- branches/rel_1_6/Open-ILS/web/js/ui/default/booking/common.js	                        (rev 0)
+++ branches/rel_1_6/Open-ILS/web/js/ui/default/booking/common.js	2010-01-13 16:35:03 UTC (rev 15312)
@@ -0,0 +1,71 @@
+/* Quick and dirty way to localize some strings; not recommended for reuse.
+ * I'm sure dojo provides a better mechanism for this, but at the moment
+ * this is faster to implement anew than figuring out the Right way to do
+ * the same thing w/ dojo.
+ */
+function init_auto_l10n(el) {
+    function do_it(myel, cls) {
+        if (cls) {
+            var clss = cls.split(" ");
+            for (var k in clss) {
+                var parts = clss[k].match(/^AUTO_ATTR_([A-Z]+)_.+$/);
+                if (parts && localeStrings[clss[k]]) {
+                    myel.setAttribute(
+                        parts[1].toLowerCase(), localeStrings[clss[k]]
+                    );
+                } else if (clss[k].match(/^AUTO_/) && localeStrings[clss[k]]) {
+                    myel.innerHTML = localeStrings[clss[k]];
+                }
+            }
+        }
+    }
+
+    for (var i in el.attributes) {
+        if (el.attributes[i].nodeName == "class") {
+            do_it(el, el.attributes[i].value);
+            break;
+        }
+    }
+    for (var i in el.childNodes) {
+        if (el.childNodes[i].nodeType == 1) { // element node?
+            init_auto_l10n(el.childNodes[i]); // recurse!
+        }
+    }
+}
+
+function get_keys(L) { var K = []; for (var k in L) K.push(k); return K; }
+function hide_dom_element(e) { e.style.display = "none"; };
+function reveal_dom_element(e) { e.style.display = ""; };
+function formal_name(u) {
+    var name = u.family_name() + ", " + u.first_given_name();
+    if (u.second_given_name())
+        name += (" " + u.second_given_name());
+    return name;
+}
+function humanize_timestamp_string(ts) {
+    /* For now, this discards time zones. */
+    var parts = ts.split("T");
+    var timeparts = parts[1].split("-")[0].split(":");
+    return parts[0] + " " + timeparts[0] + ":" + timeparts[1];
+}
+function is_ils_event(e) { return (e.ilsevent != undefined); }
+function is_ils_actor_card_error(e) {
+    return (e.textcode == "ACTOR_CARD_NOT_FOUND");
+}
+function my_ils_error(leader, e) {
+    var s = leader + "\n";
+    var keys = [
+        "ilsevent", "desc", "textcode", "servertime", "pid", "stacktrace"
+    ];
+    for (var i in keys) {
+        if (e[keys[i]]) s += ("\t" + keys[i] + ": " + e[keys[i]] + "\n");
+    }
+    return s;
+}
+function set_datagrid_empty_store(grid, flattener) {
+    grid.setStore(
+        new dojo.data.ItemFileReadStore(
+            {"data": flattener([])}
+        )
+    );
+}

Copied: branches/rel_1_6/Open-ILS/web/js/ui/default/booking/pickup.js (from rev 15285, trunk/Open-ILS/web/js/ui/default/booking/pickup.js)
===================================================================
--- branches/rel_1_6/Open-ILS/web/js/ui/default/booking/pickup.js	                        (rev 0)
+++ branches/rel_1_6/Open-ILS/web/js/ui/default/booking/pickup.js	2010-01-13 16:35:03 UTC (rev 15312)
@@ -0,0 +1,32 @@
+dojo.requireLocalization("openils.booking", "pickup_and_return");
+var localeStrings = dojo.i18n.getLocalization(
+    "openils.booking", "pickup_and_return"
+);
+var p;
+
+function react_to_pass_in(opts) {
+    if (opts && opts.patron_barcode) {
+        p.populate({"patron": opts.patron_barcode});
+
+        hide_dom_element(
+            document.getElementById("contains_barcode_control")
+        );
+        document.getElementById("patron_barcode").value = opts.patron_barcode;
+        p._extra_resetting = function() {
+            reveal_dom_element(
+                document.getElementById("contains_barcode_control")
+            );
+        };
+    }
+}
+
+function my_init() {
+    p = new Populator({
+        "ready": ready_bresv,
+        "out": out_bresv,
+        "patron": document.getElementById("patron_info")
+    }, document.getElementById("patron_barcode"));
+    init_auto_l10n(document.getElementById("auto_l10n_start_here"));
+
+    react_to_pass_in(xulG.bresv_interface_opts);
+}

Copied: branches/rel_1_6/Open-ILS/web/js/ui/default/booking/populator.js (from rev 15285, trunk/Open-ILS/web/js/ui/default/booking/populator.js)
===================================================================
--- branches/rel_1_6/Open-ILS/web/js/ui/default/booking/populator.js	                        (rev 0)
+++ branches/rel_1_6/Open-ILS/web/js/ui/default/booking/populator.js	2010-01-13 16:35:03 UTC (rev 15312)
@@ -0,0 +1,283 @@
+/* This module depends on common.js being loaded, as well as the
+ * localization (Dojo/nls) for pickup and return . */
+
+dojo.require("dojo.data.ItemFileReadStore");
+
+function Populator(widgets, primary_input) {
+    this.widgets = widgets;
+
+    this.all = [];
+    for (var k in widgets) this.all.push(k);
+
+    if (primary_input) this.primary_input = primary_input;
+
+    this.prepare_cache();
+    this.prepare_empty_stores();
+    this.reset();
+}
+Populator.prototype.prepare_cache = function(data) {
+    this.cache = {};
+    for (var k in this.all) this.cache[this.all[k]] = {};
+};
+Populator.prototype.prepare_empty_stores = function(data) {
+    this.empty_stores = {};
+
+    for (var i in this.all) {
+        var name = this.all[i];
+
+        if (this.widgets[name] && this["flatten_" + name]) {
+            this.empty_stores[name] =
+                new dojo.data.ItemFileReadStore({
+                    "data": this["flatten_" + name]([])
+                });
+            this.widgets[name].setStore(this.empty_stores[name]);
+        }
+    }
+};
+Populator.prototype.flatten_ready = function(data) {
+    return {
+        "label": "id",
+        "identifier": "id",
+        "items": data.map(function(o) {
+            return {
+                "id": o.id(),
+                "type": o.target_resource_type().name(),
+                "resource": o.current_resource().barcode(),
+                "start_time": humanize_timestamp_string(o.start_time()),
+                "end_time": humanize_timestamp_string(o.end_time())
+            };
+        })
+    };
+};
+Populator.prototype.flatten_out = function(data) {
+    return {
+        "label": "id",
+        "identifier": "id",
+        "items": data.map(function(o) {
+            return {
+                "id": o.id(),
+                "type": o.target_resource_type().name(),
+                "resource": o.current_resource().barcode(),
+                "pickup_time": humanize_timestamp_string(o.pickup_time()),
+                "end_time": humanize_timestamp_string(o.end_time())
+            };
+        })
+    };
+};
+Populator.prototype.flatten_in = function(data) {
+    return {
+        "label": "id",
+        "identifier": "id",
+        "items": data.map(function(o) {
+            return {
+                "id": o.id(),
+                "type": o.target_resource_type().name(),
+                "resource": o.current_resource().barcode(),
+                "due_time": humanize_timestamp_string(o.end_time()),
+                "return_time": humanize_timestamp_string(o.return_time())
+            };
+        })
+    };
+};
+Populator.prototype.reveal_container = function(widget) {
+    var el = document.getElementById("contains_" + widget.id);
+    if (el) reveal_dom_element(el);
+};
+Populator.prototype.hide_container = function(widget) {
+    var el = document.getElementById("contains_" + widget.id);
+    if (el) hide_dom_element(el);
+};
+Populator.prototype.populate_ready = function(data) {
+    return this._populate_any_resv_grid(data, "ready");
+};
+Populator.prototype.populate_out = function(data) {
+    return this._populate_any_resv_grid(data, "out");
+};
+Populator.prototype.populate_in = function(data) {
+    return this._populate_any_resv_grid(data, "in");
+};
+Populator.prototype._populate_any_resv_grid = function(data, which) {
+    var flattener = this["flatten_" + which];
+    var widget = this.widgets[which];
+    var cache = this.cache[which];
+    var empty_store = this.empty_stores[which];
+
+    this.reveal_container(widget);
+
+    if (!data || !data.length) {
+        widget.setStore(empty_store);
+        this.toggle_anyness(false, which);
+    } else {
+        for (var i in data) cache[data[i].id()] = data[i];
+
+        widget.setStore(
+            new dojo.data.ItemFileReadStore({"data": flattener(data)})
+        );
+
+        this.toggle_anyness(true, which);
+
+        /* Arrrgh! Horrid but necessary: */
+        setTimeout(function() { widget.sort(); }, 100);
+    }
+};
+Populator.prototype.populate_patron = function(data) {
+    var h2 = document.createElement("h2");
+    h2.setAttribute("class", "booking");
+    h2.appendChild(document.createTextNode(formal_name(data)));
+
+    this.widgets.patron.innerHTML = "";
+    this.widgets.patron.appendChild(h2);
+
+    this.reveal_container(this.widgets.patron);
+    /* Maybe add patron's home OU or something here later... */
+};
+Populator.prototype.return_by_resource = function(barcode) {
+    /* XXX instead of talking to the server every time we do this, we could
+     * also check the "out" cache, iff we have one.  */
+    var r = fieldmapper.standardRequest(
+        ["open-ils.booking",
+        "open-ils.booking.reservations.by_returnable_resource_barcode"],
+        [xulG.auth.session.key, barcode]
+    );
+    if (!r || r.length < 1) {
+        alert(localeStrings.NO_SUCH_RETURNABLE_RESOURCE);
+    } else if (is_ils_event(r)) {
+        alert(my_ils_error(localeStrings.RETURNABLE_RESOURCE_ERROR, r));
+    } else {
+        try {
+            var new_barcode = r.usr().card().barcode();
+        } catch (E) {
+            alert(localeStrings.RETURN_ERROR + "\nr: " + js2JSON(r) + "\n" + E);
+            return;
+        }
+        if (this.patron_barcode && this.patron_barcode != new_barcode) {
+            /* XXX make this more subtle, i.e. flash something in background */
+            alert(localeStrings.NOTICE_CHANGE_OF_PATRON);
+        }
+        this.patron_barcode = new_barcode;
+        var ret = this.return(r);
+        if (!ret) {
+            alert(localeStrings.RETURN_NO_RESPONSE);
+        } else if (is_ils_event(ret) && ret.textcode != "SUCCESS") {
+            alert(my_ils_error(localeStrings.RETURN_ERROR, ret));
+        } else {
+            /* XXX speedbump should go, but something has to happen else
+             * there's no indication to staff that anything happened when
+             * starting from a fresh (blank) return interface.
+             */
+            alert(localeStrings.RETURN_SUCCESS);
+        }
+        this.populate(); /* Won't recurse with no args. All is well. */
+    }
+};
+Populator.prototype.populate = function(barcode, which) {
+    if (barcode) {
+        if (barcode.patron) {
+            this.patron_barcode = barcode.patron;
+        }
+        else if (barcode.resource) { /* resource OR patron, not both */
+            if (!this.return_by_resource(barcode.resource))
+                return;
+        }
+    }
+    if (!this.patron_barcode) {
+        alert(localeStrings.NO_PATRON_BARCODE);
+        return;
+    }
+
+    if (!which) which = this.all;
+
+    var result = fieldmapper.standardRequest(
+        ["open-ils.booking", "open-ils.booking.reservations.get_captured"],
+        [xulG.auth.session.key, this.patron_barcode, which]
+    );
+
+    if (!result) {
+        this.patron_barcode = undefined;
+        alert(localeStrings.RESERVATIONS_NO_RESPONSE);
+    } else if (is_ils_event(result)) {
+        this.patron_barcode = undefined;
+        alert(my_ils_error(localeStrings.RESERVATIONS_ERROR, result));
+    } else {
+        for (var k in result)
+            this["populate_" + k](result[k]);
+    }
+};
+Populator.prototype.toggle_anyness = function(any, which) {
+    var widget = this.widgets[which].domNode;
+    var empty_alternate = document.getElementById("no_" + widget.id);
+    var controls = document.getElementById("controls_" + widget.id);
+    if (any) {
+        reveal_dom_element(widget);
+        if (empty_alternate) hide_dom_element(empty_alternate);
+        if (controls) reveal_dom_element(controls);
+    } else {
+        hide_dom_element(widget);
+        if (empty_alternate) reveal_dom_element(empty_alternate);
+        if (controls) hide_dom_element(controls);
+    }
+};
+Populator.prototype.pickup = function(reservation) {
+    return fieldmapper.standardRequest(
+        ["open-ils.circ", "open-ils.circ.reservation.pickup"],
+        [xulG.auth.session.key, {
+            "patron_barcode": this.patron_barcode,
+            "reservation": reservation
+        }]
+    );
+};
+Populator.prototype.return = function(reservation) {
+    return fieldmapper.standardRequest(
+        ["open-ils.circ", "open-ils.circ.reservation.return"],
+        [xulG.auth.session.key, {
+            "patron_barcode": this.patron_barcode,
+            "reservation": reservation.id()
+            /* yeah just id here ------^; lack of parallelism */
+        }]
+    );
+};
+Populator.prototype.act_on_selected = function(how, which) {
+    var widget = this.widgets[which];
+    var cache = this.cache[which];
+    var no_response_msg = localeStrings[how.toUpperCase() + "_NO_RESPONSE"];
+    var error_msg = localeStrings[how.toUpperCase() + "_ERROR"];
+
+    var selected_id_list =
+        widget.selection.getSelected().map(function(o) { return o.id[0]; });
+
+    if (!selected_id_list || !selected_id_list.length) {
+        alert(localeStrings.SELECT_SOMETHING);
+        return;
+    }
+
+    var reservations = selected_id_list.map(function(o) { return cache[o]; });
+
+    /* Do we have to process these one at a time?  I think so... */
+    for (var i in reservations) {
+        var result = this[how](reservations[i]);
+        if (!result) {
+            alert(no_response_msg);
+        } else if (is_ils_event(result) && result.textcode != "SUCCESS") {
+            alert(my_ils_error(error_msg, result));
+        } else {
+            continue;
+        }
+        break;
+    }
+
+    this.populate();
+};
+Populator.prototype.reset = function() {
+    for (var k in this.widgets) {
+        this.hide_container(this.widgets[k]);
+    }
+    this.patron_barcode = undefined;
+
+    if (typeof(this._extra_resetting) == "function")
+        this._extra_resetting();
+
+    if (this.primary_input) {
+        this.primary_input.value = "";
+        this.primary_input.focus();
+    }
+};

Copied: branches/rel_1_6/Open-ILS/web/js/ui/default/booking/pull_list.js (from rev 15247, trunk/Open-ILS/web/js/ui/default/booking/pull_list.js)
===================================================================
--- branches/rel_1_6/Open-ILS/web/js/ui/default/booking/pull_list.js	                        (rev 0)
+++ branches/rel_1_6/Open-ILS/web/js/ui/default/booking/pull_list.js	2010-01-13 16:35:03 UTC (rev 15312)
@@ -0,0 +1,203 @@
+dojo.require("openils.User");
+dojo.require("openils.PermaCrud");
+dojo.require("fieldmapper.OrgUtils");
+dojo.require("openils.widget.OrgUnitFilteringSelect");
+dojo.requireLocalization("openils.booking", "pull_list");
+
+var localeStrings = dojo.i18n.getLocalization("openils.booking", "pull_list");
+var pcrud = new openils.PermaCrud();
+
+var owning_lib_selected;
+var acp_cache = {};
+
+function init_owning_lib_selector() {
+    var User = new openils.User();
+    User.buildPermOrgSelector(
+        "RETRIEVE_RESERVATION_PULL_LIST", owning_lib_selector, null,
+        function() {
+            owning_lib_selected = owning_lib_selector.getValue();
+            dojo.connect(owning_lib_selector, "onChange",
+                function() { owning_lib_selected = this.getValue(); }
+            )
+        }
+    );
+}
+
+function retrieve_pull_list(ivl_in_days) {
+    var secs = Number(ivl_in_days) * 86400;
+
+    if (isNaN(secs) || secs < 1)
+        throw new Error("Invalid interval");
+
+    return fieldmapper.standardRequest(
+        ["open-ils.booking", "open-ils.booking.reservations.get_pull_list"],
+        [xulG.auth.session.key, null, secs, owning_lib_selected]
+    );
+}
+
+function dom_table_rowid(resource_id) {
+    return "pull_list_resource_" + resource_id;
+}
+
+function generate_result_row(one) {
+    function cell(id, content) {
+        var td = document.createElement("td");
+        if (id != undefined) td.setAttribute("id", id);
+        td.appendChild(document.createTextNode(content));
+        return td;
+    }
+
+    function reservation_info_cell(one) {
+        var td = document.createElement("td");
+        for (var i in one.reservations) {
+            var one_resv = one.reservations[i];
+            var div = document.createElement("div");
+            var s = humanize_timestamp_string(one_resv.start_time()) + " - " +
+                humanize_timestamp_string(one_resv.end_time()) + " " +
+                formal_name(one_resv.usr());
+            /* FIXME: The above need patron barcode instead of name, but
+             * that requires a fix in the middle layer to flesh on the
+             * right stuff. */
+            div.appendChild(document.createTextNode(s));
+            td.appendChild(div);
+        }
+        return td;
+    }
+
+    var baseid = dom_table_rowid(one.current_resource.id());
+
+    var cells = [];
+    cells.push(cell(undefined, one.target_resource_type.name()));
+    cells.push(cell(undefined, one.current_resource.barcode()));
+    cells.push(cell(baseid + "_call_number", "-"));
+    cells.push(cell(baseid + "_copy_location", "-"));
+    cells.push(cell(baseid + "_copy_number", "-"));
+    cells.push(reservation_info_cell(one));
+
+    var row = document.createElement("tr");
+    row.setAttribute("id", baseid);
+
+    for (var i in cells) row.appendChild(cells[i]);
+    return row;
+}
+
+function render_pull_list_fundamentals(list) {
+    var rows = [];
+
+    for (var i in list)
+        rows.push(generate_result_row(list[i]));
+
+    document.getElementById("the_table_body").innerHTML = "";
+
+    for (var i in rows)
+        document.getElementById("the_table_body").appendChild(rows[i]);
+}
+
+function get_all_relevant_acp(list) {
+    var barcodes = [];
+    for (var i in list) {
+        if (list[i].target_resource_type.catalog_item()) {
+            /* There shouldn't be any duplicates. No need to worry bout that */
+            barcodes.push(list[i].current_resource.barcode());
+        }
+    }
+    if (barcodes.length > 0) {
+        var results = fieldmapper.standardRequest(
+            [
+                "open-ils.booking",
+                "open-ils.booking.asset.get_copy_fleshed_just_right"
+            ],
+            [xulG.auth.session.key, barcodes]
+        );
+
+        if (!results) {
+            alert(localeStrings.COPY_LOOKUP_NO_RESPONSE);
+            return null;
+        } else if (is_ils_event(results)) {
+            alert(my_ils_error(localeStrings.COPY_LOOKUP_ERROR, results));
+            return null;
+        } else {
+            return results;
+        }
+    }
+}
+
+function fill_in_pull_list_details(list, acp_cache) {
+    for (var i in list) {
+        var one = list[i];
+        if (one.target_resource_type.catalog_item() == "t") {
+            /* FIXME: This block could stand to be a lot more elegant. */
+            var call_number_el = document.getElementById(
+                dom_table_rowid(one.current_resource.id()) + "_call_number"
+            );
+            var copy_location_el = document.getElementById(
+                dom_table_rowid(one.current_resource.id()) + "_copy_location"
+            );
+            var copy_number_el = document.getElementById(
+                dom_table_rowid(one.current_resource.id()) + "_copy_number"
+            );
+
+            var bc = one.current_resource.barcode();
+
+            if (acp_cache[bc]) {
+                if (call_number_el && acp_cache[bc].call_number()) {
+                    var value = acp_cache[bc].call_number().label();
+                    if (value) call_number_el.innerHTML = value;
+                }
+                if (copy_location_el && acp_cache[bc].location()) {
+                    var value = acp_cache[bc].location().name();
+                    if (value) copy_location_el.innerHTML = value;
+                }
+                if (copy_number_el) {
+                    var value = acp_cache[bc].copy_number();
+                    if (value) copy_number_el.innerHTML = value;
+                }
+            } else {
+                alert(localeStrings.COPY_MISSING + bc);
+            }
+        }
+    }
+}
+
+function populate_pull_list(form) {
+    /* Step 1: get the pull list from the server. */
+    try {
+        var results = retrieve_pull_list(form.interval_in_days.value);
+    } catch (E) {
+        alert(localeStrings.PULL_LIST_ERROR + E);
+        return;
+    }
+    if (results == null) {
+        alert(localeStrings.PULL_LIST_NO_RESPONSE);
+        return;
+    } else if (is_ils_event(results)) {
+        alert(my_ils_error(localeStrings.PULL_LIST_ERROR, results));
+        return;
+    }
+
+    if (results.length) {
+        reveal_dom_element(document.getElementById("table_goes_here"));
+        hide_dom_element(document.getElementById("no_results"));
+
+        /* Step 2: render the table with the pull list */
+        render_pull_list_fundamentals(results);
+
+        /* Step 3: asynchronously fill in the copy details we're missing */
+        setTimeout(function() {
+            var acp_cache = {};
+            if ((acp_cache = get_all_relevant_acp(results)))
+                fill_in_pull_list_details(results, acp_cache);
+        }, 0);
+    } else {
+        hide_dom_element(document.getElementById("table_goes_here"));
+        reveal_dom_element(document.getElementById("no_results"));
+    }
+
+}
+
+function my_init() {
+    hide_dom_element(document.getElementById("table_goes_here"));
+    hide_dom_element(document.getElementById("no_results"));
+    init_owning_lib_selector();
+    init_auto_l10n(document.getElementById("auto_l10n_start_here"));
+}

Modified: branches/rel_1_6/Open-ILS/web/js/ui/default/booking/reservation.js
===================================================================
--- trunk/Open-ILS/web/js/ui/default/booking/reservation.js	2009-12-21 16:21:48 UTC (rev 15207)
+++ branches/rel_1_6/Open-ILS/web/js/ui/default/booking/reservation.js	2010-01-13 16:35:03 UTC (rev 15312)
@@ -3,6 +3,7 @@
  */
 dojo.require("fieldmapper.OrgUtils");
 dojo.require("openils.PermaCrud");
+dojo.require("openils.widget.OrgUnitFilteringSelect");
 dojo.require("dojo.data.ItemFileReadStore");
 dojo.require("dijit.form.DateTextBox");
 dojo.require("dijit.form.TimeTextBox");
@@ -13,9 +14,13 @@
  */
 var localeStrings = dojo.i18n.getLocalization("openils.booking", "reservation");
 var pcrud = new openils.PermaCrud();
+var opts;
 var our_brt;
+var pickup_lib_selected;
+var brt_list = [];
 var brsrc_index = {};
 var bresv_index = {};
+var just_reserved_now = {};
 
 function AttrValueTable() { this.t = {}; }
 AttrValueTable.prototype.set = function(attr, value) { this.t[attr] = value; };
@@ -36,47 +41,127 @@
 var attr_value_table =  new AttrValueTable();
 
 function TimestampRange() {
-    this.start = {"date": undefined, "time": undefined};
-    this.end = {"date": undefined, "time": undefined};
+    this.start = new Date();
+    this.end = new Date();
+
+    this.validity = {"start": false, "end": false};
+    this.nodes = {
+        "start": {"date": undefined, "time": undefined},
+        "end": {"date": undefined, "time": undefined}
+    };
+    this.saved_style_properties = {};
+    this.invalid_style_properties = {
+        "backgroundColor": "#ffcccc",
+        "color": "#990000",
+        "borderColor": "#990000",
+        "fontWeight": "bold"
+    };
 }
 TimestampRange.prototype.get_timestamp = function(when) {
-    return (this[when].date + " " + this[when].time);
+    return this.any_widget.serialize(this[when]).
+        replace("T", " ").substr(0, 19);
 };
 TimestampRange.prototype.get_range = function() {
     return this.is_backwards() ?
         [this.get_timestamp("end"), this.get_timestamp("start")] :
         [this.get_timestamp("start"), this.get_timestamp("end")];
 };
-TimestampRange.prototype.split_time = function(s) {
-    /* We're not interested in seconds for our purposes,
-     * so we floor everything to :00.
-     *
-     * Also, notice that following discards all time zone information
-     * from the timestamp string represenation.  This should probably
-     * stay the way it is, even when this code is improved to support
-     * selecting time zones (it currently just assumes server's local
-     * time).  The easy way to add support will be to add a drop-down
-     * selector from which the user can pick a time zone, then use
-     * that timezone literal in an "AT TIME ZONE" clause in SQL on
-     * the server side.
-     */
-    return s.split("T")[1].replace(/(\d{2}:\d{2}:)(\d{2})(.*)/, "$100");
-};
-TimestampRange.prototype.split_date = function(s) {
-    return s.split("T")[0];
-};
 TimestampRange.prototype.update_from_widget = function(widget) {
     var when = widget.id.match(/(start|end)/)[1];
     var which = widget.id.match(/(date|time)/)[1];
 
+    if (this.any_widget == undefined)
+        this.any_widget = widget;
+    if (this.nodes[when][which] == undefined)
+        this.nodes[when][which] = widget.domNode; /* We'll need this later */
+
     if (when && which) {
-        this[when][which] =
-            this["split_" + which](widget.serialize(widget.value));
+        this.update_timestamp(when, which, widget.value);
     }
+
+    this.compute_validity();
+    this.paint_validity();
 };
+TimestampRange.prototype.compute_validity = function() {
+    if (Math.abs(this.start - this.end) < 1000) {
+        this.validity.end = false;
+    } else {
+        if (this.start < this.current_minimum())
+            this.validity.start = false;
+        else
+            this.validity.start = true;
+
+        if (this.end < this.current_minimum())
+            this.validity.end = false;
+        else
+            this.validity.end = true;
+    }
+};
+/* This method provides the minimum timestamp that is considered valid. For
+ * now it's arbitrarily "now + 15 minutes", meaning that all reservations
+ * must be made at least 15 minutes in the future.
+ *
+ * For reasons of keeping the middle layer happy, this should always return
+ * a time that is at least somewhat in the future. The ML isn't able to target
+ * any resources for a reservation with a start date that isn't in the future.
+ */
+TimestampRange.prototype.current_minimum = function() {
+    /* XXX This is going to be a problem with local clocks that are off. */
+    var n = new Date();
+    n.setTime(n.getTime() + 1000 * 900); /* XXX 15 minutes; stop hardcoding! */
+    return n;
+};
+TimestampRange.prototype.update_timestamp = function(when, which, value) {
+    if (which == "date") {
+        this[when].setFullYear(value.getFullYear());
+        this[when].setMonth(value.getMonth());
+        this[when].setDate(value.getDate());
+    } else {    /* "time" */
+        this[when].setHours(value.getHours());
+        this[when].setMinutes(value.getMinutes());
+        this[when].setSeconds(0);
+    }
+};
 TimestampRange.prototype.is_backwards = function() {
-    return (this.get_timestamp("start") > this.get_timestamp("end"));
+    return (this.start > this.end);
 };
+TimestampRange.prototype.paint_validity = function()  {
+    for (var when in this.validity) {
+        if (this.validity[when]) {
+            this.paint_valid_node(this.nodes[when].date);
+            this.paint_valid_node(this.nodes[when].time);
+        } else {
+            this.paint_invalid_node(this.nodes[when].date);
+            this.paint_invalid_node(this.nodes[when].time);
+        }
+    }
+};
+TimestampRange.prototype.paint_invalid_node = function(node) {
+    if (node) {
+        /* Just toggling the class of something would be better than
+         * manually setting style here, but I haven't been able to get that
+         * to play nicely with dojo's styling of the date/time textboxen.
+         */
+        if (this.saved_style_properties.backgroundColor == undefined) {
+            for (var k in this.invalid_style_properties) {
+                this.saved_style_properties[k] = node.style[k];
+            }
+        }
+        for (var k in this.invalid_style_properties) {
+            node.style[k] = this.invalid_style_properties[k];
+        }
+    }
+};
+TimestampRange.prototype.paint_valid_node = function(node) {
+    if (node) {
+        for (var k in this.saved_style_properties) {
+            node.style[k] = this.saved_style_properties[k];
+        }
+    }
+};
+TimestampRange.prototype.is_valid = function() {
+    return (this.validity.start && this.validity.end);
+};
 var reserve_timestamp_range = new TimestampRange();
 
 function SelectorMemory(selector) {
@@ -93,52 +178,13 @@
 SelectorMemory.prototype.restore = function() {
     for (var i = 0; i < this.selector.options.length; i++) {
         if (this.memory[this.selector.options[i].value]) {
-            this.selector.options[i].selected = true;
+            if (!this.selector.options[i].disabled)
+                this.selector.options[i].selected = true;
         }
     }
 };
 
 /*
- * Misc helper functions
- */
-function hide_dom_element(e) { e.style.display = "none"; };
-function reveal_dom_element(e) { e.style.display = ""; };
-function get_keys(L) { var K = []; for (var k in L) K.push(k); return K; }
-function formal_name(u) {
-    var name = u.family_name() + ", " + u.first_given_name();
-    if (u.second_given_name())
-        name += (" " + u.second_given_name());
-    return name;
-}
-function humanize_timestamp_string(ts) {
-    /* For now, this discards time zones. */
-    var parts = ts.split("T");
-    var timeparts = parts[1].split("-")[0].split(":");
-    return parts[0] + " " + timeparts[0] + ":" + timeparts[1];
-}
-function set_datagrid_empty_store(grid) {
-    grid.setStore(
-        new dojo.data.ItemFileReadStore(
-            {"data": flatten_to_dojo_data([])}
-        )
-    );
-}
-function is_ils_error(e) { return (e.ilsevent != undefined); }
-function is_ils_actor_card_error(e) {
-    return (e.textcode == "ACTOR_CARD_NOT_FOUND");
-}
-function my_ils_error(header, e) {
-    var s = header + "\n";
-    var keys = [
-        "ilsevent", "desc", "textcode", "servertime", "pid", "stacktrace"
-    ];
-    for (var i in keys) {
-        if (e[keys[i]]) s += ("\t" + keys[i] + ": " + e[keys[i]] + "\n");
-    }
-    return s;
-}
-
-/*
  * These functions communicate with the middle layer.
  */
 function get_all_noncat_brt() {
@@ -148,8 +194,12 @@
     );
 }
 
+function get_brt_by_id(id) {
+    return pcrud.retrieve("brt", id);
+}
+
 function get_brsrc_id_list() {
-    var options = {"type": our_brt.id()};
+    var options = {"type": our_brt.id(), "pickup_lib": pickup_lib_selected};
 
     /* This mechanism for avoiding the passing of an empty 'attribute_values'
      * option is essential because if you pass such an option to the
@@ -168,36 +218,56 @@
     );
 }
 
-// FIXME: We need failure checking after pcrud.retrieve()
-function sync_brsrc_index_from_ids(id_list) {
-    /* One pass to populate the cache with anything that's missing. */
-    for (var i in id_list) {
-        if (!brsrc_index[id_list[i]]) {
-            brsrc_index[id_list[i]] = pcrud.retrieve("brsrc", id_list[i]);
+/* FIXME: We need failure checking after pcrud.retrieve() */
+function add_brsrc_to_index_if_needed(list, further) {
+    for (var i in list) {
+        if (!brsrc_index[list[i]]) {
+            brsrc_index[list[i]] = pcrud.retrieve("brsrc", list[i]);
         }
-        brsrc_index[id_list[i]].isdeleted(false); // See NOTE below.
+        if (further)
+            further(brsrc_index[list[i]]);
     }
-    /* A second pass to indicate any entries in the cache to be hidden. */
+}
+
+function sync_brsrc_index_from_ids(available_list, additional_list) {
+    /* Default states for everything in the index. Read the further comments. */
     for (var i in brsrc_index) {
-        if (id_list.indexOf(Number(i)) < 0) { // Number() is important.
-            brsrc_index[i].isdeleted(true); // See NOTE below.
+        brsrc_index[i].isdeleted(true);
+        brsrc_index[i].ischanged(false);
+    }
+
+    /* Populate the cache with anything that's missing and tag everything
+     * in the "available" list as *not* deleted, and tag everything in the
+     * additional list as "changed." See below. */
+    add_brsrc_to_index_if_needed(
+        available_list, function(o) { o.isdeleted(false); }
+    );
+    add_brsrc_to_index_if_needed(
+        additional_list,
+        function(o) {
+            if (!(o.id() in just_reserved_now)) o.ischanged(true);
         }
-    }
-    /* NOTE: We lightly abuse the isdeleted() magic attribute of the brsrcs
-     * in our cache.  Because we're not going to pass back any brsrcs to
-     * the middle layer, it doesn't really matter what we set this attribute
-     * to. What we're using it for is to indicate in our little brsrc cache
-     * whether a given brsrc should be displayed in this UI's current state
-     * (based on whether it was returned by the last call to the middle layer,
-     * i.e., whether it matches the currently selected attributes).
+    );
+    /* NOTE: We lightly abuse the isdeleted() and ischanged() magic fieldmapper
+     * attributes of the brsrcs in our cache.  Because we're not going to
+     * pass back any brsrcs to the middle layer, it doesn't really matter
+     * what we set this attribute to. What we're using it for is to indicate
+     * in our little brsrc cache how a given brsrc should be displayed in this
+     * UI's current state (based on whether the brsrc matches timestamp range
+     * availability (isdeleted(false)) and whether the brsrc has been forced
+     * into the list because it was selected in a previous interface (like
+     * the catalog) (ischanged(true))).
      */
 }
 
 function check_bresv_targeting(results) {
     var missing = 0;
     for (var i in results) {
-        if (!(results[i].targeting && results[i].targeting.current_resource))
+        if (!(results[i].targeting && results[i].targeting.current_resource)) {
             missing++;
+        } else {
+            just_reserved_now[results[i].targeting.current_resource] = true;
+        }
     }
     return missing;
 }
@@ -207,6 +277,9 @@
     if (barcode == "") {
         alert(localeStrings.WHERES_THE_BARCODE);
         return;
+    } else if (!reserve_timestamp_range.is_valid()) {
+        alert(localeStrings.INVALID_TS_RANGE);
+        return;
     }
     var results;
     try {
@@ -216,6 +289,7 @@
                 xulG.auth.session.key,
                 barcode,
                 reserve_timestamp_range.get_range(),
+                pickup_lib_selected,
                 our_brt.id(),
                 resource_list,
                 attr_value_table.get_all_values()
@@ -225,7 +299,7 @@
         alert(localeStrings.CREATE_BRESV_LOCAL_ERROR + E);
     }
     if (results) {
-        if (is_ils_error(results)) {
+        if (is_ils_event(results)) {
             if (is_ils_actor_card_error(results)) {
                 alert(localeStrings.ACTOR_CARD_NOT_FOUND);
             } else {
@@ -276,7 +350,7 @@
     var selector = document.getElementById("brsrc_list");
     var selected_values = [];
     for (var i in selector.options) {
-        if (selector.options[i].selected)
+        if (selector.options[i] && selector.options[i].selected)
             selected_values.push(selector.options[i].value);
     }
     if (selected_values.length > 0)
@@ -285,7 +359,12 @@
         alert(localeStrings.SELECT_A_BRSRC_THEN);
 }
 
-function create_bresv_on_brt() { create_bresv(); }
+function create_bresv_on_brt() {
+    if (any_usable_brsrc())
+        create_bresv();
+    else
+        alert(localeStrings.NO_USABLE_BRSRC);
+}
 
 function get_actor_by_barcode(barcode) {
     var usr = fieldmapper.standardRequest(
@@ -294,7 +373,7 @@
     );
     if (usr == null) {
         alert(localeStrings.GET_PATRON_NO_RESULT);
-    } else if (is_ils_error(usr)) {
+    } else if (is_ils_event(usr)) {
         return null; /* XXX inelegant: this function is quiet about errors
                         here because to report them would be redundant with
                         another function that gets called right after this one.
@@ -319,56 +398,121 @@
         }, /* whole_obj */ true]
     );
     if (result == null) {
-        set_datagrid_empty_store(bresvGrid);
+        set_datagrid_empty_store(bresvGrid, flatten_to_dojo_data);
         alert(localeStrings.GET_BRESV_LIST_NO_RESULT);
-    } else if (is_ils_error(result)) {
-        set_datagrid_empty_store(bresvGrid);
+    } else if (is_ils_event(result)) {
+        set_datagrid_empty_store(bresvGrid, flatten_to_dojo_data);
         if (is_ils_actor_card_error(result)) {
             alert(localeStrings.ACTOR_CARD_NOT_FOUND);
         } else {
             alert(my_ils_error(localeStrings.GET_BRESV_LIST_ERR, result));
         }
     } else {
+        if (result.length < 1) {
+            document.getElementById("bresv_grid_alt_explanation").innerHTML =
+                localeStrings.NO_EXISTING_BRESV;
+            hide_dom_element(document.getElementById("bresv_grid"));
+            reveal_dom_element(document.getElementById("reserve_under"));
+        } else {
+            document.getElementById("bresv_grid_alt_explanation").innerHTML =
+                "";
+            reveal_dom_element(document.getElementById("bresv_grid"));
+            reveal_dom_element(document.getElementById("reserve_under"));
+        }
+        /* May as well do the following in either case... */
         bresvGrid.setStore(
             new dojo.data.ItemFileReadStore(
                 {"data": flatten_to_dojo_data(result)}
             )
         );
+        bresv_index = {};
         for (var i in result) {
             bresv_index[result[i].id()] = result[i];
         }
     }
 }
 
-function cancel_reservations(bresv_list) {
-    for (var i in bresv_list) { bresv_list[i].cancel_time("now"); }
-    pcrud.update(
-        bresv_list, {
-            "oncomplete": function() {
-                update_bresv_grid();
-                alert(localeStrings.CXL_BRESV_SUCCESS(bresv_list.length));
-            },
-            "onerror": function(o) {
-                update_bresv_grid();
-                alert(localeStrings.CXL_BRESV_FAILURE + "\n" + o);
+function cancel_reservations(bresv_id_list) {
+    try {
+        var result = fieldmapper.standardRequest(
+            ["open-ils.booking", "open-ils.booking.reservations.cancel"],
+            [xulG.auth.session.key, bresv_id_list]
+        );
+    } catch (E) {
+        alert(localeStrings.CXL_BRESV_FAILURE2 + E);
+        return;
+    }
+    setTimeout(update_bresv_grid, 0);
+    if (!result) {
+        alert(localeStrings.CXL_BRESV_FAILURE);
+    } else if (is_ils_event(result)) {
+        alert(my_ils_error(localeStrings.CXL_BRESV_FAILURE2, result));
+    } else {
+        alert(localeStrings.CXL_BRESV_SUCCESS(result.length));
+    }
+}
+
+function munge_specific_resource(barcode) {
+    try {
+        var copy_list = pcrud.search(
+            "acp", {"barcode": barcode, "deleted": "f"}
+        );
+        if (copy_list && copy_list.length > 0) {
+            var r = fieldmapper.standardRequest(
+                ["open-ils.booking",
+                    "open-ils.booking.resources.create_from_copies"],
+                [xulG.auth.session.key,
+                    copy_list.map(function(o) { return o.id(); })]
+            );
+
+            if (!r) {
+                alert(localeStrings.ON_FLY_NO_RESPONSE);
+            } else if (is_ils_event(r)) {
+                alert(my_ils_error(localeStrings.ON_FLY_ERROR, r));
+            } else {
+                if (!(our_brt = get_brt_by_id(r.brt[0][0]))) {
+                    alert(localeStrings.COULD_NOT_RETRIEVE_BRT_PASSED_IN);
+                } else {
+                    opts.booking_results = r;
+                    init_reservation_interface();
+                }
             }
+        } else {
+            alert(localeStrings.BRSRC_NOT_FOUND);
         }
-    );
+    } catch (E) {
+        alert(localeStrings.BRSRC_RETRIEVE_ERROR + E);
+    }
 }
 
 /*
  * These functions deal with interface tricks (populating widgets,
  * changing the page, etc.).
  */
+function init_pickup_lib_selector() {
+    var User = new openils.User();
+    User.buildPermOrgSelector(
+        "ADMIN_BOOKING_RESERVATION", pickup_lib_selector, null,
+        function() {
+            pickup_lib_selected = pickup_lib_selector.getValue();
+            dojo.connect(pickup_lib_selector, "onChange",
+                function() {
+                    pickup_lib_selected = this.getValue();
+                    update_brsrc_list();
+                }
+            )
+        }
+    );
+}
+
 function provide_brt_selector(targ_div) {
     if (!targ_div) {
         alert(localeStrings.NO_TARG_DIV);
     } else {
-        var brt_list = xulG.brt_list = get_all_noncat_brt();
+        brt_list = get_all_noncat_brt();
         if (!brt_list || brt_list.length < 1) {
-            targ_div.appendChild(
-                document.createTextNode(localeStrings.NO_BRT_RESULTS)
-            );
+            document.getElementById("select_noncat_brt_block").
+                style.display = "none";
         } else {
             var selector = document.createElement("select");
             selector.setAttribute("id", "brt_selector");
@@ -383,21 +527,35 @@
                 option.appendChild(document.createTextNode(brt_list[i].name()));
                 selector.appendChild(option);
             }
+            targ_div.innerHTML = "";
             targ_div.appendChild(selector);
         }
     }
 }
 
-function init_reservation_interface(f) {
+function init_resv_iface_arb() {
+    init_reservation_interface(document.getElementById("arbitrary_resource"));
+}
+
+function init_resv_iface_sel() {
+    init_reservation_interface(document.getElementById("brt_selector"));
+}
+
+function init_reservation_interface(widget) {
+    /* Save a global reference to the brt we're going to reserve */
+    if (widget && (widget.selectedIndex != undefined)) {
+        our_brt = brt_list[widget.selectedIndex];
+    } else if (widget != undefined) {
+        if (!munge_specific_resource(widget.value))
+            return;
+    }
+
     /* Hide and reveal relevant divs. */
     var search_block = document.getElementById("brt_search_block");
     var reserve_block = document.getElementById("brt_reserve_block");
     hide_dom_element(search_block);
     reveal_dom_element(reserve_block);
 
-    /* Save a global reference to the brt we're going to reserve */
-    our_brt = xulG.brt_list[f.brt_selector.selectedIndex];
-
     /* Get a list of attributes that can apply to that brt. */
     var bra_list = pcrud.search("bra", {"resource_type": our_brt.id()});
     if (!bra_list) {
@@ -411,6 +569,10 @@
         brav_by_bra[o.id()] = pcrud.search("brav", {"attr": o.id()});
     });
 
+    /* Hide the label over the attributes widgets if we have nothing to show. */
+    var domf = (bra_list.length < 1) ? hide_dom_element : reveal_dom_element;
+    domf(document.getElementById("bra_and_brav_header"));
+
     /* Create DOM widgets to represent each attribute/values set. */
     for (var i in bra_list) {
         var bra_div = document.createElement("div");
@@ -450,32 +612,49 @@
     /* Add a prominent label reminding the user what resource type they're
      * asking about. */
     document.getElementById("brsrc_list_header").innerHTML = our_brt.name();
-
+    init_pickup_lib_selector();
     update_brsrc_list();
 }
 
 function update_brsrc_list() {
     var brsrc_id_list = get_brsrc_id_list();
-    sync_brsrc_index_from_ids(brsrc_id_list);
+    var force_list = (opts.booking_results && opts.booking_results.brsrc) ?
+        opts.booking_results.brsrc.map(function(o) { return o[0]; }) : [];
 
+    sync_brsrc_index_from_ids(brsrc_id_list, force_list);
+
     var target_selector = document.getElementById("brsrc_list");
     var selector_memory = new SelectorMemory(target_selector);
     selector_memory.save();
     target_selector.innerHTML = "";
 
     for (var i in brsrc_index) {
-        if (brsrc_index[i].isdeleted()) {
+        if (brsrc_index[i].isdeleted() && (!brsrc_index[i].ischanged()))
             continue;
-        }
+
         var opt = document.createElement("option");
         opt.setAttribute("value", brsrc_index[i].id());
         opt.appendChild(document.createTextNode(brsrc_index[i].barcode()));
+
+        if (brsrc_index[i].isdeleted() && (brsrc_index[i].ischanged())) {
+            opt.setAttribute("class", "forced_unavailable");
+            opt.setAttribute("disabled", "disabled");
+        }
+
         target_selector.appendChild(opt);
     }
 
     selector_memory.restore();
 }
 
+function any_usable_brsrc() {
+    for (var i in brsrc_index) {
+        if (!brsrc_index[i].isdeleted())
+            return true;
+    }
+    return false;
+}
+
 function update_bresv_grid() {
     var widg = document.getElementById("patron_barcode");
     if (widg.value != "") {
@@ -494,8 +673,6 @@
             }
         }, 0);
         setTimeout(function() { init_bresv_grid(widg.value); }, 0);
-
-        reveal_dom_element(document.getElementById("reserve_under"));
     }
 }
 
@@ -532,48 +709,47 @@
 }
 
 function cancel_selected_bresv(bresv_dojo_items) {
-    if (bresv_dojo_items && bresv_dojo_items.length > 0) {
+    if (bresv_dojo_items && bresv_dojo_items.length > 0 &&
+        (bresv_dojo_items[0].length == undefined ||
+            bresv_dojo_items[0].length > 0)) {
         cancel_reservations(
-            bresv_dojo_items.map(function(o) { return bresv_index[o.id]; })
+            bresv_dojo_items.map(function(o) { return o.id[0]; })
         );
+        /* After some delay to allow the cancellations a chance to get
+         * committed, refresh the brsrc list as it might reflect newly
+         * available resources now. */
+        setTimeout(update_brsrc_list, 2000);
     } else {
         alert(localeStrings.CXL_BRESV_SELECT_SOMETHING);
     }
 }
 
-/* Quick and dirty way to localize some strings; not recommended for reuse.
- * I'm sure dojo provides a better mechanism for this, but at the moment
- * this is faster to implement anew than figuring out the Right way to do
- * the same thing w/ dojo.
+/* The following function should return true if the reservation interface
+ * should start normally (show a list of brt to choose from) or false if
+ * it should not (because we've "started" it some other way by setting up
+ * and displaying other widgets).
  */
-function init_auto_l10n(el) {
-    function do_it(myel, cls) {
-        if (cls) {
-            var clss = cls.split(" ");
-            for (var k in clss) {
-                var parts = clss[k].match(/^AUTO_ATTR_([A-Z]+)_.+$/);
-                if (parts && localeStrings[clss[k]]) {
-                    myel.setAttribute(
-                        parts[1].toLowerCase(), localeStrings[clss[k]]
-                    );
-                } else if (clss[k].match(/^AUTO_/) && localeStrings[clss[k]]) {
-                    myel.innerHTML = localeStrings[clss[k]];
-                }
-            }
+function early_action_passthru() {
+    if (opts.booking_results) {
+        if (opts.booking_results.brt.length != 1) {
+            alert(localeStrings.NEED_EXACTLY_ONE_BRT_PASSED_IN);
+            return true;
+        } else if (!(our_brt = get_brt_by_id(opts.booking_results.brt[0][0]))) {
+            alert(localeStrings.COULD_NOT_RETRIEVE_BRT_PASSED_IN);
+            return true;
         }
+
+        init_reservation_interface();
+        return false;
     }
 
-    for (var i in el.attributes) {
-        if (el.attributes[i].nodeName == "class") {
-            do_it(el, el.attributes[i].value);
-            break;
-        }
+    if (opts.patron_barcode) {
+        document.getElementById("contain_patron_barcode").style.display="none";
+        document.getElementById("patron_barcode").value = opts.patron_barcode;
+        update_bresv_grid();
     }
-    for (var i in el.childNodes) {
-        if (el.childNodes[i].nodeType == 1) { // element node?
-            init_auto_l10n(el.childNodes[i]); // recurse!
-        }
-    }
+
+    return true;
 }
 
 /*
@@ -583,7 +759,10 @@
     hide_dom_element(document.getElementById("brt_reserve_block"));
     reveal_dom_element(document.getElementById("brt_search_block"));
     hide_dom_element(document.getElementById("reserve_under"));
-    provide_brt_selector(document.getElementById("brt_selector_here"));
     init_auto_l10n(document.getElementById("auto_l10n_start_here"));
     init_timestamp_widgets();
+
+    if (!(opts = xulG.bresv_interface_opts)) opts = {};
+    if (early_action_passthru())
+        provide_brt_selector(document.getElementById("brt_selector_here"));
 }

Copied: branches/rel_1_6/Open-ILS/web/js/ui/default/booking/return.js (from rev 15285, trunk/Open-ILS/web/js/ui/default/booking/return.js)
===================================================================
--- branches/rel_1_6/Open-ILS/web/js/ui/default/booking/return.js	                        (rev 0)
+++ branches/rel_1_6/Open-ILS/web/js/ui/default/booking/return.js	2010-01-13 16:35:03 UTC (rev 15312)
@@ -0,0 +1,41 @@
+dojo.requireLocalization("openils.booking", "pickup_and_return");
+var localeStrings = dojo.i18n.getLocalization(
+    "openils.booking", "pickup_and_return"
+);
+var p;
+
+function react_to_pass_in(opts) {
+    if (opts && opts.patron_barcode) {
+        p.populate({"patron": opts.patron_barcode});
+
+        hide_dom_element(
+            document.getElementById("contains_barcode_control")
+        );
+
+        document.getElementById("barcode").value = opts.patron_barcode;
+        var barcode_type = document.getElementById("barcode_type");
+        for (var i in barcode_type.options) {
+            if (barcode_type.options[i].value == "patron") {
+                barcode_type.selectedIndex = i;
+                break;
+            }
+        }
+
+        p._extra_resetting = function() {
+            reveal_dom_element(
+                document.getElementById("contains_barcode_control")
+            );
+        };
+    }
+}
+
+function my_init() {
+    p = new Populator({
+        "out": out_bresv,
+        "in": in_bresv,
+        "patron": document.getElementById("patron_info")
+    }, document.getElementById("barcode"));
+    init_auto_l10n(document.getElementById("auto_l10n_start_here"));
+
+    react_to_pass_in(xulG.bresv_interface_opts);
+}

Modified: branches/rel_1_6/Open-ILS/web/opac/locale/en-US/lang.dtd
===================================================================
--- branches/rel_1_6/Open-ILS/web/opac/locale/en-US/lang.dtd	2010-01-13 16:30:42 UTC (rev 15311)
+++ branches/rel_1_6/Open-ILS/web/opac/locale/en-US/lang.dtd	2010-01-13 16:35:03 UTC (rev 15312)
@@ -262,6 +262,8 @@
 <!ENTITY staff.cat.opac.delete_record.label "Delete Record">
 <!ENTITY staff.cat.opac.undelete_record.accesskey "U">
 <!ENTITY staff.cat.opac.undelete_record.label "Undelete Record">
+<!ENTITY staff.cat.opac.create_brt_from_record.accesskey "T">
+<!ENTITY staff.cat.opac.create_brt_from_record.label "Make Item Bookable">
 <!ENTITY staff.cat.opac.menu.accesskey "A">
 <!ENTITY staff.cat.opac.menu.label "Actions for this Record">
 <!ENTITY staff.cat.opac.opac_view.accesskey "O">
@@ -635,6 +637,18 @@
 <!ENTITY staff.main.menu.admin.server_admin.conify.z3950_source.label "Z39.50 Servers">
 <!ENTITY staff.main.menu.admin.server_admin.conify.circulation_modifier.label "Circulation Modifiers">
 
+<!ENTITY staff.main.menu.admin.server_admin.booking.label "Booking">
+<!ENTITY staff.main.menu.admin.server_admin.booking.accesskey "B">
+<!ENTITY staff.main.menu.admin.server_admin.booking.resource.label "Resources">
+<!ENTITY staff.main.menu.admin.server_admin.booking.resource.accesskey "R">
+<!ENTITY staff.main.menu.admin.server_admin.booking.resource_type.label "Resource Types">
+<!ENTITY staff.main.menu.admin.server_admin.booking.resource_type.accesskey "T">
+<!ENTITY staff.main.menu.admin.server_admin.booking.resource_attr.label "Resource Attributes">
+<!ENTITY staff.main.menu.admin.server_admin.booking.resource_attr.accesskey "A">
+<!ENTITY staff.main.menu.admin.server_admin.booking.resource_attr_value.label "Resource Attribute Values">
+<!ENTITY staff.main.menu.admin.server_admin.booking.resource_attr_value.accesskey "V">
+<!ENTITY staff.main.menu.admin.server_admin.booking.resource_attr_map.label "Resource Attribute Maps">
+<!ENTITY staff.main.menu.admin.server_admin.booking.resource_attr_map.accesskey "M">
 
 <!ENTITY staff.main.menu.admin.developer.label "For developers...">
 <!ENTITY staff.main.menu.admin.download_patrons.accesskey "D">
@@ -716,6 +730,19 @@
 <!ENTITY staff.main.menu.acq.upload.label "Load Order Record">
 <!ENTITY staff.main.menu.acq.po.label "Purchase Orders">
 
+<!ENTITY staff.main.menu.booking.label "Booking">
+<!ENTITY staff.main.menu.booking.accesskey "B">
+<!ENTITY staff.main.menu.booking.reservation.label "Create or Edit Reservations">
+<!ENTITY staff.main.menu.booking.reservation.accesskey "C">
+<!ENTITY staff.main.menu.booking.pull_list.label "Pull List">
+<!ENTITY staff.main.menu.booking.pull_list.accesskey "L">
+<!ENTITY staff.main.menu.booking.capture.label "Capture Resources">
+<!ENTITY staff.main.menu.booking.capture.accesskey "A">
+<!ENTITY staff.main.menu.booking.reservation_pickup.label "Pick Up Reservations">
+<!ENTITY staff.main.menu.booking.reservation_pickup.accesskey "P">
+<!ENTITY staff.main.menu.booking.reservation_return.label "Return Reservations">
+<!ENTITY staff.main.menu.booking.reservation_return.accesskey "R">
+
 <!ENTITY staff.main.menu.acq.fund.label "Funds">
 <!ENTITY staff.main.menu.acq.funding_source.label "Funding Sources">
 <!ENTITY staff.main.menu.acq.provider.label "Providers">
@@ -1236,6 +1263,8 @@
 <!ENTITY staff.patron_navbar.holds.accesskey 'H'>
 <!ENTITY staff.patron_navbar.alert 'Display Alert and Messages'>
 <!ENTITY staff.patron_navbar.alert.accesskey 'A'>
+<!ENTITY staff.patron_navbar.booking 'Booking'>
+<!ENTITY staff.patron_navbar.booking.accesskey 'k'>
 <!ENTITY staff.patron_navbar.other 'Other'>
 <!ENTITY staff.patron_navbar.other.accesskey 'o'>
 <!ENTITY staff.patron_navbar.items 'Items Out'>
@@ -1557,7 +1586,14 @@
 <!ENTITY staff.server.admin.index.transit_list "Transit List">
 <!ENTITY staff.server.admin.index.conify "Server Settings">
 
+<!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 ">
@@ -1765,6 +1801,10 @@
 <!ENTITY staff.circ.copy_status_overlay.sel_copy_details.accesskey "I">
 <!ENTITY staff.circ.copy_status_overlay.sel_patron.label "Show Last Few Circulations">
 <!ENTITY staff.circ.copy_status_overlay.sel_patron.accesskey "L">
+<!ENTITY staff.circ.copy_status_overlay.cmd_book_item_now.label "Book Item Now">
+<!ENTITY staff.circ.copy_status_overlay.cmd_book_item_now.accesskey "N">
+<!ENTITY staff.circ.copy_status_overlay.cmd_create_brt.label "Make Item Bookable">
+<!ENTITY staff.circ.copy_status_overlay.cmd_create_brt.accesskey "K">
 <!ENTITY staff.circ.copy_status_overlay.sel_edit.label "Edit Item Attributes">
 <!ENTITY staff.circ.copy_status_overlay.sel_edit.accesskey "E">
 <!ENTITY staff.circ.copy_status_overlay.sel_mark_items_damaged.label "Mark Item Damaged">
@@ -1946,6 +1986,10 @@
 <!ENTITY staff.cat.copy_browser.actions.cmd_add_items_to_buckets.accesskey "B">
 <!ENTITY staff.cat.copy_browser.actions.sel_copy_details.label "Show Item Details">
 <!ENTITY staff.cat.copy_browser.actions.sel_copy_details.accesskey "I">
+<!ENTITY staff.cat.copy_browser.actions.cmd_book_item_now.label "Book Item Now">
+<!ENTITY staff.cat.copy_browser.actions.cmd_book_item_now.accesskey "N">
+<!ENTITY staff.cat.copy_browser.actions.cmd_create_brt.label "Make Item Bookable">
+<!ENTITY staff.cat.copy_browser.actions.cmd_create_brt.accesskey "K">
 <!ENTITY staff.cat.copy_browser.actions.sel_patron.label "Show Last Few Circulations">
 <!ENTITY staff.cat.copy_browser.actions.sel_patron.accesskey "L">
 <!ENTITY staff.cat.copy_browser.actions.cmd_edit_items.label "Edit Item Attributes">
@@ -1993,6 +2037,9 @@
 <!ENTITY staff.cat.copy_browser.holdings_maintenance.add_items_to_bucket.accesskey "B">
 <!ENTITY staff.cat.copy_browser.holdings_maintenance.sel_copy_details.label "Show Item Details">
 <!ENTITY staff.cat.copy_browser.holdings_maintenance.sel_copy_details.accesskey "I">
+<!ENTITY staff.cat.copy_browser.holdings_maintenance.cmd_create_brt.label "Make This Item Bookable">
+<!ENTITY staff.cat.copy_browser.holdings_maintenance.cmd_create_brt.accesskey "Y">
+
 <!ENTITY staff.cat.copy_browser.holdings_maintenance.sel_patron.label "Show Last Few Circulations">
 <!ENTITY staff.cat.copy_browser.holdings_maintenance.sel_patron.accesskey "L">
 <!ENTITY staff.cat.copy_browser.holdings_maintenance.cmd_edit_items.label "Edit Item Attributes">

Modified: branches/rel_1_6/Open-ILS/web/opac/skin/default/js/myopac.js
===================================================================
--- branches/rel_1_6/Open-ILS/web/opac/skin/default/js/myopac.js	2010-01-13 16:30:42 UTC (rev 15311)
+++ branches/rel_1_6/Open-ILS/web/opac/skin/default/js/myopac.js	2010-01-13 16:35:03 UTC (rev 15312)
@@ -616,6 +616,12 @@
 
 		else if(trans.xact_type() == 'grocery' ) 
 			myopacShowGenericTransaction( trans );
+
+/*      XXX need to copy circulation output function here
+		else if(trans.xact_type() == 'reservation' ) 
+			myopacShowReservationTransaction( trans );
+*/
+
 	}
 }
 

Copied: branches/rel_1_6/Open-ILS/web/templates/default/booking (from rev 15207, trunk/Open-ILS/web/templates/default/booking)

Copied: branches/rel_1_6/Open-ILS/web/templates/default/booking/capture.tt2 (from rev 15264, trunk/Open-ILS/web/templates/default/booking/capture.tt2)
===================================================================
--- branches/rel_1_6/Open-ILS/web/templates/default/booking/capture.tt2	                        (rev 0)
+++ branches/rel_1_6/Open-ILS/web/templates/default/booking/capture.tt2	2010-01-13 16:35:03 UTC (rev 15312)
@@ -0,0 +1,21 @@
+[% WRAPPER "default/base.tt2" %]
+<script src="[% ctx.media_prefix %]/js/ui/default/booking/common.js"></script>
+<script src="[% ctx.media_prefix %]/js/ui/default/booking/capture.js"></script>
+<link rel="stylesheet" type="text/css" href="[% ctx.media_prefix %]/css/skin/[% ctx.skin %]/booking.css" />
+<script type="text/javascript">openils.Util.addOnLoad(my_init);</script>
+<div id="auto_l10n_start_here">
+<!-- XXX This interface will probably go away soon in favor of merging its
+behavior into the regular checkin/process/capture interface. -->
+    <h1 class="AUTO_capture_heading booking"></h1>
+    <form class="nice_vertical_padding"
+        onsubmit="attempt_capture(); return false">
+        <label for="resource_barcode" class="AUTO_resource_barcode"></label>
+        <input id="resource_barcode" onfocus="clear_for_next();" />
+        <input type="button" class="AUTO_ATTR_VALUE_capture"
+            onclick="attempt_capture();" />
+        <span id="result_display"></span>
+    </form>
+    <div class="nice_vertical_padding" id="capture_display">
+    </div>
+</div>
+[% END %]

Copied: branches/rel_1_6/Open-ILS/web/templates/default/booking/pickup.tt2 (from rev 15285, trunk/Open-ILS/web/templates/default/booking/pickup.tt2)
===================================================================
--- branches/rel_1_6/Open-ILS/web/templates/default/booking/pickup.tt2	                        (rev 0)
+++ branches/rel_1_6/Open-ILS/web/templates/default/booking/pickup.tt2	2010-01-13 16:35:03 UTC (rev 15312)
@@ -0,0 +1,77 @@
+[% WRAPPER "default/base.tt2" %]
+<script src="[% ctx.media_prefix %]/js/ui/default/booking/common.js"></script>
+<script src="[% ctx.media_prefix %]/js/ui/default/booking/populator.js"></script>
+<script src="[% ctx.media_prefix %]/js/ui/default/booking/pickup.js"></script>
+<link rel="stylesheet" type="text/css" href="[% ctx.media_prefix %]/css/skin/[% ctx.skin %]/booking.css" />
+<script type="text/javascript">
+    dojo.require("dojox.grid.DataGrid");
+    openils.Util.addOnLoad(my_init);
+
+    function act(f) {
+        p.populate({"patron": f.patron_barcode.value});
+        return false; /* Always. */
+    }
+</script>
+<div id="auto_l10n_start_here">
+    <h1 class="booking AUTO_h1"></h1>
+    <div class="nice_vertical_padding" id="contains_barcode_control">
+        <form id="lookup" onsubmit="return act(this);">
+            <label for="patron_barcode" class="AUTO_patron_barcode"></label>
+            <input id="patron_barcode" name="patron_barcode" />
+            <input type="submit" class="AUTO_ATTR_VALUE_go" />
+        </form>
+    </div>
+    <div class="nice_vertical_padding" id="contains_patron_info">
+        <div id="patron_info"></div>
+    </div>
+    <div class="nice_vertical_padding" id="contains_ready_bresv">
+        <h3 class="booking AUTO_ready_bresv"></h3>
+        <div class="AUTO_no_ready_bresv" id="no_ready_bresv"></div>
+        <table id="ready_bresv" jsId="ready_bresv"
+            dojoType="dojox.grid.DataGrid" query="{id: '*'}"
+            rowSelector="20px" autoHeight="true" width="auto">
+            <thead>
+                <tr><!-- FIXME: i18n problem: init_auto_l10n() runs
+                        too late to take care of the below elements. -->
+                    <th width="35%" field="type">Title</th>
+                    <th width="25%" field="resource">Barcode</th>
+                    <th width="20%" field="start_time">Start time</th>
+                    <th width="20%" field="end_time">End time</th>
+                </tr>
+            </thead>
+        </table>
+        <div class="nice_vertical_padding" id="controls_ready_bresv">
+            <form>
+                <input type="button" id="pickup_button"
+                    class="AUTO_ATTR_VALUE_pickup"
+                    onclick="p.act_on_selected('pickup', 'ready');" />
+            </form>
+        </div>
+    </div>
+    <div class="nice_vertical_padding" id="contains_out_bresv">
+        <hr />
+        <h3 class="booking AUTO_out_bresv"></h3>
+        <div class="AUTO_no_out_bresv" id="no_out_bresv"></div>
+        <table id="out_bresv" jsId="out_bresv"
+            dojoType="dojox.grid.DataGrid" query="{id: '*'}"
+            rowSelector="20px" autoHeight="true" width="auto">
+            <thead>
+                <tr><!-- FIXME: i18n problem: init_auto_l10n() runs
+                        too late to take care of the below elements. -->
+                    <th width="35%" field="type">Title</th>
+                    <th width="25%" field="resource">Barcode</th>
+                    <th width="20%" field="pickup_time">Pickup time</th>
+                    <th width="20%" field="end_time">Due time</th>
+                </tr>
+            </thead>
+        </table>
+    </div>
+    <div class="nice_vertical_padding" id="contains_misc_controls">
+        <hr />
+        <form>
+            <input type="button" class="AUTO_ATTR_VALUE_reset"
+                onclick="p.reset();" />
+        </form>
+    </div>
+</div>
+[% END %]

Copied: branches/rel_1_6/Open-ILS/web/templates/default/booking/pull_list.tt2 (from rev 15247, trunk/Open-ILS/web/templates/default/booking/pull_list.tt2)
===================================================================
--- branches/rel_1_6/Open-ILS/web/templates/default/booking/pull_list.tt2	                        (rev 0)
+++ branches/rel_1_6/Open-ILS/web/templates/default/booking/pull_list.tt2	2010-01-13 16:35:03 UTC (rev 15312)
@@ -0,0 +1,50 @@
+[% WRAPPER "default/base.tt2" %]
+<script src="[% ctx.media_prefix %]/js/ui/default/booking/common.js"></script>
+<script src="[% ctx.media_prefix %]/js/ui/default/booking/pull_list.js"></script>
+<link rel="stylesheet" type="text/css" href="[% ctx.media_prefix %]/css/skin/[% ctx.skin %]/booking.css" />
+<script type="text/javascript">openils.Util.addOnLoad(my_init);</script>
+<div id="auto_l10n_start_here">
+    <h1 class="booking AUTO_pull_list_title"></h1>
+    <form onsubmit="populate_pull_list(this); return false;">
+        <div id="owning_lib_selector_row" class="nice_vertical_padding">
+            <label for="owning_lib_selector" class="AUTO_owning_lib_selector">
+            </label>
+            <select dojoType="openils.widget.OrgUnitFilteringSelect"
+                id="owning_lib_selector" jsId="owning_lib_selector"
+                searchAttr="shortname" labelAttr="shortname"></select>
+        </div>
+        <div id="interval_input_row" class="nice_vertical_padding">
+            <label for="interval_in_days" class="AUTO_interval_in_days"></label>
+
+            <!-- XXX Hardcoded values (like the ones below) are bad. -->
+            <input id="interval_in_days" name="interval_in_days"
+                value="5" maxlength="2" />
+
+        </div>
+        <input type="submit" class="AUTO_ATTR_VALUE_fetch" />
+    </form>
+    <hr />
+    <div id="table_goes_here" class="nice_vertical_padding">
+        <table id="the_table" width="100%">
+            <thead>
+                <tr>
+                    <th width="30%" class="AUTO_th_title_or_name"></th>
+                    <th width="10%" class="AUTO_th_barcode"></th>
+                    <th width="10%" class="AUTO_th_call_number"></th>
+                    <th width="10%" class="AUTO_th_copy_location"></th>
+                    <th width="10%" class="AUTO_th_copy_number"></th>
+                    <th width="30%" class="AUTO_th_resv_details"></th>
+                </tr>
+            </thead>
+            <tbody id="the_table_body">
+            </tbody>
+        </table>
+        <div id="print_holder" class="nice_vertical_padding">
+            <!-- XXX Print button probably won't stay right here -->
+            <input type="button" class="AUTO_ATTR_VALUE_print"
+                onclick="window.print();" /><!-- XXX too simplistic? -->
+        </div>
+    </div>
+    <div id="no_results" class="AUTO_no_results"></div>
+</div>
+[% END %]

Modified: branches/rel_1_6/Open-ILS/web/templates/default/booking/reservation.tt2
===================================================================
--- trunk/Open-ILS/web/templates/default/booking/reservation.tt2	2009-12-21 16:21:48 UTC (rev 15207)
+++ branches/rel_1_6/Open-ILS/web/templates/default/booking/reservation.tt2	2010-01-13 16:35:03 UTC (rev 15312)
@@ -1,4 +1,5 @@
 [% WRAPPER "default/base.tt2" %]
+<script src="[% ctx.media_prefix %]/js/ui/default/booking/common.js"></script>
 <script src="[% ctx.media_prefix %]/js/ui/default/booking/reservation.js"></script>
 <link rel="stylesheet" type="text/css" href="[% ctx.media_prefix %]/css/skin/[% ctx.skin %]/booking.css" />
 <script type="text/javascript">
@@ -8,16 +9,31 @@
 <div id="auto_l10n_start_here">
     <div id="brt_search_block" class="container">
         <h1 class="booking AUTO_choose_a_brt"></h1>
-        <form onsubmit="init_reservation_interface(this); return false;">
-            <div id="brt_selector_here" class="nice_vertical_padding"></div>
-            <div class="nice_vertical_padding">
-                <input type="submit" class="AUTO_ATTR_VALUE_next" />
+        <form onsubmit="return false;">
+            <div id="select_noncat_brt_block">
+                <div id="brt_selector_here" class="nice_vertical_padding"></div>
+                <div class="nice_vertical_padding">
+                    <input type="button" class="AUTO_ATTR_VALUE_next"
+                        onclick="init_resv_iface_sel(); return false"
+                        />
+                </div>
+                <hr />
+                <div class="nice_vertical_padding AUTO_or" id="or"></div>
             </div>
+            <div id="arbitrary_resource_block">
+                <label for="arbitrary_resource" class="AUTO_arbitrary_resource">
+                </label>
+                <input id="arbitrary_resource" name="arbitrary_resource" />
+                <input type="button"
+                    onclick="init_resv_iface_arb(); return false;"
+                    class="AUTO_ATTR_VALUE_next" />
+                <p class="AUTO_explain_bookable"></p>
+            </div>
         </form>
     </div>
 
     <div id="brt_reserve_block" class="container">
-        <form>
+        <form onsubmit="return false;">
             <div id="brsrc_available_outer">
                 <h1 class="booking" id="brsrc_list_header"></h1>
                 <!-- I'm reluctantly hardcoding the size attribute below to 12
@@ -25,12 +41,19 @@
                     anything in CSS. -->
                 <select id="brsrc_list" name="brsrc_list" multiple="multiple"
                     size="12"></select>
-                <div class="nice_vertical_padding">
+                <div id="contain_patron_barcode" class="nice_vertical_padding">
                     <label class="AUTO_patron_barcode"
                         for="patron_barcode" /></label>
                     <input name="patron_barcode" id="patron_barcode"
                         onchange="update_bresv_grid();" />
                 </div>
+                <div id="pickup_lib_selector_row" class="nice_vertical_padding">
+                    <label for="pickup_lib_selector"
+                        class="AUTO_pickup_lib_selector"></label>
+                    <select dojoType="openils.widget.OrgUnitFilteringSelect"
+                        id="pickup_lib_selector" jsId="pickup_lib_selector"
+                        searchAttr="shortname" labelAttr="shortname"></select>
+                </div>
                 <div class="nice_vertical_padding">
                     <span class="two_buttons">
                         <input type="button"
@@ -57,37 +80,38 @@
                     <input id="reserve_date_end" />
                     <input id="reserve_time_end" />
                 </div>
-                <h2 class="booking AUTO_with_these_attr"></h2>
-                <div id="bra_and_brav">
-                </div>
+                <h2 id="bra_and_brav_header"
+                    class="booking AUTO_with_these_attr"></h2>
+                <div id="bra_and_brav"></div>
             </div>
-            <div id="reserve_under">
-                <hr />
-                <h2 class="booking" id="existing_reservation_patron_line"></h2>
-                <table id="bresv_grid" jsId="bresvGrid"
-                    dojoType="dojox.grid.DataGrid" query="{id: '*'}"
-                    rowSelector="20px" autoHeight="true">
-                    <thead>
-                        <tr><!-- FIXME: i18n problem: init_auto_l10n() runs
-                                too late to take care of the below elements. -->
-                            <th field="type">Type</th>
-                            <th field="resource">Resource</th>
-                            <th field="start_time">Start time</th>
-                            <th field="end_time">End time</th>
-                        </tr>
-                    </thead>
-                </table>
-                <div class="nice_vertical_padding"
-                    id="existing_bresv_under_buttons">
-                    <input type="button" id="button_edit_existing"
-                        class="AUTO_ATTR_VALUE_button_edit_existing"
-                        disabled="disabled" />
-                    <input type="button" id="button_cancel_existing"
-                        class="AUTO_ATTR_VALUE_button_cancel_existing"
-                        onclick="cancel_selected_bresv(bresvGrid.selection.getSelected());" />
-                </div>
-            </div>
         </form>
     </div>
+
+    <div id="reserve_under">
+        <hr />
+        <h2 class="booking" id="existing_reservation_patron_line"></h2>
+        <div id="bresv_grid_alt_explanation"></div>
+        <table id="bresv_grid" jsId="bresvGrid"
+            dojoType="dojox.grid.DataGrid" query="{id: '*'}"
+            rowSelector="20px" autoHeight="true" width="auto">
+            <thead>
+                <tr><!-- FIXME: i18n problem: init_auto_l10n() runs
+                        too late to take care of the below elements. -->
+                    <th width="35%" field="type">Type</th>
+                    <th width="25%" field="resource">Resource</th>
+                    <th width="20%" field="start_time">Start time</th>
+                    <th width="20%" field="end_time">End time</th>
+                </tr>
+            </thead>
+        </table>
+        <div class="nice_vertical_padding"
+            id="existing_bresv_under_buttons">
+            <!-- <input type="button" id="button_edit_existing"
+                class="AUTO_ATTR_VALUE_button_edit_existing" /> -->
+            <input type="button" id="button_cancel_existing"
+                class="AUTO_ATTR_VALUE_button_cancel_existing"
+                onclick="cancel_selected_bresv(bresvGrid.selection.getSelected());" />
+        </div>
+    </div>
 </div>
 [% END %]

Copied: branches/rel_1_6/Open-ILS/web/templates/default/booking/return.tt2 (from rev 15285, trunk/Open-ILS/web/templates/default/booking/return.tt2)
===================================================================
--- branches/rel_1_6/Open-ILS/web/templates/default/booking/return.tt2	                        (rev 0)
+++ branches/rel_1_6/Open-ILS/web/templates/default/booking/return.tt2	2010-01-13 16:35:03 UTC (rev 15312)
@@ -0,0 +1,87 @@
+[% WRAPPER "default/base.tt2" %]
+<script src="[% ctx.media_prefix %]/js/ui/default/booking/common.js"></script>
+<script src="[% ctx.media_prefix %]/js/ui/default/booking/populator.js"></script>
+<script src="[% ctx.media_prefix %]/js/ui/default/booking/return.js"></script>
+<link rel="stylesheet" type="text/css" href="[% ctx.media_prefix %]/css/skin/[% ctx.skin %]/booking.css" />
+<script type="text/javascript">
+    dojo.require("dojox.grid.DataGrid");
+    openils.Util.addOnLoad(my_init);
+
+    function act(f) {
+        var key = f.barcode_type.options[f.barcode_type.selectedIndex].value;
+        var obj = {};
+        obj[key] = f.barcode.value;
+        p.populate(obj);
+        return false; /* Always. */
+    }
+</script>
+<div id="auto_l10n_start_here">
+    <h1 class="booking AUTO_return_h1"></h1>
+    <div class="nice_vertical_padding" id="contains_barcode_control">
+        <form id="lookup" onsubmit="return act(this);">
+            <label for="barcode_type" class="AUTO_barcode_type"></label>
+            <select name="barcode_type" id="barcode_type"
+                onchange="var b = this.form.barcode; b.focus(); b.select();">
+                <option id="option_resource" value="resource"
+                    selected="selected" class="AUTO_resource"></option>
+                <option id="option_patron" value="patron"
+                    class="AUTO_patron"></option>
+            </select>
+            <input id="barcode" name="barcode" />
+            <input type="submit" class="AUTO_ATTR_VALUE_go" />
+        </form>
+    </div>
+    <div class="nice_vertical_padding" id="contains_patron_info">
+        <div id="patron_info"></div>
+    </div>
+    <div class="nice_vertical_padding" id="contains_out_bresv">
+        <h3 class="booking AUTO_out_bresv"></h3>
+        <div class="AUTO_no_out_bresv" id="no_out_bresv"></div>
+        <table id="out_bresv" jsId="out_bresv"
+            dojoType="dojox.grid.DataGrid" query="{id: '*'}"
+            rowSelector="20px" autoHeight="true" width="auto">
+            <thead>
+                <tr><!-- FIXME: i18n problem: init_auto_l10n() runs
+                        too late to take care of the below elements. -->
+                    <th width="35%" field="type">Title</th>
+                    <th width="25%" field="resource">Barcode</th>
+                    <th width="20%" field="pickup_time">Pickup time</th>
+                    <th width="20%" field="end_time">Due time</th>
+                </tr>
+            </thead>
+        </table>
+        <div class="nice_vertical_padding" id="controls_out_bresv">
+            <form>
+                <input type="button" id="return_button"
+                    class="AUTO_ATTR_VALUE_return"
+                    onclick="p.act_on_selected('return', 'out');" />
+            </form>
+        </div>
+    </div>
+    <div class="nice_vertical_padding" id="contains_in_bresv">
+        <hr />
+        <h3 class="booking AUTO_in_bresv"></h3>
+        <div class="AUTO_no_in_bresv" id="no_in_bresv"></div>
+        <table id="in_bresv" jsId="in_bresv"
+            dojoType="dojox.grid.DataGrid" query="{id: '*'}"
+            rowSelector="20px" autoHeight="true" width="auto">
+            <thead>
+                <tr><!-- FIXME: i18n problem: init_auto_l10n() runs
+                        too late to take care of the below elements. -->
+                    <th width="35%" field="type">Title</th>
+                    <th width="25%" field="resource">Barcode</th>
+                    <th width="20%" field="due_time">Due time</th>
+                    <th width="20%" field="return_time">Return time</th>
+                </tr>
+            </thead>
+        </table>
+    </div>
+    <div class="nice_vertical_padding" id="contains_misc_controls">
+        <hr />
+        <form>
+            <input type="button" class="AUTO_ATTR_VALUE_reset"
+                onclick="p.reset();" />
+        </form>
+    </div>
+</div>
+[% END %]

Copied: branches/rel_1_6/Open-ILS/web/templates/default/conify/global/booking (from rev 15077, trunk/Open-ILS/web/templates/default/conify/global/booking)

Modified: branches/rel_1_6/Open-ILS/web/templates/default/conify/global/booking/resource.tt2
===================================================================
--- trunk/Open-ILS/web/templates/default/conify/global/booking/resource.tt2	2009-12-04 15:37:19 UTC (rev 15077)
+++ branches/rel_1_6/Open-ILS/web/templates/default/conify/global/booking/resource.tt2	2010-01-13 16:35:03 UTC (rev 15312)
@@ -7,31 +7,17 @@
     dojo.require('openils.widget.AutoGrid');
     dojo.require('openils.XUL');
 
-    function get_brsrc_ids() {
-        var cgi = new CGI();
-        var results = JSON2js(cgi.param('results'));
-        if (!results) return undefined;
-        var brsrc_ids = [];
-        for (var i in results) {
-            brsrc_ids.push(results[i][0]);
-        }
-        return brsrc_ids;
-    }
-
     openils.Util.addOnLoad(
         function() {
             var search = undefined; // default to all objs
-            var brsrc_ids = get_brsrc_ids();
-            if (brsrc_ids) {
-                search = {id: brsrc_ids};
+            if (xulG && xulG.resultant_brsrc) {
+                search = {id: xulG.resultant_brsrc};
             }
             ustGrid.loadAll({order_by:{brsrc : 'barcode'}}, search);
         }
     );
 </script>
 
-
-
 <div dojoType="dijit.layout.ContentPane" layoutAlign="client" class='oils-header-panel'>
     <div>Resources</div>
     <div>

Modified: branches/rel_1_6/Open-ILS/xul/staff_client/chrome/content/main/constants.js
===================================================================
--- branches/rel_1_6/Open-ILS/xul/staff_client/chrome/content/main/constants.js	2010-01-13 16:30:42 UTC (rev 15311)
+++ branches/rel_1_6/Open-ILS/xul/staff_client/chrome/content/main/constants.js	2010-01-13 16:35:03 UTC (rev 15312)
@@ -155,6 +155,10 @@
     'FM_AUSP_APPLY' : { 'app' : 'open-ils.actor', 'method' : 'open-ils.actor.user.penalty.apply' },
     'FM_AUSP_REMOVE' : { 'app' : 'open-ils.actor', 'method' : 'open-ils.actor.user.penalty.remove' },
     'FM_AUSP_UPDATE_NOTE' : { 'app' : 'open-ils.actor', 'method' : 'open-ils.actor.user.penalty.note.update' },
+    'FM_BOOKING_CREATE_BRT_AND_BRSRC' : { 'app' : 'open-ils.booking', 'method' : 'open-ils.booking.create_brt_and_brsrc_from_copies' },
+    'FM_BRESV_RETRIEVE_VIA_PCRUD' : { 'app' : 'open-ils.pcrud', 'method' : 'open-ils.pcrud.search.bresv.atomic' },
+    'FM_BRSRC_RETRIEVE_VIA_PCRUD' : { 'app' : 'open-ils.pcrud', 'method' : 'open-ils.pcrud.search.brsrc.atomic' },
+    'FM_BRT_RETRIEVE_VIA_PCRUD' : { 'app' : 'open-ils.pcrud', 'method' : 'open-ils.pcrud.search.brt.atomic' },
     'FM_BRE_RETRIEVE_VIA_ID' : { 'app' : 'open-ils.cat', 'method' : 'open-ils.cat.biblio.record.metadata.retrieve', 'secure' : false },
     'FM_BRE_RETRIEVE_VIA_ID.authoritative' : { 'app' : 'open-ils.cat', 'method' : 'open-ils.cat.biblio.record.metadata.retrieve.authoritative', 'secure' : false },
     'FM_BRE_ID_SEARCH_VIA_BARCODE' : { 'app' : 'open-ils.search', 'method' : 'open-ils.search.biblio.find_by_barcode', 'secure' : false },
@@ -205,6 +209,7 @@
     'FM_MP_RETRIEVE_VIA_MBTS_ID.authoritative' : { 'app' : 'open-ils.circ', 'method' : 'open-ils.circ.money.payment.retrieve.all.authoritative' },
     'FM_MG_CREATE' : { 'app' : 'open-ils.circ', 'method' : 'open-ils.circ.money.grocery.create' },
     'FM_MG_RETRIEVE' : { 'app' : 'open-ils.circ', 'method' : 'open-ils.circ.money.grocery.retrieve' },
+    'FM_BRESV_RETRIEVE' : { 'app' : 'open-ils.circ', 'method' : 'open-ils.circ.booking.reservation.retrieve' },
     'FM_MOBTS_HAVING_BALANCE' : { 'app' : 'open-ils.actor', 'method' : 'open-ils.actor.user.transactions.have_balance' },
     'FM_MOBTS_HAVING_BALANCE.authoritative' : { 'app' : 'open-ils.actor', 'method' : 'open-ils.actor.user.transactions.have_balance.authoritative' },
     'FM_MOBTS_TOTAL_HAVING_BALANCE' : { 'app' : 'open-ils.actor', 'method' : 'open-ils.actor.user.transactions.have_balance.total' },

Modified: branches/rel_1_6/Open-ILS/xul/staff_client/chrome/content/main/menu.js
===================================================================
--- branches/rel_1_6/Open-ILS/xul/staff_client/chrome/content/main/menu.js	2010-01-13 16:30:42 UTC (rev 15311)
+++ branches/rel_1_6/Open-ILS/xul/staff_client/chrome/content/main/menu.js	2010-01-13 16:35:03 UTC (rev 15312)
@@ -116,7 +116,6 @@
             );
         }
 
-
         var cmd_map = {
             'cmd_broken' : [
                 ['oncommand'],
@@ -589,6 +588,26 @@
                 ['oncommand'],
                 function() { open_eg_web_page('conify/global/config/circ_modifier'); }
             ],
+            'cmd_server_admin_booking_resource': [
+                ['oncommand'],
+                function() { open_eg_web_page('conify/global/booking/resource'); }
+            ],
+            'cmd_server_admin_booking_resource_type': [
+                ['oncommand'],
+                function() { open_eg_web_page('conify/global/booking/resource_type'); }
+            ],
+            'cmd_server_admin_booking_resource_attr': [
+                ['oncommand'],
+                function() { open_eg_web_page('conify/global/booking/resource_attr'); }
+            ],
+            'cmd_server_admin_booking_resource_attr_value': [
+                ['oncommand'],
+                function() { open_eg_web_page('conify/global/booking/resource_attr_value'); }
+            ],
+            'cmd_server_admin_booking_resource_attr_map': [
+                ['oncommand'],
+                function() { open_eg_web_page('conify/global/booking/resource_attr_map'); }
+            ],
             'cmd_acq_view_picklist' : [
                 ['oncommand'],
                 function() { open_eg_web_page('acq/picklist/list', 'menu.cmd_acq_view_picklist.tab'); }
@@ -633,7 +652,81 @@
                 ['oncommand'],
                 function() { open_eg_web_page('conify/global/acq/distribution_formula', 'menu.cmd_acq_view_distrib_formula.tab'); }
             ],
-
+            'cmd_booking_reservation' : [
+                ['oncommand'],
+                function() {
+                    obj.set_tab(
+                        "/eg/booking/reservation",
+                        {
+                            "tab_name": offlineStrings.getString(
+                                "menu.cmd_booking_reservation.tab"
+                            ),
+                            "browser": false
+                        },
+                        xulG
+                    );
+                }
+            ],
+            'cmd_booking_pull_list' : [
+                ['oncommand'],
+                function() {
+                    obj.set_tab(
+                        "/eg/booking/pull_list",
+                        {
+                            "tab_name": offlineStrings.getString(
+                                "menu.cmd_booking_pull_list.tab"
+                            ),
+                            "browser": false
+                        },
+                        xulG
+                    );
+                }
+            ],
+            'cmd_booking_capture' : [
+                ['oncommand'],
+                function() {
+                    obj.set_tab(
+                        "/eg/booking/capture",
+                        {
+                            "tab_name": offlineStrings.getString(
+                                "menu.cmd_booking_capture.tab"
+                            ),
+                            "browser": false
+                        },
+                        xulG
+                    );
+                }
+            ],
+            'cmd_booking_reservation_pickup' : [
+                ['oncommand'],
+                function() {
+                    obj.set_tab(
+                        "/eg/booking/pickup",
+                        {
+                            "tab_name": offlineStrings.getString(
+                                "menu.cmd_booking_reservation_pickup.tab"
+                            ),
+                            "browser": false
+                        },
+                        xulG
+                    );
+                }
+            ],
+            'cmd_booking_reservation_return' : [
+                ['oncommand'],
+                function() {
+                    obj.set_tab(
+                        "/eg/booking/return",
+                        {
+                            "tab_name": offlineStrings.getString(
+                                "menu.cmd_booking_reservation_return.tab"
+                            ),
+                            "browser": false
+                        },
+                        xulG
+                    );
+                }
+            ],
             'cmd_reprint' : [
                 ['oncommand'],
                 function() {

Modified: branches/rel_1_6/Open-ILS/xul/staff_client/chrome/content/main/menu_frame_menus.xul
===================================================================
--- branches/rel_1_6/Open-ILS/xul/staff_client/chrome/content/main/menu_frame_menus.xul	2010-01-13 16:30:42 UTC (rev 15311)
+++ branches/rel_1_6/Open-ILS/xul/staff_client/chrome/content/main/menu_frame_menus.xul	2010-01-13 16:35:03 UTC (rev 15312)
@@ -86,7 +86,13 @@
     <command id="cmd_acq_view_exchange_rate" />
     <command id="cmd_acq_view_distrib_formula" />
 
+    <command id="cmd_booking_reservation" />
+    <command id="cmd_booking_pull_list" />
+    <command id="cmd_booking_capture" />
+    <command id="cmd_booking_reservation_pickup" />
+    <command id="cmd_booking_reservation_return" />
 
+
     <!-- local admin menu commands -->
     <command id="cmd_local_admin_fonts_and_sounds"/>
     <command id="cmd_local_admin_printer"/>
@@ -117,6 +123,11 @@
     <command id="cmd_server_admin_z39_source"/>
     <command id="cmd_server_admin_circ_mod"/>
 
+    <command id="cmd_server_admin_booking_resource" />
+    <command id="cmd_server_admin_booking_resource_type" />
+    <command id="cmd_server_admin_booking_resource_attr" />
+    <command id="cmd_server_admin_booking_resource_attr_value" />
+    <command id="cmd_server_admin_booking_resource_attr_map" />
 </commandset>
 
 
@@ -243,6 +254,16 @@
     </menupopup>
 </menu>
 
+<!-- The Booking menu on the main menu -->
+<menu id="main.menu.booking" label="&staff.main.menu.booking.label;" accesskey="&staff.main.menu.booking.accesskey;">
+    <menupopup id="main.menu.booking.popup">
+        <menuitem label="&staff.main.menu.booking.reservation.label;" accesskey="&staff.main.menu.booking.reservation.accesskey;" command="cmd_booking_reservation"/>
+        <menuitem label="&staff.main.menu.booking.pull_list.label;" accesskey="&staff.main.menu.booking.pull_list.accesskey;" command="cmd_booking_pull_list"/>
+        <menuitem label="&staff.main.menu.booking.capture.label;" accesskey="&staff.main.menu.booking.capture.accesskey;" command="cmd_booking_capture"/>
+         <menuitem label="&staff.main.menu.booking.reservation_pickup.label;" accesskey="&staff.main.menu.booking.reservation_pickup.accesskey;" command="cmd_booking_reservation_pickup"/>
+        <menuitem label="&staff.main.menu.booking.reservation_return.label;" accesskey="&staff.main.menu.booking.reservation_return.accesskey;" command="cmd_booking_reservation_return"/>
+    </menupopup>
+</menu>
 
 <!-- The Search menu on the main menu -->
 <menu id="main.menu.search" label="&staff.main.menu.search.label;" accesskey="&staff.main.menu.search.accesskey;">
@@ -305,6 +326,15 @@
                 <menuitem label="&staff.main.menu.admin.server_admin.conify.billing_type.label;" command="cmd_server_admin_billing_type"/>
                 <menuitem label="&staff.main.menu.admin.server_admin.conify.z3950_source.label;" command="cmd_server_admin_z39_source"/>
                 <menuitem label="&staff.main.menu.admin.server_admin.conify.circulation_modifier.label;" command="cmd_server_admin_circ_mod"/>
+                <menu id="main.menu.admin.server.booking" label="&staff.main.menu.admin.server_admin.booking.label;" accesskey="&staff.main.menu.admin.server_admin.booking.label;">
+                    <menupopup id="main.menu.admin.server.booking.popup">
+                        <menuitem label="&staff.main.menu.admin.server_admin.booking.resource.label;" command="cmd_server_admin_booking_resource" accesskey="&staff.main.menu.admin.server_admin.booking.resource.accesskey;"/>
+                        <menuitem label="&staff.main.menu.admin.server_admin.booking.resource_type.label;" command="cmd_server_admin_booking_resource_type" accesskey="&staff.main.menu.admin.server_admin.booking.resource_type.accesskey;"/>
+                        <menuitem label="&staff.main.menu.admin.server_admin.booking.resource_attr.label;" command="cmd_server_admin_booking_resource_attr" accesskey="&staff.main.menu.admin.server_admin.booking.resource_attr.accesskey;"/>
+                        <menuitem label="&staff.main.menu.admin.server_admin.booking.resource_attr_value.label;" command="cmd_server_admin_booking_resource_attr_value" accesskey="&staff.main.menu.admin.server_admin.booking.resource_attr_value.accesskey;"/>
+                        <menuitem label="&staff.main.menu.admin.server_admin.booking.resource_attr_map.label;" command="cmd_server_admin_booking_resource_attr_map" accesskey="&staff.main.menu.admin.server_admin.booking.resource_attr_map.accesskey;"/>
+                    </menupopup>
+                </menu>
             </menupopup>
         </menu>
 

Modified: branches/rel_1_6/Open-ILS/xul/staff_client/chrome/content/main/menu_frame_overlay.xul
===================================================================
--- branches/rel_1_6/Open-ILS/xul/staff_client/chrome/content/main/menu_frame_overlay.xul	2010-01-13 16:30:42 UTC (rev 15311)
+++ branches/rel_1_6/Open-ILS/xul/staff_client/chrome/content/main/menu_frame_overlay.xul	2010-01-13 16:35:03 UTC (rev 15312)
@@ -72,6 +72,7 @@
         <menu id="main.menu.circ" />
         <menu id="main.menu.cat" />
         <menu id="main.menu.acq" />
+        <menu id="main.menu.booking" />
         <spacer flex="1" />
         <menu id="main.menu.admin" />
         <!--

Modified: branches/rel_1_6/Open-ILS/xul/staff_client/chrome/content/util/functional.js
===================================================================
--- branches/rel_1_6/Open-ILS/xul/staff_client/chrome/content/util/functional.js	2010-01-13 16:30:42 UTC (rev 15311)
+++ branches/rel_1_6/Open-ILS/xul/staff_client/chrome/content/util/functional.js	2010-01-13 16:35:03 UTC (rev 15312)
@@ -6,7 +6,7 @@
 util.functional.EXPORT_OK    = [ 
     'filter_list', 'filter_object', 'find_list', 'find_object', 'map_list', 'map_flat_list', 
     'map_object', 'map_object_to_list', 'convert_object_list_to_hash', 'find_id_object_in_list', 
-    'find_attr_object_in_list', 'walk_tree_preorder',
+    'find_attr_object_in_list', 'walk_tree_preorder', 'unique_list_values',
 ];
 util.functional.EXPORT_TAGS    = { ':all' : util.functional.EXPORT_OK };
 
@@ -134,4 +134,12 @@
     return null;
 }
 
+util.functional.unique_list_values = function(list) {
+    var obj = {};
+    var finished_list = [];
+    for (var i in list) { obj[list[i]] = true; }
+    for (var i in obj) { finished_list.push(i); }
+    return finished_list;
+}
+
 dump('exiting util/functional.js\n');

Modified: branches/rel_1_6/Open-ILS/xul/staff_client/chrome/locale/en-US/offline.properties
===================================================================
--- branches/rel_1_6/Open-ILS/xul/staff_client/chrome/locale/en-US/offline.properties	2010-01-13 16:30:42 UTC (rev 15311)
+++ branches/rel_1_6/Open-ILS/xul/staff_client/chrome/locale/en-US/offline.properties	2010-01-13 16:35:03 UTC (rev 15312)
@@ -227,6 +227,12 @@
 menu.cmd_acq_view_currency_type.tab=Currency Types
 menu.cmd_acq_view_exchange_rate.tab=Exchange Rates
 menu.cmd_acq_view_distrib_formula.tab=Distribution Formulas
+menu.cmd_booking_resource.tab=Resources
+menu.cmd_booking_reservation.tab=Reservations
+menu.cmd_booking_reservation_pickup.tab=Reservation Pickup
+menu.cmd_booking_reservation_return.tab=Reservation Return
+menu.cmd_booking_pull_list.tab=Booking Pull List
+menu.cmd_booking_capture.tab=Booking Capture
 menu.local_admin.circ_matrix_matchpoint.tab=Circulation Policies
 menu.local_admin.hold_matrix_matchpoint.tab=Hold Policies
 load_printer_settings_error_description=Printer settings did not load cleanly with this version of Evergreen.  You should reconfigure your printer under Printer Settings Editor.

Modified: branches/rel_1_6/Open-ILS/xul/staff_client/server/admin/index.xhtml
===================================================================
--- branches/rel_1_6/Open-ILS/xul/staff_client/server/admin/index.xhtml	2010-01-13 16:30:42 UTC (rev 15311)
+++ branches/rel_1_6/Open-ILS/xul/staff_client/server/admin/index.xhtml	2010-01-13 16:35:03 UTC (rev 15312)
@@ -9,6 +9,8 @@
 <html xmlns="http://www.w3.org/1999/xhtml">
     <head>
         <title>&staff.server.admin.index.title;</title>
+        <script type="text/javascript" src="/js/dojo/dojo/dojo.js"
+            djConfig="parseOnLoad: true, isDebug:false"></script>
         <script>
             function _l(l,p) { 
                 var url = l + location.search;
@@ -20,12 +22,19 @@
                 location.href = url;
             }
             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'>
             body { background-color: white; }
         </style>
     </head>
-    <body>
+    <body onload="my_init();">
         <center>
             <div style='height: 40px; margin-top: 20px; font-weight: bold; font-size: 14pt;'>
                 &staff.server.admin.index.title;
@@ -38,9 +47,10 @@
             <table width='100%'>
                 <thead>
                     <tr>
-                        <th width='30%'>&staff.server.admin.index.workstation_configuration;</th>
-                        <th width='30%'>&staff.server.admin.index.library_configuration;</th>
-                        <th width='30%'>&staff.server.admin.index.maintenance_reports;</th>
+                        <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>
@@ -84,6 +94,22 @@
                             <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>

Modified: branches/rel_1_6/Open-ILS/xul/staff_client/server/cat/copy_browser.js
===================================================================
--- branches/rel_1_6/Open-ILS/xul/staff_client/server/cat/copy_browser.js	2010-01-13 16:30:42 UTC (rev 15311)
+++ branches/rel_1_6/Open-ILS/xul/staff_client/server/cat/copy_browser.js	2010-01-13 16:35:03 UTC (rev 15312)
@@ -145,6 +145,56 @@
                                 }
                             }
                         ],
+                        'cmd_create_brt' : [
+                            ['command'],
+                            function() {
+                                JSAN.use("cat.util");
+                                JSAN.use("util.functional");
+
+                                /* Filter selected rows that aren"t copies. */
+                                var list = util.functional.filter_list(
+                                    obj.sel_list,
+                                    function (o) {
+                                        return o.split(/_/)[0] == "acp";
+                                    }
+                                );
+                                var results = cat.util.make_bookable(
+                                    util.functional.map_list(
+                                        list, function (o) {
+                                            return obj.map_acp[o].id();
+                                        }
+                                    )
+                                );
+                                if (results && results["brsrc"]) {
+                                    cat.util.edit_new_brsrc(results["brsrc"]);
+                                }
+                            }
+                        ],
+                        'cmd_book_item_now' : [
+                            ['command'],
+                            function() {
+                                JSAN.use("cat.util");
+                                JSAN.use("util.functional");
+
+                                /* Filter selected rows that aren"t copies. */
+                                var list = util.functional.filter_list(
+                                    obj.sel_list,
+                                    function (o) {
+                                        return o.split(/_/)[0] == "acp";
+                                    }
+                                );
+                                var results = cat.util.make_bookable(
+                                    util.functional.map_list(
+                                        list, function (o) {
+                                            return obj.map_acp[o].id();
+                                        }
+                                    )
+                                );
+                                if (results) {
+                                    cat.util.edit_new_bresv(results);
+                                }
+                            }
+                        ],
                         'cmd_add_items' : [
                             ['command'],
                             function() {
@@ -1589,6 +1639,7 @@
         try {
             var found_aou = false; var found_acn = false; var found_acp = false;
             var found_aou_with_can_have_vols = false;
+            var sel_copy_libs = {};
             for (var i = 0; i < obj.sel_list.length; i++) {
                 var type = obj.sel_list[i].split(/_/)[0];
                 switch(type) {
@@ -1598,7 +1649,15 @@
                         if ( get_bool( obj.data.hash.aout[ org.ou_type() ].can_have_vols() ) ) found_aou_with_can_have_vols = true;
                     break;
                     case 'acn' : found_acn = true; break;
-                    case 'acp' : found_acp = true; break;
+                    case 'acp' :
+                        found_acp = true;
+                        sel_copy_libs[
+                            obj.map_acn[
+                                "acn_" +
+                                obj.map_acp[obj.sel_list[i]].call_number()
+                            ].owning_lib()
+                        ] = true;
+                        break;
                 }
             }
             obj.controller.view.cmd_add_items.setAttribute('disabled','true');
@@ -1615,6 +1674,8 @@
             obj.controller.view.cmd_transfer_volume.setAttribute('disabled','true');
             obj.controller.view.cmd_transfer_items.setAttribute('disabled','true');
             obj.controller.view.sel_copy_details.setAttribute('disabled','true');
+            obj.controller.view.cmd_create_brt.setAttribute('disabled','true');
+            obj.controller.view.cmd_book_item_now.setAttribute('disabled','true');
             obj.controller.view.sel_patron.setAttribute('disabled','true');
             obj.controller.view.sel_mark_items_damaged.setAttribute('disabled','true');
             obj.controller.view.sel_mark_items_missing.setAttribute('disabled','true');
@@ -1639,7 +1700,13 @@
                 obj.controller.view.cmd_print_spine_labels.setAttribute('disabled','false');
                 obj.controller.view.cmd_transfer_items.setAttribute('disabled','false');
                 obj.controller.view.sel_copy_details.setAttribute('disabled','false');
+                obj.controller.view.cmd_create_brt.setAttribute('disabled','false');
                 obj.controller.view.sel_patron.setAttribute('disabled','false');
+
+                var L = 0; for (var k in sel_copy_libs) L++;
+                if (L < 2) {
+                    obj.controller.view.cmd_book_item_now.setAttribute('disabled','false');
+                }
             }
         } catch(E) {
             obj.error.standard_unexpected_error_alert(document.getElementById('catStrings').getString('staff.cat.copy_browser.actions.error'),E);

Modified: branches/rel_1_6/Open-ILS/xul/staff_client/server/cat/copy_browser.xul
===================================================================
--- branches/rel_1_6/Open-ILS/xul/staff_client/server/cat/copy_browser.xul	2010-01-13 16:30:42 UTC (rev 15311)
+++ branches/rel_1_6/Open-ILS/xul/staff_client/server/cat/copy_browser.xul	2010-01-13 16:35:03 UTC (rev 15312)
@@ -73,6 +73,8 @@
         <command id="save_columns" />
         <command id="cmd_broken" />
         <command id="sel_copy_details"/>
+        <command id="cmd_create_brt"/>
+        <command id="cmd_book_item_now"/>
         <command id="sel_patron"/>
         <command id="sel_clip" />
         <command id="cmd_clear" />
@@ -103,6 +105,10 @@
             <menuitem command="sel_clip" label="&staff.cat.copy_browser.actions.sel_clip.label;" accesskey="&staff.cat.copy_browser.actions.sel_clip.accesskey;"/>
             <menuitem command="cmd_add_items_to_buckets" label="&staff.cat.copy_browser.actions.cmd_add_items_to_buckets.label;" accesskey="&staff.cat.copy_browser.actions.cmd_add_items_to_buckets.accesskey;"/>
             <menuitem command="sel_copy_details" label="&staff.cat.copy_browser.actions.sel_copy_details.label;" accesskey="&staff.cat.copy_browser.actions.sel_copy_details.label;" />
+            <menuseparator/>
+            <menuitem command="cmd_create_brt" label="&staff.cat.copy_browser.actions.cmd_create_brt.label;" accesskey="&staff.cat.copy_browser.actions.cmd_create_brt.accesskey;" />
+            <menuitem command="cmd_book_item_now" label="&staff.cat.copy_browser.actions.cmd_book_item_now.label;" accesskey="&staff.cat.copy_browser.actions.cmd_book_item_now.accesskey;" />
+            <menuseparator/>
             <menuitem command="sel_patron" label="&staff.cat.copy_browser.actions.sel_patron.label;" accesskey="&staff.cat.copy_browser.actions.sel_patron.accesskey;"/>
             <menuseparator/>
             <menuitem command="cmd_edit_items" label="&staff.cat.copy_browser.actions.cmd_edit_items.label;" accesskey="&staff.cat.copy_browser.actions.cmd_edit_items.accesskey;"/>
@@ -154,6 +160,7 @@
                         <menuitem command="sel_clip" label="&staff.cat.copy_browser.holdings_maintenance.sel_clip.label;" accesskey="&staff.cat.copy_browser.holdings_maintenance.sel_clip.accesskey;"/>
                         <menuitem command="cmd_add_items_to_buckets" label="&staff.cat.copy_browser.holdings_maintenance.add_items_to_bucket.label;" accesskey="&staff.cat.copy_browser.holdings_maintenance.add_items_to_bucket.accesskey;"/>
                         <menuitem command="sel_copy_details" label="&staff.cat.copy_browser.holdings_maintenance.sel_copy_details.label;" accesskey="&staff.cat.copy_browser.holdings_maintenance.sel_copy_details.accesskey;" />
+                        <menuitem command="cmd_create_brt" label="&staff.cat.copy_browser.holdings_maintenance.cmd_create_brt.label;" accesskey="&staff.cat.copy_browser.holdings_maintenance.cmd_create_brt.accesskey;" />
                         <menuitem command="sel_patron" label="&staff.cat.copy_browser.holdings_maintenance.sel_patron.label;" accesskey="&staff.cat.copy_browser.holdings_maintenance.sel_patron.accesskey;"/>
                         <menuseparator/>
                         <menuitem command="cmd_edit_items" label="&staff.cat.copy_browser.holdings_maintenance.cmd_edit_items.label;" accesskey="&staff.cat.copy_browser.holdings_maintenance.cmd_edit_items.accesskey;"/>

Modified: branches/rel_1_6/Open-ILS/xul/staff_client/server/cat/util.js
===================================================================
--- branches/rel_1_6/Open-ILS/xul/staff_client/server/cat/util.js	2010-01-13 16:30:42 UTC (rev 15311)
+++ branches/rel_1_6/Open-ILS/xul/staff_client/server/cat/util.js	2010-01-13 16:35:03 UTC (rev 15312)
@@ -501,4 +501,77 @@
         if (error) error.standard_unexpected_error_alert('cat.util.fast_item_add',E); else alert('FIXME: ' + E);
     }
 }
+
+cat.util.make_bookable = function(copy_ids) {
+    var results = fieldmapper.standardRequest(
+        ["open-ils.booking", "open-ils.booking.resources.create_from_copies"],
+        [ses(), copy_ids]
+    );
+    if (results == null) {
+        alert(document.getElementById("catStrings").getString(
+            "staff.cat.copy_browser.make_bookable.create_failed_silent"
+        ));
+    }
+    else if (typeof results.ilsevent != "undefined") {
+        alert(document.getElementById("catStrings").getFormattedString(
+            "staff.cat.copy_browser.make_bookable.create_failed",
+            [results.ilsevent, results.textcode, results.desc, results.debug]
+        ));
+    }
+    return results;
+}
+
+cat.util.edit_new_brsrc = function(brsrc_list) {
+    /* Spawn new tab to allow editing new resources. */
+    try {
+        xulG.resultant_brsrc = brsrc_list.map(function(o) { return o[0]; });
+        xulG.new_tab(
+            urls.XUL_BROWSER + "?url=" + window.escape(
+                xulG.url_prefix("/eg/conify/global/booking/resource")
+            ), {
+                "tab_name": offlineStrings.getString(
+                    "menu.cmd_booking_resource.tab"
+                 ),
+                "browser" : true
+            }, {
+                "no_xulG": false,
+                "show_print_button": false,
+                "show_nav_buttons": true,
+                "passthru_content_params": xulG
+            }
+        );
+    } catch(E) {
+        alert(
+            document.getElementById("catStrings").getFormattedString(
+                "staff.cat.copy_browser.make_bookable.newtab_failed"
+            ), E
+        );
+    }
+}
+
+cat.util.edit_new_bresv = function(booking_results) {
+    /* Spawn new tab to allow editing new reservations. */
+    try {
+        if (xulG.auth == undefined) {
+            xulG.auth = {"session": {"key": ses()}};
+        }
+        xulG.bresv_interface_opts = {"booking_results": booking_results};
+        xulG.new_tab(
+            xulG.url_prefix("/eg/booking/reservation"),
+            {
+                "tab_name": offlineStrings.getString(
+                    "menu.cmd_booking_reservation.tab"
+                 ),
+                "browser" : false
+            }, xulG
+        );
+    } catch(E) {
+        alert(
+            document.getElementById("catStrings").getString(
+                "staff.cat.copy_browser.make_bookable.newtab_failed"
+            ) + E
+        );
+    }
+}
+
 dump('exiting cat/util.js\n');

Modified: branches/rel_1_6/Open-ILS/xul/staff_client/server/circ/copy_status.js
===================================================================
--- branches/rel_1_6/Open-ILS/xul/staff_client/server/circ/copy_status.js	2010-01-13 16:30:42 UTC (rev 15311)
+++ branches/rel_1_6/Open-ILS/xul/staff_client/server/circ/copy_status.js	2010-01-13 16:35:03 UTC (rev 15312)
@@ -63,6 +63,8 @@
                             obj.controller.view.sel_mark_items_damaged.setAttribute('disabled','true');
                             obj.controller.view.sel_mark_items_missing.setAttribute('disabled','true');
                             obj.controller.view.sel_patron.setAttribute('disabled','true');
+                            obj.controller.view.cmd_create_brt.setAttribute('disabled','true');
+                            obj.controller.view.cmd_book_item_now.setAttribute('disabled','true');
                             obj.controller.view.sel_spine.setAttribute('disabled','true');
                             obj.controller.view.sel_transit_abort.setAttribute('disabled','true');
                             obj.controller.view.sel_clip.setAttribute('disabled','true');
@@ -87,6 +89,12 @@
                             obj.controller.view.sel_copy_details.setAttribute('disabled','false');
                             obj.controller.view.sel_mark_items_damaged.setAttribute('disabled','false');
                             obj.controller.view.sel_mark_items_missing.setAttribute('disabled','false');
+                            if (obj.selected_one_unique_owning_lib()) {
+                                obj.controller.view.cmd_book_item_now.setAttribute('disabled','false');
+                            } else {
+                                obj.controller.view.cmd_book_item_now.setAttribute('disabled','true');
+                            }
+                            obj.controller.view.cmd_create_brt.setAttribute('disabled','false');
                             obj.controller.view.sel_spine.setAttribute('disabled','false');
                             obj.controller.view.sel_transit_abort.setAttribute('disabled','false');
                             obj.controller.view.sel_clip.setAttribute('disabled','false');
@@ -127,6 +135,42 @@
                     ],
                     'sel_clip' : [ ['command'], function() { obj.list.clipboard(); obj.controller.view.copy_status_barcode_entry_textbox.focus(); } ],
                     'save_columns' : [ ['command'], function() { obj.list.save_columns(); obj.controller.view.copy_status_barcode_entry_textbox.focus(); } ],
+                    'cmd_create_brt' : [
+                        ['command'],
+                        function() {
+                            JSAN.use("cat.util");
+                            JSAN.use("util.functional");
+
+                            var results = cat.util.make_bookable(
+                                util.functional.map_list(
+                                    obj.selection_list, function (o) {
+                                        return o.copy_id;
+                                    }
+                                )
+                            );
+                            if (results && results["brsrc"]) {
+                                cat.util.edit_new_brsrc(results["brsrc"]);
+                            }
+                        }
+                    ],
+                    'cmd_book_item_now' : [
+                        ['command'],
+                        function() {
+                            JSAN.use("cat.util");
+                            JSAN.use("util.functional");
+
+                            var results = cat.util.make_bookable(
+                                util.functional.map_list(
+                                    obj.selection_list, function (o) {
+                                        return o.copy_id;
+                                    }
+                                )
+                            );
+                            if (results) {
+                                cat.util.edit_new_bresv(results);
+                            }
+                        }
+                    ],
                     'sel_checkin' : [
                         ['command'],
                         function() {
@@ -949,6 +993,15 @@
 
     },
 
+    'selected_one_unique_owning_lib': function () {
+        JSAN.use('util.functional');
+        var list = util.functional.map_list(
+            this.selection_list,
+            function(o) { return o.owning_lib; }
+        );
+        return util.functional.unique_list_values(list).length == 1;
+    },
+
     'test_barcode' : function(bc) {
         var obj = this;
         var good = util.barcode.check(bc);
@@ -1041,6 +1094,7 @@
                                 'renewable' : details.circ ? 't' : 'f', 
                                 'copy_id' : details.copy.id(), 
                                 'acn_id' : details.volume ? details.volume.id() : -1, 
+                                'owning_lib' : details.volume ? details.volume.owning_lib() : -1, 
                                 'barcode' : barcode, 
                                 'doc_id' : details.mvr ? details.mvr.doc_id() : null  
                             } 

Modified: branches/rel_1_6/Open-ILS/xul/staff_client/server/circ/copy_status.xul
===================================================================
--- branches/rel_1_6/Open-ILS/xul/staff_client/server/circ/copy_status.xul	2010-01-13 16:30:42 UTC (rev 15311)
+++ branches/rel_1_6/Open-ILS/xul/staff_client/server/circ/copy_status.xul	2010-01-13 16:35:03 UTC (rev 15312)
@@ -102,6 +102,8 @@
         <command id="cmd_copy_status_upload_file" />
         <command id="cmd_copy_status_print" />
         <command id="save_columns" />
+        <command id="cmd_create_brt" disabled="true"/>
+        <command id="cmd_book_item_now" disabled="true"/>
         <command id="sel_copy_details" disabled="true"/>
         <command id="sel_mark_items_damaged" disabled="true"/>
         <command id="sel_mark_items_missing" disabled="true"/>

Modified: branches/rel_1_6/Open-ILS/xul/staff_client/server/circ/copy_status_overlay.xul
===================================================================
--- branches/rel_1_6/Open-ILS/xul/staff_client/server/circ/copy_status_overlay.xul	2010-01-13 16:30:42 UTC (rev 15311)
+++ branches/rel_1_6/Open-ILS/xul/staff_client/server/circ/copy_status_overlay.xul	2010-01-13 16:35:03 UTC (rev 15312)
@@ -18,6 +18,9 @@
         <menuitem command="sel_copy_details" label="&staff.circ.copy_status_overlay.sel_copy_details.label;" accesskey="&staff.circ.copy_status_overlay.sel_copy_details.accesskey;" />
         <menuitem command="sel_patron" label="&staff.circ.copy_status_overlay.sel_patron.label;" accesskey="&staff.circ.copy_status_overlay.sel_patron.accesskey;"/>
         <menuseparator/>
+        <menuitem command="cmd_create_brt" label="&staff.circ.copy_status_overlay.cmd_create_brt.label;" accesskey="&staff.circ.copy_status_overlay.cmd_create_brt.accesskey;"/>
+        <menuitem command="cmd_book_item_now" label="&staff.circ.copy_status_overlay.cmd_book_item_now.label;" accesskey="&staff.circ.copy_status_overlay.cmd_book_item_now.accesskey;"/>
+        <menuseparator/>
         <menuitem command="sel_edit" label="&staff.circ.copy_status_overlay.sel_edit.label;" accesskey="&staff.circ.copy_status_overlay.sel_edit.accesskey;" />
         <menuseparator/>
         <menuitem command="sel_mark_items_damaged" label="&staff.circ.copy_status_overlay.sel_mark_items_damaged.label;" accesskey="&staff.circ.copy_status_overlay.sel_mark_items_damaged.accesskey;"/>
@@ -148,6 +151,9 @@
             <menuitem command="sel_copy_details" label="&staff.circ.copy_status_overlay.sel_copy_details.label;" accesskey="&staff.circ.copy_status_overlay.sel_copy_details.accesskey;" />
             <menuitem command="sel_patron" label="&staff.circ.copy_status_overlay.sel_patron.label;" accesskey="&staff.circ.copy_status_overlay.sel_patron.accesskey;"/>
             <menuseparator />
+            <menuitem command="cmd_create_brt" label="&staff.circ.copy_status_overlay.cmd_create_brt.label;" accesskey="&staff.circ.copy_status_overlay.cmd_create_brt.accesskey;"/>
+            <menuitem command="cmd_book_item_now" label="&staff.circ.copy_status_overlay.cmd_book_item_now.label;" accesskey="&staff.circ.copy_status_overlay.cmd_book_item_now.accesskey;"/>
+            <menuseparator />
             <menuitem command="sel_edit" label="&staff.circ.copy_status_overlay.sel_edit.label;" accesskey="&staff.circ.copy_status_overlay.sel_edit.accesskey;" />
             <menuseparator />
             <menuitem command="sel_mark_items_damaged" label="&staff.circ.copy_status_overlay.sel_mark_items_damaged.label;" accesskey="&staff.circ.copy_status_overlay.sel_mark_items_damaged.accesskey;"/>

Modified: branches/rel_1_6/Open-ILS/xul/staff_client/server/locale/en-US/cat.properties
===================================================================
--- branches/rel_1_6/Open-ILS/xul/staff_client/server/locale/en-US/cat.properties	2010-01-13 16:30:42 UTC (rev 15311)
+++ branches/rel_1_6/Open-ILS/xul/staff_client/server/locale/en-US/cat.properties	2010-01-13 16:35:03 UTC (rev 15312)
@@ -11,6 +11,10 @@
 staff.cat.copy_browser.add_item.title=Add Item
 staff.cat.copy_browser.add_item.error=copy browser -> add copies
 staff.cat.copy_browser.add_items_bucket.error=copy browser -> add copies to bucket
+staff.cat.copy_browser.make_bookable.create_failed_silent=No response from server
+staff.cat.copy_browser.make_bookable.create_failed=Error from server: %1$d %2$s\n%3$s\n%4$s
+staff.cat.copy_browser.make_bookable.newtab_failed=Could not open new tab
+staff.cat.copy_browser.make_bookable.newtab_name=Resources
 staff.cat.copy_browser.replace_barcode.failed=Barcode %1$s not likely replaced.
 staff.cat.copy_browser.replace_barcode.error=copy browser -> replace barcode
 staff.cat.copy_browser.edit_items.error=Copy Browser -> Edit Items

Modified: branches/rel_1_6/Open-ILS/xul/staff_client/server/locale/en-US/patron.properties
===================================================================
--- branches/rel_1_6/Open-ILS/xul/staff_client/server/locale/en-US/patron.properties	2010-01-13 16:30:42 UTC (rev 15311)
+++ branches/rel_1_6/Open-ILS/xul/staff_client/server/locale/en-US/patron.properties	2010-01-13 16:35:03 UTC (rev 15312)
@@ -37,6 +37,9 @@
 staff.patron.bills.pay.annotate_payment.title=Annotate Payment
 staff.patron.bills.pay.refund_exceeds_desk_payment=%1$s\n\nAnother way to "zero" this transaction is to use Add Billing and add a miscellaneous bill to counter the negative balance.
 staff.patron.bills.pay.payment_failed=Bill payment likely failed
+staff.patron.bills.info_box.label_value.reservation=Reservation
+# 1 - Resource Barcode  2 - Resource Type Name
+staff.patron.bills.info_box.value_format.reservation=%1$s : %2$s
 staff.patron.bills.info_box.label_value.title=Title
 staff.patron.bills.info_box.label_value.type=Type
 staff.patron.bills.info_box.label_value.last_billing=Last Billing:

Modified: branches/rel_1_6/Open-ILS/xul/staff_client/server/patron/bills.js
===================================================================
--- branches/rel_1_6/Open-ILS/xul/staff_client/server/patron/bills.js	2010-01-13 16:30:42 UTC (rev 15311)
+++ branches/rel_1_6/Open-ILS/xul/staff_client/server/patron/bills.js	2010-01-13 16:35:03 UTC (rev 15312)
@@ -911,6 +911,50 @@
                                 }
                             );
                         break;
+                        case 'reservation':
+                            xt_label.setAttribute( 'value', $("patronStrings").getString('staff.patron.bills.info_box.label_value.reservation') );
+                            obj.network.simple_request(
+                                'FM_BRESV_RETRIEVE_VIA_PCRUD', 
+                                [ ses(), { 'id' : { '=' : my.mobts.id() } } ],
+                                function (req) {
+                                    try {
+                                        var reservation = req.getResultObject()[0];
+                                        if (typeof reservation.ilsevent != 'undefined') { return; }
+                                        obj.network.simple_request(
+                                            'FM_BRSRC_RETRIEVE_VIA_PCRUD',
+                                            [ ses(), { 'id' : { '=' : reservation.target_resource() } } ],
+                                            function (rreq) {
+                                                try {
+                                                    var resource = rreq.getResultObject()[0];
+                                                    if (typeof resource.ilsevent != 'undefined') { return; }
+                                                    obj.network.simple_request(
+                                                        'FM_BRT_RETRIEVE_VIA_PCRUD',
+                                                        [ ses(), { 'id' : { '=' : resource.type() } } ],
+                                                        function (rrreq) {
+                                                            try {
+                                                                var resource_type = rrreq.getResultObject()[0];
+                                                                if (typeof resource_type.ilsevent != 'undefined') { return; }
+                                                                xt_value.appendChild(
+                                                                    document.createTextNode(
+                                                                        $("patronStrings").getFormattedString('staff.patron.bills.info_box.value_format.reservation', [resource.barcode(), resource_type.name()]) 
+                                                                    )
+                                                                );
+                                                            } catch(E) {
+                                                                alert(E);
+                                                            }
+                                                        }
+                                                    );
+                                                } catch(E) {
+                                                    alert(E);
+                                                }
+                                            }
+                                        );
+                                    } catch(E) {
+                                        alert(E);
+                                    }
+                                }
+                            );
+                        break;
                         default:
                                 xt_label.setAttribute( 'value',
                                     my.mvr ? $("patronStrings").getString('staff.patron.bills.info_box.label_value.title') : $("patronStrings").getString('staff.patron.bills.info_box.label_value.type') );

Modified: branches/rel_1_6/Open-ILS/xul/staff_client/server/patron/display.js
===================================================================
--- branches/rel_1_6/Open-ILS/xul/staff_client/server/patron/display.js	2010-01-13 16:30:42 UTC (rev 15311)
+++ branches/rel_1_6/Open-ILS/xul/staff_client/server/patron/display.js	2010-01-13 16:35:03 UTC (rev 15312)
@@ -353,6 +353,69 @@
                             }
                         }
                     ],
+                    'cmd_patron_reservation' : [
+                        ['command'],
+                        function(ev) {
+                            if (xulG.auth == undefined) {
+                                xulG.auth = {"session": {"key": ses()}};
+                            }
+                            xulG.bresv_interface_opts = {
+                                "patron_barcode": obj.patron.card().barcode()
+                            };
+                            xulG.new_tab(
+                                "/eg/booking/reservation",
+                                {
+                                    "tab_name": offlineStrings.getString(
+                                        "menu.cmd_booking_reservation.tab"
+                                    ),
+                                    "browser": false
+                                },
+                                xulG
+                            );
+                        }
+                    ],
+                    'cmd_patron_reservation_pickup' : [
+                        ['command'],
+                        function(ev) {
+                            if (xulG.auth == undefined) {
+                                xulG.auth = {"session": {"key": ses()}};
+                            }
+                            xulG.bresv_interface_opts = {
+                                "patron_barcode": obj.patron.card().barcode()
+                            };
+                            xulG.new_tab(
+                                "/eg/booking/pickup",
+                                {
+                                    "tab_name": offlineStrings.getString(
+                                        "menu.cmd_booking_reservation_pickup.tab"
+                                    ),
+                                    "browser": false
+                                },
+                                xulG
+                            );
+                        }
+                    ],
+                    'cmd_patron_reservation_return' : [
+                        ['command'],
+                        function(ev) {
+                            if (xulG.auth == undefined) {
+                                xulG.auth = {"session": {"key": ses()}};
+                            }
+                            xulG.bresv_interface_opts = {
+                                "patron_barcode": obj.patron.card().barcode()
+                            };
+                            xulG.new_tab(
+                                "/eg/booking/return",
+                                {
+                                    "tab_name": offlineStrings.getString(
+                                        "menu.cmd_booking_reservation_return.tab"
+                                    ),
+                                    "browser": false
+                                },
+                                xulG
+                            );
+                        }
+                    ],
                     'cmd_patron_exit' : [
                         ['command'],
                         function(ev) {

Modified: branches/rel_1_6/Open-ILS/xul/staff_client/server/patron/display.xul
===================================================================
--- branches/rel_1_6/Open-ILS/xul/staff_client/server/patron/display.xul	2010-01-13 16:30:42 UTC (rev 15311)
+++ branches/rel_1_6/Open-ILS/xul/staff_client/server/patron/display.xul	2010-01-13 16:35:03 UTC (rev 15312)
@@ -102,6 +102,9 @@
         <command id="cmd_patron_info_groups" />
         <command id="cmd_patron_other" />
         <command id="cmd_patron_alert" />
+        <command id="cmd_patron_reservation" />
+        <command id="cmd_patron_reservation_pickup" />
+        <command id="cmd_patron_reservation_return" />
         <command id="cmd_patron_exit" />
         <command id="cmd_patron_retrieve" />
         <command id="cmd_patron_merge" />

Modified: branches/rel_1_6/Open-ILS/xul/staff_client/server/patron/display_horiz.xul
===================================================================
--- branches/rel_1_6/Open-ILS/xul/staff_client/server/patron/display_horiz.xul	2010-01-13 16:30:42 UTC (rev 15311)
+++ branches/rel_1_6/Open-ILS/xul/staff_client/server/patron/display_horiz.xul	2010-01-13 16:35:03 UTC (rev 15312)
@@ -102,6 +102,9 @@
         <command id="cmd_patron_info_groups" />
         <command id="cmd_patron_other" />
         <command id="cmd_patron_alert" />
+        <command id="cmd_patron_reservation" />
+        <command id="cmd_patron_reservation_pickup" />
+        <command id="cmd_patron_reservation_return" />
         <command id="cmd_patron_exit" />
         <command id="cmd_patron_retrieve" />
         <command id="cmd_patron_merge" />

Modified: branches/rel_1_6/Open-ILS/xul/staff_client/server/patron/display_horiz_overlay.xul
===================================================================
--- branches/rel_1_6/Open-ILS/xul/staff_client/server/patron/display_horiz_overlay.xul	2010-01-13 16:30:42 UTC (rev 15311)
+++ branches/rel_1_6/Open-ILS/xul/staff_client/server/patron/display_horiz_overlay.xul	2010-01-13 16:35:03 UTC (rev 15312)
@@ -91,6 +91,13 @@
                                     <menuitem label="&staff.patron_navbar.alert;" accesskey="&staff.patron_navbar.alert.accesskey;" command="cmd_patron_alert"/>
                                     <menuitem label="&staff.patron.info.notes.label;" accesskey="&staff.patron.info.notes.accesskey;" command="cmd_patron_info_notes"/>
                                     <menuitem label="&staff.patron.info.stat_cats.label;" accesskey="&staff.patron.info.stat_cats.accesskey;" command="cmd_patron_info_stats"/>
+                                    <menu id="PatronNavBar_other_booking" label="&staff.main.menu.booking.label;" accesskey="&staff.main.menu.booking.accesskey;">
+                                        <menupopup id="PatronNavBar_other_booking_popup">
+                                            <menuitem label="&staff.main.menu.booking.reservation.label;" accesskey="&staff.main.menu.booking.reservation.accesskey;" command="cmd_patron_reservation" />
+                                            <menuitem label="&staff.main.menu.booking.reservation_pickup.label;" accesskey="&staff.main.menu.booking.reservation_pickup.accesskey;" command="cmd_patron_reservation_pickup" />
+                                            <menuitem label="&staff.main.menu.booking.reservation_return.label;" accesskey="&staff.main.menu.booking.reservation_return.accesskey;" command="cmd_patron_reservation_return" />
+                                        </menupopup>
+                                    </menu>
                                     <menuitem label="&staff.patron.info.surveys.label;" accesskey="&staff.patron.info.surveys.accesskey;" command="cmd_patron_info_surveys"/>
                                     <menuitem label="&staff.patron.info.group.label;" accesskey="&staff.patron.info.group.accesskey;" command="cmd_patron_info_groups"/>
                                     <menuitem label="&staff.patron_display.verify_password.label;" accesskey="&staff.patron_display.verify_password.accesskey;" command="cmd_verify_credentials"/>

Modified: branches/rel_1_6/Open-ILS/xul/staff_client/server/patron/display_overlay.xul
===================================================================
--- branches/rel_1_6/Open-ILS/xul/staff_client/server/patron/display_overlay.xul	2010-01-13 16:30:42 UTC (rev 15311)
+++ branches/rel_1_6/Open-ILS/xul/staff_client/server/patron/display_overlay.xul	2010-01-13 16:35:03 UTC (rev 15312)
@@ -91,6 +91,13 @@
                                     <menuitem label="&staff.patron_navbar.alert;" accesskey="&staff.patron_navbar.alert.accesskey;" command="cmd_patron_alert"/>
                                     <menuitem label="&staff.patron.info.notes.label;" accesskey="&staff.patron.info.notes.accesskey;" command="cmd_patron_info_notes"/>
                                     <menuitem label="&staff.patron.info.stat_cats.label;" accesskey="&staff.patron.info.stat_cats.accesskey;" command="cmd_patron_info_stats"/>
+                                    <menu id="PatronNavBar_other_booking" label="&staff.main.menu.booking.label;" accesskey="&staff.main.menu.booking.accesskey;">
+                                        <menupopup id="PatronNavBar_other_booking_popup">
+                                            <menuitem label="&staff.main.menu.booking.reservation.label;" accesskey="&staff.main.menu.booking.reservation.accesskey;" command="cmd_patron_reservation" />
+                                            <menuitem label="&staff.main.menu.booking.reservation_pickup.label;" accesskey="&staff.main.menu.booking.reservation_pickup.accesskey;" command="cmd_patron_reservation_pickup" />
+                                            <menuitem label="&staff.main.menu.booking.reservation_return.label;" accesskey="&staff.main.menu.booking.reservation_return.accesskey;" command="cmd_patron_reservation_return" />
+                                        </menupopup>
+                                    </menu>
                                     <menuitem label="&staff.patron.info.surveys.label;" accesskey="&staff.patron.info.surveys.accesskey;" command="cmd_patron_info_surveys"/>
                                     <menuitem label="&staff.patron.info.group.label;" accesskey="&staff.patron.info.group.accesskey;" command="cmd_patron_info_groups"/>
                                     <menuitem label="&staff.patron_display.verify_password.label;" accesskey="&staff.patron_display.verify_password.accesskey;" command="cmd_verify_credentials"/>



More information about the open-ils-commits mailing list