stall 0.3.3 → 0.3.4

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 (66) hide show
  1. checksums.yaml +4 -4
  2. data/app/assets/javascripts/para/stall/inputs/variant-select.coffee +4 -3
  3. data/app/assets/javascripts/stall.coffee +3 -1
  4. data/app/assets/javascripts/stall/add-product-to-wish-list-button.coffee +99 -0
  5. data/app/assets/javascripts/stall/add-to-wish-list-button.coffee +17 -0
  6. data/app/assets/javascripts/stall/product-list-form.coffee +31 -0
  7. data/app/controllers/stall/cart_line_items_controller.rb +21 -0
  8. data/app/controllers/stall/curated_product_lists_controller.rb +6 -0
  9. data/app/controllers/stall/line_items_controller.rb +14 -7
  10. data/app/controllers/stall/products_breadcrumbs.rb +8 -1
  11. data/app/controllers/stall/products_controller.rb +12 -0
  12. data/app/controllers/stall/wish_list_line_items_controller.rb +52 -0
  13. data/app/controllers/stall/wish_lists_controller.rb +7 -0
  14. data/app/helpers/stall/add_to_cart_helper.rb +3 -2
  15. data/app/helpers/stall/add_to_wish_list_helper.rb +30 -0
  16. data/app/helpers/stall/products_helper.rb +13 -0
  17. data/app/models/stall/models/cart.rb +0 -6
  18. data/app/models/stall/models/customer.rb +9 -0
  19. data/app/models/stall/models/line_item.rb +13 -0
  20. data/app/models/stall/models/product.rb +1 -1
  21. data/app/models/stall/models/product_list.rb +4 -0
  22. data/app/models/stall/models/variant.rb +4 -0
  23. data/app/models/stall/models/wish_list.rb +18 -0
  24. data/app/models/wish_list.rb +3 -0
  25. data/app/services/stall/add_to_cart_service.rb +4 -47
  26. data/app/services/stall/add_to_product_list_service.rb +63 -0
  27. data/app/services/stall/add_to_wish_list_service.rb +17 -0
  28. data/app/services/stall/available_stocks_service.rb +1 -1
  29. data/app/services/stall/shipping_fee_calculator_service.rb +22 -9
  30. data/app/views/admin/products/_form.html.haml +5 -0
  31. data/app/views/checkout/steps/_informations.html.haml +1 -1
  32. data/app/views/stall/addresses/_fields.html.haml +4 -4
  33. data/app/views/stall/{line_items → cart_line_items}/_add_error.html.haml +0 -0
  34. data/app/views/stall/{line_items → cart_line_items}/_added.html.haml +2 -2
  35. data/app/views/stall/{line_items → cart_line_items}/_form.html.haml +1 -1
  36. data/app/views/stall/carts/_widget.html.haml +1 -1
  37. data/app/views/stall/carts/show.html.haml +4 -4
  38. data/app/views/stall/curated_product_lists/show.html.haml +1 -1
  39. data/app/views/stall/customers/_fields.html.haml +1 -1
  40. data/app/views/stall/customers/_sign_in.html.haml +2 -2
  41. data/app/views/stall/products/_list.html.haml +2 -0
  42. data/app/views/stall/products/_product.html.haml +2 -1
  43. data/app/views/stall/wish_list_line_items/_add_error.html.haml +17 -0
  44. data/app/views/stall/wish_list_line_items/_added.html.haml +19 -0
  45. data/app/views/stall/wish_list_line_items/_button.html.haml +3 -0
  46. data/app/views/stall/wish_list_line_items/_form.html.haml +12 -0
  47. data/app/views/stall/wish_lists/show.html.haml +21 -0
  48. data/config/locales/stall.fr.yml +26 -0
  49. data/db/migrate/20170425085606_add_weight_to_stall_products_and_variants.rb +6 -0
  50. data/db/migrate/20170426163450_add_vat_rate_to_stall_products.rb +5 -0
  51. data/db/migrate/20170522062334_change_variants_weight_default_to_nil.rb +11 -0
  52. data/lib/generators/stall/install/templates/initializer.rb +18 -0
  53. data/lib/stall.rb +1 -0
  54. data/lib/stall/addressable.rb +35 -4
  55. data/lib/stall/addresses/copy_source_to_target.rb +14 -10
  56. data/lib/stall/addresses/prefill_target_from_source.rb +10 -6
  57. data/lib/stall/config.rb +4 -0
  58. data/lib/stall/engine.rb +1 -0
  59. data/lib/stall/routes.rb +42 -19
  60. data/lib/stall/sellable.rb +1 -1
  61. data/lib/stall/shipping/calculator.rb +1 -1
  62. data/lib/stall/shipping/country_weight_table_calculator.rb +3 -2
  63. data/lib/stall/version.rb +1 -1
  64. data/lib/stall/wish_list_helper.rb +93 -0
  65. metadata +26 -6
  66. data/app/assets/javascripts/stall/cart-form.coffee +0 -28
@@ -29,6 +29,7 @@ module Stall
29
29
 
30
30
  before_validation :restore_valid_quantity
31
31
  before_validation :refresh_total_prices
32
+ before_validation :refresh_weight
32
33
 
33
34
  scope :ordered, -> { order(created_at: :asc) }
34
35
  end
@@ -43,6 +44,14 @@ module Stall
43
44
  product_list.try(:currency) || Money.default_currency
44
45
  end
45
46
 
47
+ def weight
48
+ read_store_attribute(:data, :weight).presence || Stall.config.default_product_weight
49
+ end
50
+
51
+ def total_weight
52
+ weight * quantity
53
+ end
54
+
46
55
  private
47
56
 
48
57
  # TODO : Stocks availibility handling
@@ -61,6 +70,10 @@ module Stall
61
70
  restore_quantity!
62
71
  end
63
72
  end
73
+
74
+ def refresh_weight
75
+ self.weight = sellable.weight if sellable.respond_to?(:weight)
76
+ end
64
77
  end
65
78
  end
66
79
  end
@@ -43,7 +43,7 @@ module Stall
43
43
  end
44
44
 
45
45
  def vat_rate
46
- Stall.config.vat_rate
46
+ read_attribute(:vat_rate).presence || Stall.config.vat_rate
47
47
  end
48
48
 
49
49
  def price
@@ -67,6 +67,10 @@ module Stall
67
67
  line_items.map(&:quantity).compact.sum
68
68
  end
69
69
 
70
+ def total_weight
71
+ line_items.map(&:total_weight).sum
72
+ end
73
+
70
74
  def wizard
71
75
  @wizard ||= self.class.wizard
72
76
  end
@@ -32,6 +32,10 @@ module Stall
32
32
  @currency ||= Money::Currency.new(Stall.config.default_currency)
33
33
  end
34
34
 
35
+ def weight
36
+ read_attribute(:weight).presence || product.try(:weight)
37
+ end
38
+
35
39
  private
36
40
 
37
41
  def refresh_name
@@ -0,0 +1,18 @@
1
+ module Stall
2
+ module Models
3
+ module WishList
4
+ extend ActiveSupport::Concern
5
+
6
+ def includes_product?(product)
7
+ line_item_for_product(product).present?
8
+ end
9
+
10
+ def line_item_for_product(product)
11
+ line_items.find do |line_item|
12
+ line_item.sellable_type == 'Variant' &&
13
+ product.variant_ids.include?(line_item.sellable_id)
14
+ end
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,3 @@
1
+ class WishList < ProductList
2
+ include Stall::Models::WishList
3
+ end
@@ -1,18 +1,11 @@
1
1
  module Stall
2
- class AddToCartService < Stall::BaseService
3
- attr_reader :cart, :params
4
-
5
- def initialize(cart, params)
6
- @cart = cart
7
- @params = params
8
- end
2
+ class AddToCartService < Stall::AddToProductListService
3
+ alias_method :cart, :product_list
9
4
 
10
5
  def call
11
6
  return false unless line_item_valid?
12
7
 
13
- unless assemble_identical_line_items
14
- cart.line_items << line_item
15
- end
8
+ cart.line_items << line_item unless assemble_identical_line_items
16
9
 
17
10
  cart.save.tap do |saved|
18
11
  return false unless saved
@@ -32,46 +25,10 @@ module Stall
32
25
  stock_service.on_hand?(line_item)
33
26
  end
34
27
 
35
- def line_item
36
- @line_item ||= sellable.to_line_item.tap do |line_item|
37
- line_item.quantity = line_item_params[:quantity]
38
- end
39
- end
40
-
41
28
  private
42
29
 
43
- def assemble_identical_line_items
44
- # Find an existing line item which is like our new one
45
- existing_line_item = cart.line_items.find do |li|
46
- line_item.like?(li)
47
- end
48
-
49
- # If we found one, we assemble both line items into the old one, to avoid
50
- # duplicating the same sellables in the product list
51
- if existing_line_item
52
- existing_line_item.quantity += line_item.quantity
53
- @line_item = existing_line_item
54
- else
55
- false
56
- end
57
- end
58
-
59
- def sellable
60
- @sellable ||= sellable_class.find(line_item_params[:sellable_id])
61
- end
62
-
63
- def sellable_class
64
- @sellable_class ||= line_item_params[:sellable_type].camelize.constantize
65
- end
66
-
67
30
  def shipping_fee_service
68
- @shipping_fee_service ||= Stall::ShippingFeeCalculatorService.new(cart)
69
- end
70
-
71
- def line_item_params
72
- @line_item_params ||= params.require(:line_item).permit(
73
- :sellable_type, :sellable_id, :quantity
74
- )
31
+ @shipping_fee_service ||= Stall.config.service_for(:shipping_fee_calculator).new(cart)
75
32
  end
76
33
  end
77
34
  end
@@ -0,0 +1,63 @@
1
+ module Stall
2
+ class AddToProductListService < Stall::BaseService
3
+ attr_reader :product_list, :params
4
+
5
+ def initialize(product_list, params)
6
+ @product_list = product_list
7
+ @params = params
8
+ end
9
+
10
+ def call
11
+ fail NotImplementedError,
12
+ 'Override #call in Stall::AddToProductListService subclasses'
13
+ end
14
+
15
+ def line_item
16
+ @line_item ||= sellable.to_line_item.tap do |line_item|
17
+ line_item.quantity = quantity
18
+ end
19
+ end
20
+
21
+ def line_item_params?
22
+ line_item_params.any?
23
+ end
24
+
25
+ private
26
+
27
+ def assemble_identical_line_items
28
+ # Find an existing line item which is like our new one
29
+ existing_line_item = product_list.line_items.find do |li|
30
+ line_item.like?(li)
31
+ end
32
+
33
+ # If we found one, we assemble both line items into the old one, to avoid
34
+ # duplicating the same sellables in the product list
35
+ if existing_line_item
36
+ existing_line_item.quantity += line_item.quantity
37
+ @line_item = existing_line_item
38
+ else
39
+ false
40
+ end
41
+ end
42
+
43
+ def sellable
44
+ @sellable ||= sellable_class.find(line_item_params[:sellable_id])
45
+ end
46
+
47
+ def sellable_class
48
+ @sellable_class ||= line_item_params[:sellable_type].camelize.constantize
49
+ end
50
+
51
+ def quantity
52
+ @quantity ||= line_item_params[:quantity]
53
+ end
54
+
55
+ def line_item_params
56
+ @line_item_params ||= params.require(:line_item).permit(
57
+ :sellable_type, :sellable_id, :quantity
58
+ )
59
+ rescue ActionController::ParameterMissing
60
+ {}
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,17 @@
1
+ module Stall
2
+ class AddToWishListService < Stall::AddToProductListService
3
+ alias_method :wish_list, :product_list
4
+
5
+ def call
6
+ return false unless line_item.valid?
7
+ wish_list.line_items << line_item unless assemble_identical_line_items
8
+ wish_list.save
9
+ end
10
+
11
+ def add(variant)
12
+ @sellable = variant
13
+ @quantity = 1
14
+ call
15
+ end
16
+ end
17
+ end
@@ -2,7 +2,7 @@ module Stall
2
2
  class AvailableStocksService < Stall::BaseService
3
3
  def on_hand?(line_item)
4
4
  if Stall.config.manage_inventory
5
- line_item.variant.stock >= line_item.quantity
5
+ line_item.sellable.stock >= line_item.quantity
6
6
  else
7
7
  true
8
8
  end
@@ -9,24 +9,37 @@ module Stall
9
9
  def call
10
10
  return unless cart.shipment && cart.shipment.shipping_method
11
11
 
12
- calculator_identifier = cart.shipment.shipping_method.identifier
13
- calculator_class = Stall::Shipping::Calculator.for(calculator_identifier)
12
+ update_price_for(cart.shipment, calculator) if calculator
13
+ end
14
+
15
+ def available?
16
+ cart.line_items.length > 0 &&
17
+ cart.try(:shipping_address) &&
18
+ cart.try(:shipment).try(:shipping_method)
19
+ end
14
20
 
15
- if calculator_class
16
- calculator = calculator_class.new(cart, cart.shipment.shipping_method)
21
+ private
17
22
 
18
- cart.shipment.update_attributes(
23
+ def update_price_for(shipment, calculator)
24
+ if calculator.price
25
+ shipment.update_attributes(
19
26
  price: calculator.price,
20
27
  eot_price: calculator.eot_price,
21
28
  vat_rate: calculator.vat_rate
22
29
  )
30
+ else
31
+ shipment.errors.add(:price, :unavailable)
23
32
  end
24
33
  end
25
34
 
26
- def available?
27
- cart.line_items.length > 0 &&
28
- cart.try(:shipping_address) &&
29
- cart.try(:shipment).try(:shipping_method)
35
+ def calculator
36
+ @calculator ||= calculator_class && calculator_class.new(cart, cart.shipment.shipping_method)
37
+ end
38
+
39
+ def calculator_class
40
+ @calculator_class ||= Stall::Shipping::Calculator.for(
41
+ cart.shipment.shipping_method.identifier
42
+ ) if cart.shipment.try(:shipping_method)
30
43
  end
31
44
  end
32
45
  end
@@ -8,6 +8,8 @@
8
8
  = form.input :product_category, as: :selectize, collection: Para.components.product_categories.resources.leaves
9
9
  = form.input :manufacturer, as: :selectize
10
10
 
11
+ = form.input :vat_rate, hint: t('para.stall.products.vat_rate_hint', rate: number_to_percentage(Stall.config.vat_rate), precision: 2).html_safe
12
+
11
13
  = form.input :suggested_products, as: :selectize
12
14
 
13
15
  = tabs.tab :images, icon: 'picture-o' do
@@ -20,4 +22,7 @@
20
22
  = tabs.tab :variants, icon: 'list' do
21
23
  = form.input :variants, as: :variants_matrix
22
24
 
25
+ = tabs.tab :shipping, icon: 'truck' do
26
+ = form.input :weight, hint: t('para.stall.products.weight_hint').html_safe
27
+
23
28
  = form.actions
@@ -25,7 +25,7 @@
25
25
  = form.input :terms, label: false do
26
26
  = form.check_box :terms, id: 'terms-checkbox'
27
27
  = form.label :terms, t('stall.checkout.informations.accept_the')
28
- = link_to Stall.config.terms_path, target: '_blank' do
28
+ = link_to Stall.config.terms_path, target: '_blank', rel: 'nofollow' do
29
29
  = form.object.class.human_attribute_name(:terms)
30
30
 
31
31
  %button.btn.btn-primary.btn-lg.btn-block{ type: 'submit' }
@@ -1,10 +1,10 @@
1
1
  .addresses-fields{ data: { :'addresses-fields' => true } }
2
2
  %fieldset{ data: { :'address-form' => :shipping } }
3
- = form.fields_for :shipping_address do |address_form|
3
+ = form.fields_for :shipping_address, form.object.shipping_address(force_actual_address: true) do |address_form|
4
4
  = render partial: 'stall/addresses/nested_fields', locals: { form: address_form }
5
5
 
6
- = form.input :use_another_address_for_billing, as: :boolean, input_html: { checked: (params[:use_another_address_for_billing] || form.object.billing_address?), data: { :'use-another-address-for-billing' => true } }
6
+ = form.input :use_another_address_for_billing, as: :boolean, input_html: { checked: (params[:use_another_address_for_billing] == '1' || form.object.billing_address?), data: { :'use-another-address-for-billing' => true } }
7
7
 
8
- %fieldset{ class: ('hidden' unless params[:use_another_address_for_billing] == '1'), data: { :'address-form' => :billing } }
9
- = form.fields_for :billing_address do |address_form|
8
+ %fieldset{ class: ('hidden' unless params[:use_another_address_for_billing] == '1' || form.object.billing_address?), data: { :'address-form' => :billing } }
9
+ = form.fields_for :billing_address, form.object.billing_address(force_actual_address: true) do |address_form|
10
10
  = render partial: 'stall/addresses/nested_fields', locals: { form: address_form }
@@ -1,4 +1,4 @@
1
- .modal.fade{ role: 'dialog', tabindex: '-1', data: { :'cart' => { token: current_cart.token, :'total-quantity' => current_cart.total_quantity, :'widget-markup' => @widget_partial } } }
1
+ .modal.fade{ role: 'dialog', tabindex: '-1', data: { :'cart' => { token: @product_list.token, :'total-quantity' => @product_list.total_quantity, :'widget-markup' => @widget_partial } } }
2
2
  .modal-dialog
3
3
  .modal-content
4
4
  .modal-header
@@ -19,5 +19,5 @@
19
19
  %button.btn.btn-default{ type: 'button', data: { dismiss: 'modal' } }
20
20
  = t('stall.line_items.added.continue_shopping')
21
21
 
22
- = link_to cart_path(@cart), class: 'btn btn-primary' do
22
+ = link_to cart_path(@product_list), class: 'btn btn-primary', rel: 'nofollow' do
23
23
  = t('stall.carts.actions.view_cart')
@@ -1,6 +1,6 @@
1
1
  = simple_form_for line_item, as: :line_item, url: cart_line_items_path(cart), remote: true, data: { :'add-to-cart-form' => true, :'error-messages' => { choose: t('stall.line_items.errors.choose'), quantity: t('stall.line_items.errors.quantity') } } do |form|
2
2
  = form.hidden_field :sellable_type, value: 'Variant'
3
- = form.input_field :sellable, as: :variant_select, product: @product
3
+ = form.input_field :sellable, as: :variant_select, product: product
4
4
 
5
5
  .input-group
6
6
  = form.input_field :quantity, spinner: false, value: 1, class: 'form-group', data: { :'quantity-field' => true }
@@ -24,5 +24,5 @@
24
24
  = number_to_currency(cart.total_price)
25
25
  %tr
26
26
  %td{ colspan: 2 }
27
- = link_to cart_path(cart), class: 'btn btn-primary btn-block' do
27
+ = link_to cart_path(cart), class: 'btn btn-primary btn-block', rel: 'nofollow' do
28
28
  = t('stall.carts.actions.view_cart')
@@ -1,5 +1,5 @@
1
- .cart-recap{ data: { :'cart-recap' => true } }
2
- = simple_form_for @cart, url: cart_path(@cart), as: :cart, remote: true, data: { :'cart-form' => true } do |form|
1
+ .cart-recap{ data: { :'product-list-recap' => true } }
2
+ = simple_form_for @cart, url: cart_path(@cart), as: :cart, remote: true, data: { :'product-list-form' => true } do |form|
3
3
  %table.table.table-striped
4
4
  %thead
5
5
  %tr
@@ -56,10 +56,10 @@
56
56
  %td
57
57
 
58
58
  .form-actions
59
- %button.btn.btn-default{ type: 'submit', data: { :'cart-update-button' => true } }
59
+ %button.btn.btn-default{ type: 'submit', data: { :'product-list-update-button' => true } }
60
60
  = t('stall.carts.recap.update')
61
61
 
62
- = link_to checkout_path(current_cart.identifier), class: 'btn btn-primary' do
62
+ = link_to checkout_path(current_cart.identifier), class: 'btn btn-primary', rel: 'nofollow' do
63
63
  = t('stall.carts.recap.validate')
64
64
 
65
65
 
@@ -5,4 +5,4 @@
5
5
  = render partial: 'stall/products/filters', locals: { search: @search, products: @filterable_products }
6
6
 
7
7
  .col-md-8
8
- = render partial: 'stall/products/list', locals: { products: @products }
8
+ = render partial: 'stall/products/list', locals: { products: @products, parent: @curated_product_list }
@@ -26,7 +26,7 @@
26
26
  %legend.omniauth-sign-up= t('stall.checkout.sign_in.omniauth_sign_up')
27
27
 
28
28
  - Stall.config.omniauth_providers.each do |provider|
29
- = link_to user_omniauth_redirect_path(provider.name, _redirect_to: request.path), class: 'btn btn-default' do
29
+ = link_to user_omniauth_redirect_path(provider.name, _redirect_to: request.path), class: 'btn btn-default', rel: 'nofollow' do
30
30
  = fa_icon provider.icon
31
31
  = t('stall.omniauth.sign_in_with', provider: provider.display_name)
32
32
 
@@ -2,7 +2,7 @@
2
2
  .panel-heading
3
3
  %i.fa.fa-user
4
4
  = t('stall.checkout.sign_in.already_have_an_account')
5
- = link_to '#sign-in-form', data: { toggle: 'collapse' } do
5
+ = link_to '#sign-in-form', data: { toggle: 'collapse' }, rel: 'nofollow' do
6
6
  = t('stall.checkout.sign_in.click_to_sign_in')
7
7
 
8
8
  #sign-in-form.panel-body.collapse{ data: { :'sign-in-form' => true } }
@@ -27,6 +27,6 @@
27
27
  %legend.omniauth-sign-in= t('stall.checkout.sign_in.omniauth_sign_in')
28
28
 
29
29
  - Stall.config.omniauth_providers.each do |provider|
30
- = link_to user_omniauth_redirect_path(provider.name, _redirect_to: request.path), class: 'btn btn-default' do
30
+ = link_to user_omniauth_redirect_path(provider.name, _redirect_to: request.path), class: 'btn btn-default', rel: 'nofollow' do
31
31
  = fa_icon provider.icon
32
32
  = t('stall.omniauth.sign_in_with', provider: provider.display_name)
@@ -1,3 +1,5 @@
1
+ - unless defined?(parent) then parent = nil; end
2
+
1
3
  .products-list
2
4
  .row
3
5
  - products.each do |product|
@@ -1,4 +1,5 @@
1
- = link_to product_path(product), class: 'product' do
1
+ = link_to product_path(product), class: 'product', data: { :'product-id' => product.id } do
2
2
  %span.product-name= product.name
3
3
  = image_tag product.image.url(:thumb), class: 'img-responsive'
4
4
  = number_to_currency(product.price)
5
+ = add_to_wish_list_button(product)