From 92bd9092229a7b2ba345018e596d7ad64c139e85 Mon Sep 17 00:00:00 2001
From: phasefx <phasefx@dcc99617-32d9-48b4-a31d-7c20da2025e4>
Date: Wed, 27 Oct 2010 18:10:45 +0000
Subject: [PATCH] Give staff the ability to select a circ lib and user group
 and mark all associated overdue circulations/items as Lost.  Admin->Local
 System Administration->Age Overdue Circs to Lost.  This may be used, for
 example, by academic libraries after the end of a semester.  Must remember to
 activate the template and to give it an editor param.

git-svn-id: svn://svn.open-ils.org/ILS/trunk@18498 dcc99617-32d9-48b4-a31d-7c20da2025e4
---
 .../src/perlmods/OpenILS/Application/AppUtils.pm   |  21 ++++
 Open-ILS/src/perlmods/OpenILS/Application/Circ.pm  |  86 +++++++++++++++
 Open-ILS/src/sql/Pg/002.schema.config.sql          |   2 +-
 Open-ILS/src/sql/Pg/950.data.seed-values.sql       |  35 ++++++
 .../0448.data.trigger.circ.staff_age_to_lost.sql   |  42 +++++++
 Open-ILS/web/opac/locale/en-US/lang.dtd            |   7 ++
 .../staff_client/chrome/content/main/constants.js  |   1 +
 .../xul/staff_client/chrome/content/main/menu.js   |   4 +
 .../chrome/content/main/menu_frame_menus.xul       |   4 +
 .../chrome/locale/en-US/offline.properties         |   1 +
 .../staff_client/server/admin/circ_age_to_lost.js  | 121 +++++++++++++++++++++
 .../staff_client/server/admin/circ_age_to_lost.xul |  65 +++++++++++
 .../server/locale/en-US/admin.properties           |   2 +
 13 files changed, 390 insertions(+), 1 deletion(-)
 create mode 100644 Open-ILS/src/sql/Pg/upgrade/0448.data.trigger.circ.staff_age_to_lost.sql
 create mode 100644 Open-ILS/xul/staff_client/server/admin/circ_age_to_lost.js
 create mode 100644 Open-ILS/xul/staff_client/server/admin/circ_age_to_lost.xul

diff --git a/Open-ILS/src/perlmods/OpenILS/Application/AppUtils.pm b/Open-ILS/src/perlmods/OpenILS/Application/AppUtils.pm
index 6bf3c4c28b..c3c7ae0c7e 100644
--- a/Open-ILS/src/perlmods/OpenILS/Application/AppUtils.pm
+++ b/Open-ILS/src/perlmods/OpenILS/Application/AppUtils.pm
@@ -640,6 +640,27 @@ sub fetch_permission_group_tree {
 		'open-ils.actor.groups.tree.retrieve' );
 }
 
+sub fetch_permission_group_descendants {
+    my( $self, $profile ) = @_;
+    my $group_tree = $self->fetch_permission_group_tree();
+    my $start_here;
+    my @groups;
+
+    # FIXME: okay, so it's not an org tree, but it is compatible
+    $self->walk_org_tree($group_tree, sub {
+        my $g = shift;
+        if ($g->id == $profile) {
+            $start_here = $g;
+        }
+    });
+
+    $self->walk_org_tree($start_here, sub {
+        my $g = shift;
+        push(@groups,$g->id);
+    });
+
+    return \@groups;
+}
 
 sub fetch_patron_circ_summary {
 	my( $self, $userid ) = @_;
diff --git a/Open-ILS/src/perlmods/OpenILS/Application/Circ.pm b/Open-ILS/src/perlmods/OpenILS/Application/Circ.pm
index 89a06fa31c..52e76bc900 100644
--- a/Open-ILS/src/perlmods/OpenILS/Application/Circ.pm
+++ b/Open-ILS/src/perlmods/OpenILS/Application/Circ.pm
@@ -252,6 +252,92 @@ sub title_from_transaction {
 	return $apputils->record_to_mvr($title);
 }
 
+__PACKAGE__->register_method(
+	method	=> "staff_age_to_lost",
+	api_name	=> "open-ils.circ.circulation.age_to_lost",
+    stream => 1,
+	signature	=> q/
+        This fires a circ.staff_age_to_lost Action-Trigger event against all
+        overdue circulations in scope of the specified context library and
+        user profile, which effectively marks the associated items as Lost.
+        This is likely to be done at the end of a semester in an academic
+        library, etc.
+		@param auth
+		@param args : circ_lib, user_profile
+	/
+);
+
+sub staff_age_to_lost {
+    my( $self, $conn, $auth, $args ) = @_;
+
+    my $orgs = $U->get_org_descendants($args->{'circ_lib'});
+    my $profiles = $U->fetch_permission_group_descendants($args->{'user_profile'});
+
+    my $ses = OpenSRF::AppSession->create('open-ils.trigger');
+
+    my $method = 'open-ils.trigger.passive.event.autocreate.batch';
+    my $hook = 'circ.staff_age_to_lost';
+    my $context_org = 'circ_lib';
+    my $opt_granularity = undef;
+    my $filter = { 
+        "checkin_time" => undef,
+        "due_date" => { "<" => "now" }, 
+        "-or" => [ 
+            { "stop_fines"  => ["MAXFINES", "LONGOVERDUE"] }, # FIXME: CLAIMSRETURNED also?
+            { "stop_fines"  => undef }
+        ],
+        "-and" => [
+            {"-exists" => {
+                "select" => {"au" => ["id"]},
+                "from"   => "au",
+                "where"  => {
+                    "profile" => $profiles,
+                    "id" => { "=" => {"+circ" => "usr"} }
+                }
+            }},
+            {"-exists" => {
+                "select" => {"aou" => ["id"]},
+                "from"   => "aou",
+                "where"  => {
+                    "-and" => [
+                        {"id" => { "=" => {"+circ" => "circ_lib"} }},
+                        {"id" => $orgs}
+                    ]
+                }
+            }}
+        ]
+    };
+    my $req_timeout = 10800;
+    my $chunk_size = 100;
+    my $progress = 1;
+
+    my $req = $ses->request($method, $hook, $context_org, $filter, $opt_granularity);
+    my @event_ids; my @chunked_ids;
+    while (my $resp = $req->recv(timeout => $req_timeout)) {
+        push(@event_ids, $resp->content);
+        push(@chunked_ids, $resp->content);
+        if (scalar(@chunked_ids) > $chunk_size) {
+            $conn->respond({'progress'=>$progress++}); # 'event_ids'=>@chunked_ids
+            @chunked_ids = ();
+        }
+    }
+    if (scalar(@chunked_ids) > 0) {
+        $conn->respond({'progress'=>$progress++}); # 'event_ids'=>@chunked_ids
+    }
+
+    if(@event_ids) {
+        $logger->info("staff_age_to_lost: created ".scalar(@event_ids)." events for circ.staff_age_to_lost");
+        $conn->respond_complete({'total_progress'=>$progress-1,'created'=>scalar(@event_ids)});
+    } elsif($req->complete) {
+        $logger->info("staff_age_to_lost: no events to create for circ.staff_age_to_lost");
+        $conn->respond_complete({'total_progress'=>$progress-1,'created'=>0});
+    } else {
+        $logger->warn("staff_age_to_lost: timeout occurred during event creation for circ.staff_age_to_lost");
+        $conn->respond_complete({'total_progress'=>$progress-1,'error'=>'timeout'});
+    }
+
+    return undef;
+}
 
 
 __PACKAGE__->register_method(
diff --git a/Open-ILS/src/sql/Pg/002.schema.config.sql b/Open-ILS/src/sql/Pg/002.schema.config.sql
index 6c4baf98df..09f9d83929 100644
--- a/Open-ILS/src/sql/Pg/002.schema.config.sql
+++ b/Open-ILS/src/sql/Pg/002.schema.config.sql
@@ -70,7 +70,7 @@ CREATE TABLE config.upgrade_log (
     install_date    TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW()
 );
 
-INSERT INTO config.upgrade_log (version) VALUES ('0447'); -- gmc
+INSERT INTO config.upgrade_log (version) VALUES ('0448'); -- phasefx
 
 CREATE TABLE config.bib_source (
 	id		SERIAL	PRIMARY KEY,
diff --git a/Open-ILS/src/sql/Pg/950.data.seed-values.sql b/Open-ILS/src/sql/Pg/950.data.seed-values.sql
index 3fbc6cd0dd..f58aaf463d 100644
--- a/Open-ILS/src/sql/Pg/950.data.seed-values.sql
+++ b/Open-ILS/src/sql/Pg/950.data.seed-values.sql
@@ -7030,4 +7030,39 @@ INSERT INTO action_trigger.validator (module, description) VALUES (
     )
 );
 
+-- 0448.data.trigger.circ.staff_age_to_lost.sql
+
+INSERT INTO action_trigger.hook (key,core_type,description,passive) VALUES 
+    (   'circ.staff_age_to_lost',
+        'circ', 
+        oils_i18n_gettext(
+            'circ.staff_age_to_lost',
+            'An overdue circulation should be aged to a Lost status.',
+            'ath',
+            'description'
+        ), 
+        TRUE
+    )
+;
+
+INSERT INTO action_trigger.event_definition (
+        id,
+        active,
+        owner,
+        name,
+        hook,
+        validator,
+        reactor,
+        delay_field
+    ) VALUES (
+        36,
+        FALSE,
+        1,
+        'circ.staff_age_to_lost',
+        'circ.staff_age_to_lost',
+        'CircIsOverdue',
+        'MarkItemLost',
+        'due_date'
+    )
+;
 
diff --git a/Open-ILS/src/sql/Pg/upgrade/0448.data.trigger.circ.staff_age_to_lost.sql b/Open-ILS/src/sql/Pg/upgrade/0448.data.trigger.circ.staff_age_to_lost.sql
new file mode 100644
index 0000000000..1eeb59de01
--- /dev/null
+++ b/Open-ILS/src/sql/Pg/upgrade/0448.data.trigger.circ.staff_age_to_lost.sql
@@ -0,0 +1,42 @@
+BEGIN;
+
+INSERT INTO config.upgrade_log (version) VALUES ('0448'); -- phasefx
+
+INSERT INTO action_trigger.hook (key,core_type,description,passive) VALUES 
+    (   'circ.staff_age_to_lost',
+        'circ', 
+        oils_i18n_gettext(
+            'circ.staff_age_to_lost',
+            'An overdue circulation should be aged to a Lost status.',
+            'ath',
+            'description'
+        ), 
+        TRUE
+    )
+;
+
+INSERT INTO action_trigger.event_definition (
+        id,
+        active,
+        owner,
+        name,
+        hook,
+        validator,
+        reactor,
+        delay_field
+    ) VALUES (
+        36,
+        FALSE,
+        1,
+        'circ.staff_age_to_lost',
+        'circ.staff_age_to_lost',
+        'CircIsOverdue',
+        'MarkItemLost',
+        'due_date'
+    )
+;
+
+-- DELETE FROM config.upgrade_log WHERE version = '0448'; DELETE FROM action_trigger.event WHERE event_def = 36; DELETE FROM action_trigger.event_params WHERE event_def = 36; DELETE FROM action_trigger.event_definition WHERE id = 36; DELETE FROM action_trigger.hook WHERE key = 'circ.staff_age_to_lost';
+
+COMMIT;
+
diff --git a/Open-ILS/web/opac/locale/en-US/lang.dtd b/Open-ILS/web/opac/locale/en-US/lang.dtd
index a1d3dc3145..c40c7c9325 100644
--- a/Open-ILS/web/opac/locale/en-US/lang.dtd
+++ b/Open-ILS/web/opac/locale/en-US/lang.dtd
@@ -1920,6 +1920,13 @@
 <!ENTITY staff.server.admin.index.testing "(Testing)">
 <!ENTITY staff.server.admin.index.hold_pull_list_classic "Pull List for Hold Requests (Classic)">
 <!ENTITY staff.server.admin.index.reports "Reports">
+<!ENTITY staff.server.admin.index.age_overdue_circulations_to_lost.label "Age Overdue Circs to Lost">
+<!ENTITY staff.server.admin.index.age_overdue_circulations_to_lost.accesskey "">
+<!ENTITY staff.server.admin.index.age_overdue_circulations_to_lost.description "Choose the user profile and circulation library for the overdue circulations you wish to age to a Lost status.  Note that descendents of these values (sub-groups, sub-libraries) will also be affected.">
+<!ENTITY staff.server.admin.index.age_overdue_circulations_to_lost.user_profile "User Profile:">
+<!ENTITY staff.server.admin.index.age_overdue_circulations_to_lost.circ_lib "Circulation Library:">
+<!ENTITY staff.server.admin.index.age_overdue_circulations_to_lost.confirm "Are you sure?">
+<!ENTITY staff.server.admin.index.age_overdue_circulations_to_lost.action "Queue for Aging">
 <!ENTITY staff.server.admin.index.cash_reports "Cash Reports">
 <!ENTITY staff.server.admin.index.transits "Transits">
 <!ENTITY staff.server.admin.index.transit_list "Transit List">
diff --git a/Open-ILS/xul/staff_client/chrome/content/main/constants.js b/Open-ILS/xul/staff_client/chrome/content/main/constants.js
index e95db3d3c6..0cb38829b4 100644
--- a/Open-ILS/xul/staff_client/chrome/content/main/constants.js
+++ b/Open-ILS/xul/staff_client/chrome/content/main/constants.js
@@ -214,6 +214,7 @@ var api = {
     'FM_BRN_FROM_MARCXML' : { 'app' : 'open-ils.search', 'method' : 'open-ils.search.z3950.marcxml_to_brn', 'secure' : false },
     'FM_CBT_RETRIEVE' : { 'app' : 'open-ils.circ', 'method' : 'open-ils.circ.billing_type.ranged.retrieve.all', 'secure' : false },
     'FM_CCS_RETRIEVE' : { 'app' : 'open-ils.search', 'method' : 'open-ils.search.config.copy_status.retrieve.all', 'secure' : false },
+    'FM_CIRC_AGE_TO_LOST' : { 'app' : 'open-ils.circ', 'method' : 'open-ils.circ.circulation.age_to_lost' },
     'FM_CIRC_CHAIN' : { 'app' : 'open-ils.circ', 'method' : 'open-ils.circ.renewal_chain.retrieve_by_circ.atomic' },
     'FM_CIRC_CHAIN_SUMMARY' : { 'app' : 'open-ils.circ', 'method' : 'open-ils.circ.renewal_chain.retrieve_by_circ.summary' },
     'FM_CIRC_PREV_CHAIN' : { 'app' : 'open-ils.circ', 'method' : 'open-ils.circ.prev_renewal_chain.retrieve_by_circ.atomic' },
diff --git a/Open-ILS/xul/staff_client/chrome/content/main/menu.js b/Open-ILS/xul/staff_client/chrome/content/main/menu.js
index fa37d37fe4..95d121edad 100644
--- a/Open-ILS/xul/staff_client/chrome/content/main/menu.js
+++ b/Open-ILS/xul/staff_client/chrome/content/main/menu.js
@@ -556,6 +556,10 @@ main.menu.prototype = {
                 ['oncommand'],
                 function() { open_admin_page('transit_list.xul', 'menu.cmd_local_admin_transit_list.tab'); }
             ],
+            'cmd_local_admin_age_overdue_circulations_to_lost' : [
+                ['oncommand'],
+                function() { open_admin_page('circ_age_to_lost.xul', 'menu.cmd_local_admin_age_overdue_circulations_to_lost.tab', true); }
+            ],
             'cmd_local_admin_cash_reports' : [
                 ['oncommand'],
                 function() { open_admin_page('cash_reports.xhtml', 'menu.cmd_local_admin_cash_reports.tab', true); }
diff --git a/Open-ILS/xul/staff_client/chrome/content/main/menu_frame_menus.xul b/Open-ILS/xul/staff_client/chrome/content/main/menu_frame_menus.xul
index ec4d000e29..ff1d29f5ea 100644
--- a/Open-ILS/xul/staff_client/chrome/content/main/menu_frame_menus.xul
+++ b/Open-ILS/xul/staff_client/chrome/content/main/menu_frame_menus.xul
@@ -121,6 +121,9 @@
     <command id="cmd_local_admin_action_trigger"/>
     <command id="cmd_local_admin_survey"/>
     <command id="cmd_local_admin_reports"/>
+    <command id="cmd_local_admin_age_overdue_circulations_to_lost"
+        label="&staff.server.admin.index.age_overdue_circulations_to_lost.label;"
+        accesskey="&staff.server.admin.index.age_overdue_circulations_to_lost.accesskey;"/>
     <command id="cmd_local_admin_cash_reports"/>
     <command id="cmd_local_admin_transit_list"/>
     <command id="cmd_local_admin_circ_matrix_matchpoint"/>
@@ -349,6 +352,7 @@
 
         <menu id="main.menu.admin.local" label="&staff.main.menu.admin.local_admin.label;">
             <menupopup id="main.menu.admin.local.popup">
+                <menuitem command="cmd_local_admin_age_overdue_circulations_to_lost"/>
                 <menuitem label="&staff.server.admin.index.cash_reports;" command="cmd_local_admin_cash_reports"/>
                 <menuitem label="&staff.main.menu.admin.local_admin.circ_matrix_matchpoint.label;" command="cmd_local_admin_circ_matrix_matchpoint"/>
                 <menuitem label="&staff.server.admin.index.closed_dates;" command="cmd_local_admin_closed_dates"/>
diff --git a/Open-ILS/xul/staff_client/chrome/locale/en-US/offline.properties b/Open-ILS/xul/staff_client/chrome/locale/en-US/offline.properties
index 8c2d949582..ba7a470e31 100644
--- a/Open-ILS/xul/staff_client/chrome/locale/en-US/offline.properties
+++ b/Open-ILS/xul/staff_client/chrome/locale/en-US/offline.properties
@@ -224,6 +224,7 @@ menu.cmd_local_admin_lib_settings.tab=Library Settings Editor
 menu.cmd_local_admin_non_cat_types.tab=Non-cataloged Types Editor
 menu.cmd_local_admin_stat_cats.tab=Statistical Categories Editor
 menu.cmd_local_admin_reports.tab=Reports
+menu.cmd_local_admin_age_overdue_circulations_to_lost.tab=Age to Lost
 menu.cmd_local_admin_cash_reports.tab=Cash Reports
 menu.cmd_local_admin_transit_list.tab=Transits
 menu.cmd_acq_create_invoice.tab=New Invoice
diff --git a/Open-ILS/xul/staff_client/server/admin/circ_age_to_lost.js b/Open-ILS/xul/staff_client/server/admin/circ_age_to_lost.js
new file mode 100644
index 0000000000..b875cc32d3
--- /dev/null
+++ b/Open-ILS/xul/staff_client/server/admin/circ_age_to_lost.js
@@ -0,0 +1,121 @@
+var error;
+var data;
+
+function my_init() {
+    try {
+        netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect");
+        if (typeof JSAN == 'undefined') { throw( "The JSAN library object is missing."); }
+        JSAN.errorLevel = "die"; // none, warn, or die
+        JSAN.addRepository('/xul/server/');
+        JSAN.use('util.error'); error = new util.error();
+        error.sdump('D_TRACE','my_init() for circ_age_to_lost.xul');
+
+        JSAN.use('OpenILS.data'); data = new OpenILS.data(); data.stash_retrieve();
+
+        build_pgt_list();
+        build_ou_list();
+
+        $('doit').addEventListener('command',doit,false);
+
+        if (typeof window.xulG == 'object' && typeof window.xulG.set_tab_name == 'function') {
+            try { window.xulG.set_tab_name( $('offlineStrings').getString('menu.cmd_local_admin_age_overdue_circulations_to_lost.tab') ); } catch(E) { alert(E); }
+        }
+
+    } catch(E) {
+        alert('Error in admin/circ_age_to_lost.xul, my_init(): ' + E);
+    }
+}
+
+function doit(ev) {
+    try {
+        $('checkbox').disabled = true;
+        ev.target.disabled = true;
+        $('deck').selectedIndex = 1;
+        var profile = $('profile').value;
+        var circ_lib = $('circ_lib').value;
+
+        function response_handler(e,r,list) {
+            try {
+                var result;
+                switch(e) {
+                    case 'oncomplete' : return; break;
+                    default: result = r.recv().content(); break;
+                }
+                dump(e + ' result = ' + js2JSON(result) + '\n');
+                if (typeof result.progress != 'undefined') {
+
+                    $('results_label').setAttribute('value', $('adminStrings').getFormattedString('staff.admin.age_overdue_circulations_to_lost.completed_so_far',[result.progress]) );
+
+                } else if (typeof result.created != 'undefined') {
+
+                    $('results_label').setAttribute('value', $('adminStrings').getFormattedString('staff.admin.age_overdue_circulations_to_lost.completed_total',[result.created]) );
+                    $('deck').selectedIndex = 0;
+
+                } else if (typeof result.error != 'undefined') {
+
+                    $('deck').selectedIndex = 0;
+                    throw(result.error);
+
+                } else {
+                    throw(result);
+                }
+            } catch(E) {
+                $('deck').selectedIndex = 0;
+                alert('Error in admin/circ_age_to_lost.js, doit(), ' + e + ': ' + r + ' => ' + E);
+            }
+        }
+        dump('firing ' + api.FM_CIRC_AGE_TO_LOST.method + ' with profile ' + profile + ' and circ_lib ' + circ_lib + '\n');
+        fieldmapper.standardRequest(
+            [ api.FM_CIRC_AGE_TO_LOST.app, api.FM_CIRC_AGE_TO_LOST.method ],
+            {   async: true,
+                params: [ses(), { 'user_profile' : profile, 'circ_lib' : circ_lib } ],
+                onresponse: function(r) { response_handler('onresponse',r); },
+                oncomplete: function(r) { response_handler('oncomplete',r); },
+                onerror: function(r) { response_handler('onerror',r); }
+            }
+        );
+
+    } catch(E) {
+        alert('Error in admin/circ_age_to_lost.js, doit(): ' + E);
+    }
+}
+
+function build_pgt_list() {
+    JSAN.use('util.functional'); JSAN.use('util.widgets');
+    var default_profile = data.tree.pgt.id();
+    var menu_data = util.functional.map_list( 
+        data.list.pgt,
+        function(obj) { 
+            var sname = obj.name();
+            for (i = sname.length; i < 20; i++) {
+                sname += ' ';
+            }
+            var depth = 0; var p = obj;
+            while (p = data.hash.pgt[ p.parent() ]) { depth++; }
+            return [ 
+                obj.description() ? sname + ' : ' + obj.description() : obj.name(),
+                obj.id(), 
+                false, // disable menuentry?
+                ( depth * 2) // spaces of indentation
+            ]; 
+        }
+    );
+    var ml = util.widgets.make_menulist( menu_data, default_profile );
+    ml.setAttribute('id','profile'); $('x_profile').appendChild(ml);
+}
+
+function build_ou_list() {
+    JSAN.use('util.file'); JSAN.use('util.widgets');
+    var file = new util.file('offline_ou_list');
+    if (file._file.exists()) {
+        var menu_data = file.get_object(); file.close();
+        for (var i = 0; i < menu_data[0].length; i++) { // make sure all entries are enabled
+            menu_data[0][i][2] = false;
+        }
+        ml = util.widgets.make_menulist( menu_data[0], menu_data[1] );
+        ml.setAttribute('id','circ_lib'); $('x_circ_lib').appendChild(ml);
+    } else {
+        throw('Missing file offline_ou_list in build_ou_list()');
+    }
+}
+
diff --git a/Open-ILS/xul/staff_client/server/admin/circ_age_to_lost.xul b/Open-ILS/xul/staff_client/server/admin/circ_age_to_lost.xul
new file mode 100644
index 0000000000..b9664fc1c7
--- /dev/null
+++ b/Open-ILS/xul/staff_client/server/admin/circ_age_to_lost.xul
@@ -0,0 +1,65 @@
+<?xml version="1.0"?>
+<!-- Application: Evergreen Staff Client -->
+<!-- Screen: Example Template for remote xul -->
+
+<!-- ///////////////////////////////////////////////////////////////////////////////////////////////////////////// -->
+<!-- STYLESHEETS -->
+<?xml-stylesheet href="chrome://global/skin/" type="text/css"?>
+<?xml-stylesheet href="/xul/server/skin/global.css" type="text/css"?>
+
+<!-- ///////////////////////////////////////////////////////////////////////////////////////////////////////////// -->
+<!-- LOCALIZATION -->
+<!DOCTYPE window PUBLIC "" ""[
+    <!--#include virtual="/opac/locale/${locale}/lang.dtd"-->
+]>
+
+<!-- ///////////////////////////////////////////////////////////////////////////////////////////////////////////// -->
+<!-- OVERLAYS -->
+<?xul-overlay href="/xul/server/OpenILS/util_overlay.xul"?>
+
+<window id="circ_age_to_lost_win" 
+    onload="try { my_init(); font_helper(); persist_helper(); } catch(E) { alert(E); }"
+    xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+
+    <!-- ///////////////////////////////////////////////////////////////////////////////////////////////////////////// -->
+    <!-- BEHAVIOR -->
+    <script type="text/javascript">
+        var myPackageDir = 'open_ils_staff_client'; var IAMXUL = true;
+    </script>
+    <scripts id="openils_util_scripts"/>
+
+    <script type="text/javascript" src="/xul/server/main/JSAN.js"/>
+    <script type="text/javascript" src="circ_age_to_lost.js"/>
+
+    <messagecatalog id="adminStrings" src="/xul/server/locale/<!--#echo var='locale'-->/admin.properties"/>
+
+    <vbox flex="1">
+        <grid>
+            <columns>
+                <column/>
+                <column/>
+            </columns>
+            <rows>
+                <row>
+                    <spacer/>
+                    <description>&staff.server.admin.index.age_overdue_circulations_to_lost.description;</description>
+                </row>
+                <row>
+                    <label value="&staff.server.admin.index.age_overdue_circulations_to_lost.user_profile;" /><vbox id="x_profile" />
+                </row>
+                <row>
+                    <label value="&staff.server.admin.index.age_overdue_circulations_to_lost.circ_lib;" /><vbox id="x_circ_lib" />
+                </row>
+                <row>
+                    <checkbox id="checkbox" label="&staff.server.admin.index.age_overdue_circulations_to_lost.confirm;" oncommand="$('doit').disabled = ! this.checked"/>
+                    <deck id="deck">
+                        <button id="doit" label="&staff.server.admin.index.age_overdue_circulations_to_lost.action;" disabled="true"/>
+                        <progressmeter mode="undetermined" />
+                    </deck>
+                </row>
+            </rows>
+        </grid>
+        <label id="results_label" style="font-size: x-large;"/>
+    </vbox>
+</window>
+
diff --git a/Open-ILS/xul/staff_client/server/locale/en-US/admin.properties b/Open-ILS/xul/staff_client/server/locale/en-US/admin.properties
index 53fbaca803..a1ae73ab94 100644
--- a/Open-ILS/xul/staff_client/server/locale/en-US/admin.properties
+++ b/Open-ILS/xul/staff_client/server/locale/en-US/admin.properties
@@ -1,3 +1,5 @@
+staff.admin.age_overdue_circulations_to_lost.completed_so_far=Completed: %1$s
+staff.admin.age_overdue_circulations_to_lost.completed_total=Total Completed: %1$s
 staff.admin.font_settings.sound=Sound preference saved to file system.
 staff.admin.font_settings.save=Global Font saved to file system.
 staff.admin.font_settings.sound.disabled=Sound is now disabled.
-- 
2.11.0