shoppe 0.0.14 → 0.0.15

Sign up to get free protection for your applications and to get access to all the features.
Files changed (59) hide show
  1. data/app/assets/javascripts/shoppe/application.coffee +57 -1
  2. data/app/assets/javascripts/shoppe/mousetrap.js +9 -0
  3. data/app/assets/stylesheets/shoppe/application.scss +70 -59
  4. data/app/assets/stylesheets/shoppe/dialog.scss +10 -0
  5. data/app/assets/stylesheets/shoppe/sub.scss +15 -0
  6. data/app/controllers/shoppe/application_controller.rb +13 -1
  7. data/app/controllers/shoppe/attachments_controller.rb +10 -8
  8. data/app/controllers/shoppe/dashboard_controller.rb +6 -4
  9. data/app/controllers/shoppe/delivery_service_prices_controller.rb +33 -31
  10. data/app/controllers/shoppe/delivery_services_controller.rb +34 -32
  11. data/app/controllers/shoppe/orders_controller.rb +40 -38
  12. data/app/controllers/shoppe/product_categories_controller.rb +34 -32
  13. data/app/controllers/shoppe/products_controller.rb +32 -44
  14. data/app/controllers/shoppe/sessions_controller.rb +24 -22
  15. data/app/controllers/shoppe/stock_level_adjustments_controller.rb +40 -0
  16. data/app/controllers/shoppe/tax_rates_controller.rb +35 -33
  17. data/app/controllers/shoppe/users_controller.rb +40 -33
  18. data/app/controllers/shoppe/variants_controller.rb +50 -0
  19. data/app/helpers/shoppe/shoppe_helper.rb +2 -2
  20. data/app/mailers/shoppe/order_mailer.rb +20 -18
  21. data/app/models/shoppe/country.rb +1 -1
  22. data/app/models/shoppe/delivery_service.rb +17 -15
  23. data/app/models/shoppe/delivery_service_price.rb +18 -16
  24. data/app/models/shoppe/order.rb +293 -290
  25. data/app/models/shoppe/order_item.rb +115 -113
  26. data/app/models/shoppe/product.rb +76 -54
  27. data/app/models/shoppe/product/product_attributes.rb +12 -10
  28. data/app/models/shoppe/product/variants.rb +26 -0
  29. data/app/models/shoppe/product_attribute.rb +40 -38
  30. data/app/models/shoppe/product_category.rb +16 -14
  31. data/app/models/shoppe/stock_level_adjustment.rb +1 -2
  32. data/app/models/shoppe/tax_rate.rb +2 -2
  33. data/app/models/shoppe/user.rb +34 -32
  34. data/app/views/shoppe/orders/index.html.haml +3 -4
  35. data/app/views/shoppe/orders/show.html.haml +5 -5
  36. data/app/views/shoppe/products/_form.html.haml +28 -27
  37. data/app/views/shoppe/products/_table.html.haml +42 -0
  38. data/app/views/shoppe/products/edit.html.haml +2 -1
  39. data/app/views/shoppe/products/index.html.haml +1 -24
  40. data/app/views/shoppe/shared/error.html.haml +4 -0
  41. data/app/views/shoppe/{products/stock_levels.html.haml → stock_level_adjustments/index.html.haml} +12 -6
  42. data/app/views/shoppe/variants/form.html.haml +64 -0
  43. data/app/views/shoppe/variants/index.html.haml +33 -0
  44. data/config/routes.rb +2 -1
  45. data/config/shoppe.example.yml +16 -2
  46. data/db/migrate/20131022090919_refactor_order_items_to_allow_any_product.rb +6 -0
  47. data/db/migrate/20131022092904_rename_product_title_to_name.rb +5 -0
  48. data/db/migrate/20131022093538_stock_level_adjustments_should_be_polymorphic.rb +6 -0
  49. data/db/migrate/20131022135331_add_parent_id_to_products.rb +5 -0
  50. data/db/migrate/20131022145653_cost_price_should_be_default_to_zero.rb +9 -0
  51. data/db/seeds.rb +20 -20
  52. data/lib/shoppe.rb +2 -0
  53. data/lib/shoppe/errors/not_enough_stock.rb +1 -1
  54. data/lib/shoppe/errors/unorderable_item.rb +11 -0
  55. data/lib/shoppe/orderable_item.rb +39 -0
  56. data/lib/shoppe/version.rb +1 -1
  57. data/test/dummy/db/schema.rb +14 -11
  58. data/test/dummy/log/development.log +75 -0
  59. metadata +37 -5
@@ -0,0 +1,50 @@
1
+ module Shoppe
2
+ class VariantsController < ApplicationController
3
+
4
+ before_filter { @active_nav = :products }
5
+ before_filter { @product = Shoppe::Product.find(params[:product_id]) }
6
+ before_filter { params[:id] && @variant = @product.variants.find(params[:id]) }
7
+
8
+ def index
9
+ @variants = @product.variants.ordered
10
+ end
11
+
12
+ def new
13
+ @variant = @product.variants.build
14
+ render :action => "form"
15
+ end
16
+
17
+ def create
18
+ @variant = @product.variants.build(safe_params)
19
+ if @variant.save
20
+ redirect_to [@product, :variants], :notice => "Varient has been added successfully"
21
+ else
22
+ render :action => "form"
23
+ end
24
+ end
25
+
26
+ def edit
27
+ render :action => "form"
28
+ end
29
+
30
+ def update
31
+ if @variant.update(safe_params)
32
+ redirect_to edit_product_variant_path(@product, @variant), :notice => "Varient has been updated successfully"
33
+ else
34
+ render :action => "form"
35
+ end
36
+ end
37
+
38
+ def destroy
39
+ @variant.destroy
40
+ redirect_to [@product, :variants], :notice => "Varient has been removed successfully"
41
+ end
42
+
43
+ private
44
+
45
+ def safe_params
46
+ params[:product].permit(:name, :permalink, :sku, :default_image_file, :price, :cost_price, :tax_rate_id, :weight, :stock_control, :active)
47
+ end
48
+
49
+ end
50
+ end
@@ -4,7 +4,7 @@ module Shoppe::ShoppeHelper
4
4
  content_tag :span, status, :class => "status-tag #{status}"
5
5
  end
6
6
 
7
- def attachment_preview(attachment)
7
+ def attachment_preview(attachment, options = {})
8
8
  if attachment
9
9
  String.new.tap do |s|
10
10
  if attachment.image?
@@ -22,7 +22,7 @@ module Shoppe::ShoppeHelper
22
22
  s << "</div>"
23
23
  s << "</div>"
24
24
  end.html_safe
25
- else
25
+ elsif !options[:hide_if_blank]
26
26
  "<div class='attachmentPreview'><div class='imgContainer'><div class='img none'></div></div><div class='desc none'>No attachment</div></div>".html_safe
27
27
  end
28
28
  end
@@ -1,25 +1,27 @@
1
- class Shoppe::OrderMailer < ActionMailer::Base
1
+ module Shoppe
2
+ class OrderMailer < ActionMailer::Base
2
3
 
3
- default :from => "#{Shoppe.config[:store_name]} <#{Shoppe.config[:email_address]}>"
4
+ default :from => "#{Shoppe.config[:store_name]} <#{Shoppe.config[:email_address]}>"
4
5
 
5
- def received(order)
6
- @order = order
7
- mail :to => order.email_address, :subject => I18n.t('shoppe.order_mailer.received.subject', :default => "Order Confirmation")
8
- end
6
+ def received(order)
7
+ @order = order
8
+ mail :to => order.email_address, :subject => I18n.t('shoppe.order_mailer.received.subject', :default => "Order Confirmation")
9
+ end
9
10
 
10
- def accepted(order)
11
- @order = order
12
- mail :to => order.email_address, :subject => I18n.t('shoppe.order_mailer.received.accepted', :default => "Order Accepted")
13
- end
11
+ def accepted(order)
12
+ @order = order
13
+ mail :to => order.email_address, :subject => I18n.t('shoppe.order_mailer.received.accepted', :default => "Order Accepted")
14
+ end
14
15
 
15
- def rejected(order)
16
- @order = order
17
- mail :to => order.email_address, :subject => I18n.t('shoppe.order_mailer.received.rejected', :default => "Order Rejected")
18
- end
16
+ def rejected(order)
17
+ @order = order
18
+ mail :to => order.email_address, :subject => I18n.t('shoppe.order_mailer.received.rejected', :default => "Order Rejected")
19
+ end
19
20
 
20
- def shipped(order)
21
- @order = order
22
- mail :to => order.email_address, :subject => I18n.t('shoppe.order_mailer.received.shipped', :default => "Order Shipped")
23
- end
21
+ def shipped(order)
22
+ @order = order
23
+ mail :to => order.email_address, :subject => I18n.t('shoppe.order_mailer.received.shipped', :default => "Order Shipped")
24
+ end
24
25
 
26
+ end
25
27
  end
@@ -5,7 +5,7 @@ module Shoppe
5
5
  self.table_name = 'shoppe_countries'
6
6
 
7
7
  # Relationships
8
- has_many :orders, :dependent => :restrict_with_exception
8
+ has_many :orders, :dependent => :restrict_with_exception, :class_name => 'Shoppe::Order'
9
9
 
10
10
  # Scopes
11
11
  scope :ordered, -> { order('shoppe_countries.name asc') }
@@ -1,22 +1,24 @@
1
- class Shoppe::DeliveryService < ActiveRecord::Base
1
+ module Shoppe
2
+ class DeliveryService < ActiveRecord::Base
2
3
 
3
- # Set the table name
4
- self.table_name = 'shoppe_delivery_services'
4
+ # Set the table name
5
+ self.table_name = 'shoppe_delivery_services'
5
6
 
6
- # Validations
7
- validates :name, :presence => true
8
- validates :courier, :presence => true
7
+ # Validations
8
+ validates :name, :presence => true
9
+ validates :courier, :presence => true
9
10
 
10
- # Relationships
11
- has_many :orders, :dependent => :restrict_with_exception, :class_name => 'Shoppe::Order'
12
- has_many :delivery_service_prices, :dependent => :destroy, :class_name => 'Shoppe::DeliveryServicePrice'
11
+ # Relationships
12
+ has_many :orders, :dependent => :restrict_with_exception, :class_name => 'Shoppe::Order'
13
+ has_many :delivery_service_prices, :dependent => :destroy, :class_name => 'Shoppe::DeliveryServicePrice'
13
14
 
14
- # Scopes
15
- scope :active, -> { where(:active => true)}
15
+ # Scopes
16
+ scope :active, -> { where(:active => true)}
16
17
 
17
- # Return the tracking URL for the given consignment number
18
- def tracking_url_for(consignment_number)
19
- tracking_url.gsub("{{consignment_number}}", consignment_number)
18
+ # Return the tracking URL for the given consignment number
19
+ def tracking_url_for(consignment_number)
20
+ tracking_url.gsub("{{consignment_number}}", consignment_number)
21
+ end
22
+
20
23
  end
21
-
22
24
  end
@@ -1,23 +1,25 @@
1
- class Shoppe::DeliveryServicePrice < ActiveRecord::Base
1
+ module Shoppe
2
+ class DeliveryServicePrice < ActiveRecord::Base
2
3
 
3
- # Set the table name
4
- self.table_name = 'shoppe_delivery_service_prices'
4
+ # Set the table name
5
+ self.table_name = 'shoppe_delivery_service_prices'
5
6
 
6
- # Tax rates are associated with countries
7
- include Shoppe::AssociatedCountries
7
+ # Tax rates are associated with countries
8
+ include Shoppe::AssociatedCountries
8
9
 
9
- # Relationships
10
- belongs_to :delivery_service, :class_name => 'Shoppe::DeliveryService'
11
- belongs_to :tax_rate, :class_name => "Shoppe::TaxRate"
10
+ # Relationships
11
+ belongs_to :delivery_service, :class_name => 'Shoppe::DeliveryService'
12
+ belongs_to :tax_rate, :class_name => "Shoppe::TaxRate"
12
13
 
13
- # Validations
14
- validates :price, :numericality => true
15
- validates :cost_price, :numericality => true, :allow_blank => true
16
- validates :min_weight, :numericality => true
17
- validates :max_weight, :numericality => true
14
+ # Validations
15
+ validates :price, :numericality => true
16
+ validates :cost_price, :numericality => true, :allow_blank => true
17
+ validates :min_weight, :numericality => true
18
+ validates :max_weight, :numericality => true
18
19
 
19
- # Scopes
20
- scope :ordered, -> { order('price asc')}
21
- scope :for_weight, -> weight { where("min_weight <= ? AND max_weight >= ?", weight, weight) }
20
+ # Scopes
21
+ scope :ordered, -> { order('price asc')}
22
+ scope :for_weight, -> weight { where("min_weight <= ? AND max_weight >= ?", weight, weight) }
22
23
 
24
+ end
23
25
  end
@@ -1,352 +1,355 @@
1
- class Shoppe::Order < ActiveRecord::Base
1
+ module Shoppe
2
+ class Order < ActiveRecord::Base
2
3
 
3
- # Set the table name
4
- self.table_name = 'shoppe_orders'
5
-
6
- # An array of all the available statuses for an order
7
- STATUSES = ['building', 'confirming', 'received', 'accepted', 'rejected', 'shipped']
8
-
9
- # Order's implement a key value store for storing arbitary properties which
10
- # may be useful (for example payment configuraiton)
11
- key_value_store :properties
12
-
13
- # These additional callbacks allow for applications to hook into other
14
- # parts of the order lifecycle.
15
- define_model_callbacks :confirmation, :payment, :acceptance, :rejection, :ship
16
-
17
- # Relationships
18
- belongs_to :delivery_service, :class_name => 'Shoppe::DeliveryService'
19
- belongs_to :country, :class_name => 'Shoppe::Country'
20
- belongs_to :accepter, :class_name => 'Shoppe::User', :foreign_key => 'accepted_by'
21
- belongs_to :rejecter, :class_name => 'Shoppe::User', :foreign_key => 'rejected_by'
22
- belongs_to :shipper, :class_name => 'Shoppe::User', :foreign_key => 'shipped_by'
23
- has_many :order_items, :dependent => :destroy, :class_name => 'Shoppe::OrderItem'
24
- has_many :products, :through => :order_items, :class_name => 'Shoppe::Product'
25
-
26
- # Validations
27
- validates :token, :presence => true
28
- validates :status, :inclusion => {:in => STATUSES}
29
- with_options :if => Proc.new { |o| !o.building? } do |order|
30
- order.validates :first_name, :presence => true
31
- order.validates :last_name, :presence => true
32
- order.validates :address1, :presence => true
33
- order.validates :address2, :presence => true
34
- order.validates :postcode, :presence => true
35
- order.validates :country, :presence => true
36
- order.validates :email_address, :format => {:with => /\A\b[A-Z0-9\.\_\%\-\+]+@(?:[A-Z0-9\-]+\.)+[A-Z]{2,6}\b\z/i}
37
- order.validates :phone_number, :format => {:with => /\A[\d\ ]{7,}\z/}
38
- end
39
- validate do
40
- unless available_delivery_services.include?(self.delivery_service)
41
- errors.add :delivery_service_id, "is not suitable for this order"
4
+ # Set the table name
5
+ self.table_name = 'shoppe_orders'
6
+
7
+ # An array of all the available statuses for an order
8
+ STATUSES = ['building', 'confirming', 'received', 'accepted', 'rejected', 'shipped']
9
+
10
+ # Order's implement a key value store for storing arbitary properties which
11
+ # may be useful (for example payment configuraiton)
12
+ key_value_store :properties
13
+
14
+ # These additional callbacks allow for applications to hook into other
15
+ # parts of the order lifecycle.
16
+ define_model_callbacks :confirmation, :payment, :acceptance, :rejection, :ship
17
+
18
+ # Relationships
19
+ belongs_to :delivery_service, :class_name => 'Shoppe::DeliveryService'
20
+ belongs_to :country, :class_name => 'Shoppe::Country'
21
+ belongs_to :accepter, :class_name => 'Shoppe::User', :foreign_key => 'accepted_by'
22
+ belongs_to :rejecter, :class_name => 'Shoppe::User', :foreign_key => 'rejected_by'
23
+ belongs_to :shipper, :class_name => 'Shoppe::User', :foreign_key => 'shipped_by'
24
+ has_many :order_items, :dependent => :destroy, :class_name => 'Shoppe::OrderItem'
25
+ has_many :products, :through => :order_items, :class_name => 'Shoppe::Product'
26
+
27
+ # Validations
28
+ validates :token, :presence => true
29
+ validates :status, :inclusion => {:in => STATUSES}
30
+ with_options :if => Proc.new { |o| !o.building? } do |order|
31
+ order.validates :first_name, :presence => true
32
+ order.validates :last_name, :presence => true
33
+ order.validates :address1, :presence => true
34
+ order.validates :address3, :presence => true
35
+ order.validates :address4, :presence => true
36
+ order.validates :postcode, :presence => true
37
+ order.validates :country, :presence => true
38
+ order.validates :email_address, :format => {:with => /\A\b[A-Z0-9\.\_\%\-\+]+@(?:[A-Z0-9\-]+\.)+[A-Z]{2,6}\b\z/i}
39
+ order.validates :phone_number, :format => {:with => /\A[\d\ \-x\(\)]{7,}\z/}
40
+ end
41
+ validate do
42
+ unless available_delivery_services.include?(self.delivery_service)
43
+ errors.add :delivery_service_id, "is not suitable for this order"
44
+ end
42
45
  end
43
- end
44
46
 
45
- # Scopes
46
- scope :received, -> {where("received_at is not null")}
47
- scope :pending, -> { where(:status => 'received') }
48
- scope :ordered, -> { order('id desc')}
47
+ # Scopes
48
+ scope :received, -> {where("received_at is not null")}
49
+ scope :pending, -> { where(:status => 'received') }
50
+ scope :ordered, -> { order('id desc')}
49
51
 
50
- # Set some defaults
51
- before_validation do
52
- self.status = 'building' if self.status.blank?
53
- self.token = SecureRandom.uuid if self.token.blank?
54
- end
52
+ # Set some defaults
53
+ before_validation do
54
+ self.status = 'building' if self.status.blank?
55
+ self.token = SecureRandom.uuid if self.token.blank?
56
+ end
55
57
 
56
- # Is this order still being built by the user?
57
- def building?
58
- self.status == 'building'
59
- end
58
+ # Is this order still being built by the user?
59
+ def building?
60
+ self.status == 'building'
61
+ end
60
62
 
61
- # Is this order in the user confirmation step?
62
- def confirming?
63
- self.status == 'confirming'
64
- end
63
+ # Is this order in the user confirmation step?
64
+ def confirming?
65
+ self.status == 'confirming'
66
+ end
65
67
 
66
- # Has this order been rejected?
67
- def rejected?
68
- !!self.rejected_at
69
- end
68
+ # Has this order been rejected?
69
+ def rejected?
70
+ !!self.rejected_at
71
+ end
70
72
 
71
- # Has this order been accepted?
72
- def accepted?
73
- !!self.accepted_at
74
- end
73
+ # Has this order been accepted?
74
+ def accepted?
75
+ !!self.accepted_at
76
+ end
75
77
 
76
- # Has this order been shipped?
77
- def shipped?
78
- !!self.shipped_at?
79
- end
78
+ # Has this order been shipped?
79
+ def shipped?
80
+ !!self.shipped_at?
81
+ end
80
82
 
81
- # Has the order been received?
82
- def received?
83
- !!self.received_at?
84
- end
83
+ # Has the order been received?
84
+ def received?
85
+ !!self.received_at?
86
+ end
85
87
 
86
- # The order number
87
- def number
88
- id.to_s.rjust(6, '0')
89
- end
88
+ # The order number
89
+ def number
90
+ id.to_s.rjust(6, '0')
91
+ end
90
92
 
91
- # The length of time the customer spent building the order before submitting it to us.
92
- # The time from first item in basket to received.
93
- def build_time
94
- return nil if self.received_at.blank?
95
- self.created_at - self.received_at
96
- end
93
+ # The length of time the customer spent building the order before submitting it to us.
94
+ # The time from first item in basket to received.
95
+ def build_time
96
+ return nil if self.received_at.blank?
97
+ self.created_at - self.received_at
98
+ end
97
99
 
98
- # The name of the customer
99
- def customer_name
100
- company.blank? ? "#{first_name} #{last_name}" : "#{company} (#{first_name} #{last_name})"
101
- end
100
+ # The name of the customer
101
+ def customer_name
102
+ company.blank? ? "#{first_name} #{last_name}" : "#{company} (#{first_name} #{last_name})"
103
+ end
102
104
 
103
- # Is this order empty? (i.e. doesn't have any items associated with it)
104
- def empty?
105
- order_items.empty?
106
- end
105
+ # Is this order empty? (i.e. doesn't have any items associated with it)
106
+ def empty?
107
+ order_items.empty?
108
+ end
107
109
 
108
- # Does this order have items?
109
- def has_items?
110
- total_items > 0
111
- end
110
+ # Does this order have items?
111
+ def has_items?
112
+ total_items > 0
113
+ end
112
114
 
113
- # Return the number of items in the order?
114
- def total_items
115
- @total_items ||= order_items.inject(0) { |t,i| t + i.quantity }
116
- end
115
+ # Return the number of items in the order?
116
+ def total_items
117
+ @total_items ||= order_items.inject(0) { |t,i| t + i.quantity }
118
+ end
117
119
 
118
- # The total cost of the order
119
- def total_cost
120
- self.delivery_cost_price +
121
- order_items.inject(BigDecimal(0)) { |t, i| t + i.total_cost }
122
- end
120
+ # The total cost of the order
121
+ def total_cost
122
+ self.delivery_cost_price +
123
+ order_items.inject(BigDecimal(0)) { |t, i| t + i.total_cost }
124
+ end
123
125
 
124
- # Return the price for the order
125
- def profit
126
- total_before_tax - total_cost
127
- end
126
+ # Return the price for the order
127
+ def profit
128
+ total_before_tax - total_cost
129
+ end
128
130
 
129
- # The total price of the order before tax
130
- def total_before_tax
131
- self.delivery_price +
132
- order_items.inject(BigDecimal(0)) { |t, i| t + i.sub_total }
133
- end
131
+ # The total price of the order before tax
132
+ def total_before_tax
133
+ self.delivery_price +
134
+ order_items.inject(BigDecimal(0)) { |t, i| t + i.sub_total }
135
+ end
134
136
 
135
- # The total amount of tax due on this order
136
- def tax
137
- self.delivery_tax_amount +
138
- order_items.inject(BigDecimal(0)) { |t, i| t + i.tax_amount }
139
- end
137
+ # The total amount of tax due on this order
138
+ def tax
139
+ self.delivery_tax_amount +
140
+ order_items.inject(BigDecimal(0)) { |t, i| t + i.tax_amount }
141
+ end
140
142
 
141
- # The total of the order including tax
142
- def total
143
- self.delivery_price +
144
- self.delivery_tax_amount +
145
- order_items.inject(BigDecimal(0)) { |t, i| t + i.total }
146
- end
143
+ # The total of the order including tax
144
+ def total
145
+ self.delivery_price +
146
+ self.delivery_tax_amount +
147
+ order_items.inject(BigDecimal(0)) { |t, i| t + i.total }
148
+ end
147
149
 
148
- # The total of the order including tax in pence
149
- def total_in_pence
150
- (total * BigDecimal(100)).to_i
151
- end
150
+ # The total of the order including tax in pence
151
+ def total_in_pence
152
+ (total * BigDecimal(100)).to_i
153
+ end
152
154
 
153
- # The total weight of the order
154
- def total_weight
155
- order_items.inject(BigDecimal(0)) { |t,i| t + i.weight}
156
- end
155
+ # The total weight of the order
156
+ def total_weight
157
+ order_items.inject(BigDecimal(0)) { |t,i| t + i.weight}
158
+ end
157
159
 
158
- # An array of all the delivery service prices which can be applied to this order.
159
- def delivery_service_prices
160
- @delivery_service_prices ||= begin
161
- prices = Shoppe::DeliveryServicePrice.joins(:delivery_service).where(:shoppe_delivery_services => {:active => true}).order("`default` desc, price asc").for_weight(total_weight)
162
- prices = prices.select { |p| p.countries.empty? || p.country?(self.country) }
163
- prices
160
+ # An array of all the delivery service prices which can be applied to this order.
161
+ def delivery_service_prices
162
+ @delivery_service_prices ||= begin
163
+ prices = Shoppe::DeliveryServicePrice.joins(:delivery_service).where(:shoppe_delivery_services => {:active => true}).order("`default` desc, price asc").for_weight(total_weight)
164
+ prices = prices.select { |p| p.countries.empty? || p.country?(self.country) }
165
+ prices
166
+ end
164
167
  end
165
- end
166
168
 
167
- # An array of all the delivery services which are suitable for this order in it's
168
- # current state (based on its current weight)
169
- def available_delivery_services
170
- @available_delivery_services ||= begin
171
- delivery_service_prices.map(&:delivery_service).uniq
169
+ # An array of all the delivery services which are suitable for this order in it's
170
+ # current state (based on its current weight)
171
+ def available_delivery_services
172
+ @available_delivery_services ||= begin
173
+ delivery_service_prices.map(&:delivery_service).uniq
174
+ end
172
175
  end
173
- end
174
176
 
175
- # The recommended delivery service for this order
176
- def delivery_service
177
- super || available_delivery_services.first
178
- end
177
+ # The recommended delivery service for this order
178
+ def delivery_service
179
+ super || available_delivery_services.first
180
+ end
179
181
 
180
- # Return the delivery price for this order in its current state
181
- def delivery_service_price
182
- @delivery_service_price ||= self.delivery_service && self.delivery_service.delivery_service_prices.for_weight(self.total_weight).first
183
- end
182
+ # Return the delivery price for this order in its current state
183
+ def delivery_service_price
184
+ @delivery_service_price ||= self.delivery_service && self.delivery_service.delivery_service_prices.for_weight(self.total_weight).first
185
+ end
184
186
 
185
- # The price for delivering this order in its current state
186
- def delivery_price
187
- @delivery_price ||= read_attribute(:delivery_price) || delivery_service_price.try(:price) || 0.0
188
- end
187
+ # The price for delivering this order in its current state
188
+ def delivery_price
189
+ @delivery_price ||= read_attribute(:delivery_price) || delivery_service_price.try(:price) || 0.0
190
+ end
189
191
 
190
- # The cost of delivering this order in its current state
191
- def delivery_cost_price
192
- @delivery_cost_price ||= read_attribute(:delivery_cost_price) || delivery_service_price.try(:cost_price) || 0.0
193
- end
192
+ # The cost of delivering this order in its current state
193
+ def delivery_cost_price
194
+ @delivery_cost_price ||= read_attribute(:delivery_cost_price) || delivery_service_price.try(:cost_price) || 0.0
195
+ end
194
196
 
195
- # The tax amount due for the delivery of this order in its current state
196
- def delivery_tax_amount
197
- @delivery_tax_amount ||= begin
198
- read_attribute(:delivery_tax_amount) ||
199
- delivery_price / BigDecimal(100) * delivery_tax_rate ||
200
- 0.0
197
+ # The tax amount due for the delivery of this order in its current state
198
+ def delivery_tax_amount
199
+ @delivery_tax_amount ||= begin
200
+ read_attribute(:delivery_tax_amount) ||
201
+ delivery_price / BigDecimal(100) * delivery_tax_rate ||
202
+ 0.0
203
+ end
201
204
  end
202
- end
203
205
 
204
- # The tax rate for the delivery of this order in its current state
205
- def delivery_tax_rate
206
- @delivery_tax_rate ||= begin
207
- read_attribute(:delivery_tax_rate) ||
208
- delivery_service_price.try(:tax_rate).try(:rate_for, self) ||
209
- 0.0
206
+ # The tax rate for the delivery of this order in its current state
207
+ def delivery_tax_rate
208
+ @delivery_tax_rate ||= begin
209
+ read_attribute(:delivery_tax_rate) ||
210
+ delivery_service_price.try(:tax_rate).try(:rate_for, self) ||
211
+ 0.0
212
+ end
210
213
  end
211
- end
212
214
 
213
- # Is the currently assigned delivery service appropriate for this order?
214
- def valid_delivery_service?
215
- self.delivery_service && self.available_delivery_services.include?(self.delivery_service)
216
- end
215
+ # Is the currently assigned delivery service appropriate for this order?
216
+ def valid_delivery_service?
217
+ self.delivery_service && self.available_delivery_services.include?(self.delivery_service)
218
+ end
217
219
 
218
- # Remove the associated delivery service if it's invalid
219
- def remove_delivery_service_if_invalid
220
- unless self.valid_delivery_service?
221
- self.delivery_service = nil
222
- self.save
220
+ # Remove the associated delivery service if it's invalid
221
+ def remove_delivery_service_if_invalid
222
+ unless self.valid_delivery_service?
223
+ self.delivery_service = nil
224
+ self.save
225
+ end
223
226
  end
224
- end
225
227
 
226
- # The URL which can be used to track the delivery of this order
227
- def courier_tracking_url
228
- return nil if self.shipped_at.blank? || self.consignment_number.blank?
229
- @courier_tracking_url ||= self.delivery_service.tracking_url_for(self.consignment_number)
230
- end
228
+ # The URL which can be used to track the delivery of this order
229
+ def courier_tracking_url
230
+ return nil if self.shipped_at.blank? || self.consignment_number.blank?
231
+ @courier_tracking_url ||= self.delivery_service.tracking_url_for(self.consignment_number)
232
+ end
231
233
 
232
- # Has this order been fully paid for?
233
- def paid?
234
- !paid_at.blank?
235
- end
234
+ # Has this order been fully paid for?
235
+ def paid?
236
+ !paid_at.blank?
237
+ end
236
238
 
237
- # This method is called by the customer when they submit their details in the first step of
238
- # the checkout process. It will update the status to 'confirmed' as well as updating their
239
- # details. Any issues with validation will cause false to be returned otherwise true. Any
240
- # more serious issues will be raised as exceptions.
241
- def proceed_to_confirm(params = {})
242
- self.status = 'confirming'
243
- if self.update(params)
244
- true
245
- else
246
- false
239
+ # This method is called by the customer when they submit their details in the first step of
240
+ # the checkout process. It will update the status to 'confirmed' as well as updating their
241
+ # details. Any issues with validation will cause false to be returned otherwise true. Any
242
+ # more serious issues will be raised as exceptions.
243
+ def proceed_to_confirm(params = {})
244
+ self.status = 'confirming'
245
+ if self.update(params)
246
+ true
247
+ else
248
+ false
249
+ end
247
250
  end
248
- end
249
251
 
250
- # This method will confirm the order If there are any issues with the order an exception
251
- # should be raised.
252
- def confirm!
252
+ # This method will confirm the order If there are any issues with the order an exception
253
+ # should be raised.
254
+ def confirm!
253
255
 
254
- # Ensure that we have the stock to fulfil this order at the current time. We may have had it when
255
- # it was placed int he basket and if we don't now, we should let the user know so they can
256
- # rethink.
257
- no_stock_of = self.order_items.select(&:validate_stock_levels)
258
- unless no_stock_of.empty?
259
- raise Shoppe::Errors::InsufficientStockToFulfil, :order => self, :out_of_stock_items => no_stock_of
260
- end
256
+ # Ensure that we have the stock to fulfil this order at the current time. We may have had it when
257
+ # it was placed int he basket and if we don't now, we should let the user know so they can
258
+ # rethink.
259
+ no_stock_of = self.order_items.select(&:validate_stock_levels)
260
+ unless no_stock_of.empty?
261
+ raise Shoppe::Errors::InsufficientStockToFulfil, :order => self, :out_of_stock_items => no_stock_of
262
+ end
261
263
 
262
- # Ensure that before we confirm the order that the delivery service which has been selected
263
- # is appropritae for the contents of the order.
264
- unless self.valid_delivery_service?
265
- raise Shoppe::Errors::InappropriateDeliveryService, :order => self
266
- end
264
+ # Ensure that before we confirm the order that the delivery service which has been selected
265
+ # is appropritae for the contents of the order.
266
+ unless self.valid_delivery_service?
267
+ raise Shoppe::Errors::InappropriateDeliveryService, :order => self
268
+ end
267
269
 
268
- # Store the delivery prices with the order
269
- if self.delivery_service
270
- write_attribute :delivery_service_id, self.delivery_service.id
271
- write_attribute :delivery_price, self.delivery_price
272
- write_attribute :delivery_cost_price, self.delivery_cost_price
273
- write_attribute :delivery_tax_amount, self.delivery_tax_amount
274
- write_attribute :delivery_tax_rate, self.delivery_tax_rate
275
- end
270
+ # Store the delivery prices with the order
271
+ if self.delivery_service
272
+ write_attribute :delivery_service_id, self.delivery_service.id
273
+ write_attribute :delivery_price, self.delivery_price
274
+ write_attribute :delivery_cost_price, self.delivery_cost_price
275
+ write_attribute :delivery_tax_amount, self.delivery_tax_amount
276
+ write_attribute :delivery_tax_rate, self.delivery_tax_rate
277
+ end
276
278
 
277
- run_callbacks :confirmation do
278
- # If we have successfully charged the card (i.e. no exception) we can go ahead and mark this
279
- # order as 'received' which means it can be accepted by staff.
280
- self.status = 'received'
281
- self.received_at = Time.now
282
- self.save!
279
+ run_callbacks :confirmation do
280
+ # If we have successfully charged the card (i.e. no exception) we can go ahead and mark this
281
+ # order as 'received' which means it can be accepted by staff.
282
+ self.status = 'received'
283
+ self.received_at = Time.now
284
+ self.save!
283
285
 
284
- self.order_items.each(&:confirm!)
286
+ self.order_items.each(&:confirm!)
285
287
 
286
- # Send an email to the customer
287
- Shoppe::OrderMailer.received(self).deliver
288
- end
288
+ # Send an email to the customer
289
+ Shoppe::OrderMailer.received(self).deliver
290
+ end
289
291
 
290
- # We're all good.
291
- true
292
- end
292
+ # We're all good.
293
+ true
294
+ end
293
295
 
294
- # This method will mark an order as paid.
295
- def pay!(reference, method)
296
- run_callbacks :payment do
297
- self.paid_at = Time.now.utc
298
- self.payment_reference = reference
299
- self.payment_method = method
300
- self.save!
296
+ # This method will mark an order as paid.
297
+ def pay!(reference, method)
298
+ run_callbacks :payment do
299
+ self.paid_at = Time.now.utc
300
+ self.payment_reference = reference
301
+ self.payment_method = method
302
+ self.save!
303
+ end
301
304
  end
302
- end
303
305
 
304
- # This method will accept the this order. It is called by a user (which is the only
305
- # parameter).
306
- def accept!(user)
307
- run_callbacks :acceptance do
308
- self.accepted_at = Time.now
309
- self.accepted_by = user.id
310
- self.status = 'accepted'
311
- self.save!
312
- self.order_items.each(&:accept!)
313
- Shoppe::OrderMailer.accepted(self).deliver
306
+ # This method will accept the this order. It is called by a user (which is the only
307
+ # parameter).
308
+ def accept!(user)
309
+ run_callbacks :acceptance do
310
+ self.accepted_at = Time.now
311
+ self.accepted_by = user.id
312
+ self.status = 'accepted'
313
+ self.save!
314
+ self.order_items.each(&:accept!)
315
+ Shoppe::OrderMailer.accepted(self).deliver
316
+ end
314
317
  end
315
- end
316
318
 
317
- # This method will reject the order. It is called by a user (which is the only parameter).
318
- def reject!(user)
319
- run_callbacks :rejection do
320
- self.rejected_at = Time.now
321
- self.rejected_by = user.id
322
- self.status = 'rejected'
323
- self.save!
324
- self.order_items.each(&:reject!)
325
- Shoppe::OrderMailer.rejected(self).deliver
319
+ # This method will reject the order. It is called by a user (which is the only parameter).
320
+ def reject!(user)
321
+ run_callbacks :rejection do
322
+ self.rejected_at = Time.now
323
+ self.rejected_by = user.id
324
+ self.status = 'rejected'
325
+ self.save!
326
+ self.order_items.each(&:reject!)
327
+ Shoppe::OrderMailer.rejected(self).deliver
328
+ end
326
329
  end
327
- end
328
330
 
329
- # This method will mark an order as shipped and store the given consignment number with the
330
- # order for use later in tracking.
331
- def ship!(user, consignment_number)
332
- run_callbacks :ship do
333
- self.shipped_at = Time.now
334
- self.shipped_by = user.id
335
- self.status = 'shipped'
336
- self.consignment_number = consignment_number
337
- self.save!
338
- Shoppe::OrderMailer.shipped(self).deliver
331
+ # This method will mark an order as shipped and store the given consignment number with the
332
+ # order for use later in tracking.
333
+ def ship!(user, consignment_number)
334
+ run_callbacks :ship do
335
+ self.shipped_at = Time.now
336
+ self.shipped_by = user.id
337
+ self.status = 'shipped'
338
+ self.consignment_number = consignment_number
339
+ self.save!
340
+ Shoppe::OrderMailer.shipped(self).deliver
341
+ end
339
342
  end
340
- end
341
343
 
342
- # Specify which attributes can be searched
343
- def self.ransackable_attributes(auth_object = nil)
344
- ["id", "postcode", "address1", "address2", "address3", "address4", "first_name", "last_name", "company", "email_address", "phone_number", "consignment_number", "status", "received_at"] + _ransackers.keys
345
- end
344
+ # Specify which attributes can be searched
345
+ def self.ransackable_attributes(auth_object = nil)
346
+ ["id", "postcode", "address1", "address2", "address3", "address4", "first_name", "last_name", "company", "email_address", "phone_number", "consignment_number", "status", "received_at"] + _ransackers.keys
347
+ end
346
348
 
347
- # Specify which associations can be searched
348
- def self.ransackable_associations(auth_object = nil)
349
- ['products']
350
- end
349
+ # Specify which associations can be searched
350
+ def self.ransackable_associations(auth_object = nil)
351
+ ['products']
352
+ end
351
353
 
354
+ end
352
355
  end