piggybak 0.1.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 (63) hide show
  1. data/.document +5 -0
  2. data/Gemfile +13 -0
  3. data/Gemfile.lock +18 -0
  4. data/README.rdoc +19 -0
  5. data/Rakefile +56 -0
  6. data/VERSION +1 -0
  7. data/app/assets/javascripts/piggybak.js +72 -0
  8. data/app/controllers/piggybak/cart_controller.rb +29 -0
  9. data/app/controllers/piggybak/orders_controller.rb +86 -0
  10. data/app/mailers/piggybak/notifier.rb +10 -0
  11. data/app/models/piggybak/address.rb +23 -0
  12. data/app/models/piggybak/cart.rb +77 -0
  13. data/app/models/piggybak/line_item.rb +31 -0
  14. data/app/models/piggybak/order.rb +141 -0
  15. data/app/models/piggybak/payment.rb +80 -0
  16. data/app/models/piggybak/payment_calculator.rb +7 -0
  17. data/app/models/piggybak/payment_calculator/authorize_net.rb +6 -0
  18. data/app/models/piggybak/payment_calculator/fake.rb +24 -0
  19. data/app/models/piggybak/payment_method.rb +40 -0
  20. data/app/models/piggybak/payment_method_value.rb +11 -0
  21. data/app/models/piggybak/product.rb +19 -0
  22. data/app/models/piggybak/shipment.rb +21 -0
  23. data/app/models/piggybak/shipping_calculator.rb +12 -0
  24. data/app/models/piggybak/shipping_calculator/flat_rate.rb +13 -0
  25. data/app/models/piggybak/shipping_calculator/pickup.rb +23 -0
  26. data/app/models/piggybak/shipping_calculator/range.rb +16 -0
  27. data/app/models/piggybak/shipping_method.rb +55 -0
  28. data/app/models/piggybak/shipping_method_value.rb +11 -0
  29. data/app/models/piggybak/state.rb +4 -0
  30. data/app/models/piggybak/tax_calculator.rb +7 -0
  31. data/app/models/piggybak/tax_calculator/flat_rate.rb +33 -0
  32. data/app/models/piggybak/tax_method.rb +43 -0
  33. data/app/models/piggybak/tax_method_value.rb +11 -0
  34. data/app/views/piggybak/cart/_form.html.erb +18 -0
  35. data/app/views/piggybak/cart/_items.html.erb +68 -0
  36. data/app/views/piggybak/cart/show.html.erb +13 -0
  37. data/app/views/piggybak/notifier/order_notification.text.erb +11 -0
  38. data/app/views/piggybak/orders/_address_form.html.erb +24 -0
  39. data/app/views/piggybak/orders/list.html.erb +12 -0
  40. data/app/views/piggybak/orders/receipt.html.erb +53 -0
  41. data/app/views/piggybak/orders/show.html.erb +112 -0
  42. data/app/views/rails_admin/main/_actions.html.erb +5 -0
  43. data/config/routes.rb +15 -0
  44. data/db/migrate/20111227150106_create_orders.rb +21 -0
  45. data/db/migrate/20111227150322_create_addresses.rb +15 -0
  46. data/db/migrate/20111227150432_create_line_items.rb +10 -0
  47. data/db/migrate/20111227213558_create_products.rb +14 -0
  48. data/db/migrate/20111228231756_create_shipping_methods.rb +9 -0
  49. data/db/migrate/20111228231806_create_payment_methods.rb +11 -0
  50. data/db/migrate/20111228231829_create_payments.rb +17 -0
  51. data/db/migrate/20111228231838_create_shipments.rb +12 -0
  52. data/db/migrate/20111228235852_create_shipping_method_values.rb +9 -0
  53. data/db/migrate/20111228235853_create_payment_method_values.rb +9 -0
  54. data/db/migrate/20120102154050_create_tax_methods.rb +9 -0
  55. data/db/migrate/20120102162415_create_states.rb +8 -0
  56. data/db/migrate/20120102162703_create_tax_method_values.rb +9 -0
  57. data/lib/acts_as_orderer/base.rb +29 -0
  58. data/lib/acts_as_product/base.rb +32 -0
  59. data/lib/application_helper.rb +12 -0
  60. data/lib/currency.rb +5 -0
  61. data/lib/piggybak.rb +295 -0
  62. data/piggybak.gemspec +115 -0
  63. metadata +213 -0
@@ -0,0 +1,11 @@
1
+ module Piggybak
2
+ class ShippingMethodValue < ActiveRecord::Base
3
+ belongs_to :shipping_method
4
+ validates_presence_of :key
5
+ validates_presence_of :value
6
+
7
+ def admin_label
8
+ "#{self.key} - #{self.value}"
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,4 @@
1
+ module Piggybak
2
+ class State < ActiveRecord::Base
3
+ end
4
+ end
@@ -0,0 +1,7 @@
1
+ module Piggybak
2
+ class TaxCalculator
3
+ def self.available?(*args)
4
+ false
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,33 @@
1
+ module Piggybak
2
+ class TaxCalculator::FlatRate < TaxCalculator
3
+ KEYS = ["state_abbr", "rate"]
4
+
5
+ def self.available?(method, object)
6
+ abbr = method.metadata.detect { |t| t.key == "state_abbr" }.value
7
+
8
+ if object.is_a?(Cart)
9
+ state = State.find(object.extra_data["state_id"])
10
+ return state.abbr == abbr
11
+ else
12
+ if object.billing_address && object.billing_address.state
13
+ return object.billing_address.state.abbr == abbr
14
+ end
15
+ end
16
+ return false
17
+ end
18
+
19
+ def self.rate(method, object)
20
+ taxable_total = 0
21
+
22
+ if object.is_a?(Cart)
23
+ taxable_total = object.total
24
+ else
25
+ object.line_items.each do |line_item|
26
+ taxable_total = line_item.total
27
+ end
28
+ end
29
+
30
+ (method.metadata.detect { |m| m.key == "rate" }.value.to_f * taxable_total).to_c
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,43 @@
1
+ module Piggybak
2
+ class TaxMethod < ActiveRecord::Base
3
+ has_many :tax_method_values, :dependent => :destroy
4
+ alias :metadata :tax_method_values
5
+
6
+ validates_presence_of :description
7
+ validates_presence_of :klass
8
+
9
+ accepts_nested_attributes_for :tax_method_values, :allow_destroy => true
10
+
11
+ validates_each :tax_method_values do |record, attr, value|
12
+ if record.klass
13
+ calculator = record.klass.constantize
14
+ metadata_keys = value.collect { |v| v.key }.sort
15
+ if calculator::KEYS.sort != metadata_keys
16
+ record.errors.add attr, "You must define key values for #{calculator::KEYS.join(', ')} for this tax method."
17
+ end
18
+ end
19
+ end
20
+
21
+ def klass_enum
22
+ #TODO: Troubleshoot use of subclasses here instead
23
+ [Piggybak::TaxCalculator::FlatRate]
24
+ end
25
+
26
+ def self.calculate_tax(object)
27
+ total_tax = 0
28
+
29
+ TaxMethod.all.each do |tax_method|
30
+ calculator = tax_method.klass.constantize
31
+ if calculator.available?(tax_method, object)
32
+ total_tax += calculator.rate(tax_method, object)
33
+ end
34
+ end
35
+
36
+ total_tax
37
+ end
38
+
39
+ def admin_label
40
+ self.description
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,11 @@
1
+ module Piggybak
2
+ class TaxMethodValue < ActiveRecord::Base
3
+ belongs_to :tax_method
4
+ validates_presence_of :key
5
+ validates_presence_of :value
6
+
7
+ def admin_label
8
+ "#{self.key} - #{self.value}"
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,18 @@
1
+ <% if object.reflections.keys.include?(:piggybak_product) -%>
2
+ <% if object.piggybak_product && object.piggybak_product.active -%>
3
+ <% if object.piggybak_product.quantity > 0 -%>
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>
8
+ </div>
9
+ <label>Quantity</label>
10
+ <%= text_field_tag :quantity %>
11
+ <%= hidden_field_tag :product_id, object.piggybak_product.id %>
12
+ <%= submit_tag "Add to Cart", :id => "submit" %>
13
+ <% end -%>
14
+ <% else -%>
15
+ Sorry, out of stock.
16
+ <% end -%>
17
+ <% end -%>
18
+ <% end -%>
@@ -0,0 +1,68 @@
1
+ <% if @cart.items.any? -%>
2
+ <%= form_tag piggybak.cart_update_url do -%>
3
+ <table cellpadding="5" cellspacing="0" width="100%">
4
+ <tr>
5
+ <th>Item</th>
6
+ <th>Price</th>
7
+ <th>Quantity</th>
8
+ <th>Subtotal</th>
9
+ <% if page == "cart" -%>
10
+ <th></th>
11
+ <% end -%>
12
+ </tr>
13
+ <% @cart.items.each do |item| %>
14
+ <tr>
15
+ <td><%= item[:product].description %></td>
16
+ <td><%= number_to_currency item[:product].price %></td>
17
+ <td>
18
+ <% if page == "cart" -%>
19
+ <%= text_field_tag "quantity[#{item[:product].id}]", item[:quantity] %>
20
+ <% else -%>
21
+ <%= item[:quantity] %>
22
+ <% end -%>
23
+ </td>
24
+ <td><%= number_to_currency item[:quantity]*item[:product].price %></td>
25
+ <% if page == "cart" -%>
26
+ <td>
27
+ <%= link_to "Remove", piggybak.remove_item_url(item[:product].id), :method => :delete %>
28
+ </td>
29
+ <% end -%>
30
+ </tr>
31
+ <% end -%>
32
+ <tr>
33
+ <td colspan="<%= page == "cart" ? "5" : "4" %>"></td>
34
+ </tr>
35
+ <tr>
36
+ <td colspan="<%= page == "cart" ? "3" : "2" %>"></td>
37
+ <td>Subtotal</td>
38
+ <td id="subtotal_total" data-total="<%= @cart.total %>"><%= number_to_currency @cart.total %></td>
39
+ </tr>
40
+ <% if page != "cart" -%>
41
+ <tr>
42
+ <td colspan="<%= page == "cart" ? "3" : "2" %>"></td>
43
+ <td>Tax</td>
44
+ <td id="tax_total"></td>
45
+ </tr>
46
+ <tr>
47
+ <td colspan="<%= page == "cart" ? "3" : "2" %>"></td>
48
+ <td>Shipping</td>
49
+ <td id="shipping_total"></td>
50
+ </tr>
51
+ <tr>
52
+ <td colspan="<%= page == "cart" ? "3" : "2" %>"></td>
53
+ <td>Total</td>
54
+ <td id="order_total"></td>
55
+ </tr>
56
+ <% end -%>
57
+ </table>
58
+
59
+ <% if page == "cart" %>
60
+ <%= submit_tag "Update", :id => "update" %>
61
+ <% end -%>
62
+
63
+ <% end -%>
64
+ <% else -%>
65
+ <p id="emtpy">
66
+ Your cart is empty.
67
+ </p>
68
+ <% end -%>
@@ -0,0 +1,13 @@
1
+ <div id="cart">
2
+
3
+ <h1>Shopping Cart</h1>
4
+ <% if @cart.errors.any? -%>
5
+ <%= raw @cart.errors.join('<br />') %>
6
+ <% end -%>
7
+
8
+ <%= render "items", :page => "cart" %>
9
+
10
+ <% if @cart.items.any? -%>
11
+ <%= link_to "Proceed to Checkout", piggybak.orders_url, :id => "checkout" %>
12
+ <% end -%>
13
+ </div>
@@ -0,0 +1,11 @@
1
+ Thanks for your order!
2
+
3
+ Items
4
+
5
+ <% @order.line_items.each do |line_item| %>
6
+ <%= line_item.product.description %> x <%= line_item.quantity %> = <%= number_to_currency line_item.total %>
7
+ <% end -%>
8
+
9
+ Total
10
+
11
+ <%= number_to_currency @order.total %>
@@ -0,0 +1,24 @@
1
+ <p>
2
+ <%= address.label :firstname %>
3
+ <%= address.text_field :firstname %>
4
+ </p>
5
+ <p>
6
+ <%= address.label :lastname %>
7
+ <%= address.text_field :lastname %>
8
+ </p>
9
+ <p>
10
+ <%= address.label :address1 %>
11
+ <%= address.text_field :address1 %>
12
+ </p>
13
+ <p>
14
+ <%= address.label :city %>
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>
22
+ <%= address.label :zip %>
23
+ <%= address.text_field :zip %>
24
+ </p>
@@ -0,0 +1,12 @@
1
+ <h1>Your Orders</h1>
2
+
3
+ <% @user.piggybak_orders.each do |order| -%>
4
+ <h2>Order #<%= order.id %></h2>
5
+
6
+ <p><%= number_to_currency order.total %></p>
7
+
8
+ <% order.line_items.each do |line_item| -%>
9
+ <%= debug(line_item) %>
10
+ <% end -%>
11
+
12
+ <% end -%>
@@ -0,0 +1,53 @@
1
+ <div id="receipt">
2
+ <h1>Thanks for your Order</h1>
3
+
4
+ <table id="items" cellpadding="5" cellspacing="0">
5
+ <tr>
6
+ <th>Item</th>
7
+ <th>Price</th>
8
+ <th>Quantity</th>
9
+ <th>Subtotal</th>
10
+ </tr>
11
+ <% @order.line_items.each do |line_item| %>
12
+ <tr>
13
+ <td><%= line_item.product.description %></td>
14
+ <td><%= line_item.product.price %></td>
15
+ <td><%= line_item.quantity %></td>
16
+ <td><%= number_to_currency line_item.total %></td>
17
+ </tr>
18
+ <% end -%>
19
+ <tr>
20
+ <td colspan="4"></td>
21
+ </tr>
22
+ <tr>
23
+ <td colspan="2"></td>
24
+ <td>Subtotal</td>
25
+ <td><%= number_to_currency @order.line_items.inject(0) { |subtotal, li| subtotal + li.total } %></td>
26
+ </tr>
27
+ <tr>
28
+ <td colspan="2"></td>
29
+ <td>Tax</td>
30
+ <td><%= number_to_currency @order.tax_charge %></td>
31
+ </tr>
32
+ <tr>
33
+ <td colspan="2"></td>
34
+ <td>Shipping</td>
35
+ <td><%= number_to_currency @order.shipments.inject(0) { |shipping, shipment| shipping + shipment.total } %></td>
36
+ </tr>
37
+ <tr>
38
+ <td colspan="2"></td>
39
+ <td>Total</td>
40
+ <td><%= number_to_currency @order.total %></td>
41
+ </tr>
42
+ </table>
43
+
44
+ Email: <%= @order.email %><br />
45
+ Phone: <%= @order.phone %>
46
+
47
+ <h3>Billing Information</h3>
48
+ <%= raw @order.billing_address.display %>
49
+
50
+ <h3>Shipping Information</h3>
51
+ <%= raw @order.shipping_address.display %>
52
+
53
+ </div>
@@ -0,0 +1,112 @@
1
+ <div id="checkout">
2
+
3
+ <h1>Checkout</h1>
4
+ <%= form_for @order, :url => piggybak.order_submit_url, :method => "POST" do |f| %>
5
+
6
+ <% if @order.errors.any? -%>
7
+ <b>You have errors with your submission:</b><br />
8
+ <%= raw @order.errors.full_messages.join("<br />") %>
9
+ <% end -%>
10
+ <div class="clear"></div>
11
+
12
+ <div id="billing_address">
13
+ <h3>Billing Address</h3>
14
+ <%= f.fields_for :billing_address do |billing_address| %>
15
+ <%= render "address_form", :address => billing_address %>
16
+ <% end -%>
17
+ </div>
18
+ <div id="shipping_address">
19
+ <h3>Shipping Address</h3>
20
+ <%= f.fields_for :shipping_address do |shipping_address| %>
21
+ <%= render "address_form", :address => shipping_address %>
22
+ <% end -%>
23
+ </div>
24
+
25
+ <div id="add_details">
26
+ <div id="user_details">
27
+ <h3>User Details</h3>
28
+ <p>
29
+ <% if current_user -%>
30
+ <%= f.label :email %>
31
+ <%= f.text_field :email, { :readonly => true, :class => "readonly" } %>
32
+ <% if false -%>
33
+ <span>or <%= link_to 'LOGOUT', destroy_user_session_path, :method => :delete, :class => "last" %></span>
34
+ <% end -%>
35
+ <% else -%>
36
+ <%= f.label :email %>
37
+ <%= f.text_field :email %>
38
+ <% if false -%>
39
+ <span>or <%= link_to 'LOG IN', new_user_session_path %></span>
40
+ <% end -%>
41
+ <% end -%>
42
+ </p>
43
+ <p>
44
+ <%= f.label :phone %>
45
+ <%= f.text_field :phone %>
46
+ </p>
47
+ </div>
48
+ <div id="shipping">
49
+ <h3>Shipping Option</h3>
50
+ <%= f.fields_for :shipments, f.object.shipments.build do |shipment| %>
51
+ <p>
52
+ <%= shipment.label :shipping_method_id %>
53
+ <%= shipment.select :shipping_method_id, [] %>
54
+ </p>
55
+ <% end -%>
56
+ </div>
57
+ <div id="payment">
58
+ <h3>Payment</h3>
59
+ <%= f.fields_for :payments, f.object.payments.build do |payment| %>
60
+ <p>
61
+ <%= payment.label :number %>
62
+ <% if @order.errors.keys.include?("payments.number".to_sym) %>
63
+ <span class="field_with_errors">
64
+ <%= payment.text_field :number %>
65
+ </span>
66
+ <% else -%>
67
+ <%= payment.text_field :number %>
68
+ <% end -%>
69
+ </p>
70
+
71
+ <p>
72
+ <%= payment.label :verification_value %>
73
+ <% if @order.errors.keys.include?("payments.verification_value".to_sym) %>
74
+ <span class="field_with_errors">
75
+ <%= payment.text_field :verification_value %>
76
+ </span>
77
+ <% else -%>
78
+ <%= payment.text_field :verification_value %>
79
+ <% end -%>
80
+ </p>
81
+
82
+ <p>
83
+ <%= payment.label :month %>
84
+ <% if @order.errors.keys.include?("payments.verification_value".to_sym) %>
85
+ <span class="field_with_errors">
86
+ <%= payment.select :month, 1.upto(12) %> /
87
+ <%= payment.select :year, Time.now.year.upto(Time.now.year + 10) %>
88
+ </span>
89
+ <% else -%>
90
+ <%= payment.select :month, 1.upto(12) %> /
91
+ <%= payment.select :year, Time.now.year.upto(Time.now.year + 10) %>
92
+ <% end -%>
93
+ </p>
94
+ <% end -%>
95
+ </div>
96
+ <div id="submit">
97
+ <%= f.submit %>
98
+ </div>
99
+ </div>
100
+ <% end -%>
101
+
102
+ <div id="totals_section">
103
+ <h3>Totals</h3>
104
+ <%= render "piggybak/cart/items", :page => "checkout" %>
105
+ </div>
106
+
107
+ </div>
108
+
109
+ <script type="text/javascript">
110
+ var shipping_lookup = "<%= piggybak.orders_shipping_url %>";
111
+ var tax_lookup = "<%= piggybak.orders_tax_url %>";
112
+ </script>
@@ -0,0 +1,5 @@
1
+ <% if params[:action] == 'edit' -%>
2
+ <%= link_to "Send Email Confirmation", piggybak.orders_email_url(params[:id]) %>
3
+ <% else -%>
4
+ <b>You may not send a confirmation email until you have a saved order.</b>
5
+ <% end -%>