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