piggybak 0.1.1 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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 %>