stall 0.3.2 → 0.3.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (126) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +15 -21
  3. data/app/assets/javascripts/para/stall/inputs/variants-matrix/input.coffee +8 -1
  4. data/app/assets/javascripts/para/stall/inputs/variants-matrix/variant.coffee +9 -0
  5. data/app/assets/javascripts/stall.coffee +7 -1
  6. data/app/assets/javascripts/stall/add-to-cart-form.coffee +1 -4
  7. data/app/assets/javascripts/stall/products-filters.coffee +29 -0
  8. data/app/assets/stylesheets/para/stall.sass +5 -0
  9. data/app/assets/stylesheets/stall.sass +0 -0
  10. data/app/controllers/stall/checkout/steps_controller.rb +6 -5
  11. data/app/controllers/stall/checkout_base_controller.rb +14 -0
  12. data/app/controllers/stall/checkouts_controller.rb +7 -4
  13. data/app/controllers/stall/curated_product_lists_controller.rb +10 -0
  14. data/app/controllers/stall/line_items_controller.rb +1 -5
  15. data/app/controllers/stall/manufacturers_controller.rb +12 -0
  16. data/app/controllers/stall/omniauth_callbacks_controller.rb +49 -0
  17. data/app/controllers/stall/product_categories_controller.rb +13 -0
  18. data/app/controllers/stall/products_breadcrumbs.rb +16 -0
  19. data/app/controllers/stall/products_controller.rb +20 -0
  20. data/app/controllers/stall/products_search.rb +11 -0
  21. data/app/helpers/stall/customers_helper.rb +0 -10
  22. data/app/helpers/stall/omniauth_helper.rb +7 -0
  23. data/app/helpers/stall/prices_helper.rb +12 -0
  24. data/app/helpers/stall/products_filters_helper.rb +34 -0
  25. data/app/mailers/stall/customer_mailer.rb +4 -3
  26. data/app/models/curated_list_product.rb +3 -0
  27. data/app/models/curated_product_list.rb +3 -0
  28. data/app/models/image.rb +3 -0
  29. data/app/models/product_suggestion.rb +3 -0
  30. data/app/models/stall/models/curated_list_product.rb +15 -0
  31. data/app/models/stall/models/curated_product_list.rb +22 -0
  32. data/app/models/stall/models/customer.rb +5 -8
  33. data/app/models/stall/models/image.rb +21 -0
  34. data/app/models/stall/models/manufacturer.rb +4 -0
  35. data/app/models/stall/models/product.rb +30 -7
  36. data/app/models/stall/models/product_category.rb +5 -2
  37. data/app/models/stall/models/product_list.rb +1 -1
  38. data/app/models/stall/models/product_suggestion.rb +14 -0
  39. data/app/models/stall/models/shipment.rb +0 -2
  40. data/app/models/stall/models/user.rb +18 -0
  41. data/app/models/stall/models/user_omniauth_account.rb +13 -0
  42. data/app/models/stall/models/variant.rb +11 -11
  43. data/app/models/user.rb +3 -0
  44. data/app/models/user_omniauth_account.rb +3 -0
  45. data/app/services/stall/add_to_cart_service.rb +19 -4
  46. data/app/services/stall/available_stocks_service.rb +11 -0
  47. data/app/services/stall/cart_credit_note_creation_service.rb +4 -0
  48. data/app/services/stall/cart_payment_validation_service.rb +5 -3
  49. data/app/services/stall/credit_usage_service.rb +8 -2
  50. data/app/services/stall/omniauth_user_authentication_service.rb +48 -0
  51. data/app/services/stall/product_list_staleness_handling_service.rb +6 -3
  52. data/app/services/stall/products_search_service.rb +23 -0
  53. data/app/services/stall/shipping_notification_service.rb +1 -1
  54. data/app/views/admin/carts/_filters.html.haml +10 -4
  55. data/app/views/admin/carts/_form.html.haml +9 -9
  56. data/app/views/admin/manufacturers/_form.html.haml +7 -0
  57. data/app/views/admin/manufacturers/_table.html.haml +8 -0
  58. data/app/views/admin/product_categories/_form.html.haml +7 -0
  59. data/app/views/admin/products/_form.html.haml +23 -0
  60. data/app/views/admin/products/_table.html.haml +4 -2
  61. data/app/views/checkout/steps/_informations.html.haml +2 -2
  62. data/app/views/para/admin/resources/_variant_row.html.haml +9 -1
  63. data/app/views/para/admin/resources/_variant_row_header.html.haml +14 -0
  64. data/app/views/para/stall/inputs/_variant_select.html.haml +3 -3
  65. data/app/views/para/stall/inputs/_variants_matrix.html.haml +7 -16
  66. data/app/views/para/stall/inputs/shipping_notes/new.html.haml +22 -0
  67. data/app/views/para/stall/inputs/shipping_notes/sent.html.haml +11 -0
  68. data/app/views/stall/carts/_cart.html.haml +1 -1
  69. data/app/views/stall/credit_note_adjustments/_form.html.haml +1 -1
  70. data/app/views/stall/curated_product_lists/show.html.haml +8 -0
  71. data/app/views/stall/customers/_fields.html.haml +17 -5
  72. data/app/views/stall/customers/_sign_in.html.haml +22 -10
  73. data/app/views/stall/line_items/_form.html.haml +2 -3
  74. data/app/views/stall/manufacturers/show.html.haml +8 -0
  75. data/app/views/stall/product_categories/show.html.haml +8 -0
  76. data/app/views/stall/products/_filters.html.haml +4 -0
  77. data/app/views/stall/products/_list.html.haml +7 -0
  78. data/app/views/stall/products/_product.html.haml +4 -0
  79. data/app/views/stall/products/_product_details.html.haml +13 -0
  80. data/app/views/stall/products/filters/_category_filter.html.haml +2 -0
  81. data/app/views/stall/products/filters/_manufacturer_filter.html.haml +2 -0
  82. data/app/views/stall/products/filters/_price_filter.html.haml +2 -0
  83. data/app/views/stall/products/filters/_property_filter.html.haml +2 -0
  84. data/app/views/stall/products/index.html.haml +8 -0
  85. data/app/views/stall/products/show.html.haml +26 -0
  86. data/config/locales/stall.fr.yml +97 -11
  87. data/db/migrate/20170221094450_add_slug_to_stall_manufacturers.rb +5 -0
  88. data/db/migrate/20170303122616_create_stall_product_suggestions.rb +13 -0
  89. data/db/migrate/20170303151939_add_position_to_stall_manufacturers.rb +5 -0
  90. data/db/migrate/20170308162740_create_stall_curated_product_lists.rb +10 -0
  91. data/db/migrate/20170308163532_create_stall_curated_list_products.rb +14 -0
  92. data/db/migrate/20170317103740_create_stall_users.rb +28 -0
  93. data/db/migrate/20170317104332_create_stall_user_omniauth_accounts.rb +14 -0
  94. data/db/migrate/20170320133513_fix_curated_product_list_bad_foreign_key.rb +6 -0
  95. data/db/migrate/20170321104203_create_stall_images.rb +11 -0
  96. data/db/migrate/20170321112248_remove_image_attachment_to_stall_products.rb +5 -0
  97. data/db/migrate/20170411134756_add_stock_to_stall_variants.rb +5 -0
  98. data/lib/ext/ransack.rb +13 -0
  99. data/lib/generators/stall/install/templates/initializer.rb +50 -15
  100. data/lib/generators/stall/service/service_generator.rb +0 -4
  101. data/lib/generators/stall/view/view_generator.rb +10 -8
  102. data/lib/para/stall/inputs/variant_select_input.rb +11 -1
  103. data/lib/para/stall/inputs/variants_matrix_input.rb +12 -6
  104. data/lib/stall.rb +14 -0
  105. data/lib/stall/addressable.rb +2 -2
  106. data/lib/stall/addresses/copy_source_to_target.rb +7 -3
  107. data/lib/stall/addresses/prefill_target_from_source.rb +4 -2
  108. data/lib/stall/cart_helper.rb +3 -3
  109. data/lib/stall/carts_cleaner.rb +2 -2
  110. data/lib/stall/checkout/informations_checkout_step.rb +2 -2
  111. data/lib/stall/config.rb +29 -9
  112. data/lib/stall/engine.rb +28 -13
  113. data/lib/stall/omniauth_provider.rb +41 -0
  114. data/lib/stall/payable.rb +3 -1
  115. data/lib/stall/product_filters.rb +12 -0
  116. data/lib/stall/product_filters/base_filter.rb +43 -0
  117. data/lib/stall/product_filters/builder.rb +59 -0
  118. data/lib/stall/product_filters/category_filter.rb +28 -0
  119. data/lib/stall/product_filters/manufacturer_filter.rb +19 -0
  120. data/lib/stall/product_filters/price_filter.rb +39 -0
  121. data/lib/stall/product_filters/property_filter.rb +38 -0
  122. data/lib/stall/routes.rb +40 -0
  123. data/lib/stall/shipping/calculator.rb +3 -1
  124. data/lib/stall/version.rb +1 -1
  125. metadata +158 -7
  126. data/app/assets/stylesheets/stall/carts.css +0 -4
@@ -1,6 +1,7 @@
1
1
  module Stall
2
2
  class Config
3
3
  extend Stall::Utils::ConfigDSL
4
+
4
5
  # Store name used in e-mails and other interfaces duisplaying such an
5
6
  # information
6
7
  param :store_name
@@ -20,17 +21,26 @@ module Stall
20
21
  # Default prices precision for rounding
21
22
  param :prices_precision, 2
22
23
 
24
+ # User omniauth providers
25
+ param :devise_for_user_config, { controllers: { omniauth_callbacks: 'stall/omniauth_callbacks' } }
26
+
23
27
  # Engine's ApplicationController parent
24
28
  param :application_controller_ancestor, '::ApplicationController'
25
29
 
26
30
  param :mailers_parent_class, 'ActionMailer::Base'
27
31
 
28
- # Default layout used for the checkout
32
+ # Default layout used for controllers
29
33
  param :default_layout
30
34
 
35
+ # Default layout used for the checkout
36
+ param :checkout_layout
37
+
31
38
  # Default currency for money objects
32
39
  param :default_currency, 'EUR'
33
40
 
41
+ # Do not manage stocks on hand by default
42
+ param :manage_inventory, false
43
+
34
44
  # Default app domain for building routes
35
45
  param :default_app_domain
36
46
 
@@ -51,9 +61,6 @@ module Stall
51
61
  # Duration after which an aborted cart is cleaned out by the rake task
52
62
  param :aborted_carts_expires_after, 14.days
53
63
 
54
- param :default_user_model_name, 'User'
55
- param :default_user_helper_method, :current_user
56
-
57
64
  # Configure the terms of service page path
58
65
  param :terms_path, 'about:blank'
59
66
 
@@ -80,13 +87,15 @@ module Stall
80
87
  end
81
88
 
82
89
  # Fetch user config and add top-namespace lookup to avoid collision
83
- # with Stall module services
90
+ # with Stall module services.
84
91
  #
85
92
  # Default allows looking up Stall namespace automatically, when no
86
93
  # config has been given
87
94
  def service_for(identifier)
88
95
  class_name = if (service_name = services[identifier])
89
96
  "::#{ services[identifier].gsub(/^::/, '') }"
97
+ elsif File.exists?(Rails.root.join('app', 'services', "#{ identifier }_service.rb"))
98
+ "::#{ identifier.to_s.camelize }Service"
90
99
  else
91
100
  "Stall::#{ identifier.to_s.camelize }Service"
92
101
  end
@@ -98,15 +107,26 @@ module Stall
98
107
  self.services.merge!(value)
99
108
  end
100
109
 
101
- def default_user_model
102
- default_user_model_name.try(:constantize)
103
- end
104
-
105
110
  def default_currency_as_money
106
111
  raw_default_currency && Money::Currency.new(raw_default_currency)
107
112
  end
108
113
 
109
114
  alias_method :raw_default_currency, :default_currency
110
115
  alias_method :default_currency, :default_currency_as_money
116
+
117
+ # User omniauth providers
118
+ def omniauth_providers_configs
119
+ @omniauth_providers_configs ||= {}
120
+ end
121
+
122
+ def omniauth_provider(name, config = {})
123
+ omniauth_providers_configs[name] = config
124
+ end
125
+
126
+ def omniauth_providers
127
+ omniauth_providers_configs.map do |provider, config|
128
+ Stall::OmniauthProvider.new(provider, config.deep_dup)
129
+ end
130
+ end
111
131
  end
112
132
  end
@@ -24,29 +24,34 @@ module Stall
24
24
  end
25
25
 
26
26
  initializer 'stall.ensure_shipping_method_for_all_calculators' do
27
- Stall::Shipping.calculators.each_key do |name|
28
- ShippingMethod.where(identifier: name).first_or_create do |method|
29
- method.name = name.to_s.humanize
30
- end
31
- end
27
+ register_class_models_for(ShippingMethod, Stall::Shipping.calculators)
32
28
  end
33
29
 
34
- initializer 'stall.require_manual_payment_gateway' do
30
+ initializer 'stall.ensure_payment_method_for_all_gateways' do
35
31
  require 'stall/payments/manual_payment_gateway'
36
- end
37
32
 
38
- initializer 'stall.ensure_payment_method_for_all_gateways' do
39
- Stall::Payments.gateways.each_key do |name|
40
- PaymentMethod.where(identifier: name).first_or_create do |method|
41
- method.name = name.to_s.humanize
42
- end
43
- end
33
+ register_class_models_for(PaymentMethod, Stall::Payments.gateways)
44
34
  end
45
35
 
46
36
  initializer 'stall.add_stall_plugin_to_para_config' do
47
37
  Para.config.plugins += [:stall]
48
38
  end
49
39
 
40
+ # For each omniauth provider in the config, declare a devise Omniauth
41
+ # provider
42
+ #
43
+ initializer 'stall.add_omniauth_providers_to_devise', before: 'devise.omniauth' do
44
+ Stall.config.omniauth_providers.each do |provider|
45
+ Devise.omniauth(provider.name, provider.app_id, provider.secret_key, provider.config)
46
+ end
47
+ end
48
+
49
+ # Add ransack search predicates
50
+ #
51
+ initializer 'stall.add_omniauth_providers_to_devise', before: 'devise.omniauth' do
52
+ Stall::Ransack.configure
53
+ end
54
+
50
55
  # Development : Configure rails generators to only generate the target
51
56
  # files and not try to generate useless complementary files
52
57
  #
@@ -57,5 +62,15 @@ module Stall
57
62
  generators.javascripts false
58
63
  generators.test_framework false
59
64
  end
65
+
66
+ def register_class_models_for(model, classes)
67
+ return unless model.table_exists?
68
+
69
+ classes.each_key do |name|
70
+ model.where(identifier: name).first_or_create do |method|
71
+ method.name = name.to_s.humanize
72
+ end
73
+ end
74
+ end
60
75
  end
61
76
  end
@@ -0,0 +1,41 @@
1
+ # This class is used as a config wrapper for devise's omniauth providers,
2
+ # allowing to easily configure new and existing providers.
3
+ #
4
+ module Stall
5
+ class OmniauthProvider
6
+ attr_reader :name, :config
7
+
8
+ def initialize(name, config = nil)
9
+ @name = name.to_s
10
+
11
+ config ||= {}
12
+
13
+ @icon = config.delete(:icon)
14
+ @display_name = config.delete(:display_name)
15
+ @app_id = config.delete(:app_id)
16
+ @secret_key = config.delete(:secret_key)
17
+
18
+ @config = config
19
+ end
20
+
21
+ def icon
22
+ @icon ||= name
23
+ end
24
+
25
+ def display_name
26
+ @display_name ||= name.humanize
27
+ end
28
+
29
+ def app_id
30
+ @app_id ||= ENV["#{ constant_name }_APP_ID"]
31
+ end
32
+
33
+ def secret_key
34
+ @secret_key ||= ENV["#{ constant_name }_SECRET_KEY"]
35
+ end
36
+
37
+ def constant_name
38
+ @constant_name ||= name.to_s.upcase
39
+ end
40
+ end
41
+ end
@@ -17,7 +17,9 @@ module Stall
17
17
  scope :aborted, ->(options = {}) {
18
18
  joins('LEFT JOIN stall_payments ON stall_payments.cart_id = stall_product_lists.id')
19
19
  .where(stall_payments: { paid_at: nil })
20
- .older_than(options.fetch(:before, 1.day.ago))
20
+ .older_than(
21
+ options.fetch(:before, Stall.config.aborted_carts_expires_after.ago)
22
+ )
21
23
  }
22
24
 
23
25
  delegate :paid?, to: :payment, allow_nil: true
@@ -0,0 +1,12 @@
1
+ module Stall
2
+ module ProductFilters
3
+ extend ActiveSupport::Autoload
4
+
5
+ autoload :Builder
6
+ autoload :BaseFilter
7
+ autoload :CategoryFilter
8
+ autoload :ManufacturerFilter
9
+ autoload :PriceFilter
10
+ autoload :PropertyFilter
11
+ end
12
+ end
@@ -0,0 +1,43 @@
1
+ module Stall
2
+ module ProductFilters
3
+ class BaseFilter
4
+ attr_reader :products, :options
5
+
6
+ def initialize(products, options = {})
7
+ @products = products
8
+ @options = options
9
+ end
10
+
11
+ def name
12
+ key.gsub(/_filter/, '')
13
+ end
14
+
15
+ def label
16
+ I18n.t("stall.products.filters.#{ key }")
17
+ end
18
+
19
+ # Can be overriden to hide the filter depending on certain conditions,
20
+ # e.g. there's more than one option so the filter is useful
21
+ #
22
+ def available?
23
+ options[:force] || true
24
+ end
25
+
26
+ def rendering_options(options = {})
27
+ { partial: partial_path, locals: partial_locals }.deep_merge(options)
28
+ end
29
+
30
+ def partial_path
31
+ "stall/products/filters/#{ key }"
32
+ end
33
+
34
+ def partial_locals
35
+ { filter: self }
36
+ end
37
+
38
+ def key
39
+ @key ||= self.class.name.demodulize.underscore
40
+ end
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,59 @@
1
+ module Stall
2
+ module ProductFilters
3
+ class Builder
4
+ attr_reader :products, :options
5
+
6
+ def initialize(products, options = {})
7
+ @products = products
8
+ @options = options
9
+ end
10
+
11
+ def filters
12
+ [category_filter, manufacturer_filter, price_filter] + properties_filters
13
+ end
14
+
15
+ def category_filter
16
+ CategoryFilter.new(products, options_for(:category)) if enabled?(:category)
17
+ end
18
+
19
+ def manufacturer_filter
20
+ ManufacturerFilter.new(products, options_for(:manufacturer)) if enabled?(:manufacturer)
21
+ end
22
+
23
+ def price_filter
24
+ PriceFilter.new(products, options_for(:price)) if enabled?(:price)
25
+ end
26
+
27
+ def properties_filters
28
+ if enabled?(:property)
29
+ properties.map do |property|
30
+ property_options = options_for(:property, property: property)
31
+ PropertyFilter.new(products, property_options)
32
+ end
33
+ else
34
+ []
35
+ end
36
+ end
37
+
38
+ private
39
+
40
+ def properties
41
+ Property.joins(
42
+ property_values: [
43
+ variant_property_values: [:variant]
44
+ ]
45
+ )
46
+ .includes(:property_values)
47
+ .distinct
48
+ end
49
+
50
+ def options_for(filter_name, overrides = {})
51
+ (options[filter_name] || {}).deep_dup.merge(overrides)
52
+ end
53
+
54
+ def enabled?(filter_name)
55
+ options[filter_name] != false
56
+ end
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,28 @@
1
+ module Stall
2
+ module ProductFilters
3
+ class CategoryFilter < BaseFilter
4
+ def available?
5
+ options[:force] || collection.count > 1
6
+ end
7
+
8
+ def collection
9
+ @collection ||= ProductCategory.joins(:products)
10
+ .where(stall_products: { id: products.select(:id) })
11
+ .distinct
12
+ end
13
+
14
+ def param
15
+ :"#{ param_with_parents }_in"
16
+ end
17
+
18
+ private
19
+
20
+ def param_with_parents
21
+ ProductCategory.max_depth.times.map do |index|
22
+ parents_string = index.times.map { 'parent' }.join('_').presence
23
+ ['product_category', parents_string, 'id'].compact.join('_')
24
+ end.join('_or_')
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,19 @@
1
+ module Stall
2
+ module ProductFilters
3
+ class ManufacturerFilter < BaseFilter
4
+ def available?
5
+ options[:force] || collection.count > 1
6
+ end
7
+
8
+ def collection
9
+ @collection ||= Manufacturer.ordered.joins(:products)
10
+ .where(stall_products: { id: products.select(:id) })
11
+ .distinct
12
+ end
13
+
14
+ def param
15
+ :manufacturer_id_in
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,39 @@
1
+ module Stall
2
+ module ProductFilters
3
+ class PriceFilter < BaseFilter
4
+ def available?
5
+ options[:force] || min != max
6
+ end
7
+
8
+ def min
9
+ return 0 unless variants.any?
10
+ variants.order(price_cents: :asc).first.price.to_d.floor
11
+ end
12
+
13
+ def max
14
+ return 0 unless variants.any?
15
+ variants.order(price_cents: :desc).first.price.to_d.ceil
16
+ end
17
+
18
+ def param
19
+ :variants_price_cents_between_cents
20
+ end
21
+
22
+ def ticks
23
+ min_tick = (min / 10.0).floor
24
+ max_tick = (max / 10.0).ceil
25
+ ticks_count = 4
26
+
27
+ (ticks_count + 1).times.map do |index|
28
+ ((((max_tick - min_tick) / ticks_count.to_f) * index) + min_tick).to_i * 10
29
+ end
30
+ end
31
+
32
+ private
33
+
34
+ def variants
35
+ @variants ||= Variant.where(product: products)
36
+ end
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,38 @@
1
+ module Stall
2
+ module ProductFilters
3
+ class PropertyFilter < BaseFilter
4
+ attr :property
5
+
6
+ def initialize(*)
7
+ super
8
+ @property = options[:property]
9
+ end
10
+
11
+ def available?
12
+ @available ||= (options[:force] || collection.count > 1)
13
+ end
14
+
15
+ def name
16
+ property.name.parameterize
17
+ end
18
+
19
+ def label
20
+ property.name
21
+ end
22
+
23
+ def param
24
+ :variants_property_values_id_in
25
+ end
26
+
27
+ def collection
28
+ @collection ||= property.property_values.joins(variants: :product)
29
+ .where(stall_products: { id: products.select(:id) })
30
+ .distinct
31
+ end
32
+
33
+ def partial_locals
34
+ { filter: self, property: property }
35
+ end
36
+ end
37
+ end
38
+ end