stall 0.2.0 → 0.3.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (140) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +60 -23
  3. data/app/assets/javascripts/para/stall.coffee +1 -0
  4. data/app/assets/javascripts/para/stall/inputs/variant-select.coffee +62 -0
  5. data/app/assets/javascripts/para/stall/inputs/variants-matrix.coffee +12 -0
  6. data/app/assets/javascripts/para/stall/inputs/variants-matrix/helpers.coffee +40 -0
  7. data/app/assets/javascripts/para/stall/inputs/variants-matrix/input.coffee +133 -0
  8. data/app/assets/javascripts/para/stall/inputs/variants-matrix/nested-fields.coffee +38 -0
  9. data/app/assets/javascripts/para/stall/inputs/variants-matrix/properties_select.coffee +45 -0
  10. data/app/assets/javascripts/para/stall/inputs/variants-matrix/variant.coffee +59 -0
  11. data/app/assets/javascripts/stall.coffee +1 -0
  12. data/app/assets/javascripts/stall/add-to-cart-form.coffee +53 -28
  13. data/app/assets/javascripts/stall/cart-form.coffee +7 -2
  14. data/app/assets/stylesheets/para/stall.sass +28 -0
  15. data/app/controllers/para/stall/admin/carts_controller.rb +27 -0
  16. data/app/controllers/stall/cart_credits_controller.rb +27 -0
  17. data/app/controllers/stall/carts_controller.rb +1 -1
  18. data/app/controllers/stall/checkout/steps_controller.rb +22 -12
  19. data/app/controllers/stall/checkouts_controller.rb +1 -0
  20. data/app/controllers/stall/line_items_controller.rb +1 -0
  21. data/app/controllers/stall/payments_controller.rb +16 -1
  22. data/app/helpers/stall/credit_notes_helper.rb +25 -0
  23. data/app/helpers/stall/customers_helper.rb +3 -1
  24. data/app/models/billing_address.rb +2 -0
  25. data/app/models/cart_credit_note_adjustment.rb +3 -0
  26. data/app/models/credit_note.rb +3 -0
  27. data/app/models/credit_note_adjustment.rb +3 -0
  28. data/app/models/credit_note_usage.rb +3 -0
  29. data/app/models/product.rb +3 -0
  30. data/app/models/product_category.rb +3 -0
  31. data/app/models/product_detail.rb +3 -0
  32. data/app/models/property.rb +3 -0
  33. data/app/models/property_value.rb +3 -0
  34. data/app/models/shipping_address.rb +2 -0
  35. data/app/models/stall/models/address.rb +2 -2
  36. data/app/models/stall/models/cart.rb +2 -15
  37. data/app/models/stall/models/cart_credit_note_adjustment.rb +11 -0
  38. data/app/models/stall/models/credit_note.rb +50 -0
  39. data/app/models/stall/models/credit_note_adjustment.rb +13 -0
  40. data/app/models/stall/models/credit_note_usage.rb +16 -0
  41. data/app/models/stall/models/customer.rb +20 -8
  42. data/app/models/stall/models/line_item.rb +9 -0
  43. data/app/models/stall/models/product.rb +45 -0
  44. data/app/models/stall/models/product_category.rb +31 -0
  45. data/app/models/stall/models/product_detail.rb +17 -0
  46. data/app/models/stall/models/product_list.rb +9 -45
  47. data/app/models/stall/models/property.rb +20 -0
  48. data/app/models/stall/models/property_value.rb +23 -0
  49. data/app/models/stall/models/variant.rb +34 -0
  50. data/app/models/stall/models/variant_property_value.rb +18 -0
  51. data/app/models/variant.rb +3 -0
  52. data/app/models/variant_property_value.rb +3 -0
  53. data/app/services/stall/cart_credit_note_creation_service.rb +40 -0
  54. data/app/services/stall/cart_payment_validation_service.rb +28 -0
  55. data/app/services/stall/cart_update_service.rb +17 -6
  56. data/app/services/stall/credit_usage_service.rb +102 -0
  57. data/app/services/stall/payment_notification_service.rb +4 -8
  58. data/app/services/stall/product_list_staleness_handling_service.rb +33 -0
  59. data/app/views/admin/addresses/_fields.html.haml +9 -0
  60. data/app/views/admin/carts/_filters.html.haml +17 -0
  61. data/app/views/admin/carts/_form.html.haml +43 -0
  62. data/app/views/admin/carts/_table.html.haml +17 -0
  63. data/app/views/admin/customers/_fields.html.haml +1 -0
  64. data/app/views/admin/line_items/_fields.html.haml +15 -0
  65. data/app/views/admin/products/_table.html.haml +12 -0
  66. data/app/views/admin/properties/_form.html.haml +6 -0
  67. data/app/views/admin/properties/_table.html.haml +8 -0
  68. data/app/views/admin/property_values/_fields.html.haml +1 -0
  69. data/app/views/admin/shipments/_fields.html.haml +7 -0
  70. data/app/views/checkout/steps/_informations.html.haml +3 -1
  71. data/app/views/checkout/steps/_payment.html.haml +2 -0
  72. data/app/views/checkout/steps/_payment_return.html.haml +0 -1
  73. data/app/views/para/admin/resources/_variant_row.html.haml +24 -0
  74. data/app/views/para/stall/inputs/_variant_select.html.haml +14 -0
  75. data/app/views/para/stall/inputs/_variants_matrix.html.haml +41 -0
  76. data/app/views/stall/addresses/_fields.html.haml +6 -12
  77. data/app/views/stall/carts/_cart.html.haml +45 -37
  78. data/app/views/stall/carts/_widget.html.haml +28 -0
  79. data/app/views/stall/carts/show.html.haml +2 -0
  80. data/app/views/stall/checkout/steps/_navigation.html.haml +13 -0
  81. data/app/views/stall/credit_note_adjustments/_form.html.haml +28 -0
  82. data/app/views/stall/line_items/_added.html.haml +2 -2
  83. data/app/views/stall/line_items/_form.html.haml +1 -1
  84. data/app/views/stall/payments/manual_payment_gateway/_form.html.haml +10 -0
  85. data/app/views/stall/shared/mailers/_cart.html.haml +1 -1
  86. data/config/locales/stall.fr.yml +82 -2
  87. data/db/migrate/20161129101956_add_type_to_stall_address_ownerships.rb +52 -0
  88. data/db/migrate/20161202080218_add_reference_to_product_lists.rb +17 -0
  89. data/db/migrate/20170118103916_create_credit_notes.rb +17 -0
  90. data/db/migrate/20170118144047_create_credit_note_adjustments.rb +13 -0
  91. data/db/migrate/20170123123115_create_stall_product_categories.rb +12 -0
  92. data/db/migrate/20170123123326_create_stall_products.rb +17 -0
  93. data/db/migrate/20170123125030_create_stall_variants.rb +13 -0
  94. data/db/migrate/20170123131748_create_stall_product_category_hierarchies.rb +16 -0
  95. data/db/migrate/20170123143704_create_stall_product_details.rb +14 -0
  96. data/db/migrate/20170125152622_convert_all_money_fields_to_decimal_to_use_infinite_precision.rb +27 -0
  97. data/db/migrate/20170131162537_add_data_to_stall_adjustments.rb +5 -0
  98. data/db/migrate/20170202165514_create_stall_properties.rb +9 -0
  99. data/db/migrate/20170202165516_create_stall_property_values.rb +13 -0
  100. data/db/migrate/20170202165518_create_stall_variant_property_values.rb +13 -0
  101. data/lib/generators/stall/install/templates/initializer.rb +21 -0
  102. data/lib/generators/stall/view/view_generator.rb +41 -19
  103. data/lib/para/stall.rb +32 -0
  104. data/lib/para/stall/inputs.rb +13 -0
  105. data/lib/para/stall/inputs/variant_input_helper.rb +34 -0
  106. data/lib/para/stall/inputs/variant_select_input.rb +79 -0
  107. data/lib/para/stall/inputs/variants_matrix_input.rb +72 -0
  108. data/lib/para/stall/routes.rb +11 -0
  109. data/lib/para/stall/variants_property_config.rb +78 -0
  110. data/lib/stall.rb +10 -0
  111. data/lib/stall/addressable.rb +11 -59
  112. data/lib/stall/addresses.rb +1 -0
  113. data/lib/stall/addresses/copier_base.rb +3 -1
  114. data/lib/stall/addresses/copy.rb +10 -0
  115. data/lib/stall/addresses/copy_source_to_target.rb +10 -24
  116. data/lib/stall/addresses/prefill_target_from_source.rb +8 -16
  117. data/lib/stall/adjustable.rb +20 -0
  118. data/lib/stall/archived_paid_cart_helper.rb +36 -0
  119. data/lib/stall/cart_helper.rb +15 -7
  120. data/lib/stall/checkout/informations_checkout_step.rb +47 -50
  121. data/lib/stall/checkout/payment_return_checkout_step.rb +4 -1
  122. data/lib/stall/checkout/step.rb +24 -3
  123. data/lib/stall/checkout/step_form.rb +11 -5
  124. data/lib/stall/checkout/wizard.rb +7 -6
  125. data/lib/stall/config.rb +9 -0
  126. data/lib/stall/default_currency_manager.rb +27 -0
  127. data/lib/stall/engine.rb +14 -3
  128. data/lib/stall/payments.rb +2 -0
  129. data/lib/stall/payments/gateway_request.rb +15 -0
  130. data/lib/stall/payments/gateway_response.rb +33 -0
  131. data/lib/stall/payments/manual_payment_gateway.rb +86 -0
  132. data/lib/stall/priceable.rb +4 -0
  133. data/lib/stall/reference_manager.rb +17 -0
  134. data/lib/stall/routes.rb +1 -0
  135. data/lib/stall/shippable.rb +18 -0
  136. data/lib/stall/total_prices_manager.rb +40 -0
  137. data/lib/stall/version.rb +1 -1
  138. metadata +120 -5
  139. data/app/models/address_ownership.rb +0 -3
  140. data/app/models/stall/models/address_ownership.rb +0 -26
@@ -0,0 +1,3 @@
1
+ class CreditNote < ActiveRecord::Base
2
+ include Stall::Models::CreditNote
3
+ end
@@ -0,0 +1,3 @@
1
+ class CreditNoteAdjustment < Adjustment
2
+ include Stall::Models::CreditNoteAdjustment
3
+ end
@@ -0,0 +1,3 @@
1
+ class CreditNoteUsage < ActiveRecord::Base
2
+ include Stall::Models::CreditNoteUsage
3
+ end
@@ -0,0 +1,3 @@
1
+ class Product < ActiveRecord::Base
2
+ include Stall::Models::Product
3
+ end
@@ -0,0 +1,3 @@
1
+ class ProductCategory < ActiveRecord::Base
2
+ include Stall::Models::ProductCategory
3
+ end
@@ -0,0 +1,3 @@
1
+ class ProductDetail < ActiveRecord::Base
2
+ include Stall::Models::ProductDetail
3
+ end
@@ -0,0 +1,3 @@
1
+ class Property < ActiveRecord::Base
2
+ include Stall::Models::Property
3
+ end
@@ -0,0 +1,3 @@
1
+ class PropertyValue < ActiveRecord::Base
2
+ include Stall::Models::PropertyValue
3
+ end
@@ -0,0 +1,2 @@
1
+ class ShippingAddress < Address
2
+ end
@@ -6,7 +6,7 @@ module Stall
6
6
  included do
7
7
  self.table_name = 'stall_addresses'
8
8
 
9
- has_one :address_ownership, dependent: :destroy
9
+ belongs_to :addressable, polymorphic: true
10
10
 
11
11
  enum civility: { :m => 1, :mme => 2 }
12
12
 
@@ -17,7 +17,7 @@ module Stall
17
17
  end
18
18
 
19
19
  def civility_name
20
- I18n.t("stall.addresses.civilities.#{ civility }")
20
+ I18n.t("stall.addresses.civilities.#{ civility }") if civility.present?
21
21
  end
22
22
 
23
23
  def country_name
@@ -6,12 +6,8 @@ module Stall
6
6
  included do
7
7
  include Stall::Addressable
8
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
9
+ include Stall::Shippable
10
+ include Stall::Adjustable
15
11
 
16
12
  attr_accessor :terms
17
13
  end
@@ -25,15 +21,6 @@ module Stall
25
21
  def active?
26
22
  !paid?
27
23
  end
28
-
29
- private
30
-
31
- def items
32
- items = line_items.to_a
33
- items << shipment if shipment
34
- items += adjustments.to_a
35
- items
36
- end
37
24
  end
38
25
  end
39
26
  end
@@ -0,0 +1,11 @@
1
+ module Stall
2
+ module Models
3
+ module CartCreditNoteAdjustment
4
+ extend ActiveSupport::Concern
5
+
6
+ included do
7
+ has_one :credit_note, as: :source, dependent: :nullify
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,50 @@
1
+ module Stall
2
+ module Models
3
+ module CreditNote
4
+ extend ActiveSupport::Concern
5
+
6
+ included do
7
+ self.table_name = 'stall_credit_notes'
8
+
9
+ include Stall::Priceable
10
+ include Stall::DefaultCurrencyManager
11
+ include Stall::ReferenceManager
12
+
13
+ monetize :eot_amount_cents, :amount_cents,
14
+ with_model_currency: :currency, allow_nil: true
15
+
16
+ belongs_to :customer
17
+ belongs_to :source, polymorphic: true
18
+
19
+ has_many :credit_note_usages, dependent: :destroy
20
+ has_many :adjustments, through: :credit_note_usages
21
+
22
+ validates :amount, :customer, presence: true
23
+
24
+ def amount_with_eot_management=(value)
25
+ (self.amount_without_eot_management = value).tap do
26
+ self.eot_amount = amount / vat_coefficient
27
+ end
28
+ end
29
+
30
+ # TODO : Check if we can use Module#prepend here without getting too
31
+ # complex
32
+ #
33
+ alias_method :amount_without_eot_management=, :amount=
34
+ alias_method :amount=, :amount_with_eot_management=
35
+ end
36
+
37
+ def remaining_amount
38
+ amount - adjustments.map(&:price).sum.abs
39
+ end
40
+
41
+ def vat_rate
42
+ read_attribute(:vat_rate) || write_attribute(:vat_rate, Stall.config.vat_rate)
43
+ end
44
+
45
+ def with_remaining_money?
46
+ remaining_amount.to_d > 0
47
+ end
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,13 @@
1
+ module Stall
2
+ module Models
3
+ module CreditNoteAdjustment
4
+ extend ActiveSupport::Concern
5
+
6
+ included do
7
+ has_one :credit_note_usage, foreign_key: :adjustment_id,
8
+ dependent: :destroy
9
+ has_one :credit_note, through: :credit_note_usage
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,16 @@
1
+ # Joint model between credit notes and adjustments
2
+ #
3
+ module Stall
4
+ module Models
5
+ module CreditNoteUsage
6
+ extend ActiveSupport::Concern
7
+
8
+ included do
9
+ self.table_name = 'stall_credit_note_usages'
10
+
11
+ belongs_to :credit_note
12
+ belongs_to :adjustment, class_name: 'CreditNoteAdjustment'
13
+ end
14
+ end
15
+ end
16
+ end
@@ -8,23 +8,35 @@ module Stall
8
8
 
9
9
  include Stall::Addressable
10
10
 
11
- belongs_to :user, polymorphic: true, inverse_of: :customer
12
- accepts_nested_attributes_for :user
11
+ if Stall.config.default_user_model
12
+ belongs_to :user, polymorphic: true, inverse_of: :customer
13
+ accepts_nested_attributes_for :user
13
14
 
14
- has_many :product_lists, dependent: :destroy
15
+ before_validation :ensure_user_email
16
+ end
15
17
 
16
- validates :email, format: { with: /\A[^@\s]+@([^@\s]+\.)+[^@\W]+\z/ },
17
- allow_blank: true
18
+ has_many :product_lists, dependent: :destroy
19
+ has_many :credit_notes, dependent: :destroy
18
20
 
19
- before_validation :ensure_user_email
21
+ validates :email, presence: true,
22
+ format: { with: /\A[^@\s]+@([^@\s]+\.)+[^@\W]+\z/ }
20
23
 
21
24
  def user_or_default
22
25
  user || build_user
23
26
  end
24
27
 
25
28
  def build_user(attributes = {})
26
- attributes.reverse_merge!(customer: self)
27
- self.user = Stall.config.default_user_model.new(attributes)
29
+ (self.user = Stall.config.default_user_model.new(attributes)).tap do
30
+ user.customer = self if user.respond_to?(:customer)
31
+ end if Stall.config.default_user_model
32
+ end
33
+
34
+ def credit(currency = Stall.config.default_currency)
35
+ credit_notes.for_currency(currency).map(&:remaining_amount).sum
36
+ end
37
+
38
+ def credit?(currency = Stall.config.default_currency)
39
+ credit(currency).to_d > 0
28
40
  end
29
41
 
30
42
  private
@@ -27,6 +27,7 @@ module Stall
27
27
 
28
28
  validate :stock_availability
29
29
 
30
+ before_validation :restore_valid_quantity
30
31
  before_validation :refresh_total_prices
31
32
 
32
33
  scope :ordered, -> { order(created_at: :asc) }
@@ -52,6 +53,14 @@ module Stall
52
53
  self.eot_price = unit_eot_price * quantity if unit_eot_price && quantity
53
54
  self.price = unit_price * quantity if unit_price && quantity
54
55
  end
56
+
57
+ # Ensures that a quantity set to 0 to an existing line item doesn't return
58
+ # an error.
59
+ def restore_valid_quantity
60
+ if persisted? && quantity && (quantity < 1) && quantity_changed?
61
+ restore_quantity!
62
+ end
63
+ end
55
64
  end
56
65
  end
57
66
  end
@@ -0,0 +1,45 @@
1
+ module Stall
2
+ module Models
3
+ module Product
4
+ extend ActiveSupport::Concern
5
+
6
+ included do
7
+ self.table_name = 'stall_products'
8
+
9
+ acts_as_orderable
10
+ extend FriendlyId
11
+ friendly_id :name, use: [:slugged, :finders]
12
+
13
+ belongs_to :product_category
14
+
15
+ has_many :variants, dependent: :destroy, inverse_of: :product
16
+ accepts_nested_attributes_for :variants, allow_destroy: true
17
+
18
+ has_many :product_details, dependent: :destroy, inverse_of: :product
19
+ accepts_nested_attributes_for :product_details, allow_destroy: true
20
+
21
+ has_attached_file :image, styles: {
22
+ thumb: '100x100#',
23
+ show: '555x'
24
+ }
25
+
26
+ validates :name, :image, presence: true
27
+ validates_attachment :image, content_type: { content_type: /\Aimage\/.*\z/ }
28
+
29
+ scope :visible, -> { where(visible: true) }
30
+
31
+ def should_generate_new_friendly_id?
32
+ slug.blank?
33
+ end
34
+
35
+ def vat_rate
36
+ Stall.config.vat_rate
37
+ end
38
+
39
+ def price
40
+ variants.map(&:price).min
41
+ end
42
+ end
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,31 @@
1
+ module Stall
2
+ module Models
3
+ module ProductCategory
4
+ extend ActiveSupport::Concern
5
+
6
+ included do
7
+ self.table_name = 'stall_product_categories'
8
+
9
+ acts_as_tree order: 'position'
10
+ class_attribute :max_depth
11
+
12
+ extend FriendlyId
13
+ friendly_id :name, use: [:slugged, :finders]
14
+
15
+ has_many :products, dependent: :nullify
16
+
17
+ validates :name, presence: true
18
+
19
+ scope :ordered, -> { order(position: 'asc') }
20
+
21
+ def self.max_depth
22
+ 2
23
+ end
24
+
25
+ def should_generate_new_friendly_id?
26
+ slug.blank?
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,17 @@
1
+ module Stall
2
+ module Models
3
+ module ProductDetail
4
+ extend ActiveSupport::Concern
5
+
6
+ included do
7
+ self.table_name = 'stall_product_details'
8
+
9
+ acts_as_orderable
10
+
11
+ belongs_to :product
12
+
13
+ validates :name, presence: true
14
+ end
15
+ end
16
+ end
17
+ end
@@ -6,7 +6,9 @@ module Stall
6
6
  included do
7
7
  self.table_name = 'stall_product_lists'
8
8
 
9
- store_accessor :data, :reference
9
+ include Stall::DefaultCurrencyManager
10
+ include Stall::ReferenceManager
11
+ include Stall::TotalPricesManager
10
12
 
11
13
  has_secure_token
12
14
 
@@ -16,13 +18,15 @@ module Stall
16
18
  belongs_to :customer
17
19
  accepts_nested_attributes_for :customer
18
20
 
21
+ has_many :generated_credit_notes, as: :source,
22
+ class_name: 'CreditNote',
23
+ dependent: :nullify
24
+
19
25
  validates :type, presence: true
20
26
 
21
- after_initialize :ensure_currency
22
27
  after_initialize :ensure_state
23
28
 
24
29
  before_save :save_customer_if_changed
25
- after_save :ensure_reference, on: :create
26
30
 
27
31
  scope :empty, -> {
28
32
  joins(
@@ -52,27 +56,11 @@ module Stall
52
56
  end
53
57
 
54
58
  def subtotal
55
- price = line_items.map(&:price).sum
56
- price = Money.new(price, currency) unless Money === price
57
- price
59
+ ensure_money(line_items.map(&:price).sum)
58
60
  end
59
61
 
60
62
  def eot_subtotal
61
- line_items.map(&:eot_price).sum
62
- end
63
-
64
- def total_price
65
- price = items.map(&:price).sum
66
- price = Money.new(price, currency) unless Money === price
67
- price
68
- end
69
-
70
- def total_eot_price
71
- items.map(&:eot_price).sum
72
- end
73
-
74
- def total_vat
75
- items.map(&:vat).sum
63
+ ensure_money(line_items.map(&:eot_price).sum)
76
64
  end
77
65
 
78
66
  def total_quantity
@@ -91,20 +79,8 @@ module Stall
91
79
  true
92
80
  end
93
81
 
94
- def currency
95
- @currency ||= if (currency = read_attribute(:currency).presence)
96
- Money::Currency.new(currency)
97
- else
98
- self.currency = Money.default_currency
99
- end
100
- end
101
-
102
82
  private
103
83
 
104
- def ensure_currency
105
- self.currency ||= Money.default_currency
106
- end
107
-
108
84
  def ensure_state
109
85
  self.state ||= (wizard.try(:steps).try(:first) || 'pending')
110
86
  end
@@ -113,23 +89,11 @@ module Stall
113
89
  line_items.to_a
114
90
  end
115
91
 
116
- def ensure_reference
117
- unless reference.present?
118
- reference = [Time.now.strftime('%Y%m%d'), ('%05d' % id)].join('-')
119
- self.reference = reference
120
- save(validate: false)
121
- end
122
- end
123
-
124
92
  def save_customer_if_changed
125
93
  customer.save if customer && customer.changed?
126
94
  end
127
95
 
128
96
  module ClassMethods
129
- def find_by_reference(reference)
130
- where("data->>'reference' = ?", reference).first
131
- end
132
-
133
97
  # The .aborted and .finalized scopes cannot be declared as actual rails
134
98
  # scopes since subclasses that override the .wizard method wouldn't
135
99
  # be taken into account, scopes being executed in the context of the