shoppe 1.0.2 → 1.0.3

Sign up to get free protection for your applications and to get access to all the features.
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