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.
Files changed (239) hide show
  1. checksums.yaml +7 -0
  2. data/MIT-LICENSE +20 -0
  3. data/README.md +55 -0
  4. data/Rakefile +36 -0
  5. data/app/assets/images/shoppe/chosen-sprite.png +0 -0
  6. data/app/assets/images/shoppe/chosen-sprite@2x.png +0 -0
  7. data/app/assets/images/shoppe/document.svg +1 -0
  8. data/app/assets/images/shoppe/icons/bag.svg +1 -0
  9. data/app/assets/images/shoppe/icons/balance.svg +1 -0
  10. data/app/assets/images/shoppe/icons/box.svg +1 -0
  11. data/app/assets/images/shoppe/icons/building.svg +1 -0
  12. data/app/assets/images/shoppe/icons/chart.svg +1 -0
  13. data/app/assets/images/shoppe/icons/chat.svg +1 -0
  14. data/app/assets/images/shoppe/icons/checkbox.svg +1 -0
  15. data/app/assets/images/shoppe/icons/checkbox2.svg +1 -0
  16. data/app/assets/images/shoppe/icons/cloud.svg +1 -0
  17. data/app/assets/images/shoppe/icons/cone.svg +1 -0
  18. data/app/assets/images/shoppe/icons/credit_card.svg +1 -0
  19. data/app/assets/images/shoppe/icons/currency.svg +1 -0
  20. data/app/assets/images/shoppe/icons/edit.svg +14 -0
  21. data/app/assets/images/shoppe/icons/flowchart.svg +1 -0
  22. data/app/assets/images/shoppe/icons/gift.svg +1 -0
  23. data/app/assets/images/shoppe/icons/globe.svg +1 -0
  24. data/app/assets/images/shoppe/icons/id.svg +1 -0
  25. data/app/assets/images/shoppe/icons/id2.svg +1 -0
  26. data/app/assets/images/shoppe/icons/locked.svg +1 -0
  27. data/app/assets/images/shoppe/icons/report.svg +1 -0
  28. data/app/assets/images/shoppe/icons/search.svg +1 -0
  29. data/app/assets/images/shoppe/icons/support.svg +1 -0
  30. data/app/assets/images/shoppe/icons/tags.svg +1 -0
  31. data/app/assets/images/shoppe/icons/toolbox.svg +1 -0
  32. data/app/assets/images/shoppe/icons/unlocked.svg +1 -0
  33. data/app/assets/images/shoppe/icons/wallet.svg +1 -0
  34. data/app/assets/images/shoppe/logo.svg +47 -0
  35. data/app/assets/images/shoppe/move.svg +1 -0
  36. data/app/assets/images/shoppe/shoppe.svg +25 -0
  37. data/app/assets/images/shoppe/square.svg +9 -0
  38. data/app/assets/images/shoppe/statuses/accepted.svg +14 -0
  39. data/app/assets/images/shoppe/statuses/paid.svg +16 -0
  40. data/app/assets/images/shoppe/statuses/received.svg +15 -0
  41. data/app/assets/images/shoppe/statuses/rejected.svg +14 -0
  42. data/app/assets/images/shoppe/statuses/shipped.svg +14 -0
  43. data/app/assets/images/shoppe/table-tear-off.png +0 -0
  44. data/app/assets/javascripts/shoppe/application.coffee +123 -0
  45. data/app/assets/javascripts/shoppe/chosen.jquery.js +1166 -0
  46. data/app/assets/javascripts/shoppe/jquery_ui.js +6 -0
  47. data/app/assets/javascripts/shoppe/mousetrap.js +9 -0
  48. data/app/assets/javascripts/shoppe/order_form.coffee +47 -0
  49. data/app/assets/stylesheets/shoppe/application.scss +601 -0
  50. data/app/assets/stylesheets/shoppe/chosen.scss +424 -0
  51. data/app/assets/stylesheets/shoppe/dialog.scss +25 -0
  52. data/app/assets/stylesheets/shoppe/elements.scss +79 -0
  53. data/app/assets/stylesheets/shoppe/printable.scss +67 -0
  54. data/app/assets/stylesheets/shoppe/reset.scss +93 -0
  55. data/app/assets/stylesheets/shoppe/sub.scss +106 -0
  56. data/app/assets/stylesheets/shoppe/variables.scss +1 -0
  57. data/app/controllers/shoppe/addresses_controller.rb +47 -0
  58. data/app/controllers/shoppe/application_controller.rb +46 -0
  59. data/app/controllers/shoppe/attachments_controller.rb +14 -0
  60. data/app/controllers/shoppe/countries_controller.rb +47 -0
  61. data/app/controllers/shoppe/customers_controller.rb +55 -0
  62. data/app/controllers/shoppe/dashboard_controller.rb +9 -0
  63. data/app/controllers/shoppe/delivery_service_prices_controller.rb +45 -0
  64. data/app/controllers/shoppe/delivery_services_controller.rb +47 -0
  65. data/app/controllers/shoppe/orders_controller.rb +119 -0
  66. data/app/controllers/shoppe/payments_controller.rb +33 -0
  67. data/app/controllers/shoppe/product_categories_controller.rb +47 -0
  68. data/app/controllers/shoppe/product_category_localisations_controller.rb +58 -0
  69. data/app/controllers/shoppe/product_localisations_controller.rb +58 -0
  70. data/app/controllers/shoppe/products_controller.rb +58 -0
  71. data/app/controllers/shoppe/sessions_controller.rb +34 -0
  72. data/app/controllers/shoppe/settings_controller.rb +16 -0
  73. data/app/controllers/shoppe/stock_level_adjustments_controller.rb +40 -0
  74. data/app/controllers/shoppe/tax_rates_controller.rb +49 -0
  75. data/app/controllers/shoppe/users_controller.rb +53 -0
  76. data/app/controllers/shoppe/variants_controller.rb +50 -0
  77. data/app/helpers/shoppe/application_helper.rb +60 -0
  78. data/app/helpers/shoppe/product_category_helper.rb +36 -0
  79. data/app/mailers/shoppe/order_mailer.rb +25 -0
  80. data/app/mailers/shoppe/user_mailer.rb +10 -0
  81. data/app/models/shoppe/address.rb +44 -0
  82. data/app/models/shoppe/country.rb +27 -0
  83. data/app/models/shoppe/customer.rb +41 -0
  84. data/app/models/shoppe/delivery_service.rb +33 -0
  85. data/app/models/shoppe/delivery_service_price.rb +31 -0
  86. data/app/models/shoppe/order.rb +100 -0
  87. data/app/models/shoppe/order/actions.rb +94 -0
  88. data/app/models/shoppe/order/billing.rb +105 -0
  89. data/app/models/shoppe/order/delivery.rb +229 -0
  90. data/app/models/shoppe/order/states.rb +69 -0
  91. data/app/models/shoppe/order_item.rb +239 -0
  92. data/app/models/shoppe/payment.rb +86 -0
  93. data/app/models/shoppe/product.rb +189 -0
  94. data/app/models/shoppe/product/product_attributes.rb +20 -0
  95. data/app/models/shoppe/product/variants.rb +51 -0
  96. data/app/models/shoppe/product_attribute.rb +66 -0
  97. data/app/models/shoppe/product_categorization.rb +14 -0
  98. data/app/models/shoppe/product_category.rb +75 -0
  99. data/app/models/shoppe/setting.rb +68 -0
  100. data/app/models/shoppe/stock_level_adjustment.rb +19 -0
  101. data/app/models/shoppe/tax_rate.rb +46 -0
  102. data/app/models/shoppe/user.rb +49 -0
  103. data/app/validators/permalink_validator.rb +7 -0
  104. data/app/views/layouts/shoppe/application.html.haml +34 -0
  105. data/app/views/layouts/shoppe/printable.html.haml +11 -0
  106. data/app/views/layouts/shoppe/sub.html.haml +10 -0
  107. data/app/views/shoppe/addresses/_form.html.haml +33 -0
  108. data/app/views/shoppe/addresses/edit.html.haml +5 -0
  109. data/app/views/shoppe/addresses/new.html.haml +5 -0
  110. data/app/views/shoppe/countries/_form.html.haml +35 -0
  111. data/app/views/shoppe/countries/edit.html.haml +6 -0
  112. data/app/views/shoppe/countries/index.html.haml +25 -0
  113. data/app/views/shoppe/countries/new.html.haml +7 -0
  114. data/app/views/shoppe/customers/_addresses.html.haml +20 -0
  115. data/app/views/shoppe/customers/_form.html.haml +30 -0
  116. data/app/views/shoppe/customers/_search_form.html.haml +13 -0
  117. data/app/views/shoppe/customers/edit.html.haml +5 -0
  118. data/app/views/shoppe/customers/index.html.haml +32 -0
  119. data/app/views/shoppe/customers/new.html.haml +5 -0
  120. data/app/views/shoppe/customers/show.html.haml +53 -0
  121. data/app/views/shoppe/delivery_service_prices/_form.html.haml +44 -0
  122. data/app/views/shoppe/delivery_service_prices/edit.html.haml +6 -0
  123. data/app/views/shoppe/delivery_service_prices/index.html.haml +23 -0
  124. data/app/views/shoppe/delivery_service_prices/new.html.haml +6 -0
  125. data/app/views/shoppe/delivery_services/_form.html.haml +38 -0
  126. data/app/views/shoppe/delivery_services/edit.html.haml +9 -0
  127. data/app/views/shoppe/delivery_services/index.html.haml +27 -0
  128. data/app/views/shoppe/delivery_services/new.html.haml +6 -0
  129. data/app/views/shoppe/order_mailer/accepted.text.erb +12 -0
  130. data/app/views/shoppe/order_mailer/received.text.erb +9 -0
  131. data/app/views/shoppe/order_mailer/rejected.text.erb +10 -0
  132. data/app/views/shoppe/order_mailer/shipped.text.erb +16 -0
  133. data/app/views/shoppe/orders/_form.html.haml +58 -0
  134. data/app/views/shoppe/orders/_order_details.html.haml +57 -0
  135. data/app/views/shoppe/orders/_order_items.html.haml +38 -0
  136. data/app/views/shoppe/orders/_order_items_form.html.haml +61 -0
  137. data/app/views/shoppe/orders/_payments_form.html.haml +15 -0
  138. data/app/views/shoppe/orders/_payments_table.html.haml +37 -0
  139. data/app/views/shoppe/orders/_search_form.html.haml +24 -0
  140. data/app/views/shoppe/orders/_status_bar.html.haml +35 -0
  141. data/app/views/shoppe/orders/despatch_note.html.haml +45 -0
  142. data/app/views/shoppe/orders/edit.html.haml +21 -0
  143. data/app/views/shoppe/orders/index.html.haml +39 -0
  144. data/app/views/shoppe/orders/new.html.haml +14 -0
  145. data/app/views/shoppe/orders/show.html.haml +25 -0
  146. data/app/views/shoppe/payments/refund.html.haml +13 -0
  147. data/app/views/shoppe/product_categories/_form.html.haml +50 -0
  148. data/app/views/shoppe/product_categories/edit.html.haml +8 -0
  149. data/app/views/shoppe/product_categories/index.html.haml +27 -0
  150. data/app/views/shoppe/product_categories/new.html.haml +6 -0
  151. data/app/views/shoppe/product_category_localisations/form.html.haml +29 -0
  152. data/app/views/shoppe/product_category_localisations/index.html.haml +26 -0
  153. data/app/views/shoppe/product_localisations/form.html.haml +32 -0
  154. data/app/views/shoppe/product_localisations/index.html.haml +26 -0
  155. data/app/views/shoppe/products/_form.html.haml +118 -0
  156. data/app/views/shoppe/products/_table.html.haml +42 -0
  157. data/app/views/shoppe/products/edit.html.haml +9 -0
  158. data/app/views/shoppe/products/import.html.haml +67 -0
  159. data/app/views/shoppe/products/index.html.haml +9 -0
  160. data/app/views/shoppe/products/new.html.haml +7 -0
  161. data/app/views/shoppe/sessions/new.html.haml +12 -0
  162. data/app/views/shoppe/sessions/reset.html.haml +12 -0
  163. data/app/views/shoppe/settings/edit.html.haml +19 -0
  164. data/app/views/shoppe/shared/error.html.haml +6 -0
  165. data/app/views/shoppe/stock_level_adjustments/index.html.haml +40 -0
  166. data/app/views/shoppe/tax_rates/form.html.haml +28 -0
  167. data/app/views/shoppe/tax_rates/index.html.haml +17 -0
  168. data/app/views/shoppe/user_mailer/new_password.text.erb +9 -0
  169. data/app/views/shoppe/users/_form.html.haml +27 -0
  170. data/app/views/shoppe/users/edit.html.haml +5 -0
  171. data/app/views/shoppe/users/index.html.haml +17 -0
  172. data/app/views/shoppe/users/new.html.haml +7 -0
  173. data/app/views/shoppe/variants/form.html.haml +68 -0
  174. data/app/views/shoppe/variants/index.html.haml +33 -0
  175. data/config/locales/de.yml +653 -0
  176. data/config/locales/en.yml +744 -0
  177. data/config/locales/es.yml +653 -0
  178. data/config/locales/pl.yml +662 -0
  179. data/config/locales/pt-BR.yml +655 -0
  180. data/config/routes.rb +53 -0
  181. data/db/countries.txt +252 -0
  182. data/db/migrate/20130926094549_create_shoppe_initial_schema.rb +175 -0
  183. data/db/migrate/20131024201501_add_address_type_to_shoppe_tax_rates.rb +5 -0
  184. data/db/migrate/20131024204815_create_shoppe_payments.rb +32 -0
  185. data/db/migrate/20131102143930_remove_default_on_order_item_weight.rb +9 -0
  186. data/db/migrate/20141013192427_create_shoppe_customers.rb +14 -0
  187. data/db/migrate/20141026175622_add_indexes_to_shoppe_order_items.rb +6 -0
  188. data/db/migrate/20141026175943_add_indexes_to_shoppe_orders.rb +7 -0
  189. data/db/migrate/20141026180333_add_indexes_to_shoppe_payments.rb +6 -0
  190. data/db/migrate/20141026180835_add_indexes_to_shoppe_product_attributes.rb +7 -0
  191. data/db/migrate/20141026180952_add_indexes_to_shoppe_product_categories.rb +5 -0
  192. data/db/migrate/20141026181040_add_indexes_to_shoppe_products.rb +8 -0
  193. data/db/migrate/20141026181312_add_indexes_to_shoppe_settings.rb +5 -0
  194. data/db/migrate/20141026181354_add_indexes_to_shoppe_stock_level_adjustments.rb +6 -0
  195. data/db/migrate/20141026181559_add_indexes_to_shoppe_users.rb +5 -0
  196. data/db/migrate/20141026181716_add_indexes_to_shoppe_delivery_services.rb +9 -0
  197. data/db/migrate/20141026181717_allow_multiple_shoppe_products_per_shoppe_product_category.rb +46 -0
  198. data/db/migrate/20141026181718_add_nested_to_product_categories.rb +30 -0
  199. data/db/migrate/20141027215005_create_shoppe_addresses.rb +17 -0
  200. data/db/migrate/20150315215633_add_customer_to_shoppe_orders.rb +5 -0
  201. data/db/migrate/20150513171350_create_shoppe_product_category_translation_table.rb +17 -0
  202. data/db/migrate/20150519173350_create_shoppe_product_translation_table.rb +18 -0
  203. data/db/schema.rb +308 -0
  204. data/db/seeds.rb +137 -0
  205. data/db/seeds_data/poe400.jpg +0 -0
  206. data/db/seeds_data/snom-870-blk.jpg +0 -0
  207. data/db/seeds_data/snom-870-grey.jpg +0 -0
  208. data/db/seeds_data/snom-mm2.jpg +0 -0
  209. data/db/seeds_data/spa303.jpg +0 -0
  210. data/db/seeds_data/t18p.jpg +0 -0
  211. data/db/seeds_data/t20p.jpg +0 -0
  212. data/db/seeds_data/t22p.jpg +0 -0
  213. data/db/seeds_data/t26p.jpg +0 -0
  214. data/db/seeds_data/t41pn.jpg +0 -0
  215. data/db/seeds_data/t46gn.jpg +0 -0
  216. data/db/seeds_data/w52p.jpg +0 -0
  217. data/db/seeds_data/yhs32.jpg +0 -0
  218. data/lib/shoppe.rb +60 -0
  219. data/lib/shoppe/associated_countries.rb +20 -0
  220. data/lib/shoppe/country_importer.rb +15 -0
  221. data/lib/shoppe/default_navigation.rb +21 -0
  222. data/lib/shoppe/engine.rb +55 -0
  223. data/lib/shoppe/error.rb +21 -0
  224. data/lib/shoppe/errors/inappropriate_delivery_service.rb +6 -0
  225. data/lib/shoppe/errors/insufficient_stock_to_fulfil.rb +15 -0
  226. data/lib/shoppe/errors/invalid_configuration.rb +6 -0
  227. data/lib/shoppe/errors/not_enough_stock.rb +15 -0
  228. data/lib/shoppe/errors/payment_declined.rb +6 -0
  229. data/lib/shoppe/errors/refund_failed.rb +6 -0
  230. data/lib/shoppe/errors/unorderable_item.rb +6 -0
  231. data/lib/shoppe/navigation_manager.rb +81 -0
  232. data/lib/shoppe/orderable_item.rb +39 -0
  233. data/lib/shoppe/settings.rb +26 -0
  234. data/lib/shoppe/settings_loader.rb +16 -0
  235. data/lib/shoppe/setup_generator.rb +10 -0
  236. data/lib/shoppe/version.rb +3 -0
  237. data/lib/shoppe/view_helpers.rb +16 -0
  238. data/lib/tasks/shoppe.rake +29 -0
  239. 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,7 @@
1
+ class PermalinkValidator < ActiveModel::EachValidator
2
+ def validate_each(record, attribute, value)
3
+ unless value =~ /\A[A-z0-9\-_]+\Z/
4
+ record.errors.add(attribute, :wrong_format)
5
+ end
6
+ end
7
+ 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 "&larr; #{t('.goto')} #{Shoppe.settings.store_name}".html_safe, '/'
34
+