kylekthompson-shoppe 1.0.7
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 +7 -0
- data/MIT-LICENSE +20 -0
- data/README.md +55 -0
- data/Rakefile +36 -0
- data/app/assets/images/shoppe/chosen-sprite.png +0 -0
- data/app/assets/images/shoppe/chosen-sprite@2x.png +0 -0
- data/app/assets/images/shoppe/document.svg +1 -0
- data/app/assets/images/shoppe/icons/bag.svg +1 -0
- data/app/assets/images/shoppe/icons/balance.svg +1 -0
- data/app/assets/images/shoppe/icons/box.svg +1 -0
- data/app/assets/images/shoppe/icons/building.svg +1 -0
- data/app/assets/images/shoppe/icons/chart.svg +1 -0
- data/app/assets/images/shoppe/icons/chat.svg +1 -0
- data/app/assets/images/shoppe/icons/checkbox.svg +1 -0
- data/app/assets/images/shoppe/icons/checkbox2.svg +1 -0
- data/app/assets/images/shoppe/icons/cloud.svg +1 -0
- data/app/assets/images/shoppe/icons/cone.svg +1 -0
- data/app/assets/images/shoppe/icons/credit_card.svg +1 -0
- data/app/assets/images/shoppe/icons/currency.svg +1 -0
- data/app/assets/images/shoppe/icons/edit.svg +14 -0
- data/app/assets/images/shoppe/icons/flowchart.svg +1 -0
- data/app/assets/images/shoppe/icons/gift.svg +1 -0
- data/app/assets/images/shoppe/icons/globe.svg +1 -0
- data/app/assets/images/shoppe/icons/id.svg +1 -0
- data/app/assets/images/shoppe/icons/id2.svg +1 -0
- data/app/assets/images/shoppe/icons/locked.svg +1 -0
- data/app/assets/images/shoppe/icons/report.svg +1 -0
- data/app/assets/images/shoppe/icons/search.svg +1 -0
- data/app/assets/images/shoppe/icons/support.svg +1 -0
- data/app/assets/images/shoppe/icons/tags.svg +1 -0
- data/app/assets/images/shoppe/icons/toolbox.svg +1 -0
- data/app/assets/images/shoppe/icons/unlocked.svg +1 -0
- data/app/assets/images/shoppe/icons/wallet.svg +1 -0
- data/app/assets/images/shoppe/logo.svg +47 -0
- data/app/assets/images/shoppe/move.svg +1 -0
- data/app/assets/images/shoppe/shoppe.svg +25 -0
- data/app/assets/images/shoppe/square.svg +9 -0
- data/app/assets/images/shoppe/statuses/accepted.svg +14 -0
- data/app/assets/images/shoppe/statuses/paid.svg +16 -0
- data/app/assets/images/shoppe/statuses/received.svg +15 -0
- data/app/assets/images/shoppe/statuses/rejected.svg +14 -0
- data/app/assets/images/shoppe/statuses/shipped.svg +14 -0
- data/app/assets/images/shoppe/table-tear-off.png +0 -0
- data/app/assets/javascripts/shoppe/application.coffee +123 -0
- data/app/assets/javascripts/shoppe/chosen.jquery.js +1166 -0
- data/app/assets/javascripts/shoppe/jquery_ui.js +6 -0
- data/app/assets/javascripts/shoppe/mousetrap.js +9 -0
- data/app/assets/javascripts/shoppe/order_form.coffee +47 -0
- data/app/assets/stylesheets/shoppe/application.scss +601 -0
- data/app/assets/stylesheets/shoppe/chosen.scss +424 -0
- data/app/assets/stylesheets/shoppe/dialog.scss +25 -0
- data/app/assets/stylesheets/shoppe/elements.scss +79 -0
- data/app/assets/stylesheets/shoppe/printable.scss +67 -0
- data/app/assets/stylesheets/shoppe/reset.scss +93 -0
- data/app/assets/stylesheets/shoppe/sub.scss +106 -0
- data/app/assets/stylesheets/shoppe/variables.scss +1 -0
- data/app/controllers/shoppe/addresses_controller.rb +47 -0
- data/app/controllers/shoppe/application_controller.rb +46 -0
- data/app/controllers/shoppe/attachments_controller.rb +14 -0
- data/app/controllers/shoppe/countries_controller.rb +47 -0
- data/app/controllers/shoppe/customers_controller.rb +55 -0
- data/app/controllers/shoppe/dashboard_controller.rb +9 -0
- data/app/controllers/shoppe/delivery_service_prices_controller.rb +45 -0
- data/app/controllers/shoppe/delivery_services_controller.rb +47 -0
- data/app/controllers/shoppe/orders_controller.rb +119 -0
- data/app/controllers/shoppe/payments_controller.rb +33 -0
- data/app/controllers/shoppe/product_categories_controller.rb +47 -0
- data/app/controllers/shoppe/product_category_localisations_controller.rb +58 -0
- data/app/controllers/shoppe/product_localisations_controller.rb +58 -0
- data/app/controllers/shoppe/products_controller.rb +58 -0
- data/app/controllers/shoppe/sessions_controller.rb +34 -0
- data/app/controllers/shoppe/settings_controller.rb +16 -0
- data/app/controllers/shoppe/stock_level_adjustments_controller.rb +40 -0
- data/app/controllers/shoppe/tax_rates_controller.rb +49 -0
- data/app/controllers/shoppe/users_controller.rb +53 -0
- data/app/controllers/shoppe/variants_controller.rb +50 -0
- data/app/helpers/shoppe/application_helper.rb +60 -0
- data/app/helpers/shoppe/product_category_helper.rb +36 -0
- data/app/mailers/shoppe/order_mailer.rb +25 -0
- data/app/mailers/shoppe/user_mailer.rb +10 -0
- data/app/models/shoppe/address.rb +44 -0
- data/app/models/shoppe/country.rb +27 -0
- data/app/models/shoppe/customer.rb +41 -0
- data/app/models/shoppe/delivery_service.rb +33 -0
- data/app/models/shoppe/delivery_service_price.rb +31 -0
- data/app/models/shoppe/order.rb +100 -0
- data/app/models/shoppe/order/actions.rb +94 -0
- data/app/models/shoppe/order/billing.rb +105 -0
- data/app/models/shoppe/order/delivery.rb +229 -0
- data/app/models/shoppe/order/states.rb +69 -0
- data/app/models/shoppe/order_item.rb +239 -0
- data/app/models/shoppe/payment.rb +86 -0
- data/app/models/shoppe/product.rb +189 -0
- data/app/models/shoppe/product/product_attributes.rb +20 -0
- data/app/models/shoppe/product/variants.rb +51 -0
- data/app/models/shoppe/product_attribute.rb +66 -0
- data/app/models/shoppe/product_categorization.rb +14 -0
- data/app/models/shoppe/product_category.rb +75 -0
- data/app/models/shoppe/setting.rb +68 -0
- data/app/models/shoppe/stock_level_adjustment.rb +19 -0
- data/app/models/shoppe/tax_rate.rb +46 -0
- data/app/models/shoppe/user.rb +49 -0
- data/app/validators/permalink_validator.rb +7 -0
- data/app/views/layouts/shoppe/application.html.haml +34 -0
- data/app/views/layouts/shoppe/printable.html.haml +11 -0
- data/app/views/layouts/shoppe/sub.html.haml +10 -0
- data/app/views/shoppe/addresses/_form.html.haml +33 -0
- data/app/views/shoppe/addresses/edit.html.haml +5 -0
- data/app/views/shoppe/addresses/new.html.haml +5 -0
- data/app/views/shoppe/countries/_form.html.haml +35 -0
- data/app/views/shoppe/countries/edit.html.haml +6 -0
- data/app/views/shoppe/countries/index.html.haml +25 -0
- data/app/views/shoppe/countries/new.html.haml +7 -0
- data/app/views/shoppe/customers/_addresses.html.haml +20 -0
- data/app/views/shoppe/customers/_form.html.haml +30 -0
- data/app/views/shoppe/customers/_search_form.html.haml +13 -0
- data/app/views/shoppe/customers/edit.html.haml +5 -0
- data/app/views/shoppe/customers/index.html.haml +32 -0
- data/app/views/shoppe/customers/new.html.haml +5 -0
- data/app/views/shoppe/customers/show.html.haml +53 -0
- data/app/views/shoppe/delivery_service_prices/_form.html.haml +44 -0
- data/app/views/shoppe/delivery_service_prices/edit.html.haml +6 -0
- data/app/views/shoppe/delivery_service_prices/index.html.haml +23 -0
- data/app/views/shoppe/delivery_service_prices/new.html.haml +6 -0
- data/app/views/shoppe/delivery_services/_form.html.haml +38 -0
- data/app/views/shoppe/delivery_services/edit.html.haml +9 -0
- data/app/views/shoppe/delivery_services/index.html.haml +27 -0
- data/app/views/shoppe/delivery_services/new.html.haml +6 -0
- data/app/views/shoppe/order_mailer/accepted.text.erb +12 -0
- data/app/views/shoppe/order_mailer/received.text.erb +9 -0
- data/app/views/shoppe/order_mailer/rejected.text.erb +10 -0
- data/app/views/shoppe/order_mailer/shipped.text.erb +16 -0
- data/app/views/shoppe/orders/_form.html.haml +58 -0
- data/app/views/shoppe/orders/_order_details.html.haml +57 -0
- data/app/views/shoppe/orders/_order_items.html.haml +38 -0
- data/app/views/shoppe/orders/_order_items_form.html.haml +61 -0
- data/app/views/shoppe/orders/_payments_form.html.haml +15 -0
- data/app/views/shoppe/orders/_payments_table.html.haml +37 -0
- data/app/views/shoppe/orders/_search_form.html.haml +24 -0
- data/app/views/shoppe/orders/_status_bar.html.haml +35 -0
- data/app/views/shoppe/orders/despatch_note.html.haml +45 -0
- data/app/views/shoppe/orders/edit.html.haml +21 -0
- data/app/views/shoppe/orders/index.html.haml +39 -0
- data/app/views/shoppe/orders/new.html.haml +14 -0
- data/app/views/shoppe/orders/show.html.haml +25 -0
- data/app/views/shoppe/payments/refund.html.haml +13 -0
- data/app/views/shoppe/product_categories/_form.html.haml +50 -0
- data/app/views/shoppe/product_categories/edit.html.haml +8 -0
- data/app/views/shoppe/product_categories/index.html.haml +27 -0
- data/app/views/shoppe/product_categories/new.html.haml +6 -0
- data/app/views/shoppe/product_category_localisations/form.html.haml +29 -0
- data/app/views/shoppe/product_category_localisations/index.html.haml +26 -0
- data/app/views/shoppe/product_localisations/form.html.haml +32 -0
- data/app/views/shoppe/product_localisations/index.html.haml +26 -0
- data/app/views/shoppe/products/_form.html.haml +118 -0
- data/app/views/shoppe/products/_table.html.haml +42 -0
- data/app/views/shoppe/products/edit.html.haml +9 -0
- data/app/views/shoppe/products/import.html.haml +67 -0
- data/app/views/shoppe/products/index.html.haml +9 -0
- data/app/views/shoppe/products/new.html.haml +7 -0
- data/app/views/shoppe/sessions/new.html.haml +12 -0
- data/app/views/shoppe/sessions/reset.html.haml +12 -0
- data/app/views/shoppe/settings/edit.html.haml +19 -0
- data/app/views/shoppe/shared/error.html.haml +6 -0
- data/app/views/shoppe/stock_level_adjustments/index.html.haml +40 -0
- data/app/views/shoppe/tax_rates/form.html.haml +28 -0
- data/app/views/shoppe/tax_rates/index.html.haml +17 -0
- data/app/views/shoppe/user_mailer/new_password.text.erb +9 -0
- data/app/views/shoppe/users/_form.html.haml +27 -0
- data/app/views/shoppe/users/edit.html.haml +5 -0
- data/app/views/shoppe/users/index.html.haml +17 -0
- data/app/views/shoppe/users/new.html.haml +7 -0
- data/app/views/shoppe/variants/form.html.haml +68 -0
- data/app/views/shoppe/variants/index.html.haml +33 -0
- data/config/locales/de.yml +653 -0
- data/config/locales/en.yml +744 -0
- data/config/locales/es.yml +653 -0
- data/config/locales/pl.yml +662 -0
- data/config/locales/pt-BR.yml +655 -0
- data/config/routes.rb +53 -0
- data/db/countries.txt +252 -0
- data/db/migrate/20130926094549_create_shoppe_initial_schema.rb +175 -0
- data/db/migrate/20131024201501_add_address_type_to_shoppe_tax_rates.rb +5 -0
- data/db/migrate/20131024204815_create_shoppe_payments.rb +32 -0
- data/db/migrate/20131102143930_remove_default_on_order_item_weight.rb +9 -0
- data/db/migrate/20141013192427_create_shoppe_customers.rb +14 -0
- 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/migrate/20141026181717_allow_multiple_shoppe_products_per_shoppe_product_category.rb +46 -0
- data/db/migrate/20141026181718_add_nested_to_product_categories.rb +30 -0
- data/db/migrate/20141027215005_create_shoppe_addresses.rb +17 -0
- data/db/migrate/20150315215633_add_customer_to_shoppe_orders.rb +5 -0
- data/db/migrate/20150513171350_create_shoppe_product_category_translation_table.rb +17 -0
- data/db/migrate/20150519173350_create_shoppe_product_translation_table.rb +18 -0
- data/db/schema.rb +308 -0
- data/db/seeds.rb +137 -0
- data/db/seeds_data/poe400.jpg +0 -0
- data/db/seeds_data/snom-870-blk.jpg +0 -0
- data/db/seeds_data/snom-870-grey.jpg +0 -0
- data/db/seeds_data/snom-mm2.jpg +0 -0
- data/db/seeds_data/spa303.jpg +0 -0
- data/db/seeds_data/t18p.jpg +0 -0
- data/db/seeds_data/t20p.jpg +0 -0
- data/db/seeds_data/t22p.jpg +0 -0
- data/db/seeds_data/t26p.jpg +0 -0
- data/db/seeds_data/t41pn.jpg +0 -0
- data/db/seeds_data/t46gn.jpg +0 -0
- data/db/seeds_data/w52p.jpg +0 -0
- data/db/seeds_data/yhs32.jpg +0 -0
- data/lib/shoppe.rb +60 -0
- data/lib/shoppe/associated_countries.rb +20 -0
- data/lib/shoppe/country_importer.rb +15 -0
- data/lib/shoppe/default_navigation.rb +21 -0
- data/lib/shoppe/engine.rb +55 -0
- data/lib/shoppe/error.rb +21 -0
- data/lib/shoppe/errors/inappropriate_delivery_service.rb +6 -0
- data/lib/shoppe/errors/insufficient_stock_to_fulfil.rb +15 -0
- data/lib/shoppe/errors/invalid_configuration.rb +6 -0
- data/lib/shoppe/errors/not_enough_stock.rb +15 -0
- data/lib/shoppe/errors/payment_declined.rb +6 -0
- data/lib/shoppe/errors/refund_failed.rb +6 -0
- data/lib/shoppe/errors/unorderable_item.rb +6 -0
- data/lib/shoppe/navigation_manager.rb +81 -0
- data/lib/shoppe/orderable_item.rb +39 -0
- data/lib/shoppe/settings.rb +26 -0
- data/lib/shoppe/settings_loader.rb +16 -0
- data/lib/shoppe/setup_generator.rb +10 -0
- data/lib/shoppe/version.rb +3 -0
- data/lib/shoppe/view_helpers.rb +16 -0
- data/lib/tasks/shoppe.rake +29 -0
- metadata +662 -0
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
module Shoppe
|
|
2
|
+
class Product < ActiveRecord::Base
|
|
3
|
+
|
|
4
|
+
# Product attributes for this product
|
|
5
|
+
has_many :product_attributes, -> { order(:position) }, :class_name => 'Shoppe::ProductAttribute'
|
|
6
|
+
|
|
7
|
+
# Used for setting an array of product attributes which will be updated. Usually
|
|
8
|
+
# received from a web browser.
|
|
9
|
+
attr_accessor :product_attributes_array
|
|
10
|
+
|
|
11
|
+
# After saving automatically try to update the product attributes based on the
|
|
12
|
+
# the contents of the product_attributes_array array.
|
|
13
|
+
after_save do
|
|
14
|
+
if product_attributes_array.is_a?(Array)
|
|
15
|
+
self.product_attributes.update_from_array(product_attributes_array)
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
end
|
|
20
|
+
end
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
module Shoppe
|
|
2
|
+
class Product < ActiveRecord::Base
|
|
3
|
+
|
|
4
|
+
# Validations
|
|
5
|
+
validate { errors.add :base, :can_belong_to_root if self.parent && self.parent.parent }
|
|
6
|
+
|
|
7
|
+
# Variants of the product
|
|
8
|
+
has_many :variants, :class_name => 'Shoppe::Product', :foreign_key => 'parent_id', :dependent => :destroy
|
|
9
|
+
|
|
10
|
+
# The parent product (only applies to variants)
|
|
11
|
+
belongs_to :parent, :class_name => 'Shoppe::Product', :foreign_key => 'parent_id'
|
|
12
|
+
|
|
13
|
+
# All products which are not variants
|
|
14
|
+
scope :root, -> { where(:parent_id => nil) }
|
|
15
|
+
|
|
16
|
+
# If a variant is created, the base product should be updated so that it doesn't have stock control enabled
|
|
17
|
+
after_save do
|
|
18
|
+
if self.parent
|
|
19
|
+
self.parent.price = 0
|
|
20
|
+
self.parent.cost_price = 0
|
|
21
|
+
self.parent.tax_rate = nil
|
|
22
|
+
self.parent.weight = 0
|
|
23
|
+
self.parent.stock_control = false
|
|
24
|
+
self.parent.save if self.parent.changed?
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
# Does this product have any variants?
|
|
29
|
+
#
|
|
30
|
+
# @return [Boolean]
|
|
31
|
+
def has_variants?
|
|
32
|
+
!variants.empty?
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
# Returns the default variant for the product or nil if none exists.
|
|
36
|
+
#
|
|
37
|
+
# @return [Shoppe::Product]
|
|
38
|
+
def default_variant
|
|
39
|
+
return nil if self.parent
|
|
40
|
+
@default_variant ||= self.variants.select { |v| v.default? }.first
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
# Is this product a variant of another?
|
|
44
|
+
#
|
|
45
|
+
# @return [Boolean]
|
|
46
|
+
def variant?
|
|
47
|
+
!self.parent_id.blank?
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
end
|
|
51
|
+
end
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
module Shoppe
|
|
2
|
+
class ProductAttribute < ActiveRecord::Base
|
|
3
|
+
|
|
4
|
+
self.table_name = 'shoppe_product_attributes'
|
|
5
|
+
|
|
6
|
+
# Validations
|
|
7
|
+
validates :key, :presence => true
|
|
8
|
+
|
|
9
|
+
# The associated product
|
|
10
|
+
#
|
|
11
|
+
# @return [Shoppe::Product]
|
|
12
|
+
belongs_to :product, :class_name => 'Shoppe::Product'
|
|
13
|
+
|
|
14
|
+
# All attributes which are searchable
|
|
15
|
+
scope :searchable, -> { where(:searchable => true) }
|
|
16
|
+
|
|
17
|
+
# All attributes which are public
|
|
18
|
+
scope :publicly_accessible, -> { where(:public => true) }
|
|
19
|
+
|
|
20
|
+
# Return the the available options as a hash
|
|
21
|
+
#
|
|
22
|
+
# @return [Hash]
|
|
23
|
+
def self.grouped_hash
|
|
24
|
+
all.group_by(&:key).inject(Hash.new) do |h, (key, attributes)|
|
|
25
|
+
h[key] = attributes.map(&:value).uniq
|
|
26
|
+
h
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
# Create/update attributes for a product based on the provided hash of
|
|
31
|
+
# keys & values.
|
|
32
|
+
#
|
|
33
|
+
# @param array [Array]
|
|
34
|
+
def self.update_from_array(array)
|
|
35
|
+
existing_keys = self.pluck(:key)
|
|
36
|
+
index = 0
|
|
37
|
+
array.each do |hash|
|
|
38
|
+
next if hash['key'].blank?
|
|
39
|
+
index += 1
|
|
40
|
+
params = hash.merge({
|
|
41
|
+
:searchable => hash['searchable'].to_s == '1',
|
|
42
|
+
:public => hash['public'].to_s == '1',
|
|
43
|
+
:position => index
|
|
44
|
+
})
|
|
45
|
+
if existing_attr = self.where(:key => hash['key']).first
|
|
46
|
+
if hash['value'].blank?
|
|
47
|
+
existing_attr.destroy
|
|
48
|
+
index -= 1
|
|
49
|
+
else
|
|
50
|
+
existing_attr.update_attributes(params)
|
|
51
|
+
end
|
|
52
|
+
else
|
|
53
|
+
attribute = self.create(params)
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
self.where(:key => existing_keys - array.map { |h| h['key']}).delete_all
|
|
57
|
+
true
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def self.public
|
|
61
|
+
ActiveSupport::Deprecation.warn("The use of Shoppe::ProductAttribute.public is deprecated. use Shoppe::ProductAttribute.publicly_accessible.")
|
|
62
|
+
self.publicly_accessible
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
end
|
|
66
|
+
end
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
module Shoppe
|
|
2
|
+
class ProductCategorization < ActiveRecord::Base
|
|
3
|
+
|
|
4
|
+
self.table_name = 'shoppe_product_categorizations'
|
|
5
|
+
|
|
6
|
+
# Links back
|
|
7
|
+
belongs_to :product, class_name: 'Shoppe::Product'
|
|
8
|
+
belongs_to :product_category, class_name: 'Shoppe::ProductCategory'
|
|
9
|
+
|
|
10
|
+
# Validations
|
|
11
|
+
validates_presence_of :product, :product_category
|
|
12
|
+
|
|
13
|
+
end
|
|
14
|
+
end
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
require 'awesome_nested_set'
|
|
2
|
+
|
|
3
|
+
module Shoppe
|
|
4
|
+
class ProductCategory < ActiveRecord::Base
|
|
5
|
+
|
|
6
|
+
# Allow the nesting of product categories
|
|
7
|
+
# :restrict_with_exception relies on a fix to the awesome_nested_set gem
|
|
8
|
+
# which has been referenced in the Gemfile as we can't add a dependency
|
|
9
|
+
# to a branch in the .gemspec
|
|
10
|
+
acts_as_nested_set dependent: :restrict_with_exception,
|
|
11
|
+
after_move: :set_ancestral_permalink
|
|
12
|
+
|
|
13
|
+
self.table_name = 'shoppe_product_categories'
|
|
14
|
+
|
|
15
|
+
# Categories have an image attachment
|
|
16
|
+
attachment :image
|
|
17
|
+
|
|
18
|
+
# All products within this category
|
|
19
|
+
has_many :product_categorizations, dependent: :restrict_with_exception, class_name: 'Shoppe::ProductCategorization', inverse_of: :product_category
|
|
20
|
+
has_many :products, class_name: 'Shoppe::Product', through: :product_categorizations
|
|
21
|
+
|
|
22
|
+
# Validations
|
|
23
|
+
validates :name, :presence => true
|
|
24
|
+
validates :permalink, :presence => true, :uniqueness => {scope: :parent_id}, :permalink => true
|
|
25
|
+
|
|
26
|
+
# Root (no parent) product categories only
|
|
27
|
+
scope :without_parent, -> { where(parent_id: nil) }
|
|
28
|
+
|
|
29
|
+
# No descendents
|
|
30
|
+
scope :except_descendants, ->(record) { where.not(id: (Array.new(record.descendants) << record).flatten) }
|
|
31
|
+
|
|
32
|
+
translates :name, :permalink, :description, :short_description
|
|
33
|
+
scope :ordered, -> { includes(:translations).order(:name) }
|
|
34
|
+
|
|
35
|
+
# Set the permalink on callback
|
|
36
|
+
before_validation :set_permalink, :set_ancestral_permalink
|
|
37
|
+
after_save :set_child_permalinks
|
|
38
|
+
|
|
39
|
+
def combined_permalink
|
|
40
|
+
if self.permalink_includes_ancestors && self.ancestral_permalink.present?
|
|
41
|
+
"#{self.ancestral_permalink}/#{self.permalink}"
|
|
42
|
+
else
|
|
43
|
+
self.permalink
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
# Return array with all the product category parents hierarchy
|
|
48
|
+
# in descending order
|
|
49
|
+
def hierarchy_array
|
|
50
|
+
return [self] unless parent
|
|
51
|
+
parent.hierarchy_array.concat [self]
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
private
|
|
55
|
+
|
|
56
|
+
def set_permalink
|
|
57
|
+
self.permalink = self.name.parameterize if self.permalink.blank? && self.name.is_a?(String)
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def set_ancestral_permalink
|
|
61
|
+
permalinks = Array.new
|
|
62
|
+
self.ancestors.each do |category|
|
|
63
|
+
permalinks << category.permalink
|
|
64
|
+
end
|
|
65
|
+
self.ancestral_permalink = permalinks.join '/'
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def set_child_permalinks
|
|
69
|
+
self.children.each do |category|
|
|
70
|
+
category.save! # save forces children to update their ancestral_permalink
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
end
|
|
75
|
+
end
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
require 'ostruct'
|
|
2
|
+
|
|
3
|
+
module Shoppe
|
|
4
|
+
class Setting < ActiveRecord::Base
|
|
5
|
+
|
|
6
|
+
# Validations
|
|
7
|
+
validates :key, :presence => true, :uniqueness => true
|
|
8
|
+
validates :value, :presence => true
|
|
9
|
+
validates :value_type, :presence => true
|
|
10
|
+
|
|
11
|
+
before_validation do
|
|
12
|
+
self.value_type = I18n.t("shoppe.settings.types")[self.key.to_sym].try(:capitalize) || self.value.class.to_s
|
|
13
|
+
self.value = encoded_value
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
# The encoded value for saving in the backend (as a string)
|
|
17
|
+
#
|
|
18
|
+
# @return [String]
|
|
19
|
+
def encoded_value
|
|
20
|
+
case value_type
|
|
21
|
+
when 'Array', 'Hash' then value.to_json
|
|
22
|
+
when 'Boolean' then value.to_s == 'true' ? 'true' : 'false'
|
|
23
|
+
else value.to_s
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
# The decoded value for the setting attribute (in it's native type)
|
|
28
|
+
#
|
|
29
|
+
# @return [Object]
|
|
30
|
+
def decoded_value
|
|
31
|
+
case value_type
|
|
32
|
+
when 'Fixnum' then value.to_i
|
|
33
|
+
when 'Float' then value.to_f
|
|
34
|
+
when 'Array', 'Hash' then JSON.parse(value)
|
|
35
|
+
when 'Boolean' then value == 'true' ? true : false
|
|
36
|
+
else value.to_s
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
# A full hash of all settings available in the current scope
|
|
41
|
+
#
|
|
42
|
+
# @return [Hash]
|
|
43
|
+
def self.to_hash
|
|
44
|
+
all.inject({}) do |h, setting|
|
|
45
|
+
h[setting.key.to_s] = setting.decoded_value
|
|
46
|
+
h
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
# Update settings from a given hash and persist them. Accepts a
|
|
51
|
+
# hash of keys (which should be strings).
|
|
52
|
+
#
|
|
53
|
+
# @return [Hash]
|
|
54
|
+
def self.update_from_hash(hash)
|
|
55
|
+
existing_settings = self.all.to_a
|
|
56
|
+
hash.each do |key, value|
|
|
57
|
+
existing = existing_settings.select { |s| s.key.to_s == key.to_s }.first
|
|
58
|
+
if existing
|
|
59
|
+
value.blank? ? existing.destroy! : existing.update!(:value => value)
|
|
60
|
+
else
|
|
61
|
+
value.blank? ? nil : self.create!(:key => key, :value => value)
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
hash
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
end
|
|
68
|
+
end
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
module Shoppe
|
|
2
|
+
class StockLevelAdjustment < ActiveRecord::Base
|
|
3
|
+
|
|
4
|
+
# The orderable item which the stock level adjustment belongs to
|
|
5
|
+
belongs_to :item, :polymorphic => true
|
|
6
|
+
|
|
7
|
+
# The parent (OrderItem) which the stock level adjustment belongs to
|
|
8
|
+
belongs_to :parent, :polymorphic => true
|
|
9
|
+
|
|
10
|
+
# Validations
|
|
11
|
+
validates :description, :presence => true
|
|
12
|
+
validates :adjustment, :numericality => true
|
|
13
|
+
validate { errors.add(:adjustment, I18n.t('shoppe.activerecord.attributes.stock_level_adjustment.must_be_greater_or_equal_zero')) if adjustment == 0 }
|
|
14
|
+
|
|
15
|
+
# All stock level adjustments ordered by their created date desending
|
|
16
|
+
scope :ordered, -> { order(:id => :desc) }
|
|
17
|
+
|
|
18
|
+
end
|
|
19
|
+
end
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
module Shoppe
|
|
2
|
+
class TaxRate < ActiveRecord::Base
|
|
3
|
+
|
|
4
|
+
self.table_name = 'shoppe_tax_rates'
|
|
5
|
+
|
|
6
|
+
include Shoppe::AssociatedCountries
|
|
7
|
+
|
|
8
|
+
# The order address types which may be used when choosing how to apply the tax rate
|
|
9
|
+
ADDRESS_TYPES = ['billing', 'delivery']
|
|
10
|
+
|
|
11
|
+
# Validations
|
|
12
|
+
validates :name, :presence => true
|
|
13
|
+
validates :address_type, :inclusion => {:in => ADDRESS_TYPES}
|
|
14
|
+
validates :rate, :numericality => true
|
|
15
|
+
|
|
16
|
+
# All products which are assigned to this tax rate
|
|
17
|
+
has_many :products, :dependent => :restrict_with_exception, :class_name => 'Shoppe::Product'
|
|
18
|
+
|
|
19
|
+
# All delivery service prices which are assigned to this tax rate
|
|
20
|
+
has_many :delivery_service_prices, :dependent => :restrict_with_exception, :class_name => 'Shoppe::DeliveryServicePrice'
|
|
21
|
+
|
|
22
|
+
# All tax rates ordered by their ID
|
|
23
|
+
scope :ordered, -> { order(:id)}
|
|
24
|
+
|
|
25
|
+
# Set the address type if appropriate
|
|
26
|
+
before_validation { self.address_type = ADDRESS_TYPES.first if self.address_type.blank? }
|
|
27
|
+
|
|
28
|
+
# A description of the tax rate including its name & percentage
|
|
29
|
+
#
|
|
30
|
+
# @return [String]
|
|
31
|
+
def description
|
|
32
|
+
"#{name} (#{rate}%)"
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
# The rate for a given order based on the rules on the tax rate
|
|
36
|
+
#
|
|
37
|
+
# @return [BigDecimal]
|
|
38
|
+
def rate_for(order)
|
|
39
|
+
return rate if countries.empty?
|
|
40
|
+
return rate if address_type == 'billing' && (order.billing_country.nil? || country?(order.billing_country))
|
|
41
|
+
return rate if address_type == 'delivery' && (order.delivery_country.nil? || country?(order.delivery_country))
|
|
42
|
+
BigDecimal(0)
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
end
|
|
46
|
+
end
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
module Shoppe
|
|
2
|
+
class User < ActiveRecord::Base
|
|
3
|
+
|
|
4
|
+
self.table_name = 'shoppe_users'
|
|
5
|
+
|
|
6
|
+
has_secure_password
|
|
7
|
+
|
|
8
|
+
# Validations
|
|
9
|
+
validates :first_name, :presence => true
|
|
10
|
+
validates :last_name, :presence => true
|
|
11
|
+
validates :email_address, :presence => true
|
|
12
|
+
|
|
13
|
+
# The user's first name & last name concatenated
|
|
14
|
+
#
|
|
15
|
+
# @return [String]
|
|
16
|
+
def full_name
|
|
17
|
+
"#{first_name} #{last_name}"
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
# The user's first name & initial of last name concatenated
|
|
21
|
+
#
|
|
22
|
+
# @return [String]
|
|
23
|
+
def short_name
|
|
24
|
+
"#{first_name} #{last_name[0,1]}"
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
# Reset the user's password to something random and e-mail it to them
|
|
28
|
+
def reset_password!
|
|
29
|
+
self.password = SecureRandom.hex(8)
|
|
30
|
+
self.password_confirmation = self.password
|
|
31
|
+
self.save!
|
|
32
|
+
Shoppe::UserMailer.new_password(self).deliver_now
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
# Attempt to authenticate a user based on email & password. Returns the
|
|
36
|
+
# user if successful otherwise returns false.
|
|
37
|
+
#
|
|
38
|
+
# @param email_address [String]
|
|
39
|
+
# @param paassword [String]
|
|
40
|
+
# @return [Shoppe::User]
|
|
41
|
+
def self.authenticate(email_address, password)
|
|
42
|
+
user = self.where(:email_address => email_address).first
|
|
43
|
+
return false if user.nil?
|
|
44
|
+
return false unless user.authenticate(password)
|
|
45
|
+
user
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
end
|
|
49
|
+
end
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
!!!
|
|
2
|
+
%html
|
|
3
|
+
%head
|
|
4
|
+
%title #{@page_title} - Shoppe
|
|
5
|
+
= stylesheet_link_tag 'shoppe/application'
|
|
6
|
+
= javascript_include_tag 'shoppe/application'
|
|
7
|
+
= csrf_meta_tags
|
|
8
|
+
:javascript
|
|
9
|
+
window.Shoppe = {}
|
|
10
|
+
window.Shoppe.currencyUnit = "#{h Shoppe.settings.currency_unit}";
|
|
11
|
+
%body
|
|
12
|
+
%nav.sidebar
|
|
13
|
+
%header.logo
|
|
14
|
+
%h1= link_to "Shoppe", root_path
|
|
15
|
+
%p v#{Shoppe::VERSION}
|
|
16
|
+
%p.logged= t('.logged_in_as', user_name: current_user.full_name)
|
|
17
|
+
|
|
18
|
+
%ul
|
|
19
|
+
- for item in Shoppe::NavigationManager.find(:admin_primary).items
|
|
20
|
+
%li= navigation_manager_link item
|
|
21
|
+
|
|
22
|
+
%li= link_to t('.logout'), [:logout], :method => :delete
|
|
23
|
+
|
|
24
|
+
%header.main
|
|
25
|
+
= yield :header
|
|
26
|
+
|
|
27
|
+
#content
|
|
28
|
+
= display_flash
|
|
29
|
+
%section.main
|
|
30
|
+
= yield
|
|
31
|
+
|
|
32
|
+
%footer
|
|
33
|
+
%p.store= link_to "← #{t('.goto')} #{Shoppe.settings.store_name}".html_safe, '/'
|
|
34
|
+
|