stall 0.1.1 → 0.1.2
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/README.md +45 -0
- data/app/assets/javascripts/stall.coffee +2 -0
- data/app/assets/javascripts/stall/cart-form.coffee +23 -0
- data/app/controllers/stall/application_controller.rb +10 -2
- data/app/controllers/stall/carts_controller.rb +31 -10
- data/app/controllers/stall/checkout/steps_controller.rb +36 -4
- data/app/controllers/stall/checkouts_controller.rb +12 -3
- data/app/controllers/stall/line_items_controller.rb +3 -3
- data/app/controllers/stall/payments_controller.rb +5 -3
- data/app/helpers/stall/add_to_cart_helper.rb +1 -1
- data/app/helpers/stall/checkout_helper.rb +22 -2
- data/app/helpers/stall/payments_helper.rb +8 -0
- data/app/mailers/stall/admin_mailer.rb +11 -0
- data/app/mailers/stall/base_mailer.rb +17 -0
- data/app/mailers/stall/customer_mailer.rb +13 -0
- data/app/models/address.rb +3 -0
- data/app/models/address_ownership.rb +3 -0
- data/app/models/adjustment.rb +3 -0
- data/app/models/cart.rb +3 -0
- data/app/models/customer.rb +3 -0
- data/app/models/line_item.rb +3 -0
- data/app/models/payment.rb +3 -0
- data/app/models/payment_method.rb +3 -0
- data/app/models/product_list.rb +3 -0
- data/app/models/shipment.rb +3 -0
- data/app/models/shipping_method.rb +3 -0
- data/app/models/stall/models.rb +4 -0
- data/app/models/stall/models/address.rb +45 -0
- data/app/models/stall/models/address_ownership.rb +26 -0
- data/app/models/stall/models/adjustment.rb +49 -0
- data/app/models/stall/models/cart.rb +37 -0
- data/app/models/stall/models/customer.rb +18 -0
- data/app/models/stall/models/line_item.rb +57 -0
- data/app/models/stall/models/payment.rb +33 -0
- data/app/models/stall/models/payment_method.rb +18 -0
- data/app/models/stall/models/product_list.rb +147 -0
- data/app/models/stall/models/shipment.rb +29 -0
- data/app/models/stall/models/shipping_method.rb +16 -0
- data/app/services/stall/add_to_cart_service.rb +11 -1
- data/app/services/stall/cart_update_service.rb +26 -0
- data/app/services/stall/payment_notification_service.rb +23 -9
- data/app/services/stall/shipping_fee_calculator_service.rb +13 -2
- data/app/views/checkout/steps/_informations.html.haml +3 -3
- data/app/views/checkout/steps/_payment.html.haml +25 -4
- data/app/views/checkout/steps/_payment_method.html.haml +1 -3
- data/app/views/checkout/steps/_payment_return.html.haml +6 -0
- data/app/views/checkout/steps/_shipping_method.html.haml +1 -1
- data/app/views/stall/admin_mailer/order_paid_email.html.haml +4 -0
- data/app/views/stall/carts/show.html.haml +36 -8
- data/app/views/stall/customer_mailer/order_paid_email.html.haml +8 -0
- data/app/views/stall/line_items/_added.html.haml +1 -1
- data/app/views/stall/shared/mailers/_address.html.haml +18 -0
- data/app/views/stall/shared/mailers/_cart.html.haml +114 -0
- data/config/locales/stall.fr.yml +90 -8
- data/db/migrate/20160304134849_change_all_json_columns_to_jsonb.rb +18 -0
- data/db/migrate/20160307142924_add_state_to_stall_addresses.rb +5 -0
- data/db/migrate/20160308142713_create_adjustments.rb +16 -0
- data/db/migrate/20160309165136_add_identifier_to_stall_product_lists.rb +5 -0
- data/db/migrate/20160316114649_add_data_to_stall_shipments.rb +5 -0
- data/db/migrate/20160317141632_add_state_to_stall_shipments.rb +5 -0
- data/db/migrate/20160629102943_add_active_to_stall_shipping_methods.rb +11 -0
- data/db/migrate/20160629104617_add_active_to_stall_payment_methods.rb +11 -0
- data/db/migrate/20160705110151_add_locale_to_stall_customers.rb +5 -0
- data/lib/generators/stall/checkout/step/templates/step.html.haml.erb +1 -1
- data/lib/generators/stall/checkout/step/templates/step.rb.erb +10 -2
- data/lib/generators/stall/checkout/wizard/templates/wizard.rb.erb +1 -1
- data/lib/generators/stall/install/templates/initializer.rb +85 -1
- data/lib/generators/stall/model/model_generator.rb +27 -0
- data/lib/generators/stall/service/service_generator.rb +39 -0
- data/lib/generators/stall/service/templates/service.rb.erb +16 -0
- data/lib/generators/stall/shipping/calculator/calculator_generator.rb +17 -0
- data/lib/generators/stall/shipping/calculator/templates/calculator.rb.erb +29 -0
- data/lib/stall.rb +4 -0
- data/lib/stall/addressable.rb +28 -2
- data/lib/stall/cart_helper.rb +84 -0
- data/lib/stall/carts_cleaner.rb +46 -0
- data/lib/stall/checkout.rb +2 -0
- data/lib/stall/checkout/informations_checkout_step.rb +3 -5
- data/lib/stall/checkout/payment_checkout_step.rb +3 -0
- data/lib/stall/checkout/payment_return_checkout_step.rb +11 -0
- data/lib/stall/checkout/shipping_method_checkout_step.rb +2 -1
- data/lib/stall/checkout/step.rb +47 -11
- data/lib/stall/checkout/step_form.rb +71 -0
- data/lib/stall/checkout/wizard.rb +15 -5
- data/lib/stall/config.rb +51 -0
- data/lib/stall/engine.rb +24 -2
- data/lib/stall/payable.rb +26 -0
- data/lib/stall/payments.rb +7 -3
- data/lib/stall/payments/config.rb +37 -0
- data/lib/stall/payments/fake_gateway_payment_notification.rb +34 -0
- data/lib/stall/payments/gateway.rb +19 -12
- data/lib/stall/payments/urls_config.rb +40 -0
- data/lib/stall/priceable.rb +7 -0
- data/lib/stall/rails/currency_helper.rb +1 -1
- data/lib/stall/routes.rb +17 -4
- data/lib/stall/sellable.rb +1 -2
- data/lib/stall/shipping.rb +3 -1
- data/lib/stall/shipping/calculator.rb +36 -3
- data/lib/stall/shipping/config.rb +17 -3
- data/lib/stall/shipping/country_weight_table_calculator.rb +1 -1
- data/lib/stall/shipping/free_shipping_calculator.rb +1 -1
- data/lib/stall/utils.rb +2 -2
- data/lib/stall/utils/config_dsl.rb +5 -1
- data/lib/stall/version.rb +1 -1
- data/lib/tasks/stall_tasks.rake +11 -0
- metadata +73 -15
- data/app/assets/javascripts/stall/application.js +0 -13
- data/app/assets/javascripts/stall/carts.js +0 -2
- data/app/helpers/stall/cart_helper.rb +0 -28
- data/app/models/stall/address.rb +0 -5
- data/app/models/stall/address_ownership.rb +0 -8
- data/app/models/stall/cart.rb +0 -31
- data/app/models/stall/customer.rb +0 -8
- data/app/models/stall/line_item.rb +0 -49
- data/app/models/stall/payment.rb +0 -14
- data/app/models/stall/payment_method.rb +0 -7
- data/app/models/stall/product_list.rb +0 -68
- data/app/models/stall/shipment.rb +0 -15
- data/app/models/stall/shipping_method.rb +0 -5
@@ -0,0 +1,45 @@
|
|
1
|
+
module Stall
|
2
|
+
module Models
|
3
|
+
module Address
|
4
|
+
extend ActiveSupport::Concern
|
5
|
+
|
6
|
+
included do
|
7
|
+
self.table_name = 'stall_addresses'
|
8
|
+
|
9
|
+
has_one :address_ownership, dependent: :destroy
|
10
|
+
|
11
|
+
enum civility: { :m => 1, :mme => 2 }
|
12
|
+
|
13
|
+
def self.civility_enum
|
14
|
+
civilities.each_with_object({}) do |(key, value), hash|
|
15
|
+
hash[I18n.t("stall.addresses.civilities.#{ key }")] = key
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def civility_name
|
20
|
+
I18n.t("stall.addresses.civilities.#{ civility }")
|
21
|
+
end
|
22
|
+
|
23
|
+
def country_name
|
24
|
+
if iso_country
|
25
|
+
iso_country.translations[I18n.locale.to_s] || iso_country.name
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def state_name
|
30
|
+
iso_state['name'] if iso_state
|
31
|
+
end
|
32
|
+
|
33
|
+
private
|
34
|
+
|
35
|
+
def iso_country
|
36
|
+
@iso_country ||= country && ISO3166::Country[country]
|
37
|
+
end
|
38
|
+
|
39
|
+
def iso_state
|
40
|
+
@iso_state ||= iso_country.subdivisions[state] if iso_country && state
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
module Stall
|
2
|
+
module Models
|
3
|
+
module AddressOwnership
|
4
|
+
extend ActiveSupport::Concern
|
5
|
+
|
6
|
+
included do
|
7
|
+
self.table_name = 'stall_address_ownerships'
|
8
|
+
|
9
|
+
belongs_to :address, inverse_of: :address_ownership
|
10
|
+
accepts_nested_attributes_for :address
|
11
|
+
|
12
|
+
belongs_to :addressable, polymorphic: true
|
13
|
+
end
|
14
|
+
|
15
|
+
[:shipping, :billing].each do |type|
|
16
|
+
define_method(:"mark_as_#{ type }") do
|
17
|
+
if (ownership = addressable.address_ownership_for(type)) && ownership != self
|
18
|
+
ownership.send(:"#{ type }=", false)
|
19
|
+
end
|
20
|
+
|
21
|
+
send(:"#{ type }=", true)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
module Stall
|
2
|
+
module Models
|
3
|
+
module Adjustment
|
4
|
+
extend ActiveSupport::Concern
|
5
|
+
|
6
|
+
included do
|
7
|
+
self.table_name = 'stall_adjustments'
|
8
|
+
|
9
|
+
monetize :eot_price_cents, :price_cents,
|
10
|
+
with_model_currency: :currency, allow_nil: true
|
11
|
+
|
12
|
+
belongs_to :cart
|
13
|
+
|
14
|
+
delegate :currency, to: :cart, allow_nil: true
|
15
|
+
|
16
|
+
validates :name, :price, :eot_price, :vat_rate, :cart, presence: true
|
17
|
+
|
18
|
+
before_validation :fill_eot_price_and_vat_rate
|
19
|
+
|
20
|
+
define_method(:eot_price) do
|
21
|
+
if (eot_price = read_attribute(:eot_price)) && eot_price.to_f != 0
|
22
|
+
eot_price
|
23
|
+
else
|
24
|
+
default_eot_price
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def vat_rate
|
30
|
+
read_attribute(:vat_rate) || Stall.config.vat_rate
|
31
|
+
end
|
32
|
+
|
33
|
+
def vat
|
34
|
+
price - eot_price
|
35
|
+
end
|
36
|
+
|
37
|
+
private
|
38
|
+
|
39
|
+
def fill_eot_price_and_vat_rate
|
40
|
+
self.vat_rate = vat_rate
|
41
|
+
self.eot_price = eot_price
|
42
|
+
end
|
43
|
+
|
44
|
+
def default_eot_price
|
45
|
+
(price / (1.0 + (vat_rate / 100.0))) if price && vat_rate
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
module Stall
|
2
|
+
module Models
|
3
|
+
module Cart
|
4
|
+
extend ActiveSupport::Concern
|
5
|
+
|
6
|
+
included do
|
7
|
+
include Stall::Addressable
|
8
|
+
include Stall::Payable
|
9
|
+
|
10
|
+
has_one :shipment, dependent: :destroy, inverse_of: :cart
|
11
|
+
accepts_nested_attributes_for :shipment
|
12
|
+
|
13
|
+
has_many :adjustments, dependent: :destroy, inverse_of: :cart
|
14
|
+
accepts_nested_attributes_for :adjustments
|
15
|
+
end
|
16
|
+
|
17
|
+
def total_weight
|
18
|
+
line_items.reduce(0) do |total, line_item|
|
19
|
+
total + (line_item.weight || Stall.config.default_product_weight)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def active?
|
24
|
+
!paid?
|
25
|
+
end
|
26
|
+
|
27
|
+
private
|
28
|
+
|
29
|
+
def items
|
30
|
+
items = line_items.to_a
|
31
|
+
items << shipment if shipment
|
32
|
+
items += adjustments.to_a
|
33
|
+
items
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
module Stall
|
2
|
+
module Models
|
3
|
+
module Customer
|
4
|
+
extend ActiveSupport::Concern
|
5
|
+
|
6
|
+
included do
|
7
|
+
self.table_name = 'stall_customers'
|
8
|
+
|
9
|
+
include Stall::Addressable
|
10
|
+
|
11
|
+
belongs_to :user, polymorphic: true, inverse_of: :customer
|
12
|
+
accepts_nested_attributes_for :user
|
13
|
+
|
14
|
+
has_many :product_lists, dependent: :destroy
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
module Stall
|
2
|
+
module Models
|
3
|
+
module LineItem
|
4
|
+
extend ActiveSupport::Concern
|
5
|
+
|
6
|
+
included do
|
7
|
+
self.table_name = 'stall_line_items'
|
8
|
+
|
9
|
+
include Stall::Priceable
|
10
|
+
|
11
|
+
store_accessor :data, :weight
|
12
|
+
|
13
|
+
monetize :unit_eot_price_cents, :unit_price_cents,
|
14
|
+
:eot_price_cents, :price_cents,
|
15
|
+
with_model_currency: :currency, allow_nil: true
|
16
|
+
|
17
|
+
belongs_to :sellable, polymorphic: true
|
18
|
+
belongs_to :product_list
|
19
|
+
|
20
|
+
validates :name, :unit_price, :unit_eot_price, :vat_rate, :price, :quantity,
|
21
|
+
:eot_price, :sellable, presence: true
|
22
|
+
|
23
|
+
validates :unit_price, :unit_eot_price, :vat_rate, :price, :eot_price,
|
24
|
+
numericality: true
|
25
|
+
|
26
|
+
validates :quantity, numericality: { greater_than: 0 }
|
27
|
+
|
28
|
+
validate :stock_availability
|
29
|
+
|
30
|
+
before_validation :refresh_total_prices
|
31
|
+
|
32
|
+
scope :ordered, -> { order(created_at: :asc) }
|
33
|
+
end
|
34
|
+
|
35
|
+
def like?(other)
|
36
|
+
[:sellable_id, :sellable_type].all? do |property|
|
37
|
+
public_send(property) == other.public_send(property)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def currency
|
42
|
+
product_list.try(:currency) || Money.default_currency
|
43
|
+
end
|
44
|
+
|
45
|
+
private
|
46
|
+
|
47
|
+
# TODO : Stocks availibility handling
|
48
|
+
def stock_availability
|
49
|
+
end
|
50
|
+
|
51
|
+
def refresh_total_prices
|
52
|
+
self.eot_price = unit_eot_price * quantity if unit_eot_price && quantity
|
53
|
+
self.price = unit_price * quantity if unit_price && quantity
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
module Stall
|
2
|
+
module Models
|
3
|
+
module Payment
|
4
|
+
extend ActiveSupport::Concern
|
5
|
+
|
6
|
+
included do
|
7
|
+
self.table_name = 'stall_payments'
|
8
|
+
|
9
|
+
store_accessor :data, :transaction_id
|
10
|
+
|
11
|
+
belongs_to :payment_method
|
12
|
+
belongs_to :cart
|
13
|
+
|
14
|
+
validates :cart, :payment_method, presence: true
|
15
|
+
|
16
|
+
scope :pending, -> { where(paid_at: nil) }
|
17
|
+
scope :paid, -> { where.not(paid_at: nil) }
|
18
|
+
end
|
19
|
+
|
20
|
+
def pay!
|
21
|
+
update_attributes!(paid_at: Time.now)
|
22
|
+
end
|
23
|
+
|
24
|
+
def paid?
|
25
|
+
paid_at != nil
|
26
|
+
end
|
27
|
+
|
28
|
+
def state
|
29
|
+
paid? ? :paid : :pending
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
module Stall
|
2
|
+
module Models
|
3
|
+
module PaymentMethod
|
4
|
+
extend ActiveSupport::Concern
|
5
|
+
|
6
|
+
included do
|
7
|
+
self.table_name = 'stall_payment_methods'
|
8
|
+
|
9
|
+
has_many :payments, dependent: :nullify
|
10
|
+
|
11
|
+
validates :name, :identifier, presence: true
|
12
|
+
|
13
|
+
scope :ordered, -> { order('stall_payment_methods.name ASC') }
|
14
|
+
scope :active, -> { where(active: true) }
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,147 @@
|
|
1
|
+
module Stall
|
2
|
+
module Models
|
3
|
+
module ProductList
|
4
|
+
extend ActiveSupport::Concern
|
5
|
+
|
6
|
+
included do
|
7
|
+
self.table_name = 'stall_product_lists'
|
8
|
+
|
9
|
+
store_accessor :data, :reference
|
10
|
+
|
11
|
+
has_secure_token
|
12
|
+
|
13
|
+
has_many :line_items, -> { ordered }, dependent: :destroy
|
14
|
+
accepts_nested_attributes_for :line_items, allow_destroy: true
|
15
|
+
|
16
|
+
belongs_to :customer
|
17
|
+
accepts_nested_attributes_for :customer
|
18
|
+
|
19
|
+
validates :type, presence: true
|
20
|
+
|
21
|
+
after_initialize :ensure_currency
|
22
|
+
after_initialize :ensure_state
|
23
|
+
|
24
|
+
after_save :ensure_reference, on: :create
|
25
|
+
|
26
|
+
scope :empty, -> {
|
27
|
+
joins(
|
28
|
+
'LEFT JOIN stall_line_items ' \
|
29
|
+
'ON stall_product_lists.id = stall_line_items.product_list_id'
|
30
|
+
).where(stall_line_items: { id: nil })
|
31
|
+
}
|
32
|
+
|
33
|
+
scope :filled, -> { joins(:line_items) }
|
34
|
+
|
35
|
+
scope :older_than, ->(date) {
|
36
|
+
where('stall_product_lists.updated_at < ?', date)
|
37
|
+
}
|
38
|
+
end
|
39
|
+
|
40
|
+
def state
|
41
|
+
read_attribute(:state).try(:to_sym)
|
42
|
+
end
|
43
|
+
|
44
|
+
def reset_state!
|
45
|
+
update_column(:state, wizard.steps.first)
|
46
|
+
end
|
47
|
+
|
48
|
+
def to_param
|
49
|
+
persisted? ? token : 'empty-cart'
|
50
|
+
end
|
51
|
+
|
52
|
+
def subtotal
|
53
|
+
price = line_items.map(&:price).sum
|
54
|
+
price = Money.new(price, currency) unless Money === price
|
55
|
+
price
|
56
|
+
end
|
57
|
+
|
58
|
+
def eot_subtotal
|
59
|
+
line_items.map(&:eot_price).sum
|
60
|
+
end
|
61
|
+
|
62
|
+
def total_price
|
63
|
+
price = items.map(&:price).sum
|
64
|
+
price = Money.new(price, currency) unless Money === price
|
65
|
+
price
|
66
|
+
end
|
67
|
+
|
68
|
+
def total_eot_price
|
69
|
+
items.map(&:eot_price).sum
|
70
|
+
end
|
71
|
+
|
72
|
+
def total_vat
|
73
|
+
items.map(&:vat).sum
|
74
|
+
end
|
75
|
+
|
76
|
+
def total_quantity
|
77
|
+
line_items.map(&:quantity).sum
|
78
|
+
end
|
79
|
+
|
80
|
+
def wizard
|
81
|
+
@wizard ||= self.class.wizard
|
82
|
+
end
|
83
|
+
|
84
|
+
def checkoutable?
|
85
|
+
line_items.length > 0
|
86
|
+
end
|
87
|
+
|
88
|
+
def active?
|
89
|
+
true
|
90
|
+
end
|
91
|
+
|
92
|
+
private
|
93
|
+
|
94
|
+
def ensure_currency
|
95
|
+
self.currency ||= Money.default_currency
|
96
|
+
end
|
97
|
+
|
98
|
+
def ensure_state
|
99
|
+
self.state ||= (wizard.try(:steps).try(:first) || 'pending')
|
100
|
+
end
|
101
|
+
|
102
|
+
def items
|
103
|
+
line_items.to_a
|
104
|
+
end
|
105
|
+
|
106
|
+
def ensure_reference
|
107
|
+
unless reference.present?
|
108
|
+
reference = [Time.now.strftime('%Y%m%d'), ('%05d' % id)].join('-')
|
109
|
+
self.reference = reference
|
110
|
+
save(validate: false)
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
module ClassMethods
|
115
|
+
def find_by_reference(reference)
|
116
|
+
where("data->>'reference' = ?", reference).first
|
117
|
+
end
|
118
|
+
|
119
|
+
# The .aborted and .finalized scopes cannot be declared as actual rails
|
120
|
+
# scopes since subclasses that override the .wizard method wouldn't
|
121
|
+
# be taken into account, scopes being executed in the context of the
|
122
|
+
# declaring class
|
123
|
+
#
|
124
|
+
def aborted(options = {})
|
125
|
+
where.not(state: wizard.steps.last)
|
126
|
+
.older_than(options.fetch(:before, 1.day.ago))
|
127
|
+
end
|
128
|
+
|
129
|
+
def finalized
|
130
|
+
where(state: wizard.steps.last)
|
131
|
+
end
|
132
|
+
|
133
|
+
def wizard
|
134
|
+
wizard_name = Stall.config.default_checkout_wizard
|
135
|
+
|
136
|
+
if (wizard = Stall::Utils.try_load_constant(wizard_name))
|
137
|
+
wizard
|
138
|
+
else
|
139
|
+
raise Stall::Checkout::WizardNotFoundError.new,
|
140
|
+
"The checkout wizard #{ wizard_name } was not found. You must generate it " +
|
141
|
+
"with `rails g stall:wizard #{ wizard_name.underscore.gsub('_checkout_wizard', '') }`"
|
142
|
+
end
|
143
|
+
end
|
144
|
+
end
|
145
|
+
end
|
146
|
+
end
|
147
|
+
end
|