shoppe 0.0.16 → 0.0.17

Sign up to get free protection for your applications and to get access to all the features.
Files changed (78) hide show
  1. checksums.yaml +7 -0
  2. data/Rakefile +5 -6
  3. data/app/assets/javascripts/shoppe/application.coffee +19 -0
  4. data/app/assets/stylesheets/shoppe/application.scss +62 -20
  5. data/app/assets/stylesheets/shoppe/{chosen.css → chosen.scss} +18 -24
  6. data/app/assets/stylesheets/shoppe/dialog.scss +17 -2
  7. data/app/controllers/shoppe/orders_controller.rb +9 -7
  8. data/app/controllers/shoppe/payments_controller.rb +33 -0
  9. data/app/controllers/shoppe/settings_controller.rb +4 -0
  10. data/app/controllers/shoppe/tax_rates_controller.rb +1 -1
  11. data/app/controllers/shoppe/users_controller.rb +1 -1
  12. data/app/helpers/shoppe/application_helper.rb +0 -9
  13. data/app/models/shoppe/country.rb +17 -20
  14. data/app/models/shoppe/delivery_service.rb +16 -22
  15. data/app/models/shoppe/delivery_service_price.rb +10 -21
  16. data/app/models/shoppe/order/actions.rb +80 -0
  17. data/app/models/shoppe/order/billing.rb +99 -0
  18. data/app/models/shoppe/order/delivery.rb +196 -0
  19. data/app/models/shoppe/order/states.rb +69 -0
  20. data/app/models/shoppe/order.rb +29 -365
  21. data/app/models/shoppe/order_item.rb +52 -39
  22. data/app/models/shoppe/payment.rb +80 -0
  23. data/app/models/shoppe/product/product_attributes.rb +6 -4
  24. data/app/models/shoppe/product/variants.rb +20 -7
  25. data/app/models/shoppe/product.rb +37 -37
  26. data/app/models/shoppe/product_attribute.rb +13 -20
  27. data/app/models/shoppe/product_category.rb +6 -19
  28. data/app/models/shoppe/setting.rb +9 -0
  29. data/app/models/shoppe/stock_level_adjustment.rb +5 -18
  30. data/app/models/shoppe/tax_rate.rb +18 -21
  31. data/app/models/shoppe/user.rb +8 -15
  32. data/app/views/shoppe/delivery_service_prices/_form.html.haml +9 -3
  33. data/app/views/shoppe/delivery_service_prices/index.html.haml +6 -4
  34. data/app/views/shoppe/delivery_services/_form.html.haml +1 -1
  35. data/app/views/shoppe/orders/edit.html.haml +62 -0
  36. data/app/views/shoppe/orders/index.html.haml +2 -3
  37. data/app/views/shoppe/orders/show.html.haml +100 -63
  38. data/app/views/shoppe/payments/refund.html.haml +14 -0
  39. data/app/views/shoppe/product_categories/_form.html.haml +1 -1
  40. data/app/views/shoppe/products/_form.html.haml +8 -2
  41. data/app/views/shoppe/settings/edit.html.haml +1 -1
  42. data/app/views/shoppe/tax_rates/form.html.haml +5 -4
  43. data/app/views/shoppe/users/_form.html.haml +1 -1
  44. data/app/views/shoppe/variants/form.html.haml +2 -2
  45. data/config/routes.rb +3 -1
  46. data/db/migrate/20130926094549_create_shoppe_initial_schema.rb +1 -1
  47. data/db/migrate/20131024201501_add_address_type_to_shoppe_tax_rates.rb +5 -0
  48. data/db/migrate/20131024204815_create_shoppe_payments.rb +32 -0
  49. data/db/schema.rb +218 -0
  50. data/db/seeds.rb +15 -15
  51. data/lib/shoppe/engine.rb +7 -2
  52. data/lib/shoppe/errors/refund_failed.rb +15 -0
  53. data/lib/shoppe/settings.rb +0 -2
  54. data/lib/shoppe/version.rb +1 -1
  55. data/lib/shoppe/view_helpers.rb +16 -0
  56. data/lib/shoppe.rb +23 -6
  57. data/test/app/db/schema.rb +21 -6
  58. data/test/app/log/development.log +12782 -0
  59. data/test/app/tmp/cache/assets/development/sass/edac894564dae62b78e653a08d1c41f10ade93f9/application.scssc +0 -0
  60. data/test/app/tmp/cache/assets/development/sass/edac894564dae62b78e653a08d1c41f10ade93f9/chosen.scssc +0 -0
  61. data/test/app/tmp/cache/assets/development/sprockets/01d6eb5fc12044a487be4b93584690f2 +0 -0
  62. data/test/app/tmp/cache/assets/development/sprockets/02d3923383f72b56dd7919e301d22d24 +0 -0
  63. data/test/app/tmp/cache/assets/development/sprockets/0a6bca3e510625f255083bd154cc470b +0 -0
  64. data/test/app/tmp/cache/assets/development/sprockets/2f80004fb2e2ce07283a83ac15cf920a +0 -0
  65. data/test/app/tmp/cache/assets/development/sprockets/322295abdd8625fcce4da08f9565cc63 +0 -0
  66. data/test/app/tmp/cache/assets/development/sprockets/4c8cb5cfd87990ebddbaa5b5fd594be5 +0 -0
  67. data/test/app/tmp/cache/assets/development/sprockets/53c0f5159a54836310b1a3f5357bc4c6 +0 -0
  68. data/test/app/tmp/cache/assets/development/sprockets/7938636d16e11b754d4dd046b89863c4 +0 -0
  69. data/test/app/tmp/cache/assets/development/sprockets/7a90d9251a7c5506f33a3c72a224e571 +0 -0
  70. data/test/app/tmp/cache/assets/development/sprockets/9da17bb4868a0b762f8884db45c76ffd +0 -0
  71. data/test/app/tmp/cache/assets/development/sprockets/a692ba7ed6cff183bb840c2622233c87 +0 -0
  72. data/test/app/tmp/cache/assets/development/sprockets/a9befe910d55141b8ba02d8198b8f966 +0 -0
  73. data/test/app/tmp/cache/assets/development/sprockets/accc4dc17ef18d0b510917a005340da5 +0 -0
  74. data/test/app/tmp/cache/assets/development/sprockets/b9ad7ea18b7e55c3626a15d1dae142ed +0 -0
  75. data/test/app/tmp/cache/assets/development/sprockets/c733f1a2fe9d05a3a634ff64a394f64b +0 -0
  76. data/test/app/tmp/cache/assets/development/sprockets/da76586dcb6d9a408b2cf33307790d66 +0 -0
  77. metadata +62 -63
  78. data/README.rdoc +0 -1
@@ -1,425 +1,89 @@
1
- # == Schema Information
2
- #
3
- # Table name: shoppe_orders
4
- #
5
- # id :integer not null, primary key
6
- # token :string(255)
7
- # first_name :string(255)
8
- # last_name :string(255)
9
- # company :string(255)
10
- # billing_address1 :string(255)
11
- # billing_address2 :string(255)
12
- # billing_address3 :string(255)
13
- # billing_address4 :string(255)
14
- # billing_postcode :string(255)
15
- # billing_country_id :integer
16
- # email_address :string(255)
17
- # phone_number :string(255)
18
- # status :string(255)
19
- # received_at :datetime
20
- # accepted_at :datetime
21
- # shipped_at :datetime
22
- # created_at :datetime
23
- # updated_at :datetime
24
- # delivery_service_id :integer
25
- # delivery_price :decimal(8, 2)
26
- # delivery_cost_price :decimal(8, 2)
27
- # delivery_tax_rate :decimal(8, 2)
28
- # delivery_tax_amount :decimal(8, 2)
29
- # paid_at :datetime
30
- # accepted_by :integer
31
- # shipped_by :integer
32
- # consignment_number :string(255)
33
- # rejected_at :datetime
34
- # rejected_by :integer
35
- # ip_address :string(255)
36
- # payment_reference :string(255)
37
- # payment_method :string(255)
38
- # notes :text
39
- # separate_delivery_address :boolean default(FALSE)
40
- # delivery_address1 :string(255)
41
- # delivery_address2 :string(255)
42
- # delivery_address3 :string(255)
43
- # delivery_address4 :string(255)
44
- # deilvery_postcode :string(255)
45
- # delivery_country_id :integer
46
- #
47
-
48
1
  module Shoppe
49
2
  class Order < ActiveRecord::Base
50
3
 
51
- # Set the table name
52
4
  self.table_name = 'shoppe_orders'
53
5
 
54
- # An array of all the available statuses for an order
55
- STATUSES = ['building', 'confirming', 'received', 'accepted', 'rejected', 'shipped']
56
-
57
- # Order's implement a key value store for storing arbitary properties which
58
- # may be useful (for example payment configuraiton)
6
+ # Orders can have properties
59
7
  key_value_store :properties
8
+
9
+ # Require dependencies
10
+ require_dependency 'shoppe/order/states'
11
+ require_dependency 'shoppe/order/actions'
12
+ require_dependency 'shoppe/order/billing'
13
+ require_dependency 'shoppe/order/delivery'
60
14
 
61
- # These additional callbacks allow for applications to hook into other
62
- # parts of the order lifecycle.
63
- define_model_callbacks :confirmation, :payment, :acceptance, :rejection, :ship
64
-
65
- # Relationships
66
- belongs_to :delivery_service, :class_name => 'Shoppe::DeliveryService'
67
- belongs_to :billing_country, :class_name => 'Shoppe::Country', :foreign_key => 'billing_country_id'
68
- belongs_to :delivery_country, :class_name => 'Shoppe::Country', :foreign_key => 'delivery_country_id'
69
- belongs_to :accepter, :class_name => 'Shoppe::User', :foreign_key => 'accepted_by'
70
- belongs_to :rejecter, :class_name => 'Shoppe::User', :foreign_key => 'rejected_by'
71
- belongs_to :shipper, :class_name => 'Shoppe::User', :foreign_key => 'shipped_by'
15
+ # All items which make up this order
72
16
  has_many :order_items, :dependent => :destroy, :class_name => 'Shoppe::OrderItem'
17
+
18
+ # All products which are part of this order (accessed through the items)
73
19
  has_many :products, :through => :order_items, :class_name => 'Shoppe::Product'
74
-
20
+
75
21
  # Validations
76
22
  validates :token, :presence => true
77
- validates :status, :inclusion => {:in => STATUSES}
78
23
  with_options :if => Proc.new { |o| !o.building? } do |order|
79
- order.validates :first_name, :presence => true
80
- order.validates :last_name, :presence => true
81
- order.validates :billing_address1, :presence => true
82
- order.validates :billing_address3, :presence => true
83
- order.validates :billing_address4, :presence => true
84
- order.validates :billing_postcode, :presence => true
85
- order.validates :billing_country, :presence => true
86
24
  order.validates :email_address, :format => {:with => /\A\b[A-Z0-9\.\_\%\-\+]+@(?:[A-Z0-9\-]+\.)+[A-Z]{2,6}\b\z/i}
87
25
  order.validates :phone_number, :format => {:with => /\A[\d\ \-x\(\)]{7,}\z/}
88
26
  end
89
- with_options :if => :separate_delivery_address? do |order|
90
- order.validates :delivery_name, :presence => true
91
- order.validates :delivery_address1, :presence => true
92
- order.validates :delivery_address3, :presence => true
93
- order.validates :delivery_address4, :presence => true
94
- order.validates :delivery_postcode, :presence => true
95
- order.validates :delivery_country, :presence => true
96
- end
97
-
98
- validate do
99
- unless available_delivery_services.include?(self.delivery_service)
100
- errors.add :delivery_service_id, "is not suitable for this order"
101
- end
102
- end
103
-
104
- # Scopes
105
- scope :received, -> {where("received_at is not null")}
106
- scope :pending, -> { where(:status => 'received') }
107
- scope :ordered, -> { order('id desc')}
108
27
 
109
28
  # Set some defaults
110
- before_validation do
111
- self.status = 'building' if self.status.blank?
112
- self.token = SecureRandom.uuid if self.token.blank?
113
- end
29
+ before_validation { self.token = SecureRandom.uuid if self.token.blank? }
114
30
 
115
- [:delivery_name, :delivery_address1, :delivery_address2, :delivery_address3, :delivery_address4, :delivery_postcode, :delivery_country].each do |f|
116
- define_method(f) do
117
- separate_delivery_address? ? super() : send(f.to_s.gsub('delivery_', 'billing_'))
118
- end
119
- end
120
-
121
- # Is this order still being built by the user?
122
- def building?
123
- self.status == 'building'
124
- end
125
-
126
- # Is this order in the user confirmation step?
127
- def confirming?
128
- self.status == 'confirming'
129
- end
130
-
131
- # Has this order been rejected?
132
- def rejected?
133
- !!self.rejected_at
134
- end
135
-
136
- # Has this order been accepted?
137
- def accepted?
138
- !!self.accepted_at
139
- end
140
-
141
- # Has this order been shipped?
142
- def shipped?
143
- !!self.shipped_at?
144
- end
145
-
146
- # Has the order been received?
147
- def received?
148
- !!self.received_at?
149
- end
150
-
151
31
  # The order number
32
+ #
33
+ # @return [String] - the order number padded with at least 5 zeros
152
34
  def number
153
35
  id.to_s.rjust(6, '0')
154
36
  end
155
37
 
156
38
  # The length of time the customer spent building the order before submitting it to us.
157
39
  # The time from first item in basket to received.
40
+ #
41
+ # @return [Float] - the length of time
158
42
  def build_time
159
43
  return nil if self.received_at.blank?
160
44
  self.created_at - self.received_at
161
45
  end
162
46
 
163
- # The name of the customer
47
+ # The name of the customer in the format of "Company (First Last)" or if they don't have
48
+ # company specified, just "First Last".
49
+ #
50
+ # @return [String]
164
51
  def customer_name
165
52
  company.blank? ? full_name : "#{company} (#{full_name})"
166
53
  end
167
54
 
168
- # The name for billing purposes
169
- def billing_name
170
- company.blank? ? full_name : "#{full_name} (#{company})"
171
- end
172
-
173
- # The full anme of the customer
55
+ # The full name of the customer created by concatinting the first & last name
56
+ #
57
+ # @return [String]
174
58
  def full_name
175
59
  "#{first_name} #{last_name}"
176
60
  end
177
61
 
178
62
  # Is this order empty? (i.e. doesn't have any items associated with it)
63
+ #
64
+ # @return [Boolean]
179
65
  def empty?
180
66
  order_items.empty?
181
67
  end
182
68
 
183
69
  # Does this order have items?
70
+ #
71
+ # @return [Boolean]
184
72
  def has_items?
185
73
  total_items > 0
186
74
  end
187
75
 
188
76
  # Return the number of items in the order?
77
+ #
78
+ # @return [Integer]
189
79
  def total_items
190
80
  @total_items ||= order_items.inject(0) { |t,i| t + i.quantity }
191
81
  end
192
-
193
- # The total cost of the order
194
- def total_cost
195
- self.delivery_cost_price +
196
- order_items.inject(BigDecimal(0)) { |t, i| t + i.total_cost }
197
- end
198
-
199
- # Return the price for the order
200
- def profit
201
- total_before_tax - total_cost
202
- end
203
-
204
- # The total price of the order before tax
205
- def total_before_tax
206
- self.delivery_price +
207
- order_items.inject(BigDecimal(0)) { |t, i| t + i.sub_total }
208
- end
209
-
210
- # The total amount of tax due on this order
211
- def tax
212
- self.delivery_tax_amount +
213
- order_items.inject(BigDecimal(0)) { |t, i| t + i.tax_amount }
214
- end
215
-
216
- # The total of the order including tax
217
- def total
218
- self.delivery_price +
219
- self.delivery_tax_amount +
220
- order_items.inject(BigDecimal(0)) { |t, i| t + i.total }
221
- end
222
-
223
- # The total of the order including tax in pence
224
- def total_in_pence
225
- (total * BigDecimal(100)).to_i
226
- end
227
-
228
- # The total weight of the order
229
- def total_weight
230
- order_items.inject(BigDecimal(0)) { |t,i| t + i.weight}
231
- end
232
-
233
- # An array of all the delivery service prices which can be applied to this order.
234
- def delivery_service_prices
235
- @delivery_service_prices ||= begin
236
- prices = Shoppe::DeliveryServicePrice.joins(:delivery_service).where(:shoppe_delivery_services => {:active => true}).order("`default` desc, price asc").for_weight(total_weight)
237
- prices = prices.select { |p| p.countries.empty? || p.country?(self.billing_country) }
238
- prices
239
- end
240
- end
241
-
242
- # An array of all the delivery services which are suitable for this order in it's
243
- # current state (based on its current weight)
244
- def available_delivery_services
245
- @available_delivery_services ||= begin
246
- delivery_service_prices.map(&:delivery_service).uniq
247
- end
248
- end
249
-
250
- # The recommended delivery service for this order
251
- def delivery_service
252
- super || available_delivery_services.first
253
- end
254
-
255
- # Return the delivery price for this order in its current state
256
- def delivery_service_price
257
- @delivery_service_price ||= self.delivery_service && self.delivery_service.delivery_service_prices.for_weight(self.total_weight).first
258
- end
259
-
260
- # The price for delivering this order in its current state
261
- def delivery_price
262
- @delivery_price ||= read_attribute(:delivery_price) || delivery_service_price.try(:price) || 0.0
263
- end
264
-
265
- # The cost of delivering this order in its current state
266
- def delivery_cost_price
267
- @delivery_cost_price ||= read_attribute(:delivery_cost_price) || delivery_service_price.try(:cost_price) || 0.0
268
- end
269
-
270
- # The tax amount due for the delivery of this order in its current state
271
- def delivery_tax_amount
272
- @delivery_tax_amount ||= begin
273
- read_attribute(:delivery_tax_amount) ||
274
- delivery_price / BigDecimal(100) * delivery_tax_rate ||
275
- 0.0
276
- end
277
- end
278
-
279
- # The tax rate for the delivery of this order in its current state
280
- def delivery_tax_rate
281
- @delivery_tax_rate ||= begin
282
- read_attribute(:delivery_tax_rate) ||
283
- delivery_service_price.try(:tax_rate).try(:rate_for, self) ||
284
- 0.0
285
- end
286
- end
287
-
288
- # Is the currently assigned delivery service appropriate for this order?
289
- def valid_delivery_service?
290
- self.delivery_service && self.available_delivery_services.include?(self.delivery_service)
291
- end
292
-
293
- # Remove the associated delivery service if it's invalid
294
- def remove_delivery_service_if_invalid
295
- unless self.valid_delivery_service?
296
- self.delivery_service = nil
297
- self.save
298
- end
299
- end
300
-
301
- # The URL which can be used to track the delivery of this order
302
- def courier_tracking_url
303
- return nil if self.shipped_at.blank? || self.consignment_number.blank?
304
- @courier_tracking_url ||= self.delivery_service.tracking_url_for(self.consignment_number)
305
- end
306
-
307
- # Has this order been fully paid for?
308
- def paid?
309
- !paid_at.blank?
310
- end
311
-
312
- # This method is called by the customer when they submit their details in the first step of
313
- # the checkout process. It will update the status to 'confirmed' as well as updating their
314
- # details. Any issues with validation will cause false to be returned otherwise true. Any
315
- # more serious issues will be raised as exceptions.
316
- def proceed_to_confirm(params = {})
317
- self.status = 'confirming'
318
- if self.update(params)
319
- true
320
- else
321
- false
322
- end
323
- end
324
-
325
- # This method will confirm the order If there are any issues with the order an exception
326
- # should be raised.
327
- def confirm!
328
-
329
- # Ensure that we have the stock to fulfil this order at the current time. We may have had it when
330
- # it was placed int he basket and if we don't now, we should let the user know so they can
331
- # rethink.
332
- no_stock_of = self.order_items.select(&:validate_stock_levels)
333
- unless no_stock_of.empty?
334
- raise Shoppe::Errors::InsufficientStockToFulfil, :order => self, :out_of_stock_items => no_stock_of
335
- end
336
-
337
- # Ensure that before we confirm the order that the delivery service which has been selected
338
- # is appropritae for the contents of the order.
339
- unless self.valid_delivery_service?
340
- raise Shoppe::Errors::InappropriateDeliveryService, :order => self
341
- end
342
-
343
- # Store the delivery prices with the order
344
- if self.delivery_service
345
- write_attribute :delivery_service_id, self.delivery_service.id
346
- write_attribute :delivery_price, self.delivery_price
347
- write_attribute :delivery_cost_price, self.delivery_cost_price
348
- write_attribute :delivery_tax_amount, self.delivery_tax_amount
349
- write_attribute :delivery_tax_rate, self.delivery_tax_rate
350
- end
351
-
352
- run_callbacks :confirmation do
353
- # If we have successfully charged the card (i.e. no exception) we can go ahead and mark this
354
- # order as 'received' which means it can be accepted by staff.
355
- self.status = 'received'
356
- self.received_at = Time.now
357
- self.save!
358
-
359
- self.order_items.each(&:confirm!)
360
-
361
- # Send an email to the customer
362
- Shoppe::OrderMailer.received(self).deliver
363
- end
364
82
 
365
- # We're all good.
366
- true
367
- end
368
-
369
- # This method will mark an order as paid.
370
- def pay!(reference, method)
371
- run_callbacks :payment do
372
- self.paid_at = Time.now.utc
373
- self.payment_reference = reference
374
- self.payment_method = method
375
- self.save!
376
- end
377
- end
378
-
379
- # This method will accept the this order. It is called by a user (which is the only
380
- # parameter).
381
- def accept!(user)
382
- run_callbacks :acceptance do
383
- self.accepted_at = Time.now
384
- self.accepted_by = user.id
385
- self.status = 'accepted'
386
- self.save!
387
- self.order_items.each(&:accept!)
388
- Shoppe::OrderMailer.accepted(self).deliver
389
- end
390
- end
391
-
392
- # This method will reject the order. It is called by a user (which is the only parameter).
393
- def reject!(user)
394
- run_callbacks :rejection do
395
- self.rejected_at = Time.now
396
- self.rejected_by = user.id
397
- self.status = 'rejected'
398
- self.save!
399
- self.order_items.each(&:reject!)
400
- Shoppe::OrderMailer.rejected(self).deliver
401
- end
402
- end
403
-
404
- # This method will mark an order as shipped and store the given consignment number with the
405
- # order for use later in tracking.
406
- def ship!(user, consignment_number)
407
- run_callbacks :ship do
408
- self.shipped_at = Time.now
409
- self.shipped_by = user.id
410
- self.status = 'shipped'
411
- self.consignment_number = consignment_number
412
- self.save!
413
- Shoppe::OrderMailer.shipped(self).deliver
414
- end
415
- end
416
-
417
- # Specify which attributes can be searched
418
83
  def self.ransackable_attributes(auth_object = nil)
419
84
  ["id", "billing_postcode", "billing_address1", "billing_address2", "billing_address3", "billing_address4", "first_name", "last_name", "company", "email_address", "phone_number", "consignment_number", "status", "received_at"] + _ransackers.keys
420
85
  end
421
86
 
422
- # Specify which associations can be searched
423
87
  def self.ransackable_associations(auth_object = nil)
424
88
  ['products']
425
89
  end
@@ -1,42 +1,32 @@
1
- # == Schema Information
2
- #
3
- # Table name: shoppe_order_items
4
- #
5
- # id :integer not null, primary key
6
- # order_id :integer
7
- # ordered_item_id :integer
8
- # ordered_item_type :string(255)
9
- # quantity :integer default(1)
10
- # unit_price :decimal(8, 2)
11
- # unit_cost_price :decimal(8, 2)
12
- # tax_amount :decimal(8, 2)
13
- # tax_rate :decimal(8, 2)
14
- # weight :decimal(8, 3) default(0.0)
15
- # created_at :datetime
16
- # updated_at :datetime
17
- #
18
-
19
1
  module Shoppe
20
2
  class OrderItem < ActiveRecord::Base
21
3
 
22
- # Set the table name
23
4
  self.table_name = 'shoppe_order_items'
24
5
 
25
- # Relationships
6
+ # The associated order
7
+ #
8
+ # @return [Shoppe::Order]
26
9
  belongs_to :order, :class_name => 'Shoppe::Order'
10
+
11
+ # The item which has been ordered
27
12
  belongs_to :ordered_item, :polymorphic => true
13
+
14
+ # Any stock level adjustments which have been made for this order item
28
15
  has_many :stock_level_adjustments, :as => :parent, :dependent => :nullify, :class_name => 'Shoppe::StockLevelAdjustment'
29
-
16
+
30
17
  # Validations
31
18
  validates :quantity, :numericality => true
32
19
 
33
- before_validation do
34
- self.weight = self.quantity * self.ordered_item.weight
35
- end
20
+ # Set the weight as appropriate on save
21
+ before_validation { self.weight = self.quantity * self.ordered_item.weight }
36
22
 
37
23
  # This allows you to add a product to the scoped order. For example Order.first.order_items.add_product(...).
38
24
  # This will either increase the quantity of the value in the order or create a new item if one does not
39
25
  # exist already.
26
+ #
27
+ # @param ordered_item [Object] an object which implements the Shoppe::OrderableItem protocol
28
+ # @param quantity [Fixnum] the number of items to order
29
+ # @return [Shoppe::OrderItem]
40
30
  def self.add_item(ordered_item, quantity = 1)
41
31
  raise Errors::UnorderableItem, :ordered_item => ordered_item unless ordered_item.orderable?
42
32
  transaction do
@@ -46,22 +36,28 @@ module Shoppe
46
36
  else
47
37
  new_item = self.create(:ordered_item => ordered_item, :quantity => 0)
48
38
  new_item.increase!(quantity)
39
+ new_item
49
40
  end
50
41
  end
51
42
  end
52
43
 
53
- # This allows you to remove a product from an order. It will also ensure that the order's
54
- # custom delivery service is updated.
44
+ # Remove a product from an order. It will also ensure that the order's custom delivery
45
+ # service is updated if appropriate.
46
+ #
47
+ # @return [Shoppe::OrderItem]
55
48
  def remove
56
49
  transaction do
57
50
  self.destroy!
58
51
  self.order.remove_delivery_service_if_invalid
52
+ self
59
53
  end
60
54
  end
61
55
 
62
56
 
63
- # Increase the quantity of items in the order by the number provided. Will raise an error if we don't have
57
+ # Increases the quantity of items in the order by the number provided. Will raise an error if we don't have
64
58
  # the stock to do this.
59
+ #
60
+ # @param quantity [Fixnum]
65
61
  def increase!(amount = 1)
66
62
  transaction do
67
63
  self.quantity += amount
@@ -74,6 +70,8 @@ module Shoppe
74
70
  end
75
71
 
76
72
  # Decreases the quantity of items in the order by the number provided.
73
+ #
74
+ # @param amount [Fixnum]
77
75
  def decrease!(amount = 1)
78
76
  transaction do
79
77
  self.quantity -= amount
@@ -82,65 +80,80 @@ module Shoppe
82
80
  end
83
81
  end
84
82
 
85
- # Return the unit price for the item
83
+ # The unit price for the item
84
+ #
85
+ # @return [BigDecimal]
86
86
  def unit_price
87
87
  @unit_price ||= read_attribute(:unit_price) || ordered_item.try(:price) || 0.0
88
88
  end
89
89
 
90
- # Return the cost price for the item
90
+ # The cost price for the item
91
+ #
92
+ # @return [BigDecimal]
91
93
  def unit_cost_price
92
94
  @unit_cost_price ||= read_attribute(:unit_cost_price) || ordered_item.try(:cost_price) || 0.0
93
95
  end
94
96
 
95
- # Return the tax rate for the item
97
+ # The tax rate for the item
98
+ #
99
+ # @return [BigDecimal]
96
100
  def tax_rate
97
101
  @tax_rate ||= read_attribute(:tax_rate) || ordered_item.try(:tax_rate).try(:rate_for, self.order) || 0.0
98
102
  end
99
103
 
100
- # Return the total tax for the item
104
+ # The total tax for the item
105
+ #
106
+ # @return [BigDecimal]
101
107
  def tax_amount
102
108
  @tax_amount ||= read_attribute(:tax_amount) || (self.sub_total / BigDecimal(100)) * self.tax_rate
103
109
  end
104
110
 
105
- # Return the total cost for the product
111
+ # The total cost for the product
112
+ #
113
+ # @return [BigDecimal]
106
114
  def total_cost
107
115
  quantity * unit_cost_price
108
116
  end
109
117
 
110
- # Return the sub total for the product
118
+ # The sub total for the product
119
+ #
120
+ # @return [BigDecimal]
111
121
  def sub_total
112
122
  quantity * unit_price
113
123
  end
114
124
 
115
- # Return the total price including tax for the order line
125
+ # The total price including tax for the order line
126
+ #
127
+ # @return [BigDecimal]
116
128
  def total
117
129
  tax_amount + sub_total
118
130
  end
119
131
 
120
- # This method will be triggered when the parent order is confirmed. This should automatically
121
- # update the stock levels on the source product.
132
+ # Trigger when the associated order is confirmed. It handles caching the values
133
+ # of the monetary items and allocating stock as appropriate.
122
134
  def confirm!
123
135
  write_attribute :unit_price, self.unit_price
124
136
  write_attribute :unit_cost_price, self.unit_cost_price
125
137
  write_attribute :tax_rate, self.tax_rate
126
138
  write_attribute :tax_amount, self.tax_amount
127
139
  save!
128
-
129
140
  if self.ordered_item.stock_control?
130
141
  self.ordered_item.stock_level_adjustments.create(:parent => self, :adjustment => 0 - self.quantity, :description => "Order ##{self.order.number} deduction")
131
142
  end
132
143
  end
133
144
 
134
- # This method will be trigger when the parent order is accepted.
145
+ # Trigger when the associated order is accepted
135
146
  def accept!
136
147
  end
137
148
 
138
- # This method will be trigger when the parent order is rejected.
149
+ # Trigged when the associated order is rejected..
139
150
  def reject!
140
151
  self.stock_level_adjustments.destroy_all
141
152
  end
142
153
 
143
154
  # Do we have the stock needed to fulfil this order?
155
+ #
156
+ # @return [Boolean]
144
157
  def in_stock?
145
158
  if self.ordered_item.stock_control?
146
159
  self.ordered_item.stock >= self.quantity