Lots of stuff, I got too tired to keep perfect revision history
authorIra W. Snyder <devel@irasnyder.com>
Tue, 27 Nov 2007 07:33:40 +0000 (23:33 -0800)
committerIra W. Snyder <devel@irasnyder.com>
Tue, 27 Nov 2007 07:33:40 +0000 (23:33 -0800)
Among the things in here are:
1) Proper bonuses
2) Bonus Policy MVC
3) Bonuses show up on the reciept
4) Menu additions

Signed-off-by: Ira W. Snyder <devel@irasnyder.com>
28 files changed:
app/controllers/bonus_policy_controller.rb [new file with mode: 0644]
app/controllers/purchase_controller.rb
app/helpers/bonus_policy_helper.rb [new file with mode: 0644]
app/models/bonus_policy.rb [new file with mode: 0644]
app/models/bonus_purchase.rb [new file with mode: 0644]
app/models/customer.rb
app/models/purchase.rb
app/models/video.rb
app/views/bonus_policy/_form.rhtml [new file with mode: 0644]
app/views/bonus_policy/edit.rhtml [new file with mode: 0644]
app/views/bonus_policy/list.rhtml [new file with mode: 0644]
app/views/bonus_policy/new.rhtml [new file with mode: 0644]
app/views/bonus_policy/show.rhtml [new file with mode: 0644]
app/views/layouts/admin.rhtml
app/views/layouts/bonus_policy.rhtml [new file with mode: 0644]
app/views/login/limits.rhtml
app/views/login/list_users.rhtml
app/views/purchase/buy_merch.rhtml
app/views/purchase/list.rhtml
app/views/purchase/menu.rhtml
app/views/purchase/receipt.rhtml
app/views/purchase/rent.rhtml
db/development.sqlite3
db/migrate/033_create_bonus_policies.rb [new file with mode: 0644]
db/schema.rb
test/fixtures/bonus_policies.yml [new file with mode: 0644]
test/functional/bonus_policy_controller_test.rb [new file with mode: 0644]
test/unit/bonus_policy_test.rb [new file with mode: 0644]

diff --git a/app/controllers/bonus_policy_controller.rb b/app/controllers/bonus_policy_controller.rb
new file mode 100644 (file)
index 0000000..a19d601
--- /dev/null
@@ -0,0 +1,56 @@
+class BonusPolicyController < ApplicationController
+  layout "admin"
+
+  before_filter :authorize, :except => ['new', 'create', 'edit', 'update', 'destroy']
+  before_filter :manager, :only => ['new', 'create', 'edit', 'update', 'destroy']
+
+  def index
+    list
+    render :action => 'list'
+  end
+
+  # GETs should be safe (see http://www.w3.org/2001/tag/doc/whenToUseGet.html)
+  verify :method => :post, :only => [ :destroy, :create, :update ],
+         :redirect_to => { :action => :list }
+
+  def list
+    @bonus_policy_pages, @bonus_policies = paginate :bonus_policies, :per_page => 10
+  end
+
+  def show
+    @bonus_policy = BonusPolicy.find(params[:id])
+  end
+
+  def new
+    @bonus_policy = BonusPolicy.new
+  end
+
+  def create
+    @bonus_policy = BonusPolicy.new(params[:bonus_policy])
+    if @bonus_policy.save
+      flash[:notice] = 'BonusPolicy was successfully created.'
+      redirect_to :action => 'list'
+    else
+      render :action => 'new'
+    end
+  end
+
+  def edit
+    @bonus_policy = BonusPolicy.find(params[:id])
+  end
+
+  def update
+    @bonus_policy = BonusPolicy.find(params[:id])
+    if @bonus_policy.update_attributes(params[:bonus_policy])
+      flash[:notice] = 'BonusPolicy was successfully updated.'
+      redirect_to :action => 'show', :id => @bonus_policy
+    else
+      render :action => 'edit'
+    end
+  end
+
+  def destroy
+    BonusPolicy.find(params[:id]).destroy
+    redirect_to :action => 'list'
+  end
+end
index a086306..37b7401 100644 (file)
@@ -77,6 +77,7 @@ class PurchaseController < ApplicationController
     render :action => 'begin'
     session[:total] = 0.00
     session[:items] = []
+    session[:bonus] = nil
   end
 
   def receipt
@@ -85,10 +86,11 @@ class PurchaseController < ApplicationController
     @debt = @customer.debt
     @total = session[:total] + @debt
     @items = session[:items]
+    @bonus = session[:bonus]
     @time = Time.now
 
     # Record a Late Fee Payment if we need to
-    if @debt
+    if not @debt.zero?
       purchase = LateFeePurchase.new
       purchase.customer = @customer
       purchase.date = Time.now.to_date
@@ -105,6 +107,7 @@ class PurchaseController < ApplicationController
     session[:customer_id] = nil
     session[:total] = nil
     session[:items] = nil
+    session[:bonus] = nil
 
     # Show the receipt
     render :action => 'receipt'
@@ -179,6 +182,51 @@ class PurchaseController < ApplicationController
         return
       end
 
+      # Check for a Bonus
+      for bonus in BonusPolicy.find_all_by_bonus_type(@rentable.class.to_s)
+        # Find the earliest the period for this bonus extends
+        since_date = Time.now.advance(:days => (-1 * bonus.days)).to_date
+
+        # Find the date of the latest bonus in this period
+        last_bonus_date = BonusPurchase.last_bonus_date(@customer, @rentable.class, since_date)
+
+        # Find the number of rentals of this type in the period since the last bonus
+        count = 0
+        for rental in RentablePurchase.find_all_by_customer_id(@customer, :conditions => ["date > ?", last_bonus_date])
+          if rental.rentable.class == @rentable.class
+            count += 1
+          end
+        end
+
+        # We are eligible for a bonus!
+        if count >= bonus.number
+
+          # Check out the item
+          checkout = Coitem.new
+          checkout.customer = @customer
+          checkout.rentable = @rentable
+          checkout.out_date = Time.now.to_date
+          checkout.due_date = @rentable.due_date
+          checkout.save!
+
+          # Record the BonusPurchase
+          purchase = BonusPurchase.new
+          purchase.customer = @customer
+          purchase.date = Time.now.to_date
+          purchase.price = @rentable.calculated_price
+          purchase.rentable = @rentable
+          purchase.save!
+
+          # Add to session variables
+          session[:items].push @rentable
+          session[:bonus] = @rentable
+
+          flash[:notice] = "Successfully made bonus purchase"
+          redirect_to :action => :menu
+          return
+        end
+      end
+      
       # Check out the item
       checkout = Coitem.new
       checkout.customer = @customer
diff --git a/app/helpers/bonus_policy_helper.rb b/app/helpers/bonus_policy_helper.rb
new file mode 100644 (file)
index 0000000..1a1dec6
--- /dev/null
@@ -0,0 +1,2 @@
+module BonusPolicyHelper
+end
diff --git a/app/models/bonus_policy.rb b/app/models/bonus_policy.rb
new file mode 100644 (file)
index 0000000..d51ec6f
--- /dev/null
@@ -0,0 +1,20 @@
+class BonusPolicy < ActiveRecord::Base
+
+  validates_presence_of :number
+  validates_presence_of :bonus_type
+  validates_presence_of :days
+
+  validates_numericality_of :number
+  validates_numericality_of :days
+
+  def description
+    return "One #{bonus_type} free after #{number} #{bonus_type} rentals in the last #{days} days"
+  end
+
+  protected
+  def validate
+    errors.add(:days, "must be at least 1") if days.nil? || days < 1
+    errors.add(:number, "must be at least 1") if number.nil? || number < 1
+    errors.add(:bonus_type, "must be either Video or Game") unless bonus_type == 'Video' or bonus_type == 'Game'
+  end
+end
diff --git a/app/models/bonus_purchase.rb b/app/models/bonus_purchase.rb
new file mode 100644 (file)
index 0000000..36f7d9b
--- /dev/null
@@ -0,0 +1,27 @@
+class BonusPurchase < Purchase
+  belongs_to :rentable
+  belongs_to :customer
+
+  validates_presence_of :rentable_id
+  validates_numericality_of :rentable_id
+
+  def self.last_bonus_date(customer,bonus_type,since_date)
+    bonuses = BonusPurchase.find_all_by_customer_id(customer, :conditions => ["date >= ?", since_date], :order => 'date DESC')
+    puts "*** BONUSES.length: #{bonuses.length} ***"
+    for bonus in bonuses
+      puts "#{bonus.rentable.class.to_s} == #{bonus_type.to_s} => #{bonus.rentable.class.to_s == bonus_type.to_s}"
+      if bonus.rentable.class.to_s == bonus_type.to_s
+        return bonus.date
+      end
+    end
+
+    # Unable to find a last bonus, no bonuses in period
+    return since_date
+  end
+      
+  protected
+  def validate
+    errors.add(:rentable_id, "is not in the database") if rentable.nil?
+  end
+end
+
index acd6869..b9e6ab9 100644 (file)
@@ -2,6 +2,8 @@ class Customer < ActiveRecord::Base
   has_many :coitems
   has_many :merchandise_purchases
   has_many :rentable_purchases
+  has_many :bonus_purchases
+  has_many :late_fee_purchases
 
   validates_presence_of :name, :email, :phone, :address
   validates_numericality_of :debt
index b596aa2..0f01813 100644 (file)
@@ -7,9 +7,11 @@ class Purchase < ActiveRecord::Base
   validates_numericality_of :price
 
   def title
-    if type == MerchandisePurchase
+    if self.class == MerchandisePurchase
       return merchandise.title
-    elsif type == RentablePurchase
+    elsif self.class == RentablePurchase
+      return rentable.title
+    elsif self.class == BonusPurchase
       return rentable.title
     else
       return 'Late Fees'
@@ -19,7 +21,8 @@ class Purchase < ActiveRecord::Base
   protected
   def validate
     errors.add(:price, "cannot be negative") if price < 0
-    errors.add(:price, "cannot be less than $0.01") if price < 0.01
+    # Need to leave this out for bonuses
+    #errors.add(:price, "cannot be less than $0.01") if price < 0.01
     errors.add(:customer_id, "does not exist in the database") if customer.nil?
   end
 end
index fd5d22f..8ca3901 100644 (file)
@@ -15,7 +15,6 @@ class Video < Rentable
   end
 
   def calculated_price
-    # FIXME: generate this based on day of week, newrelease
     day_of_week = Time.now.to_date.wday
     base_fee = VideoPolicy.find_by_day(day_of_week).fee
 
@@ -26,7 +25,6 @@ class Video < Rentable
   end
 
   def due_date
-    # FIXME: generate this based on the day of week, newrelease
     # NOTE: a Date.wday will tell you the day of week (0-6, meaning Sunday-Saturday)
     day_of_week = Time.now.to_date.wday
     base_period = VideoPolicy.find_by_day(day_of_week).period
diff --git a/app/views/bonus_policy/_form.rhtml b/app/views/bonus_policy/_form.rhtml
new file mode 100644 (file)
index 0000000..1f7fec4
--- /dev/null
@@ -0,0 +1,13 @@
+<%= error_messages_for 'bonus_policy' %>
+
+<!--[form:bonus_policy]-->
+<p><label for="bonus_policy_number">Number</label><br/>
+<%= text_field 'bonus_policy', 'number'  %></p>
+
+<p><label for="bonus_policy_bonus_type">Type</label><br/>
+<%= select 'bonus_policy', 'bonus_type', [Game, Video] %></p>
+
+<p><label for="bonus_policy_days">Days</label><br/>
+<%= text_field 'bonus_policy', 'days'  %></p>
+<!--[eoform:bonus_policy]-->
+
diff --git a/app/views/bonus_policy/edit.rhtml b/app/views/bonus_policy/edit.rhtml
new file mode 100644 (file)
index 0000000..e1d1cf8
--- /dev/null
@@ -0,0 +1,9 @@
+<h1>Editing bonus_policy</h1>
+
+<% form_tag :action => 'update', :id => @bonus_policy do %>
+  <%= render :partial => 'form' %>
+  <%= submit_tag 'Edit' %>
+<% end %>
+
+<%= link_to 'Show', :action => 'show', :id => @bonus_policy %> |
+<%= link_to 'Back', :action => 'list' %>
diff --git a/app/views/bonus_policy/list.rhtml b/app/views/bonus_policy/list.rhtml
new file mode 100644 (file)
index 0000000..6535464
--- /dev/null
@@ -0,0 +1,29 @@
+<h1>Listing bonus_policies</h1>
+
+<table>
+  <tr>
+    <th>Number</th>
+    <th>Bonus Type</th>
+    <th>Days</th>
+    <th>Description</th>
+  </tr>
+  
+<% for bonus_policy in @bonus_policies %>
+  <tr>
+    <td><%=h bonus_policy.number %></td>
+    <td><%=h bonus_policy.bonus_type %></td>
+    <td><%=h bonus_policy.days %></td>
+    <td><%=h bonus_policy.description %></td>
+    <td><%= link_to 'Show', :action => 'show', :id => bonus_policy %></td>
+    <td><%= link_to 'Edit', :action => 'edit', :id => bonus_policy %></td>
+    <td><%= link_to 'Destroy', { :action => 'destroy', :id => bonus_policy }, :confirm => 'Are you sure?', :method => :post %></td>
+  </tr>
+<% end %>
+</table>
+
+<%= link_to 'Previous page', { :page => @bonus_policy_pages.current.previous } if @bonus_policy_pages.current.previous %>
+<%= link_to 'Next page', { :page => @bonus_policy_pages.current.next } if @bonus_policy_pages.current.next %> 
+
+<br />
+
+<%= link_to 'New bonus_policy', :action => 'new' %>
diff --git a/app/views/bonus_policy/new.rhtml b/app/views/bonus_policy/new.rhtml
new file mode 100644 (file)
index 0000000..3d2d5b4
--- /dev/null
@@ -0,0 +1,8 @@
+<h1>New bonus_policy</h1>
+
+<% form_tag :action => 'create' do %>
+  <%= render :partial => 'form' %>
+  <%= submit_tag "Create" %>
+<% end %>
+
+<%= link_to 'Back', :action => 'list' %>
diff --git a/app/views/bonus_policy/show.rhtml b/app/views/bonus_policy/show.rhtml
new file mode 100644 (file)
index 0000000..1d03f7a
--- /dev/null
@@ -0,0 +1,8 @@
+<% for column in BonusPolicy.content_columns %>
+<p>
+  <b><%= column.human_name %>:</b> <%=h @bonus_policy.send(column.name) %>
+</p>
+<% end %>
+
+<%= link_to 'Edit', :action => 'edit', :id => @bonus_policy %> |
+<%= link_to 'Back', :action => 'list' %>
index 7a3e403..5ad36b2 100644 (file)
@@ -23,7 +23,7 @@
       <p><%= link_to "Customer Maintenence", :controller => 'customer', :action => 'index' %></p>
       <p><%= link_to "Financial Information", :controller => 'purchase', :action => 'index' %></p>
       <p><%= link_to "System Maintenence", :controller => 'login', :action => 'maintenence' %></p>
-      <p><%= link_to "Rental Policies and Limits", :controller => 'login', :action => 'limits' %></p>
+      <p><%= link_to "Rental and Bonus Policies", :controller => 'login', :action => 'limits' %></p>
       <br/>
       <p><%= link_to "Logout", :controller => 'login', :action => 'logout' %></p>
     </div>
diff --git a/app/views/layouts/bonus_policy.rhtml b/app/views/layouts/bonus_policy.rhtml
new file mode 100644 (file)
index 0000000..11cf200
--- /dev/null
@@ -0,0 +1,17 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
+       "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
+<head>
+  <meta http-equiv="content-type" content="text/html;charset=UTF-8" />
+  <title>BonusPolicy: <%= controller.action_name %></title>
+  <%= stylesheet_link_tag 'scaffold' %>
+</head>
+<body>
+
+<p style="color: green"><%= flash[:notice] %></p>
+
+<%= yield  %>
+
+</body>
+</html>
index 5d86493..5c3a6b1 100644 (file)
@@ -1,9 +1,10 @@
-<h1>Rental Limits and Pricing</h1>
+<h1>Rental and Bonus Policies</h1>
 
 <h3>Actions</h3>
 <ul>
   <li><%= link_to 'Game Rental Pricing', :controller => 'game_policy', :action => 'index' %></li>
   <li><%= link_to 'Video Rental Pricing', :controller => 'video_policy', :action => 'index' %></li>
   <li><%= link_to 'Rental Limits', :controller => 'rentable_policy', :action => 'index' %></li>
+  <li><%= link_to 'Bonus Policies', :controller => 'bonus_policy', :action => 'index' %></li>
 </ul>
 
index d730d82..0f00737 100644 (file)
@@ -1,4 +1,7 @@
 <h1>Employees</h1>
+
+<p>Press the "[X]" next to the username to delete that user</p>
+
 <ul>
   <% for user in @all_users %>
     <li><%=link_to "[X]", { # link_to options
index de2509e..8e2785b 100644 (file)
@@ -4,8 +4,10 @@
 the box below.</p>
 
 <%= start_form_tag :action => 'buy_merch'%>
-<%= text_field 'merchandise_id', nil %>
-  <%= submit_tag 'Ok' %></form>
+  <%= text_field 'merchandise_id', nil %>
+  <%= submit_tag 'Ok' %>
 <%= end_form_tag %>
 
+<br/>
+<p><%=link_to 'Check Out', :action => 'receipt' %></p>
 
index 504bc61..4b2306d 100644 (file)
@@ -12,7 +12,7 @@
 <% for purchase in @purchases %>
   <tr>
     <td><%=link_to purchase.date, :action => 'filterbydate', :id => purchase.date %></td>
-    <td><%=link_to purchase.class, :action => 'filterbytype', :id => purchase.type %></td>
+    <td><%=link_to purchase.class, :action => 'filterbytype', :id => purchase.class %></td>
     <td><%=h purchase.title %></td>
     <td><%=h number_to_currency(purchase.price) %></td>
     <td><%=link_to purchase.customer.name, :action => 'filterbycust', :id => purchase.customer_id %></td>
index 13f5685..a57bc22 100644 (file)
@@ -27,6 +27,6 @@
   <br/>
   <br/>
   <br/>
-  <li><%=link_to 'End Purchase', :action => 'receipt' %></li>
+  <li><%=link_to 'Checkout', :action => 'receipt' %></li>
 </ul>
 
index d5054cf..87e1c15 100644 (file)
@@ -16,7 +16,7 @@
   </tr>
 
   <!-- Debt -->
-  <% if @debt %>
+  <% if not @debt.zero? %>
   <tr>
     <td><b>Overdue Item Fees</b></td>
     <td></td>
     <td><%=h number_to_currency(item.price) %></td>
   <% end %>
   </tr>
+<% end %>
+<% if @bonus %>
+  <tr>
+    <td><b>Bonus: </b><%=h @bonus.title %></td>
+    <td></td>
+    <td>- <%= number_to_currency(@bonus.calculated_price) %></td>
+  </tr>
 <% end %>
   <tr>
     <td><b>Total</b></td>
index 07b2973..831325c 100644 (file)
@@ -4,7 +4,10 @@
 the box below.</p>
 
 <%= start_form_tag :action => 'rent'%>
-<%= text_field 'rentable_id', nil %>
-  <%= submit_tag 'Ok' %></form>
+  <%= text_field 'rentable_id', nil %>
+  <%= submit_tag 'Ok' %>
 <%= end_form_tag %>
 
+<br/>
+<p><%=link_to 'Check Out', :action => 'receipt' %></p>
+
index 2f9ea91..2dcc744 100644 (file)
Binary files a/db/development.sqlite3 and b/db/development.sqlite3 differ
diff --git a/db/migrate/033_create_bonus_policies.rb b/db/migrate/033_create_bonus_policies.rb
new file mode 100644 (file)
index 0000000..6845ca3
--- /dev/null
@@ -0,0 +1,13 @@
+class CreateBonusPolicies < ActiveRecord::Migration
+  def self.up
+    create_table :bonus_policies do |t|
+      t.column :number, :integer
+      t.column :bonus_type, :string
+      t.column :days, :integer
+    end
+  end
+
+  def self.down
+    drop_table :bonus_policies
+  end
+end
index 45c829e..3f4f6a8 100644 (file)
@@ -2,7 +2,13 @@
 # migrations feature of ActiveRecord to incrementally modify your database, and
 # then regenerate this schema definition.
 
-ActiveRecord::Schema.define(:version => 32) do
+ActiveRecord::Schema.define(:version => 33) do
+
+  create_table "bonus_policies", :force => true do |t|
+    t.column "number",     :integer
+    t.column "bonus_type", :string
+    t.column "days",       :integer
+  end
 
   create_table "coitems", :force => true do |t|
     t.column "customer_id", :integer
diff --git a/test/fixtures/bonus_policies.yml b/test/fixtures/bonus_policies.yml
new file mode 100644 (file)
index 0000000..b49c4eb
--- /dev/null
@@ -0,0 +1,5 @@
+# Read about fixtures at http://ar.rubyonrails.org/classes/Fixtures.html
+one:
+  id: 1
+two:
+  id: 2
diff --git a/test/functional/bonus_policy_controller_test.rb b/test/functional/bonus_policy_controller_test.rb
new file mode 100644 (file)
index 0000000..97676f6
--- /dev/null
@@ -0,0 +1,92 @@
+require File.dirname(__FILE__) + '/../test_helper'
+require 'bonus_policy_controller'
+
+# Re-raise errors caught by the controller.
+class BonusPolicyController; def rescue_action(e) raise e end; end
+
+class BonusPolicyControllerTest < Test::Unit::TestCase
+  fixtures :bonus_policies
+
+  def setup
+    @controller = BonusPolicyController.new
+    @request    = ActionController::TestRequest.new
+    @response   = ActionController::TestResponse.new
+
+    @first_id = bonus_policies(:first).id
+  end
+
+  def test_index
+    get :index
+    assert_response :success
+    assert_template 'list'
+  end
+
+  def test_list
+    get :list
+
+    assert_response :success
+    assert_template 'list'
+
+    assert_not_nil assigns(:bonus_policies)
+  end
+
+  def test_show
+    get :show, :id => @first_id
+
+    assert_response :success
+    assert_template 'show'
+
+    assert_not_nil assigns(:bonus_policy)
+    assert assigns(:bonus_policy).valid?
+  end
+
+  def test_new
+    get :new
+
+    assert_response :success
+    assert_template 'new'
+
+    assert_not_nil assigns(:bonus_policy)
+  end
+
+  def test_create
+    num_bonus_policies = BonusPolicy.count
+
+    post :create, :bonus_policy => {}
+
+    assert_response :redirect
+    assert_redirected_to :action => 'list'
+
+    assert_equal num_bonus_policies + 1, BonusPolicy.count
+  end
+
+  def test_edit
+    get :edit, :id => @first_id
+
+    assert_response :success
+    assert_template 'edit'
+
+    assert_not_nil assigns(:bonus_policy)
+    assert assigns(:bonus_policy).valid?
+  end
+
+  def test_update
+    post :update, :id => @first_id
+    assert_response :redirect
+    assert_redirected_to :action => 'show', :id => @first_id
+  end
+
+  def test_destroy
+    assert_nothing_raised {
+      BonusPolicy.find(@first_id)
+    }
+
+    post :destroy, :id => @first_id
+    assert_response :redirect
+    assert_redirected_to :action => 'list'
+
+    assert_raise(ActiveRecord::RecordNotFound) {
+      BonusPolicy.find(@first_id)
+    }
+  end
+end
diff --git a/test/unit/bonus_policy_test.rb b/test/unit/bonus_policy_test.rb
new file mode 100644 (file)
index 0000000..a68f3ef
--- /dev/null
@@ -0,0 +1,10 @@
+require File.dirname(__FILE__) + '/../test_helper'
+
+class BonusPolicyTest < Test::Unit::TestCase
+  fixtures :bonus_policies
+
+  # Replace this with your real tests.
+  def test_truth
+    assert true
+  end
+end