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.
- checksums.yaml +8 -8
- data/app/assets/javascripts/caboose/admin_products.js +79 -0
- data/app/assets/javascripts/caboose/application.js +2 -1
- data/app/assets/javascripts/caboose/cart.js +168 -0
- data/app/assets/javascripts/caboose/checkout.js +151 -0
- data/app/assets/javascripts/caboose/checkout_module.js +312 -0
- data/app/assets/javascripts/caboose/checkout_step1.js +179 -0
- data/app/assets/javascripts/caboose/checkout_step2.js +39 -0
- data/app/assets/javascripts/caboose/checkout_step3.js +34 -0
- data/app/assets/javascripts/caboose/checkout_step4.js +97 -0
- data/app/assets/javascripts/caboose/main.js +99 -8
- data/app/assets/javascripts/caboose/product.js +284 -0
- data/app/assets/stylesheets/caboose/admin_products.css +86 -0
- data/app/assets/templates/caboose/cart/add_to_cart.jst.ejs +7 -0
- data/app/assets/templates/caboose/cart/line_items.jst.ejs +41 -0
- data/app/assets/templates/caboose/checkout/address.jst.ejs +53 -0
- data/app/assets/templates/caboose/checkout/forms/guest.jst.ejs +8 -0
- data/app/assets/templates/caboose/checkout/forms/register.jst.ejs +11 -0
- data/app/assets/templates/caboose/checkout/forms/signin.jst.ejs +7 -0
- data/app/assets/templates/caboose/checkout/line_items.jst.ejs +31 -0
- data/app/assets/templates/caboose/checkout/login.jst.ejs +21 -0
- data/app/assets/templates/caboose/checkout/payment.jst.ejs +5 -0
- data/app/assets/templates/caboose/checkout/shipping.jst.ejs +18 -0
- data/app/assets/templates/caboose/product/images.jst.ejs +8 -0
- data/app/assets/templates/caboose/product/options.jst.ejs +19 -0
- data/app/controllers/caboose/application_controller.rb +29 -1
- data/app/controllers/caboose/cart_controller.rb +52 -0
- data/app/controllers/caboose/categories_controller.rb +108 -0
- data/app/controllers/caboose/checkout_controller.rb +325 -0
- data/app/controllers/caboose/orders_controller.rb +439 -0
- data/app/controllers/caboose/product_images_controller.rb +38 -0
- data/app/controllers/caboose/products_controller.rb +737 -0
- data/app/controllers/caboose/reviews_controller.rb +15 -0
- data/app/controllers/caboose/variants_controller.rb +218 -0
- data/app/controllers/caboose/vendors_controller.rb +73 -0
- data/app/helpers/caboose/application_helper.rb +4 -0
- data/app/helpers/caboose/categories_helper.rb +82 -0
- data/app/helpers/caboose/checkout_helper.rb +20 -0
- data/app/helpers/caboose/products_helper.rb +8 -0
- data/app/mailers/caboose/orders_mailer.rb +30 -0
- data/app/models/caboose/address.rb +26 -0
- data/app/models/caboose/category.rb +89 -0
- data/app/models/caboose/category_membership.rb +10 -0
- data/app/models/caboose/core_plugin.rb +15 -37
- data/app/models/caboose/customization_membership.rb +10 -0
- data/app/models/caboose/discount.rb +16 -0
- data/app/models/caboose/line_item.rb +81 -0
- data/app/models/caboose/message.rb +20 -0
- data/app/models/caboose/order.rb +191 -0
- data/app/models/caboose/order_discount.rb +10 -0
- data/app/models/caboose/order_pdf.rb +78 -0
- data/app/models/caboose/payment_processors/authorizenet.rb +53 -0
- data/app/models/caboose/payment_processors/base.rb +39 -0
- data/app/models/caboose/payment_processors/payscape.rb +94 -0
- data/app/models/caboose/product.rb +145 -0
- data/app/models/caboose/product_image.rb +62 -0
- data/app/models/caboose/product_image_variant.rb +10 -0
- data/app/models/caboose/review.rb +13 -0
- data/app/models/caboose/schema.rb +205 -1
- data/app/models/caboose/search_filter.rb +191 -0
- data/app/models/caboose/shipping_calculator.rb +81 -0
- data/app/models/caboose/states.rb +61 -52
- data/app/models/caboose/tax_calculator.rb +23 -0
- data/app/models/caboose/tax_line.rb +9 -0
- data/app/models/caboose/variant.rb +99 -0
- data/app/models/caboose/vendor.rb +22 -0
- data/app/views/caboose/cart/index.html.erb +8 -0
- data/app/views/caboose/categories/admin_edit.html.erb +79 -0
- data/app/views/caboose/categories/admin_index.html.erb +11 -0
- data/app/views/caboose/categories/admin_new.html.erb +62 -0
- data/app/views/caboose/checkout/_address_form.html.erb +111 -0
- data/app/views/caboose/checkout/_billing_form.html.erb +47 -0
- data/app/views/caboose/checkout/_cart.html.erb +52 -0
- data/app/views/caboose/checkout/_confirm.html.erb +61 -0
- data/app/views/caboose/checkout/_order_discount.html.erb +40 -0
- data/app/views/caboose/checkout/_shipping_address.html.erb +10 -0
- data/app/views/caboose/checkout/_shipping_method.html.erb +2 -0
- data/app/views/caboose/checkout/_shipping_method_form.html.erb +21 -0
- data/app/views/caboose/checkout/billing.html.erb +11 -0
- data/app/views/caboose/checkout/discount.html.erb +11 -0
- data/app/views/caboose/checkout/empty.html.erb +2 -0
- data/app/views/caboose/checkout/error.html.erb +2 -0
- data/app/views/caboose/checkout/index.html.erb +43 -0
- data/app/views/caboose/checkout/login.html.erb +2 -0
- data/app/views/caboose/checkout/payment.html.erb +79 -0
- data/app/views/caboose/checkout/relay.html.erb +23 -0
- data/app/views/caboose/checkout/relay_old.html.erb +12 -0
- data/app/views/caboose/checkout/relay_postMessage.html.erb +19 -0
- data/app/views/caboose/checkout/shipping.html.erb +15 -0
- data/app/views/caboose/checkout/step_four.html.erb +93 -0
- data/app/views/caboose/checkout/step_one.html.erb +56 -0
- data/app/views/caboose/checkout/step_one_old.html.erb +13 -0
- data/app/views/caboose/checkout/step_three.html.erb +23 -0
- data/app/views/caboose/checkout/step_two.html.erb +52 -0
- data/app/views/caboose/checkout/step_two_old.html.erb +14 -0
- data/app/views/caboose/checkout/thanks.html.erb +5 -0
- data/app/views/caboose/orders/_admin_footer.html.erb +2 -0
- data/app/views/caboose/orders/_admin_header.html.erb +31 -0
- data/app/views/caboose/orders/_quickbooks_order.html.erb +0 -0
- data/app/views/caboose/orders/admin_delete_form.html.erb +21 -0
- data/app/views/caboose/orders/admin_edit.html.erb +271 -0
- data/app/views/caboose/orders/admin_index.html.erb +89 -0
- data/app/views/caboose/orders/admin_new.html.erb +42 -0
- data/app/views/caboose/orders/admin_print.html.erb +72 -0
- data/app/views/caboose/orders_mailer/customer_new_order.html.erb +1 -0
- data/app/views/caboose/orders_mailer/customer_status_updated.html.erb +1 -0
- data/app/views/caboose/orders_mailer/fulfillment_new_order.html.erb +1 -0
- data/app/views/caboose/orders_mailer/shipping_order_ready.html.erb +1 -0
- data/app/views/caboose/products/_admin_footer.html.erb +2 -0
- data/app/views/caboose/products/_admin_header.html.erb +32 -0
- data/app/views/caboose/products/_sort_options.html.erb +19 -0
- data/app/views/caboose/products/admin_add_upcs.html.erb +58 -0
- data/app/views/caboose/products/admin_delete_form.html.erb +21 -0
- data/app/views/caboose/products/admin_edit_categories.html.erb +73 -0
- data/app/views/caboose/products/admin_edit_category_images.html.erb +233 -0
- data/app/views/caboose/products/admin_edit_description.html.erb +38 -0
- data/app/views/caboose/products/admin_edit_general.html.erb +104 -0
- data/app/views/caboose/products/admin_edit_images.html.erb +236 -0
- data/app/views/caboose/products/admin_edit_options.html.erb +51 -0
- data/app/views/caboose/products/admin_edit_seo.html.erb +37 -0
- data/app/views/caboose/products/admin_edit_variant_columns.html.erb +75 -0
- data/app/views/caboose/products/admin_edit_variant_sort_order.html.erb +63 -0
- data/app/views/caboose/products/admin_edit_variants.html.erb +171 -0
- data/app/views/caboose/products/admin_edit_variants_single.html.erb +68 -0
- data/app/views/caboose/products/admin_group_variants.html.erb +433 -0
- data/app/views/caboose/products/admin_index.html.erb +95 -0
- data/app/views/caboose/products/admin_new.html.erb +41 -0
- data/app/views/caboose/products/admin_sort.html copy.erb +155 -0
- data/app/views/caboose/products/admin_sort.html.erb +254 -0
- data/app/views/caboose/products/details.html.erb +438 -0
- data/app/views/caboose/products/index.html.erb +46 -0
- data/app/views/caboose/products/not_available.html.erb +35 -0
- data/app/views/caboose/variants/admin_edit.html.erb +82 -0
- data/app/views/caboose/variants/admin_group.html.erb +184 -0
- data/app/views/caboose/variants/admin_new.html.erb +59 -0
- data/app/views/caboose/vendors/admin_edit.html.erb +24 -0
- data/app/views/caboose/vendors/admin_index.html.erb +30 -0
- data/app/views/caboose/vendors/admin_new.html.erb +34 -0
- data/app/views/layouts/caboose/store/_banner.html.erb +10 -0
- data/app/views/layouts/caboose/store/_banner2.html.erb +10 -0
- data/app/views/layouts/caboose/store/_footer.html.erb +55 -0
- data/app/views/layouts/caboose/store/_header.html.erb +69 -0
- data/app/views/layouts/caboose/store/_sidebar.html.erb +27 -0
- data/app/views/layouts/caboose/store/application.html.erb +33 -0
- data/app/views/layouts/caboose/store/authorize_net.erb +18 -0
- data/app/views/layouts/caboose/store/layout_about.html.erb +42 -0
- data/app/views/layouts/caboose/store/layout_blog.html.erb +159 -0
- data/app/views/layouts/caboose/store/layout_confirm.html.erb +85 -0
- data/app/views/layouts/caboose/store/layout_contact.html.erb +38 -0
- data/app/views/layouts/caboose/store/layout_default.html.erb +10 -0
- data/app/views/layouts/caboose/store/layout_detail.html.erb +114 -0
- data/app/views/layouts/caboose/store/layout_order.html.erb +77 -0
- data/app/views/layouts/caboose/store/layout_pricing.html.erb +182 -0
- data/app/views/layouts/caboose/store/layout_product.html.erb +110 -0
- data/app/views/layouts/caboose/store/layout_profile.html.erb +55 -0
- data/app/views/layouts/caboose/store/layout_single.html.erb +3 -0
- data/app/views/layouts/caboose/store/layout_testimonial.html.erb +110 -0
- data/app/views/layouts/caboose/store/layout_testing.html.erb +4 -0
- data/config/routes.rb +126 -0
- data/lib/caboose.rb +46 -1
- data/lib/caboose/engine.rb +39 -1
- data/lib/caboose/version.rb +1 -1
- data/lib/tasks/caboose.rake +12 -0
- metadata +151 -4
- data/app/assets/javascripts/caboose/admin_page_edit_content_bak.js +0 -164
- 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
|
@@ -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
|
-
|
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
|
+
|