caboose-cms 0.4.151 → 0.5.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (166) hide show
  1. checksums.yaml +8 -8
  2. data/app/assets/javascripts/caboose/admin_products.js +79 -0
  3. data/app/assets/javascripts/caboose/application.js +2 -1
  4. data/app/assets/javascripts/caboose/cart.js +168 -0
  5. data/app/assets/javascripts/caboose/checkout.js +151 -0
  6. data/app/assets/javascripts/caboose/checkout_module.js +312 -0
  7. data/app/assets/javascripts/caboose/checkout_step1.js +179 -0
  8. data/app/assets/javascripts/caboose/checkout_step2.js +39 -0
  9. data/app/assets/javascripts/caboose/checkout_step3.js +34 -0
  10. data/app/assets/javascripts/caboose/checkout_step4.js +97 -0
  11. data/app/assets/javascripts/caboose/main.js +99 -8
  12. data/app/assets/javascripts/caboose/product.js +284 -0
  13. data/app/assets/stylesheets/caboose/admin_products.css +86 -0
  14. data/app/assets/templates/caboose/cart/add_to_cart.jst.ejs +7 -0
  15. data/app/assets/templates/caboose/cart/line_items.jst.ejs +41 -0
  16. data/app/assets/templates/caboose/checkout/address.jst.ejs +53 -0
  17. data/app/assets/templates/caboose/checkout/forms/guest.jst.ejs +8 -0
  18. data/app/assets/templates/caboose/checkout/forms/register.jst.ejs +11 -0
  19. data/app/assets/templates/caboose/checkout/forms/signin.jst.ejs +7 -0
  20. data/app/assets/templates/caboose/checkout/line_items.jst.ejs +31 -0
  21. data/app/assets/templates/caboose/checkout/login.jst.ejs +21 -0
  22. data/app/assets/templates/caboose/checkout/payment.jst.ejs +5 -0
  23. data/app/assets/templates/caboose/checkout/shipping.jst.ejs +18 -0
  24. data/app/assets/templates/caboose/product/images.jst.ejs +8 -0
  25. data/app/assets/templates/caboose/product/options.jst.ejs +19 -0
  26. data/app/controllers/caboose/application_controller.rb +29 -1
  27. data/app/controllers/caboose/cart_controller.rb +52 -0
  28. data/app/controllers/caboose/categories_controller.rb +108 -0
  29. data/app/controllers/caboose/checkout_controller.rb +325 -0
  30. data/app/controllers/caboose/orders_controller.rb +439 -0
  31. data/app/controllers/caboose/product_images_controller.rb +38 -0
  32. data/app/controllers/caboose/products_controller.rb +737 -0
  33. data/app/controllers/caboose/reviews_controller.rb +15 -0
  34. data/app/controllers/caboose/variants_controller.rb +218 -0
  35. data/app/controllers/caboose/vendors_controller.rb +73 -0
  36. data/app/helpers/caboose/application_helper.rb +4 -0
  37. data/app/helpers/caboose/categories_helper.rb +82 -0
  38. data/app/helpers/caboose/checkout_helper.rb +20 -0
  39. data/app/helpers/caboose/products_helper.rb +8 -0
  40. data/app/mailers/caboose/orders_mailer.rb +30 -0
  41. data/app/models/caboose/address.rb +26 -0
  42. data/app/models/caboose/category.rb +89 -0
  43. data/app/models/caboose/category_membership.rb +10 -0
  44. data/app/models/caboose/core_plugin.rb +15 -37
  45. data/app/models/caboose/customization_membership.rb +10 -0
  46. data/app/models/caboose/discount.rb +16 -0
  47. data/app/models/caboose/line_item.rb +81 -0
  48. data/app/models/caboose/message.rb +20 -0
  49. data/app/models/caboose/order.rb +191 -0
  50. data/app/models/caboose/order_discount.rb +10 -0
  51. data/app/models/caboose/order_pdf.rb +78 -0
  52. data/app/models/caboose/payment_processors/authorizenet.rb +53 -0
  53. data/app/models/caboose/payment_processors/base.rb +39 -0
  54. data/app/models/caboose/payment_processors/payscape.rb +94 -0
  55. data/app/models/caboose/product.rb +145 -0
  56. data/app/models/caboose/product_image.rb +62 -0
  57. data/app/models/caboose/product_image_variant.rb +10 -0
  58. data/app/models/caboose/review.rb +13 -0
  59. data/app/models/caboose/schema.rb +205 -1
  60. data/app/models/caboose/search_filter.rb +191 -0
  61. data/app/models/caboose/shipping_calculator.rb +81 -0
  62. data/app/models/caboose/states.rb +61 -52
  63. data/app/models/caboose/tax_calculator.rb +23 -0
  64. data/app/models/caboose/tax_line.rb +9 -0
  65. data/app/models/caboose/variant.rb +99 -0
  66. data/app/models/caboose/vendor.rb +22 -0
  67. data/app/views/caboose/cart/index.html.erb +8 -0
  68. data/app/views/caboose/categories/admin_edit.html.erb +79 -0
  69. data/app/views/caboose/categories/admin_index.html.erb +11 -0
  70. data/app/views/caboose/categories/admin_new.html.erb +62 -0
  71. data/app/views/caboose/checkout/_address_form.html.erb +111 -0
  72. data/app/views/caboose/checkout/_billing_form.html.erb +47 -0
  73. data/app/views/caboose/checkout/_cart.html.erb +52 -0
  74. data/app/views/caboose/checkout/_confirm.html.erb +61 -0
  75. data/app/views/caboose/checkout/_order_discount.html.erb +40 -0
  76. data/app/views/caboose/checkout/_shipping_address.html.erb +10 -0
  77. data/app/views/caboose/checkout/_shipping_method.html.erb +2 -0
  78. data/app/views/caboose/checkout/_shipping_method_form.html.erb +21 -0
  79. data/app/views/caboose/checkout/billing.html.erb +11 -0
  80. data/app/views/caboose/checkout/discount.html.erb +11 -0
  81. data/app/views/caboose/checkout/empty.html.erb +2 -0
  82. data/app/views/caboose/checkout/error.html.erb +2 -0
  83. data/app/views/caboose/checkout/index.html.erb +43 -0
  84. data/app/views/caboose/checkout/login.html.erb +2 -0
  85. data/app/views/caboose/checkout/payment.html.erb +79 -0
  86. data/app/views/caboose/checkout/relay.html.erb +23 -0
  87. data/app/views/caboose/checkout/relay_old.html.erb +12 -0
  88. data/app/views/caboose/checkout/relay_postMessage.html.erb +19 -0
  89. data/app/views/caboose/checkout/shipping.html.erb +15 -0
  90. data/app/views/caboose/checkout/step_four.html.erb +93 -0
  91. data/app/views/caboose/checkout/step_one.html.erb +56 -0
  92. data/app/views/caboose/checkout/step_one_old.html.erb +13 -0
  93. data/app/views/caboose/checkout/step_three.html.erb +23 -0
  94. data/app/views/caboose/checkout/step_two.html.erb +52 -0
  95. data/app/views/caboose/checkout/step_two_old.html.erb +14 -0
  96. data/app/views/caboose/checkout/thanks.html.erb +5 -0
  97. data/app/views/caboose/orders/_admin_footer.html.erb +2 -0
  98. data/app/views/caboose/orders/_admin_header.html.erb +31 -0
  99. data/app/views/caboose/orders/_quickbooks_order.html.erb +0 -0
  100. data/app/views/caboose/orders/admin_delete_form.html.erb +21 -0
  101. data/app/views/caboose/orders/admin_edit.html.erb +271 -0
  102. data/app/views/caboose/orders/admin_index.html.erb +89 -0
  103. data/app/views/caboose/orders/admin_new.html.erb +42 -0
  104. data/app/views/caboose/orders/admin_print.html.erb +72 -0
  105. data/app/views/caboose/orders_mailer/customer_new_order.html.erb +1 -0
  106. data/app/views/caboose/orders_mailer/customer_status_updated.html.erb +1 -0
  107. data/app/views/caboose/orders_mailer/fulfillment_new_order.html.erb +1 -0
  108. data/app/views/caboose/orders_mailer/shipping_order_ready.html.erb +1 -0
  109. data/app/views/caboose/products/_admin_footer.html.erb +2 -0
  110. data/app/views/caboose/products/_admin_header.html.erb +32 -0
  111. data/app/views/caboose/products/_sort_options.html.erb +19 -0
  112. data/app/views/caboose/products/admin_add_upcs.html.erb +58 -0
  113. data/app/views/caboose/products/admin_delete_form.html.erb +21 -0
  114. data/app/views/caboose/products/admin_edit_categories.html.erb +73 -0
  115. data/app/views/caboose/products/admin_edit_category_images.html.erb +233 -0
  116. data/app/views/caboose/products/admin_edit_description.html.erb +38 -0
  117. data/app/views/caboose/products/admin_edit_general.html.erb +104 -0
  118. data/app/views/caboose/products/admin_edit_images.html.erb +236 -0
  119. data/app/views/caboose/products/admin_edit_options.html.erb +51 -0
  120. data/app/views/caboose/products/admin_edit_seo.html.erb +37 -0
  121. data/app/views/caboose/products/admin_edit_variant_columns.html.erb +75 -0
  122. data/app/views/caboose/products/admin_edit_variant_sort_order.html.erb +63 -0
  123. data/app/views/caboose/products/admin_edit_variants.html.erb +171 -0
  124. data/app/views/caboose/products/admin_edit_variants_single.html.erb +68 -0
  125. data/app/views/caboose/products/admin_group_variants.html.erb +433 -0
  126. data/app/views/caboose/products/admin_index.html.erb +95 -0
  127. data/app/views/caboose/products/admin_new.html.erb +41 -0
  128. data/app/views/caboose/products/admin_sort.html copy.erb +155 -0
  129. data/app/views/caboose/products/admin_sort.html.erb +254 -0
  130. data/app/views/caboose/products/details.html.erb +438 -0
  131. data/app/views/caboose/products/index.html.erb +46 -0
  132. data/app/views/caboose/products/not_available.html.erb +35 -0
  133. data/app/views/caboose/variants/admin_edit.html.erb +82 -0
  134. data/app/views/caboose/variants/admin_group.html.erb +184 -0
  135. data/app/views/caboose/variants/admin_new.html.erb +59 -0
  136. data/app/views/caboose/vendors/admin_edit.html.erb +24 -0
  137. data/app/views/caboose/vendors/admin_index.html.erb +30 -0
  138. data/app/views/caboose/vendors/admin_new.html.erb +34 -0
  139. data/app/views/layouts/caboose/store/_banner.html.erb +10 -0
  140. data/app/views/layouts/caboose/store/_banner2.html.erb +10 -0
  141. data/app/views/layouts/caboose/store/_footer.html.erb +55 -0
  142. data/app/views/layouts/caboose/store/_header.html.erb +69 -0
  143. data/app/views/layouts/caboose/store/_sidebar.html.erb +27 -0
  144. data/app/views/layouts/caboose/store/application.html.erb +33 -0
  145. data/app/views/layouts/caboose/store/authorize_net.erb +18 -0
  146. data/app/views/layouts/caboose/store/layout_about.html.erb +42 -0
  147. data/app/views/layouts/caboose/store/layout_blog.html.erb +159 -0
  148. data/app/views/layouts/caboose/store/layout_confirm.html.erb +85 -0
  149. data/app/views/layouts/caboose/store/layout_contact.html.erb +38 -0
  150. data/app/views/layouts/caboose/store/layout_default.html.erb +10 -0
  151. data/app/views/layouts/caboose/store/layout_detail.html.erb +114 -0
  152. data/app/views/layouts/caboose/store/layout_order.html.erb +77 -0
  153. data/app/views/layouts/caboose/store/layout_pricing.html.erb +182 -0
  154. data/app/views/layouts/caboose/store/layout_product.html.erb +110 -0
  155. data/app/views/layouts/caboose/store/layout_profile.html.erb +55 -0
  156. data/app/views/layouts/caboose/store/layout_single.html.erb +3 -0
  157. data/app/views/layouts/caboose/store/layout_testimonial.html.erb +110 -0
  158. data/app/views/layouts/caboose/store/layout_testing.html.erb +4 -0
  159. data/config/routes.rb +126 -0
  160. data/lib/caboose.rb +46 -1
  161. data/lib/caboose/engine.rb +39 -1
  162. data/lib/caboose/version.rb +1 -1
  163. data/lib/tasks/caboose.rake +12 -0
  164. metadata +151 -4
  165. data/app/assets/javascripts/caboose/admin_page_edit_content_bak.js +0 -164
  166. data/app/assets/javascripts/caboose/model/#Untitled-1# +0 -2
@@ -0,0 +1,26 @@
1
+ module Caboose
2
+ class Address < ActiveRecord::Base
3
+ self.table_name = 'store_addresses'
4
+
5
+ attr_accessible :id,
6
+ :name,
7
+ :first_name,
8
+ :last_name,
9
+ :street,
10
+ :address1,
11
+ :address2,
12
+ :company,
13
+ :city,
14
+ :state,
15
+ :province,
16
+ :province_code,
17
+ :zip,
18
+ :country,
19
+ :country_code,
20
+ :phone
21
+
22
+ validates :first_name, :last_name, :address1, :city, :state, :zip, :presence => true
23
+ validates :zip, :format => { :with => /^\d{5}(-\d{4})?$/, :message => 'Invalid zip code' }
24
+ end
25
+ end
26
+
@@ -0,0 +1,89 @@
1
+ #
2
+ # Category
3
+ #
4
+ # :: Class Methods
5
+ # :: Instance Methods
6
+
7
+ module Caboose
8
+ class Category < ActiveRecord::Base
9
+ self.table_name = 'store_categories'
10
+
11
+ belongs_to :parent, :class_name => 'Category', :foreign_key => 'parent_id'
12
+ has_many :children, :class_name => 'Category', :foreign_key => 'parent_id', :order => 'name'
13
+ has_many :products, :through => :category_memberships, :order => 'title'
14
+ has_many :category_memberships
15
+
16
+ has_attached_file :image,
17
+ :path => 'categories/:id_:style.jpg',
18
+ :default_url => '/categories/default.jpg',
19
+ :s3_protocol => :https,
20
+ :styles => {
21
+ tiny: '100x100>',
22
+ thumb: '250x250>',
23
+ medium: '400x400>',
24
+ large: '800x800>',
25
+ huge: '1200x1200>'
26
+ }
27
+
28
+ validates_attachment_content_type :image, :content_type => %w(image/jpeg image/jpg image/png)
29
+
30
+ attr_accessible :id,
31
+ :parent_id,
32
+ :name,
33
+ :url,
34
+ :slug,
35
+ :sort_order,
36
+ :status,
37
+ :image_file_name,
38
+ :image_content_type,
39
+ :image_file_size,
40
+ :image_updated_at,
41
+ :square_offset_x,
42
+ :square_offset_y,
43
+ :square_scale_factor
44
+
45
+ #
46
+ # Class Methods
47
+ #
48
+
49
+ def self.root
50
+ self.find_by_url('/products')
51
+ end
52
+
53
+ def self.top_level
54
+ self.root.children
55
+ end
56
+
57
+ def self.sample(number)
58
+ Caboose::Category.top_level.collect { |category| category if category.active_products.any? }.compact.sample(number)
59
+ end
60
+
61
+ #
62
+ # Instance Methods
63
+ #
64
+
65
+ def generate_slug
66
+ self.name.gsub(' ', '-').downcase
67
+ end
68
+
69
+ def update_child_slugs
70
+ return if self.children.nil? or self.children.empty?
71
+
72
+ self.children.each do |child|
73
+ child.update_attribute(:url, "#{self.url}/#{child.slug}")
74
+ child.update_child_slugs
75
+ end
76
+ end
77
+
78
+ def active_products
79
+ self.products.where(:status => 'Active')
80
+ end
81
+
82
+ def ancestry
83
+ return [self] if self.parent.nil?
84
+ ancestors = self.parent.ancestry
85
+ ancestors << self
86
+ return ancestors
87
+ end
88
+ end
89
+ end
@@ -0,0 +1,10 @@
1
+ module Caboose
2
+ class CategoryMembership < ActiveRecord::Base
3
+ self.table_name = 'store_category_memberships'
4
+
5
+ belongs_to :category
6
+ belongs_to :product
7
+
8
+ attr_accessible :category_id, :product_id
9
+ end
10
+ end
@@ -5,22 +5,8 @@ class Caboose::CorePlugin < Caboose::CaboosePlugin
5
5
 
6
6
  nav << { 'id' => 'logout' , 'text' => 'Logout' , 'href' => '/logout' , 'modal' => false }
7
7
  nav << { 'id' => 'my-account' , 'text' => 'My Account' , 'href' => '/my-account' , 'modal' => true }
8
-
9
- #item = {
10
- # 'id' => 'profile',
11
- # 'text' => 'Profile',
12
- # 'children' => []
13
- #}
14
- #item['children'] << { 'id' => 'my-account' , 'text' => 'Edit Profile' , 'href' => '/my-account' , 'modal' => false }
15
- #item['children'] << { 'id' => 'logout' , 'text' => 'Logout' , 'href' => '/logout' , 'modal' => false }
16
- #nav << item
17
8
 
18
- item = {
19
- 'id' => 'core',
20
- 'text' => 'Settings',
21
- 'children' => []
22
- }
23
-
9
+ item = { 'id' => 'core', 'text' => 'Settings', 'children' => [] }
24
10
  item['children'] << { 'id' => 'users' , 'text' => 'Users' , 'href' => '/admin/users' , 'modal' => false } if user.is_allowed('users' , 'view')
25
11
  item['children'] << { 'id' => 'roles' , 'text' => 'Roles' , 'href' => '/admin/roles' , 'modal' => false } if user.is_allowed('roles' , 'view')
26
12
  item['children'] << { 'id' => 'permissions' , 'text' => 'Permissions' , 'href' => '/admin/permissions' , 'modal' => false } if user.is_allowed('permissions' , 'view')
@@ -28,35 +14,27 @@ class Caboose::CorePlugin < Caboose::CaboosePlugin
28
14
  item['children'] << { 'id' => 'blocktypes' , 'text' => 'AB Test Variants' , 'href' => '/admin/ab-variants' , 'modal' => false } if user.is_allowed('abvariants' , 'view')
29
15
  item['children'] << { 'id' => 'variables' , 'text' => 'Variables' , 'href' => '/admin/settings' , 'modal' => false } if user.is_allowed('settings' , 'view')
30
16
  item['children'] << { 'id' => 'blocktypes' , 'text' => 'Block Types' , 'href' => '/admin/block-types' , 'modal' => false } if user.is_allowed('blocktypes' , 'view')
31
- item['children'] << { 'id' => 'redirects' , 'text' => 'Permanent Redirects' , 'href' => '/admin/redirects' , 'modal' => false } if user.is_allowed('redirects' , 'view')
32
-
17
+ item['children'] << { 'id' => 'redirects' , 'text' => 'Permanent Redirects' , 'href' => '/admin/redirects' , 'modal' => false } if user.is_allowed('redirects' , 'view')
33
18
  nav << item if item['children'].count > 0
34
19
 
35
- item = {
36
- 'id' => 'content',
37
- 'text' => 'Content',
38
- 'children' => []
39
- }
40
-
20
+ item = { 'id' => 'content', 'text' => 'Content', 'children' => [] }
41
21
  item['children'] << { 'id' => 'pages' , 'text' => 'Pages' , 'href' => '/admin/pages' , 'modal' => false } if user.is_allowed('pages' , 'view')
42
22
  item['children'] << { 'id' => 'posts' , 'text' => 'Posts' , 'href' => '/admin/posts' , 'modal' => false } if user.is_allowed('posts' , 'view')
23
+ nav << item if item['children'].count > 0
43
24
 
44
- nav << item if item['children'].count > 0
25
+ item = {
26
+ 'id' => 'store',
27
+ 'text' => 'Store',
28
+ 'children' => []
29
+ }
30
+ item['children'] << { 'id' => 'categories', 'href' => '/admin/categories' , 'text' => 'Categories' , 'modal' => false } if user.is_allowed('categories' , 'view')
31
+ item['children'] << { 'id' => 'vendors' , 'href' => '/admin/vendors' , 'text' => 'Vendors' , 'modal' => false } if user.is_allowed('vendors' , 'view')
32
+ item['children'] << { 'id' => 'products' , 'href' => '/admin/products' , 'text' => 'Products' , 'modal' => false } if user.is_allowed('products' , 'view')
33
+ item['children'] << { 'id' => 'orders' , 'href' => '/admin/orders' , 'text' => 'Orders' , 'modal' => false } if user.is_allowed('orders' , 'view')
34
+ nav << item if item['children'].count > 0
35
+
45
36
  return nav
46
37
  end
47
-
48
- #def self.admin_js
49
- # return "
50
- # $('#use_redirect_urls').click(function() {
51
- # uru = $('#use_redirect_urls');
52
- # val = (uru.html() == 'Enable' ? 1 : 0);
53
- # $.ajax({
54
- # url: '/admin/settings/toggle-redirect-urls',
55
- # data: 'val='+val,
56
- # succes: function(resp) { uri.html(val == 1 ? 'Disable' : 'Enable'); }
57
- # });
58
- # });"
59
- #end
60
38
 
61
39
  def self.block_types(block_types)
62
40
  block_types << {
@@ -0,0 +1,10 @@
1
+ module Caboose
2
+ class CustomizationMembership < ActiveRecord::Base
3
+ self.table_name = 'store_customization_memberships'
4
+
5
+ belongs_to :product
6
+ belongs_to :customization, :class_name => 'Caboose::Product'
7
+
8
+ attr_accessible :product_id, :customization_id
9
+ end
10
+ end
@@ -0,0 +1,16 @@
1
+ module Caboose
2
+ class Discount < ActiveRecord::Base
3
+ self.table_name = 'store_discounts'
4
+ self.primary_key = 'id'
5
+
6
+ attr_accessible :id,
7
+ :name, # The name of this discount
8
+ :code, # The code the customer has to input to apply for this discount
9
+ :amount_current,
10
+ :amount_total,
11
+ :amount_flat, # Amount of the savings flat off the total
12
+ :amount_percentage, # Amount of savings as a percentage off the total
13
+ :no_shipping, # Whether or not it's a free shipping discount
14
+ :no_tax # Whether or not it's a free shipping discount
15
+ end
16
+ end
@@ -0,0 +1,81 @@
1
+ module Caboose
2
+ class LineItem < ActiveRecord::Base
3
+ self.table_name = 'store_line_items'
4
+
5
+ belongs_to :variant
6
+ belongs_to :order, :dependent => :destroy
7
+ belongs_to :parent, :class_name => 'LineItem', :foreign_key => 'parent_id'
8
+ has_many :children, :class_name => 'LineItem', :foreign_key => 'parent_id'
9
+
10
+ attr_accessible :id,
11
+ :variant_id,
12
+ :quantity,
13
+ :price,
14
+ :notes,
15
+ :order_id,
16
+ :status,
17
+ :tracking_number,
18
+ :custom1,
19
+ :custom2,
20
+ :custom3
21
+
22
+ #
23
+ # Scopes
24
+ #
25
+
26
+ scope :pending, where('status = ?', 'pending')
27
+ scope :fulfilled, where('status = ?', 'shipped')
28
+ scope :unfulfilled, where('status != ?', 'shipped')
29
+ #
30
+ # Validations
31
+ #
32
+
33
+ validates :status, :inclusion => {
34
+ :in => ['pending', 'shipped'],
35
+ :message => "%{value} is not a valid status. Must be either 'pending' or 'shipped'"
36
+ }
37
+
38
+ validates :quantity, :numericality => { :greater_than_or_equal_to => 0 }
39
+
40
+ validate :quantity_in_stock
41
+ def quantity_in_stock
42
+ errors.add(:base, "There #{self.variant.quantity_in_stock > 1 ? 'are' : 'is'} only #{self.variant.quantity_in_stock} left in stock.") if self.variant.quantity_in_stock - self.quantity < 0
43
+ end
44
+
45
+ #
46
+ # Callbacks
47
+ #
48
+
49
+ before_save :update_price
50
+ after_save { self.order.calculate }
51
+
52
+ #
53
+ # Methods
54
+ #
55
+
56
+ def update_price
57
+ self.price = self.variant.price * self.quantity
58
+ end
59
+
60
+ def title
61
+ if self.variant.product.variants.count > 1
62
+ "#{self.variant.product.title} - #{self.variant.title}"
63
+ else
64
+ self.variant.product.title
65
+ end
66
+ end
67
+
68
+ def as_json(options={})
69
+ self.attributes.merge({
70
+ :variant => self.variant,
71
+ :title => self.title,
72
+ :product => { :images => self.variant.product.product_images }
73
+ })
74
+ end
75
+
76
+ def subtotal
77
+ return self.quantity * self.price
78
+ end
79
+ end
80
+ end
81
+
@@ -0,0 +1,20 @@
1
+ module Caboose
2
+ class Message
3
+ include ActiveModel::Validations
4
+ include ActiveModel::Conversion
5
+ extend ActiveModel::Naming
6
+
7
+ attr_accessor :name, :email, :body
8
+
9
+ validates :name, :email, :body, presence: true
10
+ validates :email, format: { :with => %r{.+@.+\..+} }, allow_blank: false
11
+
12
+ def initialize(attributes={})
13
+ attributes.each { |name, value| send("#{name}=", value) }
14
+ end
15
+
16
+ def persisted?
17
+ false
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,191 @@
1
+ #
2
+ # Order
3
+ #
4
+
5
+ module Caboose
6
+ class Order < ActiveRecord::Base
7
+ self.table_name = 'store_orders'
8
+ self.primary_key = 'id'
9
+
10
+ belongs_to :customer, :class_name => 'Caboose::User'
11
+ belongs_to :shipping_address, :class_name => 'Address'
12
+ belongs_to :billing_address, :class_name => 'Address'
13
+ has_many :discounts, :through => :order_discounts
14
+ has_many :order_discounts
15
+ has_many :line_items, :after_add => :line_item_added, :after_remove => :line_item_removed
16
+
17
+ attr_accessible :id,
18
+ :order_number,
19
+ :subtotal,
20
+ :tax,
21
+ :shipping_method,
22
+ :shipping_method_code,
23
+ :shipping,
24
+ :handling,
25
+ :discount,
26
+ :amount_discounted,
27
+ :total,
28
+ :status,
29
+ :payment_status,
30
+ :notes,
31
+ :referring_site,
32
+ :landing_site,
33
+ :landing_site_ref,
34
+ :cancel_reason,
35
+ :date_created,
36
+ :date_authorized,
37
+ :date_captured,
38
+ :date_cancelled,
39
+ :email,
40
+ :customer_id,
41
+ :payment_id,
42
+ :gateway_id,
43
+ :financial_status,
44
+ :shipping_address_id,
45
+ :billing_address_id,
46
+ :landing_page,
47
+ :landing_page_ref,
48
+ :transaction_d,
49
+ :auth_code,
50
+ :alternate_id,
51
+ :auth_amount,
52
+ :date_shipped,
53
+ :transaction_service,
54
+ :transaction_id
55
+
56
+ #
57
+ # Scopes
58
+ #
59
+
60
+ scope :test, where('status = ?', 'testing')
61
+ scope :cancelled, where('status = ?', 'cancelled')
62
+ scope :pending, where('status = ?', 'pending')
63
+ #TODO scope :fulfilled
64
+ #TODO scope :unfulfilled
65
+ scope :authorized, where('financial_status = ?', 'authorized')
66
+ scope :captured, where('financial_status = ?', 'captured')
67
+ scope :refunded, where('financial_status = ?', 'refunded')
68
+ scope :voided, where('financial_status = ?', 'voided')
69
+
70
+ #
71
+ # Validations
72
+ #
73
+
74
+ validates :status, :inclusion => {
75
+ :in => ['cart', 'pending', 'cancelled', 'shipped', 'testing'],
76
+ :message => "%{value} is not a valid status. Must be either 'pending' or 'shipped'"
77
+ }
78
+
79
+ validates :financial_status, :inclusion => {
80
+ :in => ['pending', 'authorized', 'captured', 'refunded', 'voided'],
81
+ :message => "%{value} is not a valid financial status. Must be 'authorized', 'captured', 'refunded' or 'voided'"
82
+ }
83
+
84
+ #
85
+ # Callbacks
86
+ #
87
+
88
+ after_update :calculate
89
+
90
+ #
91
+ # Methods
92
+ #
93
+
94
+ def as_json(options={})
95
+ self.attributes.merge({
96
+ :line_items => self.line_items,
97
+ :shipping_address => self.shipping_address,
98
+ :billing_address => self.billing_address
99
+ })
100
+ end
101
+
102
+ def decrement_quantities
103
+ return false if self.decremented
104
+
105
+ self.line_items.each do |line_item|
106
+ line_item.variant.update_attribute(:quantity, line_item.variant.quantity_in_stock - line_item.quantity)
107
+ end
108
+
109
+ self.update_attribute(:decremented, true)
110
+ end
111
+
112
+ def increment_quantities
113
+ return false if !self.decremented
114
+
115
+ self.line_items.each do |line_item|
116
+ line_item.variant.update_attribute(:quantity, line_item.variant.quantity_in_stock - line_item.quantity)
117
+ end
118
+
119
+ self.update_attribute(:decremented, false)
120
+ end
121
+
122
+ def resend_confirmation
123
+ OrdersMailer.customer_new_order(self).deliver
124
+ end
125
+
126
+ def test?
127
+ self.status == 'testing'
128
+ end
129
+
130
+ def authorized?
131
+ self.financial_status == 'authorized'
132
+ end
133
+
134
+ def capture
135
+ PaymentProcessor.capture(self)
136
+ end
137
+
138
+ def refuned
139
+ PaymentProcessor.refund(self)
140
+ end
141
+
142
+ def void
143
+ PaymentProcessor.void(self)
144
+ end
145
+
146
+ def line_item_added(line_item)
147
+ self.calculate
148
+ end
149
+
150
+ def line_item_removed(line_item)
151
+ self.calculate
152
+ end
153
+
154
+ def calculate
155
+ self.update_column(:subtotal, (self.calculate_subtotal * 100).ceil / 100.00)
156
+ self.update_column(:tax, (self.calculate_tax * 100).ceil / 100.00)
157
+ self.update_column(:shipping, (self.calculate_shipping * 100).ceil / 100.00)
158
+ self.update_column(:handling, (self.calculate_handling * 100).ceil / 100.00)
159
+ self.update_column(:total, (self.calculate_total * 100).ceil / 100.00)
160
+ end
161
+
162
+ def calculate_subtotal
163
+ return 0 if self.line_items.empty?
164
+ self.line_items.collect { |line_item| line_item.price }.inject { |sum, price| sum + price }
165
+ end
166
+
167
+ def calculate_tax
168
+ return 0 if !self.shipping_address
169
+ self.subtotal * TaxCalculator.tax_rate(self.shipping_address)
170
+ end
171
+
172
+ def calculate_shipping
173
+ return 0 if !self.shipping_address || !self.shipping_method_code
174
+ ShippingCalculator.rate(self)[:total_price]
175
+ end
176
+
177
+ def calculate_handling
178
+ return 0 if !Caboose::handling_percentage
179
+ self.shipping * Caboose::handling_percentage
180
+ end
181
+
182
+ def calculate_total
183
+ [self.subtotal, self.tax, self.shipping, self.handling].compact.inject { |sum, price| sum + price }
184
+ end
185
+
186
+ def shipping_and_handling
187
+ (self.shipping ? self.shipping : 0.0) + (self.handling ? self.handling : 0.0)
188
+ end
189
+ end
190
+ end
191
+