piggybak 0.1.1 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (53) hide show
  1. data/Gemfile +2 -0
  2. data/Gemfile.lock +5 -0
  3. data/README.md +117 -0
  4. data/Rakefile +2 -1
  5. data/VERSION +1 -1
  6. data/app/assets/javascripts/piggybak.js +62 -2
  7. data/app/controllers/piggybak/orders_controller.rb +26 -5
  8. data/{lib/application_helper.rb → app/helpers/piggybak_helper.rb} +6 -1
  9. data/app/models/piggybak/address.rb +14 -1
  10. data/app/models/piggybak/cart.rb +19 -12
  11. data/app/models/piggybak/country.rb +5 -0
  12. data/app/models/piggybak/credit.rb +8 -0
  13. data/app/models/piggybak/line_item.rb +13 -6
  14. data/app/models/piggybak/order.rb +12 -6
  15. data/app/models/piggybak/payment.rb +5 -3
  16. data/app/models/piggybak/payment_calculator/authorize_net.rb +4 -0
  17. data/app/models/piggybak/payment_calculator/fake.rb +4 -0
  18. data/app/models/piggybak/payment_method.rb +8 -3
  19. data/app/models/piggybak/shipping_calculator/free.rb +13 -0
  20. data/app/models/piggybak/shipping_calculator/pickup.rb +1 -1
  21. data/app/models/piggybak/shipping_method.rb +9 -7
  22. data/app/models/piggybak/state.rb +1 -0
  23. data/app/models/piggybak/tax_calculator/{flat_rate.rb → percent.rb} +1 -1
  24. data/app/models/piggybak/tax_method.rb +8 -2
  25. data/app/models/piggybak/{product.rb → variant.rb} +5 -4
  26. data/app/views/piggybak/cart/_form.html.erb +7 -7
  27. data/app/views/piggybak/cart/_items.html.erb +9 -9
  28. data/app/views/piggybak/cart/show.html.erb +2 -0
  29. data/app/views/piggybak/notifier/order_notification.text.erb +1 -1
  30. data/app/views/piggybak/orders/_address_form.html.erb +18 -14
  31. data/app/views/piggybak/orders/_details.html.erb +48 -0
  32. data/app/views/piggybak/orders/download.text.erb +19 -0
  33. data/app/views/piggybak/orders/list.html.erb +12 -12
  34. data/app/views/piggybak/orders/no_access.text.erb +1 -0
  35. data/app/views/piggybak/orders/receipt.html.erb +1 -49
  36. data/app/views/piggybak/orders/show.html.erb +36 -31
  37. data/app/views/rails_admin/main/_actions.html.erb +9 -2
  38. data/config/routes.rb +12 -3
  39. data/db/migrate/20111227150106_create_orders.rb +4 -3
  40. data/db/migrate/20111227150322_create_addresses.rb +2 -1
  41. data/db/migrate/20111227150432_create_line_items.rb +2 -2
  42. data/db/migrate/{20111227213558_create_products.rb → 20111227213558_create_variants.rb} +3 -3
  43. data/db/migrate/20111228231829_create_payments.rb +3 -1
  44. data/db/migrate/20111228231838_create_shipments.rb +1 -1
  45. data/db/migrate/20120102162414_create_countries.rb +10 -0
  46. data/db/migrate/20120102162415_create_states.rb +1 -0
  47. data/db/migrate/20120104020930_populate_countries_and_states.rb +18 -0
  48. data/db/migrate/20120106010412_create_credits.rb +14 -0
  49. data/lib/{acts_as_product → acts_as_variant}/base.rb +6 -6
  50. data/lib/piggybak.rb +59 -23
  51. data/piggybak.gemspec +26 -11
  52. metadata +64 -62
  53. data/README.rdoc +0 -19
@@ -1,9 +1,9 @@
1
1
  module Piggybak
2
2
  class LineItem < ActiveRecord::Base
3
3
  belongs_to :order
4
- belongs_to :product
4
+ belongs_to :variant
5
5
 
6
- validates_presence_of :product_id
6
+ validates_presence_of :variant_id
7
7
  validates_presence_of :total
8
8
  validates_presence_of :quantity
9
9
  validates_numericality_of :quantity, :only_integer => true, :greater_than_or_equal_to => 0
@@ -13,19 +13,26 @@ module Piggybak
13
13
  after_update :update_inventory
14
14
 
15
15
  def admin_label
16
- "#{self.quantity} x #{self.product.description}"
16
+ "#{self.quantity} x #{self.variant.description}"
17
17
  end
18
18
 
19
19
  def decrease_inventory
20
- self.product.update_inventory(-1 * self.quantity)
20
+ self.variant.update_inventory(-1 * self.quantity)
21
21
  end
22
22
 
23
23
  def increase_inventory
24
- self.product.update_inventory(self.quantity)
24
+ self.variant.update_inventory(self.quantity)
25
25
  end
26
26
 
27
27
  def update_inventory
28
- Rails.logger.warn "steph inside update inventory: #{self.inspect}"
28
+ if self.variant_id != self.variant_id_was
29
+ old_variant = Variant.find(self.variant_id_was)
30
+ old_variant.update_inventory(self.quantity_was)
31
+ self.variant.update_inventory(-1*self.quantity)
32
+ else
33
+ quantity_diff = self.quantity_was - self.quantity
34
+ self.variant.update_inventory(quantity_diff)
35
+ end
29
36
  end
30
37
  end
31
38
  end
@@ -3,6 +3,8 @@ module Piggybak
3
3
  has_many :line_items, :inverse_of => :order
4
4
  has_many :payments, :inverse_of => :order
5
5
  has_many :shipments, :inverse_of => :order
6
+ has_many :credits, :inverse_of => :order
7
+
6
8
  belongs_to :billing_address, :class_name => "Piggybak::Address"
7
9
  belongs_to :shipping_address, :class_name => "Piggybak::Address"
8
10
  belongs_to :user
@@ -11,7 +13,7 @@ module Piggybak
11
13
  accepts_nested_attributes_for :shipping_address, :allow_destroy => true
12
14
  accepts_nested_attributes_for :shipments, :allow_destroy => true
13
15
  accepts_nested_attributes_for :line_items, :allow_destroy => true
14
- accepts_nested_attributes_for :payments
16
+ accepts_nested_attributes_for :payments # test :allow_destroy
15
17
 
16
18
  validates_presence_of :status
17
19
  validates_presence_of :email
@@ -58,8 +60,8 @@ module Piggybak
58
60
  def add_line_items(cart)
59
61
  cart.update_quantities
60
62
  cart.items.each do |item|
61
- line_item = Piggybak::LineItem.new({ :product_id => item[:product].id,
62
- :total => item[:product].price*item[:quantity],
63
+ line_item = Piggybak::LineItem.new({ :variant_id => item[:variant].id,
64
+ :total => item[:variant].price*item[:quantity],
63
65
  :quantity => item[:quantity] })
64
66
  self.line_items << line_item
65
67
  end
@@ -73,7 +75,7 @@ module Piggybak
73
75
  self.tax_charge = 0
74
76
 
75
77
  self.line_items.each do |line_item|
76
- line_item.total = line_item.product.price * line_item.quantity
78
+ line_item.total = line_item.variant.price * line_item.quantity
77
79
  end
78
80
  end
79
81
 
@@ -92,10 +94,14 @@ module Piggybak
92
94
  calculator = shipment.shipping_method.klass.constantize
93
95
  shipment.total = calculator.rate(shipment.shipping_method, self)
94
96
  end
95
- logger.warn "steph shipment total is #{shipment.total}"
96
97
  self.total += shipment.total
97
98
  end
98
- logger.warn "steph: total is #{self.total}"
99
+
100
+ # Hook in credits, TBD
101
+ credits.each do |credit|
102
+ self.total -= credit.total
103
+ end
104
+
99
105
  self.total = self.total.to_c
100
106
 
101
107
  self.total_due = self.total
@@ -10,7 +10,7 @@ module Piggybak
10
10
  validates_presence_of :year
11
11
 
12
12
  def status_enum
13
- ["paid"]
13
+ ["paid", "refunded"]
14
14
  end
15
15
 
16
16
  def month_enum
@@ -39,7 +39,8 @@ module Piggybak
39
39
  if gateway_response.success?
40
40
  self.attributes = { :total => self.order.total_due,
41
41
  :number => 'hidden',
42
- :verification_value => 'hidden' }
42
+ :verification_value => 'hidden',
43
+ :transaction_id => payment_gateway.transaction_id(gateway_response) }
43
44
  gateway.capture(1000, gateway_response.authorization)
44
45
  return true
45
46
  else
@@ -56,6 +57,7 @@ module Piggybak
56
57
  return "Payment ##{self.id} (#{self.created_at.strftime("%m-%d-%Y")})<br />" +
57
58
  "Payment Method: #{self.payment_method.description}<br />" +
58
59
  "Status: #{self.status}<br />" +
60
+ "Transaction ID: #{self.transaction_id}<br />" +
59
61
  "$#{"%.2f" % self.total}"
60
62
  else
61
63
  return ""
@@ -70,7 +72,7 @@ module Piggybak
70
72
  if !credit_card.valid?
71
73
  credit_card.errors.each do |key, value|
72
74
  if value.any? && !["first_name", "last_name", "type"].include?(key)
73
- record.errors.add key, value
75
+ record.errors.add key, (value.is_a?(Array) ? value.join(', ') : value)
74
76
  end
75
77
  end
76
78
  end
@@ -2,5 +2,9 @@ module Piggybak
2
2
  class PaymentCalculator::AuthorizeNet < PaymentCalculator
3
3
  KEYS = ["login", "password"]
4
4
  KLASS = ::ActiveMerchant::Billing::AuthorizeNetGateway
5
+
6
+ def self.transaction_id(gateway_response)
7
+ gateway_response.params["transaction_id"]
8
+ end
5
9
  end
6
10
  end
@@ -20,5 +20,9 @@ module Piggybak
20
20
 
21
21
  def self.capture(*args)
22
22
  end
23
+
24
+ def self.transaction_id(*args)
25
+ "N/A"
26
+ end
23
27
  end
24
28
  end
@@ -1,5 +1,12 @@
1
1
  module Piggybak
2
2
  class PaymentMethod < ActiveRecord::Base
3
+
4
+ # klass_enum requires the ShippingCalculator subclasses to be loaded
5
+ shipping_calcs_path = File.expand_path("../payment_calculator", __FILE__)
6
+ Dir.glob(shipping_calcs_path + "**/*.rb").each do |subclass|
7
+ ActiveSupport::Dependencies.require_or_load subclass
8
+ end
9
+
3
10
  has_many :payment_method_values, :dependent => :destroy
4
11
  alias :metadata :payment_method_values
5
12
 
@@ -9,9 +16,7 @@ module Piggybak
9
16
  validates_presence_of :description
10
17
 
11
18
  def klass_enum
12
- #TODO: Troubleshoot use of subclasses here instead
13
- [Piggybak::PaymentCalculator::AuthorizeNet,
14
- Piggybak::PaymentCalculator::Fake]
19
+ Piggybak::PaymentCalculator.subclasses
15
20
  end
16
21
 
17
22
  validates_each :payment_method_values do |record, attr, value|
@@ -0,0 +1,13 @@
1
+ module Piggybak
2
+ class ShippingCalculator::Free < ShippingCalculator
3
+ KEYS = []
4
+
5
+ def self.available?(method, object)
6
+ true
7
+ end
8
+
9
+ def self.rate(method, object)
10
+ 0.00
11
+ end
12
+ end
13
+ end
@@ -7,7 +7,7 @@ module Piggybak
7
7
 
8
8
  if object.is_a?(Cart)
9
9
  state = State.find(object.extra_data["state_id"])
10
- return true if state.abbr == abbr
10
+ return true if state && state.abbr == abbr
11
11
  else
12
12
  if object.billing_address && object.billing_address.state
13
13
  return object.billing_address.state.abbr == abbr
@@ -1,5 +1,12 @@
1
1
  module Piggybak
2
2
  class ShippingMethod < ActiveRecord::Base
3
+
4
+ # klass_enum requires the ShippingCalculator subclasses to be loaded
5
+ shipping_calcs_path = File.expand_path("../shipping_calculator", __FILE__)
6
+ Dir.glob(shipping_calcs_path + "**/*.rb").each do |subclass|
7
+ ActiveSupport::Dependencies.require_or_load subclass
8
+ end
9
+
3
10
  has_many :shipping_method_values, :dependent => :destroy
4
11
  alias :metadata :shipping_method_values
5
12
 
@@ -18,11 +25,8 @@ module Piggybak
18
25
  end
19
26
  end
20
27
 
21
- def klass_enum
22
- #TODO: Troubleshoot use of subclasses here instead
23
- [Piggybak::ShippingCalculator::FlatRate,
24
- Piggybak::ShippingCalculator::Range,
25
- Piggybak::ShippingCalculator::Pickup]
28
+ def klass_enum
29
+ Piggybak::ShippingCalculator.subclasses
26
30
  end
27
31
 
28
32
  def self.available_methods(cart)
@@ -36,10 +40,8 @@ module Piggybak
36
40
 
37
41
  active_methods.inject([]) do |arr, method|
38
42
  klass = method.klass.constantize
39
- logger.warn "steph: inside here!! #{method.inspect}"
40
43
  if klass.available?(method, cart)
41
44
  rate = klass.rate(method, cart)
42
- logger.warn "steph rate is #{rate.inspect}"
43
45
  arr << {
44
46
  :label => "#{method.description} $#{"%.2f" % rate}",
45
47
  :id => method.id,
@@ -1,4 +1,5 @@
1
1
  module Piggybak
2
2
  class State < ActiveRecord::Base
3
+ belongs_to :country
3
4
  end
4
5
  end
@@ -1,5 +1,5 @@
1
1
  module Piggybak
2
- class TaxCalculator::FlatRate < TaxCalculator
2
+ class TaxCalculator::Percent < TaxCalculator
3
3
  KEYS = ["state_abbr", "rate"]
4
4
 
5
5
  def self.available?(method, object)
@@ -1,5 +1,12 @@
1
1
  module Piggybak
2
2
  class TaxMethod < ActiveRecord::Base
3
+
4
+ # klass_enum requires the ShippingCalculator subclasses to be loaded
5
+ shipping_calcs_path = File.expand_path("../tax_calculator", __FILE__)
6
+ Dir.glob(shipping_calcs_path + "**/*.rb").each do |subclass|
7
+ ActiveSupport::Dependencies.require_or_load subclass
8
+ end
9
+
3
10
  has_many :tax_method_values, :dependent => :destroy
4
11
  alias :metadata :tax_method_values
5
12
 
@@ -19,8 +26,7 @@ module Piggybak
19
26
  end
20
27
 
21
28
  def klass_enum
22
- #TODO: Troubleshoot use of subclasses here instead
23
- [Piggybak::TaxCalculator::FlatRate]
29
+ Piggybak::TaxCalculator.subclasses
24
30
  end
25
31
 
26
32
  def self.calculate_tax(object)
@@ -1,6 +1,7 @@
1
- class Piggybak::Product < ActiveRecord::Base
2
- belongs_to :item, :polymorphic => true, :inverse_of => :piggybak_product
3
- attr_accessible :sku, :description, :price, :quantity, :active, :unlimited_inventory, :item_id, :item_type
1
+ class Piggybak::Variant < ActiveRecord::Base
2
+ belongs_to :item, :polymorphic => true, :inverse_of => :piggybak_variant
3
+ attr_accessible :sku, :description, :price, :quantity, :active, :unlimited_inventory, :item_id, :item_type
4
+ attr_accessible :item # to allow direct assignment from code or console
4
5
 
5
6
  validates_presence_of :sku
6
7
  validates_uniqueness_of :sku
@@ -10,7 +11,7 @@ class Piggybak::Product < ActiveRecord::Base
10
11
  validates_numericality_of :quantity, :only_integer => true, :greater_than_or_equal_to => 0
11
12
 
12
13
  def admin_label
13
- "Product: #{self.description}"
14
+ "Variant: #{self.description}"
14
15
  end
15
16
 
16
17
  def update_inventory(purchased)
@@ -1,14 +1,14 @@
1
- <% if object.reflections.keys.include?(:piggybak_product) -%>
2
- <% if object.piggybak_product && object.piggybak_product.active -%>
3
- <% if object.piggybak_product.quantity > 0 -%>
1
+ <% if object.reflections.keys.include?(:piggybak_variant) -%>
2
+ <% if object.piggybak_variant && object.piggybak_variant.active -%>
3
+ <% if object.piggybak_variant.quantity > 0 || object.piggybak_variant.unlimited_inventory -%>
4
4
  <%= form_tag piggybak.cart_add_url do -%>
5
- <div id="product_details">
6
- <span id="title"><%= object.piggybak_product.description %></span>
7
- <span id="price"><%= number_to_currency object.piggybak_product.price %></span>
5
+ <div id="variant_details">
6
+ <span id="title"><%= object.piggybak_variant.description %></span>
7
+ <span id="price"><%= number_to_currency object.piggybak_variant.price %></span>
8
8
  </div>
9
9
  <label>Quantity</label>
10
10
  <%= text_field_tag :quantity %>
11
- <%= hidden_field_tag :product_id, object.piggybak_product.id %>
11
+ <%= hidden_field_tag :variant_id, object.piggybak_variant.id %>
12
12
  <%= submit_tag "Add to Cart", :id => "submit" %>
13
13
  <% end -%>
14
14
  <% else -%>
@@ -2,29 +2,29 @@
2
2
  <%= form_tag piggybak.cart_update_url do -%>
3
3
  <table cellpadding="5" cellspacing="0" width="100%">
4
4
  <tr>
5
- <th>Item</th>
6
- <th>Price</th>
7
- <th>Quantity</th>
8
- <th>Subtotal</th>
5
+ <th id="item_col">Item</th>
6
+ <th id="price_col">Price</th>
7
+ <th id="quantity_col">Quantity</th>
8
+ <th id="subtotal_col">Subtotal</th>
9
9
  <% if page == "cart" -%>
10
10
  <th></th>
11
11
  <% end -%>
12
12
  </tr>
13
13
  <% @cart.items.each do |item| %>
14
14
  <tr>
15
- <td><%= item[:product].description %></td>
16
- <td><%= number_to_currency item[:product].price %></td>
15
+ <td><%= item[:variant].description %></td>
16
+ <td><%= number_to_currency item[:variant].price %></td>
17
17
  <td>
18
18
  <% if page == "cart" -%>
19
- <%= text_field_tag "quantity[#{item[:product].id}]", item[:quantity] %>
19
+ <%= text_field_tag "quantity[#{item[:variant].id}]", item[:quantity] %>
20
20
  <% else -%>
21
21
  <%= item[:quantity] %>
22
22
  <% end -%>
23
23
  </td>
24
- <td><%= number_to_currency item[:quantity]*item[:product].price %></td>
24
+ <td><%= number_to_currency item[:quantity]*item[:variant].price %></td>
25
25
  <% if page == "cart" -%>
26
26
  <td>
27
- <%= link_to "Remove", piggybak.remove_item_url(item[:product].id), :method => :delete %>
27
+ <%= link_to "Remove", piggybak.remove_item_url(item[:variant].id), :method => :delete %>
28
28
  </td>
29
29
  <% end -%>
30
30
  </tr>
@@ -2,7 +2,9 @@
2
2
 
3
3
  <h1>Shopping Cart</h1>
4
4
  <% if @cart.errors.any? -%>
5
+ <div id="cart_error">
5
6
  <%= raw @cart.errors.join('<br />') %>
7
+ </div>
6
8
  <% end -%>
7
9
 
8
10
  <%= render "items", :page => "cart" %>
@@ -3,7 +3,7 @@ Thanks for your order!
3
3
  Items
4
4
 
5
5
  <% @order.line_items.each do |line_item| %>
6
- <%= line_item.product.description %> x <%= line_item.quantity %> = <%= number_to_currency line_item.total %>
6
+ <%= line_item.variant.description %> x <%= line_item.quantity %> = <%= number_to_currency line_item.total %>
7
7
  <% end -%>
8
8
 
9
9
  Total
@@ -1,24 +1,28 @@
1
- <p>
1
+ <div class="item">
2
2
  <%= address.label :firstname %>
3
3
  <%= address.text_field :firstname %>
4
- </p>
5
- <p>
4
+ </div>
5
+ <div class="item">
6
6
  <%= address.label :lastname %>
7
7
  <%= address.text_field :lastname %>
8
- </p>
9
- <p>
8
+ </div>
9
+ <div class="item">
10
10
  <%= address.label :address1 %>
11
11
  <%= address.text_field :address1 %>
12
- </p>
13
- <p>
12
+ </div>
13
+ <div class="item">
14
14
  <%= address.label :city %>
15
15
  <%= address.text_field :city %>
16
- </p>
17
- <p>
18
- <%= address.label :state %>
19
- <%= address.select :state_id, ::Piggybak::State.all.collect {|p| [ p.name, p.id ] }, {:include_blank => false} %>
20
- </p>
21
- <p>
16
+ </div>
17
+ <div class="item">
18
+ <%= address.label :country_id %>
19
+ <%= address.collection_select :country_id, Piggybak::Country.all, :id, :name, { :selected => address.object.country_id.to_s } %>
20
+ </div>
21
+ <div class="item">
22
+ <%= address.label :state_id %>
23
+ <%= address.collection_select :state_id, address.object.country.states, :id, :name, { :selected => address.object.state_id.to_s } %>
24
+ </div>
25
+ <div class="item">
22
26
  <%= address.label :zip %>
23
27
  <%= address.text_field :zip %>
24
- </p>
28
+ </div>
@@ -0,0 +1,48 @@
1
+ <table id="items" cellpadding="5" cellspacing="0">
2
+ <tr>
3
+ <th>Item</th>
4
+ <th>Price</th>
5
+ <th>Quantity</th>
6
+ <th>Subtotal</th>
7
+ </tr>
8
+ <% order.line_items.each do |line_item| %>
9
+ <tr>
10
+ <td><%= line_item.variant.description %></td>
11
+ <td><%= line_item.variant.price %></td>
12
+ <td><%= line_item.quantity %></td>
13
+ <td><%= number_to_currency line_item.total %></td>
14
+ </tr>
15
+ <% end -%>
16
+ <tr>
17
+ <td colspan="4"></td>
18
+ </tr>
19
+ <tr>
20
+ <td colspan="2"></td>
21
+ <td>Subtotal</td>
22
+ <td><%= number_to_currency order.line_items.inject(0) { |subtotal, li| subtotal + li.total } %></td>
23
+ </tr>
24
+ <tr>
25
+ <td colspan="2"></td>
26
+ <td>Tax</td>
27
+ <td><%= number_to_currency order.tax_charge %></td>
28
+ </tr>
29
+ <tr>
30
+ <td colspan="2"></td>
31
+ <td>Shipping</td>
32
+ <td><%= number_to_currency order.shipments.inject(0) { |shipping, shipment| shipping + shipment.total } %></td>
33
+ </tr>
34
+ <tr>
35
+ <td colspan="2"></td>
36
+ <td>Total</td>
37
+ <td><%= number_to_currency order.total %></td>
38
+ </tr>
39
+ </table>
40
+
41
+ Email: <%= order.email %><br />
42
+ Phone: <%= order.phone %>
43
+
44
+ <h3>Billing Information</h3>
45
+ <%= raw order.billing_address.display %>
46
+
47
+ <h3>Shipping Information</h3>
48
+ <%= raw order.shipping_address.display %>