From aecd5ae25e9350f7fb32013ed4acd081a8992546 Mon Sep 17 00:00:00 2001
From: Per Cederqvist <ceder@lysator.liu.se>
Date: Sat, 19 Aug 2006 20:40:39 +0000
Subject: [PATCH] Simulation of the future implemented.

---
 app/controllers/simulation_controller.rb      | 86 +++++++++++++++++++
 app/models/recurring_item_range.rb            | 82 ++++++++++++++++++
 app/views/simulation/simulate.rhtml           | 42 ++++++++-
 db/migrate/004_create_saldos.rb               |  2 +
 .../005_create_recurring_item_ranges.rb       |  4 +-
 public/index.html                             |  1 +
 6 files changed, 213 insertions(+), 4 deletions(-)

diff --git a/app/controllers/simulation_controller.rb b/app/controllers/simulation_controller.rb
index ee27999..d64f70d 100644
--- a/app/controllers/simulation_controller.rb
+++ b/app/controllers/simulation_controller.rb
@@ -1,6 +1,92 @@
+# -*- coding: utf-8 -*-
 class SimulationController < ApplicationController
 
   def simulate
+    @start_saldos = Saldo.find_by_sql(
+        "SELECT saldos.id, saldos.amount, saldos.date, saldos.account_id FROM" +
+	" (SELECT account_id, MAX(date) AS maxdate" +
+	"  FROM saldos GROUP BY account_id) AS maxx, saldos" +
+	" WHERE maxx.account_id = saldos.account_id" +
+	" AND maxx.maxdate = saldos.date" +
+	" ORDER BY id")
+
+    @start_amount = 0
+    @start_date = nil
+    for saldo in @start_saldos
+      @start_amount += saldo.amount
+      if @start_date.nil? or @start_date < saldo.date
+      	 @start_date = saldo.date
+      end
+    end
+	
+    items = RecurringItemRange.find(:all,
+      :conditions => ["enddate > ? OR enddate IS NULL", @start_date],
+      :include => [:recurring_item, :schedule])
+
+    items.each { |item|
+      item.set_simulation_date(@start_date)
+    }
+
+    if false
+      items.sort!
+      
+      items.each { |item|
+        puts item.recurring_item.description, item.next_event, item.amount
+      }
+    end
+
+    now = Time.utc(@start_date.year, @start_date.month, @start_date.day)
+    @start_time = now
+
+    if not params[:enddate].nil?
+      @end_time = Time.parse(params[:enddate])
+    else
+      start = Time.utc(@start_date.year, @start_date.month, @start_date.day)
+      @end_time = now.advance(:months => if params[:months].nil?
+      	       	 		         then 3
+					 else params[:months].to_i
+					 end)
+    end
+
+    @end_date = Date.civil(@end_time.year, @end_time.month, @end_time.day)
+
+    amount = @start_amount
+    @min_amount = amount
+    @max_amount = amount
+
+    @actions = [[time_to_date(now), "Ingående saldo", 0, amount]]
+
+    while now <= @end_time
+      items.sort!
+      i = items[0]
+      if i.next_event.nil?
+        break
+      end
+      change = i.next_amount
+      amount += change
+      entry = [i.next_date, i.recurring_item.description, change, amount]
+      @actions << entry
+
+      if @min_amount > amount
+        @min_amount = amount
+      end
+
+      if @max_amount < amount
+        @max_amount = amount
+      end
+
+      now = i.next_event
+      i.forward
+    end
+
+  end
+
+  def time_to_date(t)
+    Date.civil(t.year, t.month, t.day)
+  end
+
+  def date_to_time(d)
+    Time.utc(d.year, d.month, d.day)
   end
 
 end
diff --git a/app/models/recurring_item_range.rb b/app/models/recurring_item_range.rb
index 2861a8d..7119853 100644
--- a/app/models/recurring_item_range.rb
+++ b/app/models/recurring_item_range.rb
@@ -1,4 +1,86 @@
 class RecurringItemRange < ActiveRecord::Base
   belongs_to :schedule
   belongs_to :recurring_item
+
+  def set_simulation_date(date)
+    @simulation_date = date
+    @simulation_time = date_to_time(date)
+    if self.spread_out
+      @next_event = date_to_time(date)
+    else
+      @next_event = date_to_time(self.startdate)
+    end
+      
+    # FIXME: This can probably be done more efficiently, by finding
+    # out how many months we need to forward.
+    while (not @next_event.nil?) and @next_event < @simulation_time
+      self.forward
+    end
+  end
+
+  def <=>(other)
+    if @next_event.nil? and other.next_event.nil?
+      return 0
+    elsif @next_event.nil?
+      return 1
+    elsif other.next_event.nil?
+      return -1
+    elsif @next_event != other.next_event
+      return @next_event <=> other.next_event
+    else
+      return amount <=> other.amount
+    end
+  end
+
+  def next_event
+    return @next_event
+  end
+
+  def next_amount
+    if self.spread_out
+      this_month = Time.utc(@next_event.year, @next_event.month, 1)
+      next_month = this_month.advance(:months => 1)
+      days_in_month = time_to_date(next_month) - time_to_date(this_month)
+
+      month_amount = self.amount.to_f
+
+      yesterday = ((month_amount * (@next_event.day - 1)) / days_in_month).to_i
+      today = ((month_amount * @next_event.day) / days_in_month).to_i
+
+      return today - yesterday
+    else
+      return self.amount
+    end
+  end
+
+  def time_to_date(t)
+    Date.civil(t.year, t.month, t.day)
+  end
+
+  def date_to_time(d)
+    Time.utc(d.year, d.month, d.day)
+  end
+
+  def next_date
+    time_to_date(@next_event)
+  end
+
+  def forward
+    if not @next_event.nil?
+      if self.spread_out
+        @next_event = @next_event.advance(:days => 1)
+      else
+        @next_event = @next_event.advance(:months => 
+		      			  self.schedule.monthly_repeat)
+      end
+      if not self.enddate.nil? and @next_event > self.endtime
+        @next_event = nil
+      end
+    end
+  end
+
+  def endtime
+    date_to_time(self.enddate)
+  end
+
 end
diff --git a/app/views/simulation/simulate.rhtml b/app/views/simulation/simulate.rhtml
index c769da5..00280f9 100644
--- a/app/views/simulation/simulate.rhtml
+++ b/app/views/simulation/simulate.rhtml
@@ -1,2 +1,40 @@
-<h1>Simulation#simulate</h1>
-<p>Find me in app/views/simulation/simulate.rhtml</p>
+<h1>Simulation result</h1>
+
+<p>Minimum: <%= @min_amount %></p>
+<p>Maximum: <%= @max_amount %></p>
+
+<p>Incoming saldos:</p>
+
+<table border="1">
+  <tr>
+    <th>Date</th>
+    <th>Account name</th>
+    <th>Amount</th>
+  </tr>
+  <% for saldo in @start_saldos %>
+    <tr class="<%= if saldo.amount < 0 then "debet" else "credit" end %>">
+      <td><%=h saldo.date %></td>
+      <td><%=h saldo.account.name %></td>
+      <td class="amount"><%=h saldo.amount %></td>
+    </tr>
+  <% end %>
+</table>
+
+<p>Transactions:</p>
+
+<table border="1">
+  <tr>
+    <th>Date</th>
+    <th>Description</th>
+    <th>Change</th>
+    <th>Total saldo</th>
+  </tr>
+  <% for action in @actions %>
+    <tr class="<%= if action[2] < 0 then "debet" else "credit" end %>">
+      <td><%=h action[0] %></td>
+      <td><%=h action[1] %></td>
+      <td class="amount"><%=h action[2] %></td>
+      <td class="amount"><%=h action[3] %></td>
+    </tr>
+  <% end %>
+</table>
diff --git a/db/migrate/004_create_saldos.rb b/db/migrate/004_create_saldos.rb
index 7354fe3..b060e79 100644
--- a/db/migrate/004_create_saldos.rb
+++ b/db/migrate/004_create_saldos.rb
@@ -19,6 +19,7 @@ class CreateSaldos < ActiveRecord::Migration
       
       date1 = Date.civil(2006, 8, 10)
       date2 = Date.civil(2006, 8, 14)
+      date3 = Date.civil(2006, 8, 18)
       
       Saldo.create :amount => 1231, :date => date1, :account_id => ppay
       Saldo.create :amount => 4000, :date => date2, :account_id => ppay
@@ -34,6 +35,7 @@ class CreateSaldos < ActiveRecord::Migration
       Saldo.create :amount => 23000, :date => date2, :account_id => pfund
       Saldo.create :amount => 18000, :date => date1, :account_id => psaved
       Saldo.create :amount => 18000, :date => date2, :account_id => psaved
+      Saldo.create :amount => 18020, :date => date3, :account_id => psaved
     end
 
   end
diff --git a/db/migrate/005_create_recurring_item_ranges.rb b/db/migrate/005_create_recurring_item_ranges.rb
index 28f6fed..a24f2ff 100644
--- a/db/migrate/005_create_recurring_item_ranges.rb
+++ b/db/migrate/005_create_recurring_item_ranges.rb
@@ -22,7 +22,7 @@ class CreateRecurringItemRanges < ActiveRecord::Migration
       
       RecurringItemRange.create(:recurring_item_id => pay,
       			        :amount => "22200",
-				:startdate => Date.civil(2006, 8, 25),
+				:startdate => Date.civil(2006, 2, 25),
 				:enddate => Date.civil(2006, 10, 24),
 				:schedule_id => month,
 				:spread_out => false)
@@ -54,7 +54,7 @@ class CreateRecurringItemRanges < ActiveRecord::Migration
 
       RecurringItemRange.create(:recurring_item_id => food,
       			        :amount => "-4000",
-				:startdate => Date.civil(2006, 8, 25),
+				:startdate => Date.civil(2006, 7, 25),
 				:schedule_id => month,
 				:spread_out => true)
     end
diff --git a/public/index.html b/public/index.html
index 47e0a2f..0fa900e 100644
--- a/public/index.html
+++ b/public/index.html
@@ -277,6 +277,7 @@
 	    <li><a href="saldos">Saldos</a></li>
 	    <li><a href="recurring_items">RecurringItems</a></li>
 	    <li><a href="recurring_item_ranges">RecurringItemRanges</a></li>
+	    <li><a href="simulation/simulate">Simulation</a></li>
 	  </ul>
 	</div>
       </div>
-- 
GitLab