Backporting r18396 from trunk: Patch from Thomas Berezansky providing an alternate...
authormiker <miker@dcc99617-32d9-48b4-a31d-7c20da2025e4>
Wed, 20 Oct 2010 13:31:47 +0000 (13:31 +0000)
committermiker <miker@dcc99617-32d9-48b4-a31d-7c20da2025e4>
Wed, 20 Oct 2010 13:31:47 +0000 (13:31 +0000)
git-svn-id: svn://svn.open-ils.org/ILS/branches/rel_1_6@18398 dcc99617-32d9-48b4-a31d-7c20da2025e4

Open-ILS/examples/fm_IDL.xml
Open-ILS/src/Makefile.am
Open-ILS/src/perlmods/OpenILS/Application/AppUtils.pm
Open-ILS/src/perlmods/OpenILS/Application/Circ/Circulate.pm
Open-ILS/src/sql/Pg/002.schema.config.sql
Open-ILS/src/sql/Pg/100.circ_matrix.sql
Open-ILS/src/sql/Pg/upgrade/0442.schema.due_date_ceiling_alt_impl.sql [new file with mode: 0644]
Open-ILS/src/support-scripts/update_hard_due_dates.srfsh [new file with mode: 0755]

index a424b78..4c372d4 100644 (file)
@@ -902,6 +902,7 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
                        <field reporter:label="Duration Rule" name="duration_rule" reporter:datatype="link"/>
                        <field reporter:label="Recurring Fine Rule" name="recurring_fine_rule" reporter:datatype="link"/>
                        <field reporter:label="Max Fine Rule" name="max_fine_rule" reporter:datatype="link"/>
+            <field reporter:label="Hard Due Date" name="hard_due_date" reporter:datatype="link"/>
                        <field reporter:label="Script Test" name="script_test" reporter:datatype="text"/>
                </fields>
                <links>
@@ -914,6 +915,7 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
                        <link field="duration_rule" reltype="has_a" key="id" map="" class="crcd"/>
                        <link field="max_fine_rule" reltype="has_a" key="id" map="" class="crmf"/>
                        <link field="recurring_fine_rule" reltype="has_a" key="id" map="" class="crrf"/>
+            <link field="hard_due_date" reltype="has_a" key="id" map="" class="chdd"/>
                </links>
         <permacrud xmlns="http://open-ils.org/spec/opensrf/IDL/permacrud/v1">
             <actions>
@@ -1562,7 +1564,6 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
                        <field name="name" reporter:datatype="text"/>
                        <field name="normal" reporter:datatype="interval"/>
                        <field name="shrt" reporter:datatype="interval"/>
-                       <field name="date_ceiling" reporter:datatype="timestamp"/>
                </fields>
                <links>
                </links>
@@ -1576,19 +1577,46 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
         </permacrud>
        </class>
 
-    <class id="chdd" controller="open-ils.cstore open-ils.pcrud" oils_obj:fieldmapper="config::hard_due_date" oils_persist:tablename="config.hard_due_date" reporter:label="Hard Due Date">
-        <fields oils_persist:primary="id" oils_persist:sequence="config.hard_due_date_id_seq">
-            <field reporter:label="ID" name="id" reporter:datatype="id"/>
-            <field reporter:label="Duration Rule" name="duration_rule" reporter:datatype="link"/>
-            <field reporter:label="Ceiling Date" name="ceiling_date" reporter:datatype="timestamp"/>
+       <class id="chdd" controller="open-ils.cstore open-ils.pcrud" oils_obj:fieldmapper="config::hard_due_date" oils_persist:tablename="config.hard_due_date" reporter:label="Hard Due Date">
+               <fields oils_persist:primary="id" oils_persist:sequence="config.hard_due_date_id_seq">
+                       <field reporter:label="ID" name="id" reporter:datatype="id" reporter:selector="name"/>
+                       <field reporter:label="Name" name="name" reporter:datatype="text"/>
+            <field reporter:label="Always Use?" name="forceto" reporter:datatype="bool"/>
+                       <field reporter:label="Current Ceiling Date" name="ceiling_date" reporter:datatype="timestamp"/>
+            <field reporter:label="Owner" name="owner" reporter:datatype="org_unit"/>
+               </fields>
+               <links>
+            <link field="owner" reltype="has_a" key="id" map="" class="aou"/>
+               </links>
+               <permacrud xmlns="http://open-ils.org/spec/opensrf/IDL/permacrud/v1">
+            <actions>
+                <create permission="CREATE_CIRC_DURATION" global_required="true"/>
+                <retrieve/>
+                <update permission="UPDATE_CIRC_DURATION" global_required="true"/>
+                <delete permission="DELETE_CIRC_DURATION" global_required="true"/>
+            </actions>
+               </permacrud>
+       </class>
+
+       <class id="chddv" controller="open-ils.cstore open-ils.pcrud" oils_obj:fieldmapper="config::hard_due_date_values" oils_persist:tablename="config.hard_due_date_values" reporter:label="Hard Due Date Values">
+               <fields oils_persist:primary="id" oils_persist:sequence="config.hard_due_date_vales_id_seq">
+                       <field reporter:label="ID" name="id" reporter:datatype="id"/>
+                       <field reporter:label="Hard Due Date" name="hard_due_date" reporter:datatype="link"/>
+                       <field reporter:label="Ceiling Date" name="ceiling_date" reporter:datatype="timestamp"/>
             <field reporter:label="Active Date" name="active_date" reporter:datatype="timestamp"/>
-        </fields>
-        <links>
-            <link field="duration_rule" reltype="has_a" key="id" map="" class="crcd"/>
-        </links>
-        <permacrud xmlns="http://open-ils.org/spec/opensrf/IDL/permacrud/v1">
-        </permacrud>
-    </class>
+               </fields>
+               <links>
+                       <link field="hard_due_date" reltype="has_a" key="id" map="" class="chdd"/>
+               </links>
+               <permacrud xmlns="http://open-ils.org/spec/opensrf/IDL/permacrud/v1">
+            <actions>
+                <create permission="CREATE_CIRC_DURATION" global_required="true"/>
+                <retrieve/>
+                <update permission="UPDATE_CIRC_DURATION" global_required="true"/>
+                <delete permission="DELETE_CIRC_DURATION" global_required="true"/>
+            </actions>
+               </permacrud>
+       </class>
 
        <class id="mobts" controller="open-ils.cstore" oils_obj:fieldmapper="money::open_billable_transaction_summary" oils_persist:tablename="money.open_billable_xact_summary" reporter:label="Open Billable Transaction Summary">
                <fields oils_persist:primary="id" oils_persist:sequence="">
index df390b8..55c0249 100644 (file)
@@ -77,6 +77,8 @@ core_scripts =   $(examples)/oils_ctl.sh \
                 $(supportscr)/fine_generator.pl \
                 $(supportscr)/hold_targeter.pl \
                 $(supportscr)/reshelving_complete.srfsh \
+                $(supportscr)/clear_expired_circ_history.srfsh \
+                $(supportscr)/update_hard_due_dates.srfsh \
                 $(supportscr)/juv_to_adult.srfsh \
                 $(supportscr)/thaw_expired_frozen_holds.srfsh \
                 $(supportscr)/long-overdue-status-update.pl \
@@ -229,6 +231,8 @@ ilscore-install:
        sed -i 's|LOCALSTATEDIR|@localstatedir@|g' '$(DESTDIR)@bindir@/autogen.sh'
        sed -i 's|SYSCONFDIR|@sysconfdir@|g' '$(DESTDIR)@bindir@/autogen.sh'
        sed -i 's|BINDIR|@bindir@|g' '$(DESTDIR)@bindir@/reshelving_complete.srfsh'
+       sed -i 's|BINDIR|@bindir@|g' '$(DESTDIR)@bindir@/clear_expired_circ_history.srfsh'
+       sed -i 's|BINDIR|@bindir@|g' '$(DESTDIR)@bindir@/update_hard_due_dates.srfsh'
        sed -i 's|BINDIR|@bindir@|g' '$(DESTDIR)@bindir@/juv_to_adult.srfsh'
        sed -i 's|BINDIR|@bindir@|g' '$(DESTDIR)@bindir@/long-overdue-status-update.pl'
        sed -i 's|SYSCONFDIR|@sysconfdir@|g' '$(DESTDIR)@bindir@/long-overdue-status-update.pl'
index eb77f24..9b2774e 100644 (file)
@@ -903,6 +903,17 @@ sub fetch_max_fine_by_name {
        return ($obj, $evt);
 }
 
+sub fetch_hard_due_date_by_name {
+       my( $self, $name ) = @_;
+       my( $obj, $evt );
+       $obj = $self->simplereq(
+               'open-ils.cstore', 
+               'open-ils.cstore.direct.config.hard_due_date.search.atomic', { name => $name } );
+       $obj = $obj->[0];
+       $evt = OpenILS::Event->new('CONFIG_RULES_HARD_DUE_DATE_NOT_FOUND') unless $obj;
+       return ($obj, $evt);
+}
+
 sub storagereq {
        my( $self, $method, @params ) = @_;
        return $self->simplereq(
index 25285e3..450331b 100644 (file)
@@ -467,6 +467,7 @@ my @AUTOLOAD_FIELDS = qw/
     recurring_fines_rule
     max_fine_rule
     renewal_remaining
+    hard_due_date
     due_date
     fulfilled_holds
     transit
@@ -1075,7 +1076,7 @@ sub run_indb_circ_test {
                 $mp,
                 {   flesh => 1,
                     flesh_fields => {ccmm => 
-                        ['duration_rule', 'recurring_fine_rule', 'max_fine_rule']}
+                        ['duration_rule', 'recurring_fine_rule', 'max_fine_rule', 'hard_due_date']}
                 }
             ])
         );
@@ -1113,9 +1114,10 @@ sub do_inspect {
         my $duration_rule = $self->circ_matrix_matchpoint->duration_rule;
         my $recurring_fine_rule = $self->circ_matrix_matchpoint->recurring_fine_rule;
         my $max_fine_rule = $self->circ_matrix_matchpoint->max_fine_rule;
+        my $hard_due_date = $self->circ_matrix_matchpoint->hard_due_date;
     
         my $policy = $self->get_circ_policy(
-            $duration_rule, $recurring_fine_rule, $max_fine_rule);
+            $duration_rule, $recurring_fine_rule, $max_fine_rule, $hard_due_date);
     
         $$results{$_} = $$policy{$_} for keys %$policy;
     }
@@ -1128,7 +1130,7 @@ sub do_inspect {
 # fine based on the current copy
 # ---------------------------------------------------------------------
 sub get_circ_policy {
-    my($self, $duration_rule, $recurring_fine_rule, $max_fine_rule) = @_;
+    my($self, $duration_rule, $recurring_fine_rule, $max_fine_rule, $hard_due_date) = @_;
 
     my $policy = {
         duration_rule => $duration_rule->name,
@@ -1136,10 +1138,18 @@ sub get_circ_policy {
         max_fine_rule => $max_fine_rule->name,
         max_fine => $self->get_max_fine_amount($max_fine_rule),
         fine_interval => $recurring_fine_rule->recurance_interval,
-        renewal_remaining => $duration_rule->max_renewals,
-        duration_date_ceiling => $duration_rule->date_ceiling
+        renewal_remaining => $duration_rule->max_renewals
     };
 
+    if($hard_due_date) {
+        $policy->{duration_date_ceiling} = $hard_due_date->ceiling_date;
+        $policy->{duration_date_ceiling_force} = $hard_due_date->forceto;
+    }
+    else {
+        $policy->{duration_date_ceiling} = undef;
+        $policy->{duration_date_ceiling_force} = undef;
+    }
+
     $policy->{duration} = $duration_rule->shrt
         if $self->copy->loan_duration == OILS_CIRC_DURATION_SHORT;
     $policy->{duration} = $duration_rule->normal
@@ -1560,15 +1570,18 @@ sub run_checkout_scripts {
     my $duration;
     my $recurring;
     my $max_fine;
+    my $hard_due_date;
     my $duration_name;
     my $recurring_name;
     my $max_fine_name;
+    my $hard_due_date_name;
 
     if(!$self->legacy_script_support) {
         $self->run_indb_circ_test();
         $duration = $self->circ_matrix_matchpoint->duration_rule;
         $recurring = $self->circ_matrix_matchpoint->recurring_fine_rule;
         $max_fine = $self->circ_matrix_matchpoint->max_fine_rule;
+        $hard_due_date = $self->circ_matrix_matchpoint->hard_due_date;
 
     } else {
 
@@ -1580,6 +1593,7 @@ sub run_checkout_scripts {
        $duration_name   = $result->{durationRule};
        $recurring_name  = $result->{recurringFinesRule};
        $max_fine_name   = $result->{maxFine};
+       $hard_due_date_name  = $result->{hardDueDate};
     }
 
     $duration_name = $duration->name if $duration;
@@ -1594,6 +1608,11 @@ sub run_checkout_scripts {
         
             ($max_fine, $evt) = $U->fetch_max_fine_by_name($max_fine_name);
             return $self->bail_on_events($evt) if ($evt && !$nobail);
+
+            if($hard_due_date_name) {
+                ($hard_due_date, $evt) = $U->fetch_hard_due_date_by_name($hard_due_date_name);
+                return $self->bail_on_events($evt) if ($evt && !$nobail);
+            }
         }
 
     } else {
@@ -1602,11 +1621,13 @@ sub run_checkout_scripts {
         $duration   = undef;
         $recurring  = undef;
         $max_fine   = undef;
+        $hard_due_date = undef;
     }
 
    $self->duration_rule($duration);
    $self->recurring_fines_rule($recurring);
    $self->max_fine_rule($max_fine);
+   $self->hard_due_date($hard_due_date);
 }
 
 
@@ -1617,21 +1638,28 @@ sub build_checkout_circ_object {
    my $duration   = $self->duration_rule;
    my $max        = $self->max_fine_rule;
    my $recurring  = $self->recurring_fines_rule;
+   my $hard_due_date    = $self->hard_due_date;
    my $copy       = $self->copy;
    my $patron     = $self->patron;
    my $duration_date_ceiling;
+   my $duration_date_ceiling_force;
 
     if( $duration ) {
 
-        my $policy = $self->get_circ_policy($duration, $recurring, $max);
+        my $policy = $self->get_circ_policy($duration, $recurring, $max, $hard_due_date);
         $duration_date_ceiling = $policy->{duration_date_ceiling};
+        $duration_date_ceiling_force = $policy->{duration_date_ceiling_force};
 
         my $dname = $duration->name;
         my $mname = $max->name;
         my $rname = $recurring->name;
+        my $hdname;
+        if($hard_due_date) {
+            $hdname = $hard_due_date->name;
+        }
 
         $logger->debug("circulator: building circulation ".
-            "with duration=$dname, maxfine=$mname, recurring=$rname");
+            "with duration=$dname, maxfine=$mname, recurring=$rname, hard due date=$hdname");
     
         $circ->duration($policy->{duration});
         $circ->recuring_fine($policy->{recurring_fine});
@@ -1672,7 +1700,7 @@ sub build_checkout_circ_object {
 
    # if a patron is renewing, 'requestor' will be the patron
    $circ->circ_staff($self->editor->requestor->id);
-    $circ->due_date( $self->create_due_date($circ->duration, $duration_date_ceiling) ) if $circ->duration;
+    $circ->due_date( $self->create_due_date($circ->duration, $duration_date_ceiling, $duration_date_ceiling_force) ) if $circ->duration;
 
     $self->circ($circ);
 }
@@ -1872,7 +1900,7 @@ sub apply_modified_due_date {
 
 
 sub create_due_date {
-    my( $self, $duration, $date_ceiling ) = @_;
+    my( $self, $duration, $date_ceiling, $force_date ) = @_;
 
     # if there is a raw time component (e.g. from postgres), 
     # turn it into an interval that interval_to_seconds can parse
@@ -1886,7 +1914,7 @@ sub create_due_date {
 
     if($date_ceiling) {
         my $cdate = DateTime::Format::ISO8601->new->parse_datetime(cleanse_ISO8601($date_ceiling));
-        if ($cdate > DateTime->now and $cdate < $due_date) {
+        if ($cdate > DateTime->now and ($cdate < $due_date or $force_date)) {
             $logger->info("circulator: overriding due date with date ceiling: $date_ceiling");
             $due_date = $cdate;
         }
index f2d8a05..e942ebc 100644 (file)
@@ -250,8 +250,7 @@ CREATE TABLE config.rule_circ_duration (
        extended        INTERVAL        NOT NULL,
        normal          INTERVAL        NOT NULL,
        shrt            INTERVAL        NOT NULL,
-       max_renewals    INT             NOT NULL,
-       date_ceiling    TIMESTAMPTZ
+       max_renewals    INT             NOT NULL
 );
 COMMENT ON TABLE config.rule_circ_duration IS $$
 /*
@@ -278,13 +277,47 @@ COMMENT ON TABLE config.rule_circ_duration IS $$
 $$;
 
 CREATE TABLE config.hard_due_date (
-    id              SERIAL      PRIMARY KEY,
-    duration_rule   INT         NOT NULL REFERENCES config.rule_circ_duration (id)
-                                DEFERRABLE INITIALLY DEFERRED,
-    ceiling_date    TIMESTAMPTZ NOT NULL,
-    active_date     TIMESTAMPTZ NOT NULL
+    id                  SERIAL      PRIMARY KEY,
+    name                TEXT        NOT NULL UNIQUE CHECK ( name ~ E'^\\w+$' ),
+    ceiling_date        TIMESTAMPTZ NOT NULL,
+    forceto             BOOL        NOT NULL,
+    owner               INT         NOT NULL   -- REFERENCES actor.org_unit (id) DEFERRABLE INITIALLY DEFERRED,
 );
 
+CREATE TABLE config.hard_due_date_values (
+    id                  SERIAL      PRIMARY KEY,
+    hard_due_date       INT         NOT NULL REFERENCES config.hard_due_date (id)
+                                    DEFERRABLE INITIALLY DEFERRED,
+    ceiling_date        TIMESTAMPTZ NOT NULL,
+    active_date         TIMESTAMPTZ NOT NULL
+);
+
+
+CREATE OR REPLACE FUNCTION config.update_hard_due_dates () RETURNS INT AS $func$
+DECLARE
+    temp_value  config.hard_due_date_values%ROWTYPE;
+    updated     INT := 0;
+BEGIN
+    FOR temp_value IN
+      SELECT  DISTINCT ON (hard_due_date) *
+        FROM  config.hard_due_date_values
+        WHERE active_date <= NOW() -- We've passed (or are at) the rollover time
+        ORDER BY active_date DESC -- Latest (nearest to us) active time
+   LOOP
+        UPDATE  config.hard_due_date
+          SET   ceiling_date = temp_value.ceiling_date
+          WHERE id = temp_value.hard_due_date
+                AND ceiling_date <> temp_value.ceiling_date; -- Time is equal if we've already updated the chdd
+
+        IF FOUND THEN
+            updated := updated + 1;
+        END IF;
+    END LOOP;
+
+    RETURN updated;
+END;
+$func$ LANGUAGE plpgsql;
+
 CREATE TABLE config.rule_max_fine (
     id          SERIAL          PRIMARY KEY,
     name        TEXT            NOT NULL UNIQUE CHECK ( name ~ E'^\\w+$' ),
index 34909c4..5811f1e 100644 (file)
@@ -113,6 +113,7 @@ CREATE TABLE config.circ_matrix_matchpoint (
     duration_rule        INT     NOT NULL REFERENCES config.rule_circ_duration (id) DEFERRABLE INITIALLY DEFERRED,
     recurring_fine_rule  INT     NOT NULL REFERENCES config.rule_recuring_fine (id) DEFERRABLE INITIALLY DEFERRED,
     max_fine_rule        INT     NOT NULL REFERENCES config.rule_max_fine (id) DEFERRABLE INITIALLY DEFERRED,
+    hard_due_date        INT     REFERENCES config.hard_due_date (id) DEFERRABLE INITIALLY DEFERRED,
     script_test          TEXT,                           -- javascript source 
     CONSTRAINT ep_once_per_grp_loc_mod_marc UNIQUE (grp, org_unit, circ_modifier, marc_type, marc_form, marc_vr_format, ref_flag, juvenile_flag, usr_age_lower_bound, usr_age_upper_bound, is_renewal)
 );
diff --git a/Open-ILS/src/sql/Pg/upgrade/0442.schema.due_date_ceiling_alt_impl.sql b/Open-ILS/src/sql/Pg/upgrade/0442.schema.due_date_ceiling_alt_impl.sql
new file mode 100644 (file)
index 0000000..4e01f02
--- /dev/null
@@ -0,0 +1,53 @@
+BEGIN;
+
+INSERT INTO config.upgrade_log (version) VALUES ('0442'); -- tsbere via miker
+
+DROP TABLE config.hard_due_date;
+
+CREATE TABLE config.hard_due_date (
+               id                      SERIAL          PRIMARY KEY,
+               name            TEXT            NOT NULL UNIQUE CHECK ( name ~ E'^\\w+$' ),
+               ceiling_date    TIMESTAMPTZ     NOT NULL,
+               forceto         BOOL            NOT NULL,
+               owner           INT                     NOT NULL
+);
+
+CREATE TABLE config.hard_due_date_values (
+    id                  SERIAL      PRIMARY KEY,
+    hard_due_date       INT         NOT NULL REFERENCES config.hard_due_date (id)
+                                    DEFERRABLE INITIALLY DEFERRED,
+    ceiling_date        TIMESTAMPTZ NOT NULL,
+    active_date         TIMESTAMPTZ NOT NULL
+);
+
+ALTER TABLE config.circ_matrix_matchpoint ADD COLUMN hard_due_date INT REFERENCES config.hard_due_date (id);
+
+ALTER TABLE config.rule_circ_duration DROP COLUMN date_ceiling;
+
+CREATE OR REPLACE FUNCTION config.update_hard_due_dates () RETURNS INT AS $func$
+DECLARE
+    temp_value  config.hard_due_date_values%ROWTYPE;
+    updated     INT := 0;
+BEGIN
+    FOR temp_value IN
+      SELECT  DISTINCT ON (hard_due_date) *
+        FROM  config.hard_due_date_values
+        WHERE active_date <= NOW() -- We've passed (or are at) the rollover time
+        ORDER BY active_date DESC -- Latest (nearest to us) active time
+   LOOP
+        UPDATE  config.hard_due_date
+          SET   ceiling_date = temp_value.ceiling_date
+          WHERE id = temp_value.hard_due_date
+                AND ceiling_date <> temp_value.ceiling_date; -- Time is equal if we've already updated the chdd
+
+        IF FOUND THEN
+            updated := updated + 1;
+        END IF;
+    END LOOP;
+
+    RETURN updated;
+END;
+$func$ LANGUAGE plpgsql;
+
+COMMIT;
+
diff --git a/Open-ILS/src/support-scripts/update_hard_due_dates.srfsh b/Open-ILS/src/support-scripts/update_hard_due_dates.srfsh
new file mode 100755 (executable)
index 0000000..f14a24c
--- /dev/null
@@ -0,0 +1,7 @@
+#!/openils/bin/srfsh
+open open-ils.cstore
+request open-ils.cstore open-ils.cstore.transaction.begin
+request open-ils.cstore open-ils.cstore.json_query {"from":["config.update_hard_due_dates"]}
+request open-ils.cstore open-ils.cstore.transaction.commit
+close open-ils.cstore
+