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.
- checksums.yaml +4 -4
- data/app/assets/javascripts/para/stall/inputs/variant-select.coffee +4 -3
- data/app/assets/javascripts/stall.coffee +3 -1
- data/app/assets/javascripts/stall/add-product-to-wish-list-button.coffee +99 -0
- data/app/assets/javascripts/stall/add-to-wish-list-button.coffee +17 -0
- data/app/assets/javascripts/stall/product-list-form.coffee +31 -0
- data/app/controllers/stall/cart_line_items_controller.rb +21 -0
- data/app/controllers/stall/curated_product_lists_controller.rb +6 -0
- data/app/controllers/stall/line_items_controller.rb +14 -7
- data/app/controllers/stall/products_breadcrumbs.rb +8 -1
- data/app/controllers/stall/products_controller.rb +12 -0
- data/app/controllers/stall/wish_list_line_items_controller.rb +52 -0
- data/app/controllers/stall/wish_lists_controller.rb +7 -0
- data/app/helpers/stall/add_to_cart_helper.rb +3 -2
- data/app/helpers/stall/add_to_wish_list_helper.rb +30 -0
- data/app/helpers/stall/products_helper.rb +13 -0
- data/app/models/stall/models/cart.rb +0 -6
- data/app/models/stall/models/customer.rb +9 -0
- data/app/models/stall/models/line_item.rb +13 -0
- data/app/models/stall/models/product.rb +1 -1
- data/app/models/stall/models/product_list.rb +4 -0
- data/app/models/stall/models/variant.rb +4 -0
- data/app/models/stall/models/wish_list.rb +18 -0
- data/app/models/wish_list.rb +3 -0
- data/app/services/stall/add_to_cart_service.rb +4 -47
- data/app/services/stall/add_to_product_list_service.rb +63 -0
- data/app/services/stall/add_to_wish_list_service.rb +17 -0
- data/app/services/stall/available_stocks_service.rb +1 -1
- data/app/services/stall/shipping_fee_calculator_service.rb +22 -9
- data/app/views/admin/products/_form.html.haml +5 -0
- data/app/views/checkout/steps/_informations.html.haml +1 -1
- data/app/views/stall/addresses/_fields.html.haml +4 -4
- data/app/views/stall/{line_items → cart_line_items}/_add_error.html.haml +0 -0
- data/app/views/stall/{line_items → cart_line_items}/_added.html.haml +2 -2
- data/app/views/stall/{line_items → cart_line_items}/_form.html.haml +1 -1
- data/app/views/stall/carts/_widget.html.haml +1 -1
- data/app/views/stall/carts/show.html.haml +4 -4
- data/app/views/stall/curated_product_lists/show.html.haml +1 -1
- data/app/views/stall/customers/_fields.html.haml +1 -1
- data/app/views/stall/customers/_sign_in.html.haml +2 -2
- data/app/views/stall/products/_list.html.haml +2 -0
- data/app/views/stall/products/_product.html.haml +2 -1
- data/app/views/stall/wish_list_line_items/_add_error.html.haml +17 -0
- data/app/views/stall/wish_list_line_items/_added.html.haml +19 -0
- data/app/views/stall/wish_list_line_items/_button.html.haml +3 -0
- data/app/views/stall/wish_list_line_items/_form.html.haml +12 -0
- data/app/views/stall/wish_lists/show.html.haml +21 -0
- data/config/locales/stall.fr.yml +26 -0
- data/db/migrate/20170425085606_add_weight_to_stall_products_and_variants.rb +6 -0
- data/db/migrate/20170426163450_add_vat_rate_to_stall_products.rb +5 -0
- data/db/migrate/20170522062334_change_variants_weight_default_to_nil.rb +11 -0
- data/lib/generators/stall/install/templates/initializer.rb +18 -0
- data/lib/stall.rb +1 -0
- data/lib/stall/addressable.rb +35 -4
- data/lib/stall/addresses/copy_source_to_target.rb +14 -10
- data/lib/stall/addresses/prefill_target_from_source.rb +10 -6
- data/lib/stall/config.rb +4 -0
- data/lib/stall/engine.rb +1 -0
- data/lib/stall/routes.rb +42 -19
- data/lib/stall/sellable.rb +1 -1
- data/lib/stall/shipping/calculator.rb +1 -1
- data/lib/stall/shipping/country_weight_table_calculator.rb +3 -2
- data/lib/stall/version.rb +1 -1
- data/lib/stall/wish_list_helper.rb +93 -0
- metadata +26 -6
- 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
|
@@ -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
|
@@ -1,18 +1,11 @@
|
|
1
1
|
module Stall
|
2
|
-
class AddToCartService < Stall::
|
3
|
-
|
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
|
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
|
@@ -9,24 +9,37 @@ module Stall
|
|
9
9
|
def call
|
10
10
|
return unless cart.shipment && cart.shipment.shipping_method
|
11
11
|
|
12
|
-
|
13
|
-
|
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
|
-
|
16
|
-
calculator = calculator_class.new(cart, cart.shipment.shipping_method)
|
21
|
+
private
|
17
22
|
|
18
|
-
|
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
|
27
|
-
|
28
|
-
|
29
|
-
|
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 }
|
File without changes
|
@@ -1,4 +1,4 @@
|
|
1
|
-
.modal.fade{ role: 'dialog', tabindex: '-1', data: { :'cart' => { token:
|
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(@
|
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:
|
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 }
|
@@ -1,5 +1,5 @@
|
|
1
|
-
.cart-recap{ data: { :'
|
2
|
-
= simple_form_for @cart, url: cart_path(@cart), as: :cart, remote: true, data: { :'
|
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: { :'
|
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,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)
|