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
@@ -0,0 +1,7 @@
1
+ module Stall
2
+ module OmniauthHelper
3
+ def user_omniauth_authorize_path_for(provider)
4
+ send(:"user_#{ provider }_omniauth_authorize_path")
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,12 @@
1
+ module Stall
2
+ module PricesHelper
3
+ def displayed_price_for_variants variants
4
+ if variants.length == 1
5
+ number_to_currency(variants.first.price)
6
+ else
7
+ price = variants.map(&:price).min
8
+ t('stall.products.prices.from', price: number_to_currency(price)) if price
9
+ end
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,34 @@
1
+ module Stall
2
+ module ProductsFiltersHelper
3
+ # Display various filters for the given products including :
4
+ #
5
+ # - Product categories filters (:category)
6
+ # - Manufacturer filters (:manufacturer)
7
+ # - Price filter (:price)
8
+ # - Properties filters (:property)
9
+ #
10
+ # Those filters allow automatically searching into the provided products
11
+ # and should only be provided a list of prodcts that can be displayed
12
+ # for the current products list. e.g. for the brands#show products list,
13
+ # we pass the filters all the products from the manufacturer.
14
+ #
15
+ # In Stall, it is handled by the @filterable_products instance variables
16
+ # from the ProductCategoriesController, ManufacturersController and
17
+ # ProductsController.
18
+ #
19
+ # By default, all available filters (which contains possible results) are
20
+ # shown. If you need to remove a filter, you can pass an option with
21
+ # its name as false. The following would remove the category filter :
22
+ #
23
+ # product_filters_for(@filterable_products, category: false)
24
+ #
25
+ # If, at the opposite, you need to force the display of a certain filter
26
+ # even if there's no results available, you can do the following :
27
+ #
28
+ # product_filters_for(@filterable_products, category: { force: true })
29
+ #
30
+ def product_filters_for(products, options = {})
31
+ Stall::ProductFilters::Builder.new(products, options).filters.select(&:available?)
32
+ end
33
+ end
34
+ end
@@ -14,10 +14,11 @@ module Stall
14
14
  I18n.with_locale(cart.customer.locale) do
15
15
  @cart = cart
16
16
 
17
- calculator_class = Stall::Shipping::Calculator.for(cart.shipment.shipping_method)
18
- @calculator = calculator_class.new(cart, cart.shipment.shipping_method)
17
+ @calculator = if (calculator_class = Stall::Shipping::Calculator.for(cart.shipment.shipping_method))
18
+ calculator_class.new(cart, cart.shipment.shipping_method)
19
+ end
19
20
 
20
- @tracking_url = if @calculator.trackable?
21
+ @tracking_url = if @calculator.try(:trackable?)
21
22
  cart.shipment.tracking_code.presence && @calculator.tracking_url
22
23
  end
23
24
 
@@ -0,0 +1,3 @@
1
+ class CuratedListProduct < ActiveRecord::Base
2
+ include Stall::Models::CuratedListProduct
3
+ end
@@ -0,0 +1,3 @@
1
+ class CuratedProductList < ActiveRecord::Base
2
+ include Stall::Models::CuratedProductList
3
+ end
@@ -0,0 +1,3 @@
1
+ class Image < ActiveRecord::Base
2
+ include Stall::Models::Image
3
+ end
@@ -0,0 +1,3 @@
1
+ class ProductSuggestion < ActiveRecord::Base
2
+ include Stall::Models::ProductSuggestion
3
+ end
@@ -0,0 +1,15 @@
1
+ module Stall
2
+ module Models
3
+ module CuratedListProduct
4
+ extend ActiveSupport::Concern
5
+
6
+ included do
7
+ self.table_name = 'stall_curated_list_products'
8
+ acts_as_orderable
9
+
10
+ belongs_to :product
11
+ belongs_to :curated_product_list
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,22 @@
1
+ module Stall
2
+ module Models
3
+ module CuratedProductList
4
+ extend ActiveSupport::Concern
5
+
6
+ included do
7
+ self.table_name = 'stall_curated_product_lists'
8
+
9
+ has_many :curated_list_products, -> { ordered }, dependent: :destroy
10
+ has_many :products, through: :curated_list_products
11
+
12
+ accepts_nested_attributes_for :curated_list_products,
13
+ allow_destroy: true
14
+
15
+ extend FriendlyId
16
+ friendly_id :name, use: [:slugged, :finders]
17
+
18
+ validates :name, presence: true
19
+ end
20
+ end
21
+ end
22
+ end
@@ -8,15 +8,14 @@ module Stall
8
8
 
9
9
  include Stall::Addressable
10
10
 
11
- if Stall.config.default_user_model
12
- belongs_to :user, polymorphic: true, inverse_of: :customer
13
- accepts_nested_attributes_for :user
11
+ belongs_to :user, polymorphic: true, inverse_of: :customer
12
+ accepts_nested_attributes_for :user
14
13
 
15
- before_validation :ensure_user_email
16
- end
14
+ before_validation :ensure_user_email
17
15
 
18
16
  has_many :product_lists, dependent: :destroy
19
17
  has_many :credit_notes, dependent: :destroy
18
+ has_many :carts, dependent: :nullify
20
19
 
21
20
  validates :email, presence: true,
22
21
  format: { with: /\A[^@\s]+@([^@\s]+\.)+[^@\W]+\z/ }
@@ -26,9 +25,7 @@ module Stall
26
25
  end
27
26
 
28
27
  def build_user(attributes = {})
29
- (self.user = Stall.config.default_user_model.new(attributes)).tap do
30
- user.customer = self if user.respond_to?(:customer)
31
- end if Stall.config.default_user_model
28
+ self.user = ::User.new(attributes.merge(customer: self))
32
29
  end
33
30
 
34
31
  def credit(currency = Stall.config.default_currency)
@@ -0,0 +1,21 @@
1
+ module Stall
2
+ module Models
3
+ module Image
4
+ extend ActiveSupport::Concern
5
+
6
+ included do
7
+ self.table_name = 'stall_images'
8
+
9
+ acts_as_orderable
10
+
11
+ belongs_to :imageable, polymorphic: true
12
+
13
+ has_attached_file :file, styles: {
14
+ thumb: '100x100#',
15
+ show: '555x'
16
+ }
17
+ validates_attachment :file, content_type: { content_type: /\Aimage\/.*\z/ }
18
+ end
19
+ end
20
+ end
21
+ end
@@ -6,6 +6,10 @@ module Stall
6
6
  included do
7
7
  self.table_name = 'stall_manufacturers'
8
8
 
9
+ acts_as_orderable
10
+ extend FriendlyId
11
+ friendly_id :name, use: [:slugged, :finders]
12
+
9
13
  has_many :products, dependent: :nullify
10
14
 
11
15
  has_attached_file :logo
@@ -16,16 +16,28 @@ module Stall
16
16
  has_many :variants, dependent: :destroy, inverse_of: :product
17
17
  accepts_nested_attributes_for :variants, allow_destroy: true
18
18
 
19
- has_many :product_details, dependent: :destroy, inverse_of: :product
19
+ has_many :product_details, -> { ordered }, dependent: :destroy,
20
+ inverse_of: :product
20
21
  accepts_nested_attributes_for :product_details, allow_destroy: true
21
22
 
22
- has_attached_file :image, styles: {
23
- thumb: '100x100#',
24
- show: '555x'
25
- }
23
+ has_many :product_suggestions, dependent: :destroy
24
+ has_many :suggested_products, through: :product_suggestions,
25
+ source: :suggestion
26
26
 
27
- validates :name, :image, presence: true
28
- validates_attachment :image, content_type: { content_type: /\Aimage\/.*\z/ }
27
+ has_many :suggester_product_suggestions, dependent: :destroy,
28
+ foreign_key: :suggestion_id,
29
+ class_name: 'ProductSuggestion'
30
+ has_many :suggester_products, through: :suggester_product_suggestions,
31
+ source: :product
32
+
33
+ has_many :images, -> { ordered }, as: :imageable
34
+
35
+ has_many :curated_list_products, dependent: :destroy
36
+ has_many :curated_product_lists, through: :curated_list_products
37
+
38
+ accepts_nested_attributes_for :images, allow_destroy: true
39
+
40
+ validates :name, presence: true
29
41
 
30
42
  scope :visible, -> { where(visible: true) }
31
43
  end
@@ -37,6 +49,17 @@ module Stall
37
49
  def price
38
50
  variants.map(&:price).min
39
51
  end
52
+
53
+ def image(*args)
54
+ if (image = images.first)
55
+ image.file
56
+ end
57
+ end
58
+
59
+ # Support paperclip attachment presence helper for #image
60
+ def image?
61
+ !!image
62
+ end
40
63
  end
41
64
  end
42
65
  end
@@ -7,7 +7,6 @@ module Stall
7
7
  self.table_name = 'stall_product_categories'
8
8
 
9
9
  acts_as_tree order: 'position'
10
- class_attribute :max_depth
11
10
 
12
11
  extend FriendlyId
13
12
  friendly_id :name, use: [:slugged, :finders]
@@ -16,7 +15,11 @@ module Stall
16
15
 
17
16
  validates :name, presence: true
18
17
 
19
- scope :ordered, -> { order(position: 'asc') }
18
+ scope :ordered, -> { order(position: 'ASC') }
19
+ end
20
+
21
+ def all_child_products
22
+ ::Product.where(product_category_id: ([id] + descendant_ids))
20
23
  end
21
24
 
22
25
  module ClassMethods
@@ -64,7 +64,7 @@ module Stall
64
64
  end
65
65
 
66
66
  def total_quantity
67
- line_items.map(&:quantity).sum
67
+ line_items.map(&:quantity).compact.sum
68
68
  end
69
69
 
70
70
  def wizard
@@ -0,0 +1,14 @@
1
+ module Stall
2
+ module Models
3
+ module ProductSuggestion
4
+ extend ActiveSupport::Concern
5
+
6
+ included do
7
+ self.table_name = 'stall_product_suggestions'
8
+
9
+ belongs_to :product
10
+ belongs_to :suggestion, class_name: 'Product'
11
+ end
12
+ end
13
+ end
14
+ end
@@ -17,8 +17,6 @@ module Stall
17
17
  with_model_currency: :currency, allow_nil: true
18
18
 
19
19
  enum state: { pending: 0, shipped: 1 }
20
-
21
- validates :cart, :shipping_method, presence: true
22
20
  end
23
21
 
24
22
  def currency
@@ -0,0 +1,18 @@
1
+ module Stall
2
+ module Models
3
+ module User
4
+ extend ActiveSupport::Concern
5
+
6
+ included do
7
+ self.table_name = 'stall_users'
8
+
9
+ devise :database_authenticatable, :registerable, :recoverable,
10
+ :rememberable, :trackable, :validatable, :omniauthable,
11
+ omniauth_providers: Stall.config.omniauth_providers.map(&:name)
12
+
13
+ has_one :customer, as: :user, dependent: :nullify
14
+ has_many :user_omniauth_accounts, dependent: :destroy
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,13 @@
1
+ module Stall
2
+ module Models
3
+ module UserOmniauthAccount
4
+ extend ActiveSupport::Concern
5
+
6
+ included do
7
+ self.table_name = 'stall_user_omniauth_accounts'
8
+
9
+ belongs_to :user
10
+ end
11
+ end
12
+ end
13
+ end
@@ -26,22 +26,22 @@ module Stall
26
26
  delegate :image, :image?, :vat_rate, to: :product, allow_nil: true
27
27
 
28
28
  scope :published, -> { where(published: true) }
29
+ end
29
30
 
30
- def currency
31
- @currency ||= Money::Currency.new(Stall.config.default_currency)
32
- end
33
-
34
- private
31
+ def currency
32
+ @currency ||= Money::Currency.new(Stall.config.default_currency)
33
+ end
35
34
 
36
- def refresh_name
37
- product_name = product.try(:name)
35
+ private
38
36
 
39
- properties = variant_property_values.map do |variant_property_value|
40
- variant_property_value.property_value.name
41
- end
37
+ def refresh_name
38
+ product_name = product.try(:name)
42
39
 
43
- self.name = [product_name, properties.join(' - ')].join(' / ')
40
+ properties = variant_property_values.map do |variant_property_value|
41
+ variant_property_value.property_value.name
44
42
  end
43
+
44
+ self.name = [product_name, properties.join(' - ')].join(' / ')
45
45
  end
46
46
  end
47
47
  end
@@ -0,0 +1,3 @@
1
+ class User < ActiveRecord::Base
2
+ include Stall::Models::User
3
+ end
@@ -0,0 +1,3 @@
1
+ class UserOmniauthAccount < ActiveRecord::Base
2
+ include Stall::Models::UserOmniauthAccount
3
+ end
@@ -8,7 +8,7 @@ module Stall
8
8
  end
9
9
 
10
10
  def call
11
- return false unless line_item.valid?
11
+ return false unless line_item_valid?
12
12
 
13
13
  unless assemble_identical_line_items
14
14
  cart.line_items << line_item
@@ -23,9 +23,18 @@ module Stall
23
23
  end
24
24
  end
25
25
 
26
+ def line_item_valid?
27
+ line_item.valid? && enough_stock?
28
+ end
29
+
30
+ def enough_stock?
31
+ stock_service = Stall.config.service_for(:available_stocks).new
32
+ stock_service.on_hand?(line_item)
33
+ end
34
+
26
35
  def line_item
27
36
  @line_item ||= sellable.to_line_item.tap do |line_item|
28
- line_item.quantity = params[:quantity]
37
+ line_item.quantity = line_item_params[:quantity]
29
38
  end
30
39
  end
31
40
 
@@ -48,15 +57,21 @@ module Stall
48
57
  end
49
58
 
50
59
  def sellable
51
- @sellable ||= sellable_class.find(params[:sellable_id])
60
+ @sellable ||= sellable_class.find(line_item_params[:sellable_id])
52
61
  end
53
62
 
54
63
  def sellable_class
55
- @sellable_class ||= params[:sellable_type].camelize.constantize
64
+ @sellable_class ||= line_item_params[:sellable_type].camelize.constantize
56
65
  end
57
66
 
58
67
  def shipping_fee_service
59
68
  @shipping_fee_service ||= Stall::ShippingFeeCalculatorService.new(cart)
60
69
  end
70
+
71
+ def line_item_params
72
+ @line_item_params ||= params.require(:line_item).permit(
73
+ :sellable_type, :sellable_id, :quantity
74
+ )
75
+ end
61
76
  end
62
77
  end
@@ -0,0 +1,11 @@
1
+ module Stall
2
+ class AvailableStocksService < Stall::BaseService
3
+ def on_hand?(line_item)
4
+ if Stall.config.manage_inventory
5
+ line_item.variant.stock >= line_item.quantity
6
+ else
7
+ true
8
+ end
9
+ end
10
+ end
11
+ end