stall 0.1.1 → 0.1.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (120) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +45 -0
  3. data/app/assets/javascripts/stall.coffee +2 -0
  4. data/app/assets/javascripts/stall/cart-form.coffee +23 -0
  5. data/app/controllers/stall/application_controller.rb +10 -2
  6. data/app/controllers/stall/carts_controller.rb +31 -10
  7. data/app/controllers/stall/checkout/steps_controller.rb +36 -4
  8. data/app/controllers/stall/checkouts_controller.rb +12 -3
  9. data/app/controllers/stall/line_items_controller.rb +3 -3
  10. data/app/controllers/stall/payments_controller.rb +5 -3
  11. data/app/helpers/stall/add_to_cart_helper.rb +1 -1
  12. data/app/helpers/stall/checkout_helper.rb +22 -2
  13. data/app/helpers/stall/payments_helper.rb +8 -0
  14. data/app/mailers/stall/admin_mailer.rb +11 -0
  15. data/app/mailers/stall/base_mailer.rb +17 -0
  16. data/app/mailers/stall/customer_mailer.rb +13 -0
  17. data/app/models/address.rb +3 -0
  18. data/app/models/address_ownership.rb +3 -0
  19. data/app/models/adjustment.rb +3 -0
  20. data/app/models/cart.rb +3 -0
  21. data/app/models/customer.rb +3 -0
  22. data/app/models/line_item.rb +3 -0
  23. data/app/models/payment.rb +3 -0
  24. data/app/models/payment_method.rb +3 -0
  25. data/app/models/product_list.rb +3 -0
  26. data/app/models/shipment.rb +3 -0
  27. data/app/models/shipping_method.rb +3 -0
  28. data/app/models/stall/models.rb +4 -0
  29. data/app/models/stall/models/address.rb +45 -0
  30. data/app/models/stall/models/address_ownership.rb +26 -0
  31. data/app/models/stall/models/adjustment.rb +49 -0
  32. data/app/models/stall/models/cart.rb +37 -0
  33. data/app/models/stall/models/customer.rb +18 -0
  34. data/app/models/stall/models/line_item.rb +57 -0
  35. data/app/models/stall/models/payment.rb +33 -0
  36. data/app/models/stall/models/payment_method.rb +18 -0
  37. data/app/models/stall/models/product_list.rb +147 -0
  38. data/app/models/stall/models/shipment.rb +29 -0
  39. data/app/models/stall/models/shipping_method.rb +16 -0
  40. data/app/services/stall/add_to_cart_service.rb +11 -1
  41. data/app/services/stall/cart_update_service.rb +26 -0
  42. data/app/services/stall/payment_notification_service.rb +23 -9
  43. data/app/services/stall/shipping_fee_calculator_service.rb +13 -2
  44. data/app/views/checkout/steps/_informations.html.haml +3 -3
  45. data/app/views/checkout/steps/_payment.html.haml +25 -4
  46. data/app/views/checkout/steps/_payment_method.html.haml +1 -3
  47. data/app/views/checkout/steps/_payment_return.html.haml +6 -0
  48. data/app/views/checkout/steps/_shipping_method.html.haml +1 -1
  49. data/app/views/stall/admin_mailer/order_paid_email.html.haml +4 -0
  50. data/app/views/stall/carts/show.html.haml +36 -8
  51. data/app/views/stall/customer_mailer/order_paid_email.html.haml +8 -0
  52. data/app/views/stall/line_items/_added.html.haml +1 -1
  53. data/app/views/stall/shared/mailers/_address.html.haml +18 -0
  54. data/app/views/stall/shared/mailers/_cart.html.haml +114 -0
  55. data/config/locales/stall.fr.yml +90 -8
  56. data/db/migrate/20160304134849_change_all_json_columns_to_jsonb.rb +18 -0
  57. data/db/migrate/20160307142924_add_state_to_stall_addresses.rb +5 -0
  58. data/db/migrate/20160308142713_create_adjustments.rb +16 -0
  59. data/db/migrate/20160309165136_add_identifier_to_stall_product_lists.rb +5 -0
  60. data/db/migrate/20160316114649_add_data_to_stall_shipments.rb +5 -0
  61. data/db/migrate/20160317141632_add_state_to_stall_shipments.rb +5 -0
  62. data/db/migrate/20160629102943_add_active_to_stall_shipping_methods.rb +11 -0
  63. data/db/migrate/20160629104617_add_active_to_stall_payment_methods.rb +11 -0
  64. data/db/migrate/20160705110151_add_locale_to_stall_customers.rb +5 -0
  65. data/lib/generators/stall/checkout/step/templates/step.html.haml.erb +1 -1
  66. data/lib/generators/stall/checkout/step/templates/step.rb.erb +10 -2
  67. data/lib/generators/stall/checkout/wizard/templates/wizard.rb.erb +1 -1
  68. data/lib/generators/stall/install/templates/initializer.rb +85 -1
  69. data/lib/generators/stall/model/model_generator.rb +27 -0
  70. data/lib/generators/stall/service/service_generator.rb +39 -0
  71. data/lib/generators/stall/service/templates/service.rb.erb +16 -0
  72. data/lib/generators/stall/shipping/calculator/calculator_generator.rb +17 -0
  73. data/lib/generators/stall/shipping/calculator/templates/calculator.rb.erb +29 -0
  74. data/lib/stall.rb +4 -0
  75. data/lib/stall/addressable.rb +28 -2
  76. data/lib/stall/cart_helper.rb +84 -0
  77. data/lib/stall/carts_cleaner.rb +46 -0
  78. data/lib/stall/checkout.rb +2 -0
  79. data/lib/stall/checkout/informations_checkout_step.rb +3 -5
  80. data/lib/stall/checkout/payment_checkout_step.rb +3 -0
  81. data/lib/stall/checkout/payment_return_checkout_step.rb +11 -0
  82. data/lib/stall/checkout/shipping_method_checkout_step.rb +2 -1
  83. data/lib/stall/checkout/step.rb +47 -11
  84. data/lib/stall/checkout/step_form.rb +71 -0
  85. data/lib/stall/checkout/wizard.rb +15 -5
  86. data/lib/stall/config.rb +51 -0
  87. data/lib/stall/engine.rb +24 -2
  88. data/lib/stall/payable.rb +26 -0
  89. data/lib/stall/payments.rb +7 -3
  90. data/lib/stall/payments/config.rb +37 -0
  91. data/lib/stall/payments/fake_gateway_payment_notification.rb +34 -0
  92. data/lib/stall/payments/gateway.rb +19 -12
  93. data/lib/stall/payments/urls_config.rb +40 -0
  94. data/lib/stall/priceable.rb +7 -0
  95. data/lib/stall/rails/currency_helper.rb +1 -1
  96. data/lib/stall/routes.rb +17 -4
  97. data/lib/stall/sellable.rb +1 -2
  98. data/lib/stall/shipping.rb +3 -1
  99. data/lib/stall/shipping/calculator.rb +36 -3
  100. data/lib/stall/shipping/config.rb +17 -3
  101. data/lib/stall/shipping/country_weight_table_calculator.rb +1 -1
  102. data/lib/stall/shipping/free_shipping_calculator.rb +1 -1
  103. data/lib/stall/utils.rb +2 -2
  104. data/lib/stall/utils/config_dsl.rb +5 -1
  105. data/lib/stall/version.rb +1 -1
  106. data/lib/tasks/stall_tasks.rake +11 -0
  107. metadata +73 -15
  108. data/app/assets/javascripts/stall/application.js +0 -13
  109. data/app/assets/javascripts/stall/carts.js +0 -2
  110. data/app/helpers/stall/cart_helper.rb +0 -28
  111. data/app/models/stall/address.rb +0 -5
  112. data/app/models/stall/address_ownership.rb +0 -8
  113. data/app/models/stall/cart.rb +0 -31
  114. data/app/models/stall/customer.rb +0 -8
  115. data/app/models/stall/line_item.rb +0 -49
  116. data/app/models/stall/payment.rb +0 -14
  117. data/app/models/stall/payment_method.rb +0 -7
  118. data/app/models/stall/product_list.rb +0 -68
  119. data/app/models/stall/shipment.rb +0 -15
  120. data/app/models/stall/shipping_method.rb +0 -5
@@ -0,0 +1,3 @@
1
+ class ShippingMethod < ActiveRecord::Base
2
+ include Stall::Models::ShippingMethod
3
+ end
@@ -0,0 +1,4 @@
1
+ module Stall
2
+ module Models
3
+ end
4
+ end
@@ -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