stall 0.3.2 → 0.3.3

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