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