caboose-cms 0.5.122 → 0.5.123

Sign up to get free protection for your applications and to get access to all the features.
Files changed (50) hide show
  1. checksums.yaml +8 -8
  2. data/app/assets/javascripts/caboose/admin.js +1 -0
  3. data/app/assets/javascripts/caboose/admin_edit_order.js +6 -8
  4. data/app/assets/javascripts/caboose/cart.js +2 -2
  5. data/app/assets/javascripts/caboose/jquery.datetimepicker.js +1925 -0
  6. data/app/assets/javascripts/caboose/model/all.js +1 -0
  7. data/app/assets/javascripts/caboose/model/bound_date.js +10 -3
  8. data/app/assets/javascripts/caboose/model/bound_date_time.js +121 -0
  9. data/app/assets/javascripts/caboose/model/model_binder.js +3 -0
  10. data/app/assets/stylesheets/caboose/admin.css +1 -0
  11. data/app/assets/stylesheets/caboose/jquery.datetimepicker.css +523 -0
  12. data/app/assets/templates/caboose/cart/line_items.jst.ejs +1 -1
  13. data/app/assets/templates/caboose/checkout/line_items.jst.ejs +2 -2
  14. data/app/controllers/caboose/checkout_controller.rb +3 -2
  15. data/app/controllers/caboose/line_items_controller.rb +4 -2
  16. data/app/controllers/caboose/my_account_controller.rb +43 -0
  17. data/app/controllers/caboose/my_account_orders_controller.rb +40 -6
  18. data/app/controllers/caboose/orders_controller.rb +26 -20
  19. data/app/controllers/caboose/products_controller.rb +1 -0
  20. data/app/controllers/caboose/sites_controller.rb +15 -1
  21. data/app/controllers/caboose/users_controller.rb +0 -35
  22. data/app/controllers/caboose/variants_controller.rb +51 -2
  23. data/app/controllers/caboose/vendors_controller.rb +3 -2
  24. data/app/models/caboose/address.rb +9 -1
  25. data/app/models/caboose/line_item.rb +17 -9
  26. data/app/models/caboose/model_binder.rb +63 -0
  27. data/app/models/caboose/order.rb +21 -14
  28. data/app/models/caboose/order_pdf.rb +3 -3
  29. data/app/models/caboose/pending_orders_pdf.rb +1 -1
  30. data/app/models/caboose/product.rb +15 -16
  31. data/app/models/caboose/schema.rb +20 -13
  32. data/app/models/caboose/site.rb +9 -0
  33. data/app/models/caboose/store_config.rb +6 -0
  34. data/app/models/caboose/variant.rb +15 -1
  35. data/app/views/caboose/checkout/_cart.html.erb +3 -3
  36. data/app/views/caboose/checkout/_confirm.html.erb +3 -3
  37. data/app/views/caboose/login/index.html.erb +7 -3
  38. data/app/views/caboose/{users/my_account.html.erb → my_account/index.html.erb} +7 -6
  39. data/app/views/caboose/my_account_orders/edit.html.erb +104 -0
  40. data/app/views/caboose/my_account_orders/index.html.erb +36 -0
  41. data/app/views/caboose/sites/admin_edit.html.erb +2 -0
  42. data/app/views/caboose/variants/admin_edit.html.erb +11 -1
  43. data/app/views/caboose/variants/admin_index.html.erb +5 -2
  44. data/config/routes.rb +7 -2
  45. data/lib/caboose/#Untitled-1# +264 -0
  46. data/lib/caboose/engine.rb +23 -140
  47. data/lib/caboose/version.rb +1 -1
  48. data/lib/tasks/caboose.rake +15 -1
  49. metadata +11 -4
  50. data/app/views/caboose/orders/admin_edit_old.html.erb +0 -155
@@ -18,7 +18,15 @@ module Caboose
18
18
  :country,
19
19
  :country_code,
20
20
  :phone
21
-
21
+
22
+ def name_and_address
23
+ str = "#{self.first_name} #{self.last_name}"
24
+ str << "<br />#{self.address1}"
25
+ str << "<br />#{self.address2}" if self.address2 && self.address2.length > 0
26
+ str << "<br />#{self.city}, #{self.state} #{self.zip}"
27
+ return str
28
+ end
29
+
22
30
  end
23
31
  end
24
32
 
@@ -11,8 +11,9 @@ module Caboose
11
11
  attr_accessible :id,
12
12
  :order_package_id,
13
13
  :variant_id,
14
+ :unit_price,
14
15
  :quantity,
15
- :price,
16
+ :subtotal,
16
17
  :notes,
17
18
  :order_id,
18
19
  :status,
@@ -47,20 +48,23 @@ module Caboose
47
48
  # Callbacks
48
49
  #
49
50
 
50
- before_save :update_price
51
+ before_save :update_subtotal
51
52
  after_save { self.order.calculate }
52
53
  after_initialize :check_nil_fields
53
54
 
54
- def check_nil_fields
55
- self.price = 0.00 if self.price.nil?
55
+ def check_nil_fields
56
+ self.subtotal = 0.00 if self.subtotal.nil?
56
57
  end
57
58
 
58
59
  #
59
60
  # Methods
60
61
  #
61
62
 
62
- def update_price
63
- self.price = self.variant.price * self.quantity
63
+ def update_subtotal
64
+ if self.unit_price.nil?
65
+ self.unit_price = self.variant.on_sale? ? self.variant.sale_price : self.variant.price
66
+ end
67
+ self.subtotal = self.unit_price * self.quantity
64
68
  end
65
69
 
66
70
  def title
@@ -78,15 +82,19 @@ module Caboose
78
82
  })
79
83
  end
80
84
 
81
- def subtotal
82
- return self.quantity * self.price
85
+ def verify_unit_price
86
+ if self.unit_price.nil?
87
+ self.unit_price = self.variant.on_sale? ? self.variant.sale_price : self.variant.price
88
+ self.save
89
+ end
83
90
  end
84
91
 
85
92
  def copy
86
93
  LineItem.new(
87
94
  :variant_id => self.variant_id ,
88
95
  :quantity => self.quantity ,
89
- :price => self.price ,
96
+ :unit_price => self.unit_price ,
97
+ :subtotal => self.subtotal ,
90
98
  :notes => self.notes ,
91
99
  :order_id => self.order_id ,
92
100
  :status => self.status ,
@@ -0,0 +1,63 @@
1
+
2
+ module Caboose
3
+ class ModelBinder
4
+
5
+ # Converts a 12-hour time string to a 24-hour time string.
6
+ # Example: 3:37 pm -> 15:37
7
+ def self.military_time(str)
8
+ return false if str.nil? || str.length == 0
9
+ arr = str.split(' ')
10
+ return str if arr.length == 1
11
+ hm = arr[0]
12
+ ampm = arr[1].downcase
13
+ return hm if ampm == 'am'
14
+ arr2 = hm.split(':')
15
+ arr2[0] = (arr2[0].to_i + 12).to_s
16
+ return arr2.join(':')
17
+ end
18
+
19
+ # Given a local date and time string (iso8601 format) and the local timezone,
20
+ # return a datetime object in the UTC zone.
21
+ def self.local_datetime_to_utc(str, zone)
22
+
23
+ return false if str.nil? || zone.nil?
24
+
25
+ # Split into date and time
26
+ arr = str.split('T')
27
+ arr = str.split(' ') if arr.count == 1
28
+ return false if arr.count == 1
29
+
30
+ # Split up the date into its components
31
+ date_string = arr.shift
32
+ d = date_string.split('-')
33
+ d = date_string.split('/') if d.count == 1
34
+ return false if d.count != 3
35
+ d = [d[2], d[0], d[1]] if date_string.include?('/')
36
+
37
+ # Split up the time into its components
38
+ time_string = arr.join(' ')
39
+ time_string = ModelBinder.military_time(time_string)
40
+ t = time_string.split(':')
41
+ return false if t.count != 2 && t.count != 3
42
+
43
+ # Convert timezones
44
+ old_timezone = Time.zone
45
+ Time.zone = zone
46
+ local_d = Time.zone.local(d[0].to_i, d[1].to_i, d[2].to_i, t[0].to_i, t[1].to_i, (t.count > 2 ? t[2].to_i : 0)).to_datetime.utc
47
+ Time.zone = old_timezone
48
+
49
+ return local_d
50
+ end
51
+
52
+ def self.update_date(d, value, timezone)
53
+ t = d ? d.in_time_zone(timezone).strftime('%H:%M') : '10:00'
54
+ return ModelBinder.local_datetime_to_utc("#{value} #{t}", timezone)
55
+ end
56
+
57
+ def self.update_time(d, value, timezone)
58
+ d2 = d ? d.in_time_zone(timezone).strftime('%Y-%m-%d') : DateTime.now.strftime('%Y-%m-%d')
59
+ return ModelBinder.local_datetime_to_utc("#{d2} #{value}", timezone)
60
+ end
61
+
62
+ end
63
+ end
@@ -38,21 +38,27 @@ module Caboose
38
38
  :date_created,
39
39
  :notes
40
40
 
41
- STATUS_CART = 'cart'
42
- STATUS_PENDING = 'pending'
43
- STATUS_CANCELED = 'canceled'
44
- STATUS_SHIPPED = 'shipped'
45
- STATUS_TESTING = 'testing'
41
+ STATUS_CART = 'cart'
42
+ STATUS_PENDING = 'pending'
43
+ STATUS_CANCELED = 'canceled'
44
+ STATUS_READY_TO_SHIP = 'ready to ship'
45
+ STATUS_SHIPPED = 'shipped'
46
+ STATUS_TESTING = 'testing'
47
+
48
+ FINANCIAL_STATUS_AUTHORIZED = 'authorized'
49
+ FINANCIAL_STATUS_CAPTURED = 'captured'
50
+ FINANCIAL_STATUS_REFUNDED = 'refunded'
51
+ FINANCIAL_STATUS_VOIDED = 'voided'
46
52
 
47
53
  #
48
54
  # Scopes
49
- #
50
-
55
+ #
56
+ scope :cart , where('status = ?', 'cart')
57
+ scope :pending , where('status = ?', 'pending')
58
+ scope :canceled , where('status = ?', 'canceled')
59
+ scope :shipped , where('status = ?', 'shipped')
51
60
  scope :test , where('status = ?', 'testing')
52
- scope :cancelled , where('status = ?', 'cancelled')
53
- scope :pending , where('status = ?', 'pending')
54
- #TODO scope :fulfilled
55
- #TODO scope :unfulfilled
61
+
56
62
  scope :authorized , where('financial_status = ?', 'authorized')
57
63
  scope :captured , where('financial_status = ?', 'captured')
58
64
  scope :refunded , where('financial_status = ?', 'refunded')
@@ -63,7 +69,7 @@ module Caboose
63
69
  #
64
70
 
65
71
  validates :status, :inclusion => {
66
- :in => ['cart', 'pending', 'cancelled', 'shipped', 'testing'],
72
+ :in => ['cart', 'pending', 'canceled', 'ready to ship', 'shipped', 'testing'],
67
73
  :message => "%{value} is not a valid status. Must be either 'pending' or 'shipped'"
68
74
  }
69
75
 
@@ -124,7 +130,7 @@ module Caboose
124
130
  PaymentProcessor.capture(self)
125
131
  end
126
132
 
127
- def refuned
133
+ def refund
128
134
  PaymentProcessor.refund(self)
129
135
  end
130
136
 
@@ -143,8 +149,9 @@ module Caboose
143
149
 
144
150
  def calculate_subtotal
145
151
  return 0.0 if self.line_items.empty?
152
+ self.line_items.each{ |li| li.verify_unit_price } # Make sure the unit prices are populated
146
153
  x = 0.0
147
- self.line_items.each{ |li| x = x + (li.variant.price * li.quantity) } # Fixed issue with quantity
154
+ self.line_items.each{ |li| x = x + (li.unit_price * li.quantity) } # Fixed issue with quantity
148
155
  return x
149
156
  end
150
157
 
@@ -92,9 +92,9 @@ module Caboose
92
92
  tbl << [
93
93
  "#{li.variant.product.title}\n#{li.variant.sku}\n#{li.variant.title}",
94
94
  { :content => li.tracking_number },
95
- { :content => sprintf("%.2f", li.variant.price) , :align => :right },
96
- { :content => "#{li.quantity}" , :align => :right },
97
- { :content => sprintf("%.2f", li.subtotal) , :align => :right }
95
+ { :content => sprintf("%.2f", li.unit_price) , :align => :right },
96
+ { :content => "#{li.quantity}" , :align => :right },
97
+ { :content => sprintf("%.2f", li.subtotal) , :align => :right }
98
98
  ]
99
99
  end
100
100
  tbl << [{ :content => "Subtotal" , :colspan => 4, :align => :right }, { :content => sprintf("%.2f", order.subtotal ) , :align => :right }]
@@ -66,7 +66,7 @@ module Caboose
66
66
  tbl << [
67
67
  "#{li.variant.product.title}\n#{li.variant.sku}\n#{li.variant.title}",
68
68
  { :content => li.tracking_number },
69
- { :content => sprintf("%.2f", li.variant.price) , :align => :right },
69
+ { :content => sprintf("%.2f", li.unit_price) , :align => :right },
70
70
  { :content => "#{li.quantity}" , :align => :right },
71
71
  { :content => sprintf("%.2f", li.subtotal) , :align => :right }
72
72
  ]
@@ -105,10 +105,11 @@ module Caboose
105
105
  min = 100000
106
106
  max = 0
107
107
 
108
- self.variants.each do |variant|
109
- next if variant.nil? or variant.price.nil? or variant.price <= 0
110
- min = variant.price if variant.price < min
111
- max = variant.price if variant.price > max
108
+ self.variants.each do |variant|
109
+ next if variant.nil? || variant.price.nil? || variant.price <= 0
110
+ price = variant.on_sale? ? v.sale_price : v.price
111
+ min = price if price < min
112
+ max = price if price > max
112
113
  end
113
114
 
114
115
  return [min, max]
@@ -171,18 +172,16 @@ module Caboose
171
172
  end
172
173
  end
173
174
 
174
- # TODO: Implement product sales
175
- #def self.products_on_sale
176
- #
177
- # query[0] = "select distinct product_id from store_variants
178
- # where sale_price is not null
179
- # and date_sale_starts < now()
180
- # and date_sale_ends > now()
181
- # order by title limit 20"
182
- # rows = ActiveRecord::Base.connection.select_rows(ActiveRecord::Base.send(:sanitize_sql_array, query))
183
- # arr = rows.collect{ |row| { :id => row[0], :title => row[1] }}
184
- #
185
- #end
175
+ def update_on_sale
176
+ is_on_sale = false
177
+ self.variants.each do |v|
178
+ is_on_sale = true if v.on_sale?
179
+ end
180
+ if (is_on_sale && !self.on_sale) || (!is_on_sale && self.on_sale)
181
+ self.on_sale = is_on_sale
182
+ self.save
183
+ end
184
+ end
186
185
 
187
186
  end
188
187
  end
@@ -57,13 +57,12 @@ class Caboose::Schema < Caboose::Utilities::Schema
57
57
  Caboose::Order => [
58
58
  :shipping_method ,
59
59
  :shipping_method_code ,
60
- :email ,
61
- :order_number ,
60
+ :email ,
62
61
  :payment_id ,
63
62
  :gateway_id ,
64
63
  :date_authorized ,
65
64
  :date_captured ,
66
- :date_cancelled ,
65
+ :date_canceled ,
67
66
  :shipping_carrier ,
68
67
  :shipping_service_code ,
69
68
  :shipping_service_name ,
@@ -79,7 +78,7 @@ class Caboose::Schema < Caboose::Utilities::Schema
79
78
  Caboose::ShippingPackage => [:price, :carrier, :service_code, :service_name, :shipping_method_id, :length, :width, :height],
80
79
  Caboose::Site => [:shipping_cost_function],
81
80
  Caboose::StoreConfig => [:use_usps, :allowed_shipping_codes, :default_shipping_code, :pp_relay_url, :pp_response_url],
82
- Caboose::Variant => [:quantity],
81
+ Caboose::Variant => [:quantity, :on_sale],
83
82
  Caboose::Vendor => [:vendor, :vendor_id]
84
83
  }
85
84
  end
@@ -285,11 +284,12 @@ class Caboose::Schema < Caboose::Utilities::Schema
285
284
  [ :order_id , :integer ],
286
285
  [ :order_package_id , :integer ],
287
286
  [ :variant_id , :integer ],
288
- [ :parent_id , :integer ],
289
- [ :quantity , :integer , :default => 0 ],
287
+ [ :parent_id , :integer ],
290
288
  [ :status , :string ],
291
- [ :tracking_number , :string ],
292
- [ :price , :decimal , { :precision => 8, :scale => 2 }],
289
+ [ :tracking_number , :string ],
290
+ [ :quantity , :integer , :default => 0 ],
291
+ [ :unit_price , :decimal , { :precision => 8, :scale => 2 }],
292
+ [ :subtotal , :decimal , { :precision => 8, :scale => 2 }],
293
293
  [ :notes , :text ],
294
294
  [ :custom1 , :string ],
295
295
  [ :custom2 , :string ],
@@ -336,6 +336,7 @@ class Caboose::Schema < Caboose::Utilities::Schema
336
336
  ],
337
337
  Caboose::Order => [
338
338
  [ :site_id , :integer ],
339
+ [ :order_number , :integer ],
339
340
  [ :alternate_id , :integer ],
340
341
  [ :subtotal , :decimal , { :precision => 8, :scale => 2 }],
341
342
  [ :tax , :decimal , { :precision => 8, :scale => 2 }],
@@ -354,7 +355,9 @@ class Caboose::Schema < Caboose::Utilities::Schema
354
355
  [ :referring_site , :text ],
355
356
  [ :landing_page , :string ],
356
357
  [ :landing_page_ref , :string ],
357
- [ :auth_amount , :decimal , { :precision => 8, :scale => 2 }]
358
+ [ :auth_amount , :decimal , { :precision => 8, :scale => 2 }],
359
+ [ :gift_message , :text ],
360
+ [ :include_receipt , :boolean , { :default => true }]
358
361
 
359
362
  #[ :email , :string ],
360
363
  #[ :order_number , :string ],
@@ -467,7 +470,8 @@ class Caboose::Schema < Caboose::Utilities::Schema
467
470
  [ :custom_input , :text ],
468
471
  [ :sort_order , :integer ],
469
472
  [ :featured , :boolean , :default => false ],
470
- [ :stackable_group_id , :integer ]
473
+ [ :stackable_group_id , :integer ],
474
+ [ :on_sale , :boolean , { :default => false }]
471
475
  ],
472
476
  Caboose::ProductImage => [
473
477
  [ :product_id , :integer ],
@@ -550,7 +554,8 @@ class Caboose::Schema < Caboose::Utilities::Schema
550
554
  [ :name , :string ],
551
555
  [ :description , :text ],
552
556
  [ :under_construction_html , :text ],
553
- [ :use_store , :boolean , { :default => false }]
557
+ [ :use_store , :boolean , { :default => false }],
558
+ [ :logo , :attachment ]
554
559
  ],
555
560
  Caboose::SiteMembership => [
556
561
  [ :site_id , :integer ],
@@ -618,7 +623,8 @@ class Caboose::Schema < Caboose::Utilities::Schema
618
623
  [ :shipping_rates_function , :text ],
619
624
  [ :length_unit , :string , { :default => 'in' }],
620
625
  [ :weight_unit , :string , { :default => 'oz' }],
621
- [ :download_url_expires_in , :string , { :default => 5 }]
626
+ [ :download_url_expires_in , :string , { :default => 5 }],
627
+ [ :starting_order_number , :integer , { :default => 1000 }]
622
628
  ],
623
629
  Caboose::User => [
624
630
  [ :site_id , :integer ],
@@ -652,7 +658,8 @@ class Caboose::Schema < Caboose::Utilities::Schema
652
658
  [ :price , :decimal , { :precision => 8, :scale => 2 }],
653
659
  [ :sale_price , :decimal , { :precision => 8, :scale => 2 }],
654
660
  [ :date_sale_starts , :datetime ],
655
- [ :date_sale_end , :datetime ],
661
+ [ :date_sale_ends , :datetime ],
662
+ [ :date_sale_end , :datetime ],
656
663
  [ :available , :boolean ],
657
664
  [ :quantity_in_stock , :integer , :default => 0 ],
658
665
  [ :ignore_quantity , :boolean ],
@@ -8,6 +8,15 @@ class Caboose::Site < ActiveRecord::Base
8
8
  has_many :domains, :class_name => 'Caboose::Domain', :dependent => :delete_all
9
9
  has_many :post_categories, :class_name => 'Caboose::PostCategory'
10
10
  has_one :store_config
11
+ has_attached_file :logo,
12
+ :path => ':path_prefixsite_logos/:id_:style.:extension',
13
+ :default_url => 'http://placehold.it/300x300',
14
+ :styles => {
15
+ :tiny => '150x200>',
16
+ :thumb => '300x400>',
17
+ :large => '600x800>'
18
+ }
19
+ do_not_validate_attachment_file_type :logo
11
20
  attr_accessible :id, :name, :description, :under_construction_html
12
21
 
13
22
  def smtp_config
@@ -32,6 +32,12 @@ module Caboose
32
32
  :shipping_rates_function,
33
33
  :length_unit,
34
34
  :weight_unit
35
+
36
+ def next_order_number
37
+ x = Order.where("order_number is not null").reorder("order_number desc").limit(1).first
38
+ return x.order_number + 1 if x
39
+ return self.starting_order_number
40
+ end
35
41
 
36
42
  end
37
43
  end
@@ -102,6 +102,20 @@ module Caboose
102
102
  arr << self.option3 if self.option3 && self.option3.strip.length > 0
103
103
  return arr
104
104
  end
105
-
105
+
106
+ def product_image
107
+ return self.product_images.first if self.product_images
108
+ return self.product.product_images.first if self.product.product_images
109
+ return nil
110
+ end
111
+
112
+ def on_sale?
113
+ return false if self.sale_price.nil?
114
+ d = DateTime.now.utc
115
+ return false if self.date_sale_starts && d < self.date_sale_starts
116
+ return false if self.date_sale_ends && d > self.date_sale_ends
117
+ return true
118
+ end
119
+
106
120
  end
107
121
  end
@@ -16,9 +16,9 @@
16
16
  <% end %>
17
17
  </td>
18
18
  <td valign='top'><%= li.title %></td>
19
- <td valign='top' align='right' class='qty' style='text-align: right;'><%= li.quantity %></td>
20
- <td valign='top' align='right' class='price' style='text-align: right;'><%= number_to_currency(li.price, :precision => 2) %></td>
21
- <td valign='top' align='right' class='subtotal' style='text-align: right;'><%= number_to_currency(li.price, :precision => 2) %></td>
19
+ <td valign='top' align='right' class='qty' style='text-align: right;'><%= li.quantity %></td>
20
+ <td valign='top' align='right' class='unit_price' style='text-align: right;'><%= number_to_currency(li.unit_price , :precision => 2) %></td>
21
+ <td valign='top' align='right' class='subtotal' style='text-align: right;'><%= number_to_currency(li.subtotal , :precision => 2) %></td>
22
22
  </tr>
23
23
  <% end %>
24
24
  <tr><td colspan='4' align='right' style='text-align: right;'>Subtotal: </td><td align='right' style='text-align: right;'><%= number_to_currency(@order.subtotal , :precision => 2) %></td></tr>
@@ -16,9 +16,9 @@
16
16
  <% end %>
17
17
  <p><%= li.title %></p>
18
18
  </td>
19
- <td valign='top' align='right' class='qty' style='text-align: right;'><%= li.quantity %></td>
20
- <td valign='top' align='right' class='price' style='text-align: right;'><%= number_to_currency(li.price, :precision => 2) %></td>
21
- <td valign='top' align='right' class='subtotal' style='text-align: right;'><%= number_to_currency(li.price, :precision => 2) %></td>
19
+ <td valign='top' align='right' class='qty' style='text-align: right;'><%= li.quantity %></td>
20
+ <td valign='top' align='right' class='unit_price' style='text-align: right;'><%= number_to_currency(li.unit_price , :precision => 2) %></td>
21
+ <td valign='top' align='right' class='subtotal' style='text-align: right;'><%= number_to_currency(li.subtotal , :precision => 2) %></td>
22
22
  </tr>
23
23
  <% end %>
24
24
  <tr><td colspan='3' align='right' style='text-align: right;'>Subtotal: </td><td align='right' style='text-align: right;'><%= number_to_currency(@order.subtotal, :precision => 2) %></td></tr>