shoppe 1.0.2 → 1.0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (100) hide show
  1. checksums.yaml +4 -4
  2. data/app/assets/images/shoppe/table-tear-off.png +0 -0
  3. data/app/assets/javascripts/shoppe/application.coffee +2 -2
  4. data/app/assets/stylesheets/shoppe/application.scss +80 -37
  5. data/app/assets/stylesheets/shoppe/elements.scss +1 -2
  6. data/app/controllers/shoppe/attachments_controller.rb +3 -3
  7. data/app/controllers/shoppe/countries_controller.rb +13 -13
  8. data/app/controllers/shoppe/delivery_service_prices_controller.rb +11 -11
  9. data/app/controllers/shoppe/delivery_services_controller.rb +13 -13
  10. data/app/controllers/shoppe/orders_controller.rb +20 -20
  11. data/app/controllers/shoppe/payments_controller.rb +8 -8
  12. data/app/controllers/shoppe/product_categories_controller.rb +13 -13
  13. data/app/controllers/shoppe/products_controller.rb +24 -13
  14. data/app/controllers/shoppe/sessions_controller.rb +7 -7
  15. data/app/controllers/shoppe/settings_controller.rb +6 -6
  16. data/app/controllers/shoppe/stock_level_adjustments_controller.rb +5 -5
  17. data/app/controllers/shoppe/tax_rates_controller.rb +13 -13
  18. data/app/controllers/shoppe/users_controller.rb +15 -15
  19. data/app/controllers/shoppe/variants_controller.rb +13 -13
  20. data/app/helpers/shoppe/application_helper.rb +12 -12
  21. data/app/models/shoppe/order/actions.rb +17 -3
  22. data/app/models/shoppe/order/billing.rb +18 -18
  23. data/app/models/shoppe/order/delivery.rb +30 -31
  24. data/app/models/shoppe/order_item.rb +33 -34
  25. data/app/models/shoppe/payment.rb +15 -15
  26. data/app/models/shoppe/product.rb +82 -21
  27. data/app/models/shoppe/product/variants.rb +10 -10
  28. data/app/models/shoppe/product_category.rb +7 -7
  29. data/app/models/shoppe/stock_level_adjustment.rb +6 -6
  30. data/app/validators/permalink_validator.rb +7 -0
  31. data/app/views/layouts/shoppe/application.html.haml +10 -20
  32. data/app/views/shoppe/countries/_form.html.haml +15 -11
  33. data/app/views/shoppe/countries/edit.html.haml +5 -4
  34. data/app/views/shoppe/countries/index.html.haml +9 -9
  35. data/app/views/shoppe/countries/new.html.haml +6 -4
  36. data/app/views/shoppe/delivery_service_prices/_form.html.haml +16 -16
  37. data/app/views/shoppe/delivery_service_prices/edit.html.haml +5 -4
  38. data/app/views/shoppe/delivery_service_prices/index.html.haml +8 -9
  39. data/app/views/shoppe/delivery_service_prices/new.html.haml +5 -4
  40. data/app/views/shoppe/delivery_services/_form.html.haml +15 -14
  41. data/app/views/shoppe/delivery_services/edit.html.haml +7 -5
  42. data/app/views/shoppe/delivery_services/index.html.haml +11 -12
  43. data/app/views/shoppe/delivery_services/new.html.haml +5 -4
  44. data/app/views/shoppe/orders/_form.html.haml +19 -19
  45. data/app/views/shoppe/orders/_order_details.html.haml +20 -15
  46. data/app/views/shoppe/orders/_order_items.html.haml +6 -6
  47. data/app/views/shoppe/orders/_order_items_form.html.haml +11 -11
  48. data/app/views/shoppe/orders/_payments_form.html.haml +4 -4
  49. data/app/views/shoppe/orders/_payments_table.html.haml +12 -12
  50. data/app/views/shoppe/orders/_search_form.html.haml +10 -10
  51. data/app/views/shoppe/orders/_status_bar.html.haml +17 -13
  52. data/app/views/shoppe/orders/despatch_note.html.haml +15 -15
  53. data/app/views/shoppe/orders/edit.html.haml +8 -8
  54. data/app/views/shoppe/orders/index.html.haml +12 -12
  55. data/app/views/shoppe/orders/new.html.haml +7 -7
  56. data/app/views/shoppe/orders/show.html.haml +9 -12
  57. data/app/views/shoppe/payments/refund.html.haml +6 -7
  58. data/app/views/shoppe/product_categories/_form.html.haml +9 -9
  59. data/app/views/shoppe/product_categories/edit.html.haml +4 -4
  60. data/app/views/shoppe/product_categories/index.html.haml +5 -5
  61. data/app/views/shoppe/product_categories/new.html.haml +4 -3
  62. data/app/views/shoppe/products/_form.html.haml +49 -50
  63. data/app/views/shoppe/products/_table.html.haml +10 -10
  64. data/app/views/shoppe/products/edit.html.haml +6 -6
  65. data/app/views/shoppe/products/import.html.haml +63 -0
  66. data/app/views/shoppe/products/index.html.haml +6 -4
  67. data/app/views/shoppe/products/new.html.haml +6 -4
  68. data/app/views/shoppe/sessions/new.html.haml +5 -5
  69. data/app/views/shoppe/sessions/reset.html.haml +6 -5
  70. data/app/views/shoppe/settings/edit.html.haml +5 -5
  71. data/app/views/shoppe/shared/error.html.haml +3 -3
  72. data/app/views/shoppe/stock_level_adjustments/index.html.haml +13 -12
  73. data/app/views/shoppe/tax_rates/form.html.haml +13 -13
  74. data/app/views/shoppe/tax_rates/index.html.haml +5 -5
  75. data/app/views/shoppe/users/_form.html.haml +10 -11
  76. data/app/views/shoppe/users/edit.html.haml +4 -4
  77. data/app/views/shoppe/users/index.html.haml +5 -5
  78. data/app/views/shoppe/users/new.html.haml +6 -4
  79. data/app/views/shoppe/variants/form.html.haml +27 -29
  80. data/app/views/shoppe/variants/index.html.haml +11 -11
  81. data/config/locales/en.yml +619 -14
  82. data/config/locales/es.yml +650 -0
  83. data/config/locales/pl.yml +650 -0
  84. data/config/locales/pt-BR.yml +643 -0
  85. data/config/routes.rb +16 -8
  86. data/db/migrate/20141026175622_add_indexes_to_shoppe_order_items.rb +6 -0
  87. data/db/migrate/20141026175943_add_indexes_to_shoppe_orders.rb +7 -0
  88. data/db/migrate/20141026180333_add_indexes_to_shoppe_payments.rb +6 -0
  89. data/db/migrate/20141026180835_add_indexes_to_shoppe_product_attributes.rb +7 -0
  90. data/db/migrate/20141026180952_add_indexes_to_shoppe_product_categories.rb +5 -0
  91. data/db/migrate/20141026181040_add_indexes_to_shoppe_products.rb +8 -0
  92. data/db/migrate/20141026181312_add_indexes_to_shoppe_settings.rb +5 -0
  93. data/db/migrate/20141026181354_add_indexes_to_shoppe_stock_level_adjustments.rb +6 -0
  94. data/db/migrate/20141026181559_add_indexes_to_shoppe_users.rb +5 -0
  95. data/db/migrate/20141026181716_add_indexes_to_shoppe_delivery_services.rb +9 -0
  96. data/db/schema.rb +36 -1
  97. data/lib/shoppe/engine.rb +6 -0
  98. data/lib/shoppe/navigation_manager.rb +1 -1
  99. data/lib/shoppe/version.rb +1 -1
  100. metadata +67 -5
@@ -1,43 +1,43 @@
1
1
  module Shoppe
2
2
  class OrderItem < ActiveRecord::Base
3
-
3
+
4
4
  self.table_name = 'shoppe_order_items'
5
-
5
+
6
6
  # The associated order
7
7
  #
8
8
  # @return [Shoppe::Order]
9
9
  belongs_to :order, :class_name => 'Shoppe::Order', :touch => true
10
-
10
+
11
11
  # The item which has been ordered
12
12
  belongs_to :ordered_item, :polymorphic => true
13
-
13
+
14
14
  # Any stock level adjustments which have been made for this order item
15
15
  has_many :stock_level_adjustments, :as => :parent, :dependent => :nullify, :class_name => 'Shoppe::StockLevelAdjustment'
16
-
16
+
17
17
  # Validations
18
18
  validates :quantity, :numericality => true
19
19
  validates :ordered_item, :presence => true
20
-
20
+
21
21
  validate do
22
22
  unless in_stock?
23
- errors.add :quantity, "is too high for the quantity in stock"
23
+ errors.add :quantity, :too_hight_quantity
24
24
  end
25
25
  end
26
-
26
+
27
27
  # Before saving an order item which belongs to a received order, cache the pricing again if appropriate.
28
28
  before_save do
29
29
  if order.received? && (unit_price_changed? || unit_cost_price_changed? || tax_rate_changed? || tax_amount_changed?)
30
30
  cache_pricing
31
31
  end
32
32
  end
33
-
33
+
34
34
  # After saving, if the order has been shipped, reallocate stock appropriate
35
35
  after_save do
36
36
  if order.shipped?
37
37
  allocate_unallocated_stock!
38
38
  end
39
39
  end
40
-
40
+
41
41
  # This allows you to add a product to the scoped order. For example Order.first.order_items.add_product(...).
42
42
  # This will either increase the quantity of the value in the order or create a new item if one does not
43
43
  # exist already.
@@ -70,8 +70,7 @@ module Shoppe
70
70
  self
71
71
  end
72
72
  end
73
-
74
-
73
+
75
74
  # Increases the quantity of items in the order by the number provided. Will raise an error if we don't have
76
75
  # the stock to do this.
77
76
  #
@@ -86,7 +85,7 @@ module Shoppe
86
85
  self.order.remove_delivery_service_if_invalid
87
86
  end
88
87
  end
89
-
88
+
90
89
  # Decreases the quantity of items in the order by the number provided.
91
90
  #
92
91
  # @param amount [Fixnum]
@@ -97,70 +96,70 @@ module Shoppe
97
96
  self.order.remove_delivery_service_if_invalid
98
97
  end
99
98
  end
100
-
99
+
101
100
  # The total weight of the item
102
101
  #
103
102
  # @return [BigDecimal]
104
103
  def weight
105
104
  read_attribute(:weight) || ordered_item.try(:weight) || BigDecimal(0)
106
105
  end
107
-
106
+
108
107
  # Return the total weight of the item
109
108
  #
110
109
  # @return [BigDecimal]
111
110
  def total_weight
112
111
  quantity * weight
113
112
  end
114
-
113
+
115
114
  # The unit price for the item
116
115
  #
117
116
  # @return [BigDecimal]
118
117
  def unit_price
119
118
  read_attribute(:unit_price) || ordered_item.try(:price) || BigDecimal(0)
120
119
  end
121
-
120
+
122
121
  # The cost price for the item
123
122
  #
124
123
  # @return [BigDecimal]
125
124
  def unit_cost_price
126
125
  read_attribute(:unit_cost_price) || ordered_item.try(:cost_price) || BigDecimal(0)
127
126
  end
128
-
127
+
129
128
  # The tax rate for the item
130
129
  #
131
130
  # @return [BigDecimal]
132
131
  def tax_rate
133
- read_attribute(:tax_rate) || ordered_item.try(:tax_rate).try(:rate_for, self.order) || BigDecimal(0)
132
+ read_attribute(:tax_rate) || ordered_item.try(:tax_rate).try(:rate_for, self.order) || BigDecimal(0)
134
133
  end
135
-
134
+
136
135
  # The total tax for the item
137
136
  #
138
137
  # @return [BigDecimal]
139
138
  def tax_amount
140
139
  read_attribute(:tax_amount) || (self.sub_total / BigDecimal(100)) * self.tax_rate
141
140
  end
142
-
141
+
143
142
  # The total cost for the product
144
143
  #
145
144
  # @return [BigDecimal]
146
145
  def total_cost
147
146
  quantity * unit_cost_price
148
147
  end
149
-
148
+
150
149
  # The sub total for the product
151
150
  #
152
151
  # @return [BigDecimal]
153
152
  def sub_total
154
153
  quantity * unit_price
155
154
  end
156
-
155
+
157
156
  # The total price including tax for the order line
158
157
  #
159
158
  # @return [BigDecimal]
160
159
  def total
161
160
  tax_amount + sub_total
162
161
  end
163
-
162
+
164
163
  # Cache the pricing for this order item
165
164
  def cache_pricing
166
165
  write_attribute :weight, self.weight
@@ -168,29 +167,29 @@ module Shoppe
168
167
  write_attribute :unit_cost_price, self.unit_cost_price
169
168
  write_attribute :tax_rate, self.tax_rate
170
169
  end
171
-
170
+
172
171
  # Cache the pricing for this order item and save
173
172
  def cache_pricing!
174
173
  cache_pricing
175
174
  save!
176
175
  end
177
-
176
+
178
177
  # Trigger when the associated order is confirmed. It handles caching the values
179
178
  # of the monetary items and allocating stock as appropriate.
180
179
  def confirm!
181
180
  cache_pricing!
182
181
  allocate_unallocated_stock!
183
182
  end
184
-
183
+
185
184
  # Trigger when the associated order is accepted
186
185
  def accept!
187
186
  end
188
-
187
+
189
188
  # Trigged when the associated order is rejected..
190
189
  def reject!
191
190
  self.stock_level_adjustments.destroy_all
192
191
  end
193
-
192
+
194
193
  # Do we have the stock needed to fulfil this order?
195
194
  #
196
195
  # @return [Boolean]
@@ -201,21 +200,21 @@ module Shoppe
201
200
  true
202
201
  end
203
202
  end
204
-
203
+
205
204
  # How much stock remains to be allocated for this order?
206
205
  #
207
206
  # @return [Fixnum]
208
207
  def unallocated_stock
209
208
  self.quantity - allocated_stock
210
209
  end
211
-
210
+
212
211
  # How much stock has been allocated to this item?
213
212
  #
214
213
  # @return [Fixnum]
215
214
  def allocated_stock
216
215
  0 - self.stock_level_adjustments.sum(:adjustment)
217
216
  end
218
-
217
+
219
218
  # Validate the stock level against the product and update as appropriate. This method will be executed
220
219
  # before an order is completed. If we have run out of this product, we will update the quantity to an
221
220
  # appropriate level (or remove the order item) and return the object.
@@ -228,13 +227,13 @@ module Shoppe
228
227
  self
229
228
  end
230
229
  end
231
-
230
+
232
231
  # Allocate any unallocated stock for this order item. There is no return value.
233
232
  def allocate_unallocated_stock!
234
233
  if self.ordered_item.stock_control? && self.unallocated_stock != 0
235
234
  self.ordered_item.stock_level_adjustments.create!(:parent => self, :adjustment => 0 - self.unallocated_stock, :description => "Order ##{self.order.number}")
236
235
  end
237
236
  end
238
-
237
+
239
238
  end
240
239
  end
@@ -1,51 +1,51 @@
1
1
  module Shoppe
2
2
  class Payment < ActiveRecord::Base
3
-
3
+
4
4
  # The associated order
5
5
  #
6
6
  # @return [Shoppe::Order]
7
7
  belongs_to :order, :class_name => 'Shoppe::Order'
8
-
8
+
9
9
  # An associated payment (only applies to refunds)
10
10
  #
11
11
  # @return [Shoppe::Payment]
12
12
  belongs_to :parent, :class_name => "Shoppe::Payment", :foreign_key => "parent_payment_id"
13
-
13
+
14
14
  # Validatiosn
15
15
  validates :amount, :numericality => true
16
16
  validates :reference, :presence => true
17
17
  validates :method, :presence => true
18
-
18
+
19
19
  # Payments can have associated properties
20
20
  key_value_store :properties
21
-
21
+
22
22
  # Callbacks
23
23
  after_create :cache_amount_paid
24
24
  after_destroy :cache_amount_paid
25
25
  before_destroy { self.parent.update_attribute(:amount_refunded, self.parent.amount_refunded + amount) if self.parent }
26
-
26
+
27
27
  # Is this payment a refund?
28
28
  #
29
29
  # @return [Boolean]
30
30
  def refund?
31
31
  self.amount < BigDecimal(0)
32
32
  end
33
-
33
+
34
34
  # Has this payment had any refunds taken from it?
35
35
  #
36
36
  # @return [Boolean]
37
37
  def refunded?
38
38
  self.amount_refunded > BigDecimal(0)
39
39
  end
40
-
40
+
41
41
  # How much of the payment can be refunded
42
42
  #
43
43
  # @return [BigDecimal]
44
44
  def refundable_amount
45
45
  refundable? ? (self.amount - self.amount_refunded) : BigDecimal(0)
46
46
  end
47
-
48
- # Process a refund from this payment.
47
+
48
+ # Process a refund from this payment.
49
49
  #
50
50
  # @param amount [String] the amount which should be refunded
51
51
  # @return [Boolean]
@@ -58,10 +58,10 @@ module Shoppe
58
58
  true
59
59
  end
60
60
  else
61
- raise Shoppe::Errors::RefundFailed, :message => "Refunds must be less than the payment (#{refundable_amount})"
61
+ raise Shoppe::Errors::RefundFailed, :message => I18n.t('.refund_failed', refundable_amount: refundable_amount)
62
62
  end
63
63
  end
64
-
64
+
65
65
  # Return a transaction URL for viewing further information about this
66
66
  # payment.
67
67
  #
@@ -69,12 +69,12 @@ module Shoppe
69
69
  def transaction_url
70
70
  nil
71
71
  end
72
-
72
+
73
73
  private
74
-
74
+
75
75
  def cache_amount_paid
76
76
  self.order.update_attribute(:amount_paid, self.order.payments.sum(:amount))
77
77
  end
78
-
78
+
79
79
  end
80
80
  end
@@ -1,35 +1,37 @@
1
+ require 'roo'
2
+
1
3
  module Shoppe
2
4
  class Product < ActiveRecord::Base
3
-
4
- self.table_name = 'shoppe_products'
5
-
5
+
6
+ self.table_name = 'shoppe_products'
7
+
6
8
  # Add dependencies for products
7
9
  require_dependency 'shoppe/product/product_attributes'
8
10
  require_dependency 'shoppe/product/variants'
9
-
11
+
10
12
  # Products have a default_image and a data_sheet
11
13
  attachment :default_image
12
14
  attachment :data_sheet
13
-
15
+
14
16
  # The product's category
15
17
  #
16
18
  # @return [Shoppe::ProductCategory]
17
19
  belongs_to :product_category, :class_name => 'Shoppe::ProductCategory'
18
-
20
+
19
21
  # The product's tax rate
20
22
  #
21
23
  # @return [Shoppe::TaxRate]
22
24
  belongs_to :tax_rate, :class_name => "Shoppe::TaxRate"
23
-
25
+
24
26
  # Ordered items which are associated with this product
25
27
  has_many :order_items, :dependent => :restrict_with_exception, :class_name => 'Shoppe::OrderItem', :as => :ordered_item
26
-
28
+
27
29
  # Orders which have ordered this product
28
30
  has_many :orders, :through => :order_items, :class_name => 'Shoppe::Order'
29
-
31
+
30
32
  # Stock level adjustments for this product
31
33
  has_many :stock_level_adjustments, :dependent => :destroy, :class_name => 'Shoppe::StockLevelAdjustment', :as => :item
32
-
34
+
33
35
  # Validations
34
36
  with_options :if => Proc.new { |p| p.parent.nil? } do |product|
35
37
  product.validates :product_category_id, :presence => true
@@ -37,31 +39,31 @@ module Shoppe
37
39
  product.validates :short_description, :presence => true
38
40
  end
39
41
  validates :name, :presence => true
40
- validates :permalink, :presence => true, :uniqueness => true
42
+ validates :permalink, :presence => true, :uniqueness => true, :permalink => true
41
43
  validates :sku, :presence => true
42
44
  validates :weight, :numericality => true
43
45
  validates :price, :numericality => true
44
46
  validates :cost_price, :numericality => true, :allow_blank => true
45
-
47
+
46
48
  # Before validation, set the permalink if we don't already have one
47
49
  before_validation { self.permalink = self.name.parameterize if self.permalink.blank? && self.name.is_a?(String) }
48
-
50
+
49
51
  # All active products
50
52
  scope :active, -> { where(:active => true) }
51
-
53
+
52
54
  # All featured products
53
55
  scope :featured, -> {where(:featured => true)}
54
-
56
+
55
57
  # All products ordered with default items first followed by name ascending
56
58
  scope :ordered, -> {order(:default => :desc, :name => :asc)}
57
-
59
+
58
60
  # Return the name of the product
59
61
  #
60
62
  # @return [String]
61
63
  def full_name
62
64
  self.parent ? "#{self.parent.name} (#{name})" : name
63
65
  end
64
-
66
+
65
67
  # Is this product orderable?
66
68
  #
67
69
  # @return [Boolean]
@@ -70,7 +72,7 @@ module Shoppe
70
72
  return false if self.has_variants?
71
73
  true
72
74
  end
73
-
75
+
74
76
  # The price for the product
75
77
  #
76
78
  # @return [BigDecimal]
@@ -84,15 +86,15 @@ module Shoppe
84
86
  def in_stock?
85
87
  self.default_variant ? self.default_variant.in_stock? : (stock_control? ? stock > 0 : true)
86
88
  end
87
-
89
+
88
90
  # Return the total number of items currently in stock
89
91
  #
90
92
  # @return [Fixnum]
91
93
  def stock
92
94
  self.stock_level_adjustments.sum(:adjustment)
93
95
  end
94
-
95
- # Search for products which include the guven attributes and return an active record
96
+
97
+ # Search for products which include the given attributes and return an active record
96
98
  # scope of these products. Chainable with other scopes and with_attributes methods.
97
99
  # For example:
98
100
  #
@@ -104,5 +106,64 @@ module Shoppe
104
106
  where(:id => product_ids)
105
107
  end
106
108
 
109
+ # Imports products from a spreadsheet file
110
+ # Example:
111
+ #
112
+ # Shoppe:Product.import("path/to/file.csv")
113
+ def self.import(file)
114
+ spreadsheet = open_spreadsheet(file)
115
+ spreadsheet.default_sheet = spreadsheet.sheets.first
116
+ header = spreadsheet.row(1)
117
+ (2..spreadsheet.last_row).each do |i|
118
+ row = Hash[[header, spreadsheet.row(i)].transpose]
119
+
120
+ # Don't import products where the name is blank
121
+ unless row["name"].nil?
122
+ if product = find_by(name: row["name"])
123
+ # Dont import products with the same name but update quantities if they're not the same
124
+ qty = row["qty"].to_i
125
+ if qty > 0 && qty != product.stock
126
+ product.stock_level_adjustments.create!(description: I18n.t('shoppe.import'), adjustment: qty)
127
+ end
128
+ else
129
+ product = new
130
+ product.name = row["name"]
131
+ product.sku = row["sku"]
132
+ product.description = row["description"]
133
+ product.short_description = row["short_description"]
134
+ product.weight = row["weight"]
135
+ product.price = row["price"].nil? ? 0 : row["price"]
136
+
137
+ product.product_category_id = begin
138
+ if Shoppe::ProductCategory.find_by(name: row["category_name"]).present?
139
+ # Find and set the category
140
+ Shoppe::ProductCategory.find_by(name: row["category_name"]).id
141
+ else
142
+ # Create the category
143
+ Shoppe::ProductCategory.create(name: row["category_name"]).id
144
+ end
145
+ end
146
+
147
+ product.save!
148
+
149
+ # Create quantities
150
+ qty = row["qty"].to_i
151
+ if qty > 0
152
+ product.stock_level_adjustments.create!(description: I18n.t('shoppe.import'), adjustment: qty)
153
+ end
154
+ end
155
+ end
156
+ end
157
+ end
158
+
159
+ def self.open_spreadsheet(file)
160
+ case File.extname(file.original_filename)
161
+ when ".csv" then Roo::CSV.new(file.path)
162
+ when ".xls" then Roo::Excel.new(file.path)
163
+ when ".xlsx" then Roo::Excelx.new(file.path)
164
+ else raise I18n.t('shoppe.imports.errors.unknown_format', filename: File.original_filename)
165
+ end
166
+ end
167
+
107
168
  end
108
169
  end