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.
- checksums.yaml +4 -4
- data/app/assets/images/shoppe/table-tear-off.png +0 -0
- data/app/assets/javascripts/shoppe/application.coffee +2 -2
- data/app/assets/stylesheets/shoppe/application.scss +80 -37
- data/app/assets/stylesheets/shoppe/elements.scss +1 -2
- data/app/controllers/shoppe/attachments_controller.rb +3 -3
- data/app/controllers/shoppe/countries_controller.rb +13 -13
- data/app/controllers/shoppe/delivery_service_prices_controller.rb +11 -11
- data/app/controllers/shoppe/delivery_services_controller.rb +13 -13
- data/app/controllers/shoppe/orders_controller.rb +20 -20
- data/app/controllers/shoppe/payments_controller.rb +8 -8
- data/app/controllers/shoppe/product_categories_controller.rb +13 -13
- data/app/controllers/shoppe/products_controller.rb +24 -13
- data/app/controllers/shoppe/sessions_controller.rb +7 -7
- data/app/controllers/shoppe/settings_controller.rb +6 -6
- data/app/controllers/shoppe/stock_level_adjustments_controller.rb +5 -5
- data/app/controllers/shoppe/tax_rates_controller.rb +13 -13
- data/app/controllers/shoppe/users_controller.rb +15 -15
- data/app/controllers/shoppe/variants_controller.rb +13 -13
- data/app/helpers/shoppe/application_helper.rb +12 -12
- data/app/models/shoppe/order/actions.rb +17 -3
- data/app/models/shoppe/order/billing.rb +18 -18
- data/app/models/shoppe/order/delivery.rb +30 -31
- data/app/models/shoppe/order_item.rb +33 -34
- data/app/models/shoppe/payment.rb +15 -15
- data/app/models/shoppe/product.rb +82 -21
- data/app/models/shoppe/product/variants.rb +10 -10
- data/app/models/shoppe/product_category.rb +7 -7
- data/app/models/shoppe/stock_level_adjustment.rb +6 -6
- data/app/validators/permalink_validator.rb +7 -0
- data/app/views/layouts/shoppe/application.html.haml +10 -20
- data/app/views/shoppe/countries/_form.html.haml +15 -11
- data/app/views/shoppe/countries/edit.html.haml +5 -4
- data/app/views/shoppe/countries/index.html.haml +9 -9
- data/app/views/shoppe/countries/new.html.haml +6 -4
- data/app/views/shoppe/delivery_service_prices/_form.html.haml +16 -16
- data/app/views/shoppe/delivery_service_prices/edit.html.haml +5 -4
- data/app/views/shoppe/delivery_service_prices/index.html.haml +8 -9
- data/app/views/shoppe/delivery_service_prices/new.html.haml +5 -4
- data/app/views/shoppe/delivery_services/_form.html.haml +15 -14
- data/app/views/shoppe/delivery_services/edit.html.haml +7 -5
- data/app/views/shoppe/delivery_services/index.html.haml +11 -12
- data/app/views/shoppe/delivery_services/new.html.haml +5 -4
- data/app/views/shoppe/orders/_form.html.haml +19 -19
- data/app/views/shoppe/orders/_order_details.html.haml +20 -15
- data/app/views/shoppe/orders/_order_items.html.haml +6 -6
- data/app/views/shoppe/orders/_order_items_form.html.haml +11 -11
- data/app/views/shoppe/orders/_payments_form.html.haml +4 -4
- data/app/views/shoppe/orders/_payments_table.html.haml +12 -12
- data/app/views/shoppe/orders/_search_form.html.haml +10 -10
- data/app/views/shoppe/orders/_status_bar.html.haml +17 -13
- data/app/views/shoppe/orders/despatch_note.html.haml +15 -15
- data/app/views/shoppe/orders/edit.html.haml +8 -8
- data/app/views/shoppe/orders/index.html.haml +12 -12
- data/app/views/shoppe/orders/new.html.haml +7 -7
- data/app/views/shoppe/orders/show.html.haml +9 -12
- data/app/views/shoppe/payments/refund.html.haml +6 -7
- data/app/views/shoppe/product_categories/_form.html.haml +9 -9
- data/app/views/shoppe/product_categories/edit.html.haml +4 -4
- data/app/views/shoppe/product_categories/index.html.haml +5 -5
- data/app/views/shoppe/product_categories/new.html.haml +4 -3
- data/app/views/shoppe/products/_form.html.haml +49 -50
- data/app/views/shoppe/products/_table.html.haml +10 -10
- data/app/views/shoppe/products/edit.html.haml +6 -6
- data/app/views/shoppe/products/import.html.haml +63 -0
- data/app/views/shoppe/products/index.html.haml +6 -4
- data/app/views/shoppe/products/new.html.haml +6 -4
- data/app/views/shoppe/sessions/new.html.haml +5 -5
- data/app/views/shoppe/sessions/reset.html.haml +6 -5
- data/app/views/shoppe/settings/edit.html.haml +5 -5
- data/app/views/shoppe/shared/error.html.haml +3 -3
- data/app/views/shoppe/stock_level_adjustments/index.html.haml +13 -12
- data/app/views/shoppe/tax_rates/form.html.haml +13 -13
- data/app/views/shoppe/tax_rates/index.html.haml +5 -5
- data/app/views/shoppe/users/_form.html.haml +10 -11
- data/app/views/shoppe/users/edit.html.haml +4 -4
- data/app/views/shoppe/users/index.html.haml +5 -5
- data/app/views/shoppe/users/new.html.haml +6 -4
- data/app/views/shoppe/variants/form.html.haml +27 -29
- data/app/views/shoppe/variants/index.html.haml +11 -11
- data/config/locales/en.yml +619 -14
- data/config/locales/es.yml +650 -0
- data/config/locales/pl.yml +650 -0
- data/config/locales/pt-BR.yml +643 -0
- data/config/routes.rb +16 -8
- data/db/migrate/20141026175622_add_indexes_to_shoppe_order_items.rb +6 -0
- data/db/migrate/20141026175943_add_indexes_to_shoppe_orders.rb +7 -0
- data/db/migrate/20141026180333_add_indexes_to_shoppe_payments.rb +6 -0
- data/db/migrate/20141026180835_add_indexes_to_shoppe_product_attributes.rb +7 -0
- data/db/migrate/20141026180952_add_indexes_to_shoppe_product_categories.rb +5 -0
- data/db/migrate/20141026181040_add_indexes_to_shoppe_products.rb +8 -0
- data/db/migrate/20141026181312_add_indexes_to_shoppe_settings.rb +5 -0
- data/db/migrate/20141026181354_add_indexes_to_shoppe_stock_level_adjustments.rb +6 -0
- data/db/migrate/20141026181559_add_indexes_to_shoppe_users.rb +5 -0
- data/db/migrate/20141026181716_add_indexes_to_shoppe_delivery_services.rb +9 -0
- data/db/schema.rb +36 -1
- data/lib/shoppe/engine.rb +6 -0
- data/lib/shoppe/navigation_manager.rb +1 -1
- data/lib/shoppe/version.rb +1 -1
- 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,
|
|
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 =>
|
|
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
|
|
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
|