spree_core 3.0.10 → 3.1.0.rc1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (289) hide show
  1. checksums.yaml +4 -4
  2. data/app/assets/javascripts/spree.js.coffee.erb +12 -3
  3. data/app/helpers/spree/base_helper.rb +4 -1
  4. data/app/helpers/spree/products_helper.rb +37 -6
  5. data/app/mailers/spree/base_mailer.rb +11 -2
  6. data/app/models/concerns/spree/adjustment_source.rb +3 -10
  7. data/app/models/concerns/spree/default_price.rb +7 -1
  8. data/app/models/concerns/spree/named_type.rb +1 -1
  9. data/app/models/concerns/spree/user_api_authentication.rb +7 -1
  10. data/app/models/concerns/spree/user_methods.rb +47 -0
  11. data/app/models/concerns/spree/user_reporting.rb +2 -2
  12. data/app/models/concerns/spree/vat_price_calculation.rb +47 -0
  13. data/app/models/spree/address.rb +8 -7
  14. data/app/models/spree/adjustable/adjuster/base.rb +25 -0
  15. data/app/models/spree/adjustable/adjuster/promotion.rb +41 -0
  16. data/app/models/spree/adjustable/adjuster/tax.rb +26 -0
  17. data/app/models/spree/adjustable/adjustments_updater.rb +22 -45
  18. data/app/models/spree/adjustment.rb +8 -10
  19. data/app/models/spree/app_configuration.rb +5 -2
  20. data/app/models/spree/base.rb +4 -0
  21. data/app/models/spree/calculator.rb +0 -5
  22. data/app/models/spree/calculator/default_tax.rb +2 -10
  23. data/app/models/spree/classification.rb +7 -3
  24. data/app/models/spree/country.rb +14 -3
  25. data/app/models/spree/credit_card.rb +21 -31
  26. data/app/models/spree/customer_return.rb +7 -15
  27. data/app/models/spree/gateway.rb +7 -6
  28. data/app/models/spree/image.rb +1 -1
  29. data/app/models/spree/inventory_unit.rb +9 -6
  30. data/app/models/spree/legacy_user.rb +1 -6
  31. data/app/models/spree/line_item.rb +69 -68
  32. data/app/models/spree/log_entry.rb +1 -4
  33. data/app/models/spree/option_type.rb +15 -6
  34. data/app/models/spree/option_type_prototype.rb +9 -0
  35. data/app/models/spree/option_value.rb +11 -3
  36. data/app/models/spree/option_value_variant.rb +6 -0
  37. data/app/models/spree/order.rb +113 -64
  38. data/app/models/spree/order/checkout.rb +8 -11
  39. data/app/models/spree/order/currency_updater.rb +1 -1
  40. data/app/models/spree/order/store_credit.rb +96 -0
  41. data/app/models/spree/order_contents.rb +6 -1
  42. data/app/models/spree/order_inventory.rb +4 -8
  43. data/app/models/spree/order_promotion.rb +6 -0
  44. data/app/models/spree/order_updater.rb +2 -7
  45. data/app/models/spree/payment.rb +46 -19
  46. data/app/models/spree/payment/gateway_options.rb +8 -4
  47. data/app/models/spree/payment_method.rb +12 -13
  48. data/app/models/spree/payment_method/store_credit.rb +130 -0
  49. data/app/models/spree/preference.rb +1 -1
  50. data/app/models/spree/price.rb +16 -6
  51. data/app/models/spree/product.rb +52 -49
  52. data/app/models/spree/product/scopes.rb +7 -2
  53. data/app/models/spree/product_option_type.rb +7 -2
  54. data/app/models/spree/product_promotion_rule.rb +9 -0
  55. data/app/models/spree/product_property.rb +8 -10
  56. data/app/models/spree/promotion.rb +19 -19
  57. data/app/models/spree/promotion/rules/product.rb +3 -1
  58. data/app/models/spree/promotion/rules/taxon.rb +2 -1
  59. data/app/models/spree/promotion/rules/user.rb +4 -4
  60. data/app/models/spree/promotion_action.rb +3 -3
  61. data/app/models/spree/promotion_category.rb +1 -1
  62. data/app/models/spree/promotion_rule_taxon.rb +9 -0
  63. data/app/models/spree/promotion_rule_user.rb +9 -0
  64. data/app/models/spree/property.rb +2 -1
  65. data/app/models/spree/property_prototype.rb +9 -0
  66. data/app/models/spree/prototype.rb +8 -3
  67. data/app/models/spree/prototype_taxon.rb +9 -0
  68. data/app/models/spree/refund.rb +10 -7
  69. data/app/models/spree/refund_reason.rb +1 -1
  70. data/app/models/spree/reimbursement.rb +12 -16
  71. data/app/models/spree/reimbursement_type/reimbursement_helpers.rb +23 -6
  72. data/app/models/spree/reimbursement_type/store_credit.rb +28 -0
  73. data/app/models/spree/return_authorization.rb +8 -13
  74. data/app/models/spree/return_authorization_reason.rb +1 -1
  75. data/app/models/spree/return_item.rb +13 -12
  76. data/app/models/spree/return_item/eligibility_validator/time_since_purchase.rb +1 -1
  77. data/app/models/spree/role.rb +3 -2
  78. data/app/models/spree/role_user.rb +6 -0
  79. data/app/models/spree/shipment.rb +18 -23
  80. data/app/models/spree/shipment_handler.rb +2 -2
  81. data/app/models/spree/shipping_category.rb +6 -3
  82. data/app/models/spree/shipping_method.rb +11 -10
  83. data/app/models/spree/shipping_method_zone.rb +6 -0
  84. data/app/models/spree/shipping_rate.rb +16 -29
  85. data/app/models/spree/state.rb +3 -2
  86. data/app/models/spree/state_change.rb +1 -1
  87. data/app/models/spree/stock/content_item.rb +10 -12
  88. data/app/models/spree/stock/coordinator.rb +13 -14
  89. data/app/models/spree/stock/estimator.rb +28 -30
  90. data/app/models/spree/stock/inventory_unit_builder.rb +1 -9
  91. data/app/models/spree/stock/packer.rb +1 -1
  92. data/app/models/spree/stock/quantifier.rb +5 -5
  93. data/app/models/spree/stock/splitter/backordered.rb +2 -2
  94. data/app/models/spree/stock_item.rb +12 -18
  95. data/app/models/spree/stock_location.rb +4 -7
  96. data/app/models/spree/stock_movement.rb +11 -9
  97. data/app/models/spree/stock_transfer.rb +11 -12
  98. data/app/models/spree/store.rb +14 -6
  99. data/app/models/spree/store_credit.rb +252 -0
  100. data/app/models/spree/store_credit_category.rb +22 -0
  101. data/app/models/spree/store_credit_event.rb +38 -0
  102. data/app/models/spree/store_credit_type.rb +6 -0
  103. data/app/models/spree/tax_category.rb +3 -8
  104. data/app/models/spree/tax_rate.rb +56 -122
  105. data/app/models/spree/taxon.rb +11 -5
  106. data/app/models/spree/tracker.rb +12 -1
  107. data/app/models/spree/validations/db_maximum_length_validator.rb +2 -1
  108. data/app/models/spree/variant.rb +82 -50
  109. data/app/models/spree/zone.rb +21 -17
  110. data/app/models/spree/zone_member.rb +6 -0
  111. data/app/validators/db_maximum_length_validator.rb +11 -0
  112. data/app/views/layouts/spree/base_mailer.html.erb +38 -781
  113. data/app/views/spree/order_mailer/_adjustment.html.erb +8 -0
  114. data/app/views/spree/order_mailer/_subtotal.html.erb +8 -0
  115. data/app/views/spree/order_mailer/_total.html.erb +8 -0
  116. data/app/views/spree/order_mailer/cancel_email.html.erb +13 -28
  117. data/app/views/spree/order_mailer/cancel_email.text.erb +1 -1
  118. data/app/views/spree/order_mailer/confirm_email.html.erb +49 -63
  119. data/app/views/spree/order_mailer/confirm_email.text.erb +1 -1
  120. data/app/views/spree/shared/_base_mailer_header.html.erb +5 -7
  121. data/app/views/spree/shared/_base_mailer_stylesheets.html.erb +777 -0
  122. data/app/views/spree/shared/_error_messages.html.erb +1 -1
  123. data/app/views/spree/shared/_mailer_line_item.html.erb +12 -0
  124. data/app/views/spree/shipment_mailer/shipped_email.html.erb +21 -14
  125. data/app/views/spree/shipment_mailer/shipped_email.text.erb +3 -3
  126. data/config/initializers/user_class_extensions.rb +7 -38
  127. data/config/locales/en.yml +113 -13
  128. data/config/routes.rb +7 -0
  129. data/db/default/spree/default_reimbursement_type.rb +1 -1
  130. data/db/default/spree/zones.rb +4 -5
  131. data/db/migrate/20150118210639_create_spree_store_credits.rb +24 -0
  132. data/db/migrate/20150118211500_create_spree_store_credit_categories.rb +8 -0
  133. data/db/migrate/20150118212051_create_spree_store_credit_events.rb +17 -0
  134. data/db/migrate/20150118212101_create_spree_store_credit_types.rb +10 -0
  135. data/db/migrate/20150314013438_add_missing_indexes.rb +25 -0
  136. data/db/migrate/20150317174308_remove_duplicated_indexes_from_multi_columns.rb +18 -0
  137. data/db/migrate/20150324104002_remove_user_index_from_spree_state_changes.rb +14 -0
  138. data/db/migrate/20150522071831_add_position_to_spree_payment_methods.rb +5 -0
  139. data/db/migrate/20150626181949_add_taxable_adjustment_total_to_line_item.rb +19 -0
  140. data/db/migrate/20150627090949_migrate_payment_methods_display.rb +12 -0
  141. data/db/migrate/20150714154102_spree_payment_method_store_credits.rb +12 -0
  142. data/db/migrate/20150726141425_rename_has_and_belongs_to_associations_to_model_names.rb +18 -0
  143. data/db/migrate/20150727191614_spree_store_credit_types.rb +11 -0
  144. data/db/migrate/20150819154308_add_discontinued_to_products_and_variants.rb +68 -0
  145. data/db/migrate/20151220072838_remove_shipping_method_id_from_spree_orders.rb +13 -0
  146. data/db/migrate/20160207191757_add_id_column_to_earlier_habtm_tables.rb +16 -0
  147. data/db/migrate/20160219165458_add_indexes.rb +14 -0
  148. data/lib/generators/spree/dummy/templates/rails/database.yml +31 -24
  149. data/lib/generators/spree/dummy/templates/rails/test.rb +2 -1
  150. data/lib/spree/core.rb +16 -0
  151. data/lib/spree/core/controller_helpers/auth.rb +1 -1
  152. data/lib/spree/core/controller_helpers/common.rb +3 -3
  153. data/lib/spree/core/controller_helpers/order.rb +6 -5
  154. data/lib/spree/core/controller_helpers/search.rb +1 -1
  155. data/lib/spree/core/controller_helpers/store.rb +29 -0
  156. data/lib/spree/core/delegate_belongs_to.rb +2 -2
  157. data/lib/spree/core/engine.rb +30 -25
  158. data/lib/spree/core/environment.rb +1 -1
  159. data/lib/spree/core/importer/order.rb +37 -40
  160. data/lib/spree/core/number_generator.rb +52 -0
  161. data/lib/spree/core/product_filters.rb +1 -1
  162. data/lib/spree/core/search/base.rb +4 -3
  163. data/lib/spree/core/version.rb +1 -1
  164. data/lib/spree/localized_number.rb +3 -1
  165. data/lib/spree/permitted_attributes.rb +5 -2
  166. data/lib/spree/testing_support/common_rake.rb +3 -3
  167. data/lib/spree/testing_support/factories.rb +3 -3
  168. data/lib/spree/testing_support/factories/address_factory.rb +1 -1
  169. data/lib/spree/testing_support/factories/country_factory.rb +2 -2
  170. data/lib/spree/testing_support/factories/order_factory.rb +2 -2
  171. data/lib/spree/testing_support/factories/payment_factory.rb +5 -0
  172. data/lib/spree/testing_support/factories/payment_method_factory.rb +8 -0
  173. data/lib/spree/testing_support/factories/promotion_rule_factory.rb +5 -0
  174. data/lib/spree/testing_support/factories/refund_factory.rb +9 -1
  175. data/lib/spree/testing_support/factories/return_authorization_factory.rb +2 -0
  176. data/lib/spree/testing_support/factories/state_factory.rb +2 -2
  177. data/lib/spree/testing_support/factories/store_credit_category_factory.rb +9 -0
  178. data/lib/spree/testing_support/factories/store_credit_event_factory.rb +8 -0
  179. data/lib/spree/testing_support/factories/store_credit_factory.rb +17 -0
  180. data/lib/spree/testing_support/factories/store_credit_type_factory.rb +11 -0
  181. data/lib/spree/testing_support/factories/user_factory.rb +1 -1
  182. data/lib/spree/testing_support/factories/zone_member_factory.rb +6 -0
  183. data/lib/spree/testing_support/microdata.rb +189 -0
  184. data/lib/tasks/core.rake +68 -0
  185. data/lib/tasks/exchanges.rake +2 -2
  186. data/spec/fixtures/microdata.html +22 -0
  187. data/spec/fixtures/microdata_itemref.html +15 -0
  188. data/spec/fixtures/microdata_no_itemscope.html +20 -0
  189. data/spec/helpers/base_helper_spec.rb +64 -1
  190. data/spec/helpers/products_helper_spec.rb +75 -3
  191. data/spec/lib/i18n_spec.rb +2 -2
  192. data/spec/lib/search/base_spec.rb +2 -2
  193. data/spec/lib/spree/core/controller_helpers/auth_spec.rb +4 -2
  194. data/spec/lib/spree/core/controller_helpers/store_spec.rb +56 -0
  195. data/spec/lib/spree/core/importer/order_spec.rb +226 -123
  196. data/spec/lib/spree/core/number_generator_spec.rb +175 -0
  197. data/spec/lib/spree/core_spec.rb +23 -0
  198. data/spec/lib/spree/localized_number_spec.rb +10 -0
  199. data/spec/mailers/order_mailer_spec.rb +11 -13
  200. data/spec/mailers/shipment_mailer_spec.rb +26 -8
  201. data/spec/mailers/test_mailer_spec.rb +15 -1
  202. data/spec/models/option_type_prototype_spec.rb +9 -0
  203. data/spec/models/spree/ability_spec.rb +6 -13
  204. data/spec/models/spree/address_spec.rb +1 -1
  205. data/spec/models/spree/adjustable/adjuster/base_spec.rb +10 -0
  206. data/spec/models/spree/adjustable/adjuster/promotion_spec.rb +211 -0
  207. data/spec/models/spree/adjustable/adjuster/tax_spec.rb +86 -0
  208. data/spec/models/spree/adjustable/adjustments_updater_spec.rb +2 -262
  209. data/spec/models/spree/adjustment_spec.rb +27 -1
  210. data/spec/models/spree/app_configuration_spec.rb +5 -2
  211. data/spec/models/spree/calculator/default_tax_spec.rb +39 -14
  212. data/spec/models/spree/concerns/user_methods_spec.rb +55 -0
  213. data/spec/models/spree/concerns/vat_price_calculation_spec.rb +66 -0
  214. data/spec/models/spree/country_spec.rb +45 -8
  215. data/spec/models/spree/credit_card_spec.rb +8 -8
  216. data/spec/models/spree/customer_return_spec.rb +4 -26
  217. data/spec/models/spree/gateway_spec.rb +7 -0
  218. data/spec/models/spree/image_spec.rb +3 -0
  219. data/spec/models/spree/inventory_unit_spec.rb +1 -18
  220. data/spec/models/spree/line_item_spec.rb +78 -18
  221. data/spec/models/spree/option_type_spec.rb +2 -2
  222. data/spec/models/spree/option_value_spec.rb +8 -3
  223. data/spec/models/spree/order/checkout_spec.rb +49 -39
  224. data/spec/models/spree/order/currency_updater_spec.rb +3 -3
  225. data/spec/models/spree/order/finalizing_spec.rb +0 -3
  226. data/spec/models/spree/order/payment_spec.rb +1 -1
  227. data/spec/models/spree/order/state_machine_spec.rb +1 -6
  228. data/spec/models/spree/order/store_credit_spec.rb +423 -0
  229. data/spec/models/spree/order/updating_spec.rb +2 -2
  230. data/spec/models/spree/order_contents_spec.rb +42 -1
  231. data/spec/models/spree/order_inventory_spec.rb +27 -17
  232. data/spec/models/spree/order_spec.rb +65 -52
  233. data/spec/models/spree/payment/gateway_options_spec.rb +10 -2
  234. data/spec/models/spree/payment/store_credit_spec.rb +60 -0
  235. data/spec/models/spree/payment_method/store_credit_spec.rb +291 -0
  236. data/spec/models/spree/payment_method_spec.rb +22 -14
  237. data/spec/models/spree/payment_spec.rb +37 -44
  238. data/spec/models/spree/price_spec.rb +86 -0
  239. data/spec/models/spree/product/scopes_spec.rb +35 -0
  240. data/spec/models/spree/product_option_type_spec.rb +6 -2
  241. data/spec/models/spree/product_promotion_rule_spec.rb +9 -0
  242. data/spec/models/spree/product_property_spec.rb +11 -0
  243. data/spec/models/spree/product_spec.rb +82 -15
  244. data/spec/models/spree/promotion/actions/create_item_adjustments_spec.rb +1 -1
  245. data/spec/models/spree/promotion/rules/user_spec.rb +8 -0
  246. data/spec/models/spree/promotion_action_spec.rb +1 -1
  247. data/spec/models/spree/promotion_rule_spec.rb +1 -1
  248. data/spec/models/spree/promotion_rule_taxon_spec.rb +9 -0
  249. data/spec/models/spree/promotion_rule_user_spec.rb +9 -0
  250. data/spec/models/spree/promotion_spec.rb +57 -36
  251. data/spec/models/spree/property_prototype_spec.rb +9 -0
  252. data/spec/models/spree/prototype_taxon_spec.rb +9 -0
  253. data/spec/models/spree/refund_reason_spec.rb +7 -0
  254. data/spec/models/spree/reimbursement_spec.rb +3 -30
  255. data/spec/models/spree/reimbursement_tax_calculator_spec.rb +17 -5
  256. data/spec/models/spree/reimbursement_type/store_credit_spec.rb +101 -0
  257. data/spec/models/spree/return_authorization_reason_spec.rb +7 -0
  258. data/spec/models/spree/return_authorization_spec.rb +2 -22
  259. data/spec/models/spree/return_item_spec.rb +50 -1
  260. data/spec/models/spree/returns_calculator_spec.rb +1 -1
  261. data/spec/models/spree/role_spec.rb +7 -0
  262. data/spec/models/spree/shipment_spec.rb +17 -17
  263. data/spec/models/spree/shipping_calculator_spec.rb +2 -2
  264. data/spec/models/spree/shipping_category_spec.rb +14 -0
  265. data/spec/models/spree/shipping_method_spec.rb +9 -2
  266. data/spec/models/spree/shipping_rate_spec.rb +40 -41
  267. data/spec/models/spree/state_spec.rb +12 -1
  268. data/spec/models/spree/stock/content_item_spec.rb +9 -0
  269. data/spec/models/spree/stock/estimator_spec.rb +56 -8
  270. data/spec/models/spree/stock/quantifier_spec.rb +61 -32
  271. data/spec/models/spree/stock_item_spec.rb +19 -1
  272. data/spec/models/spree/store_credit_event_spec.rb +101 -0
  273. data/spec/models/spree/store_credit_spec.rb +786 -0
  274. data/spec/models/spree/store_spec.rb +39 -11
  275. data/spec/models/spree/tax_category_spec.rb +6 -1
  276. data/spec/models/spree/tax_rate_spec.rb +204 -44
  277. data/spec/models/spree/user_spec.rb +105 -38
  278. data/spec/models/spree/variant_spec.rb +281 -9
  279. data/spec/models/spree/zone_member_spec.rb +38 -0
  280. data/spec/models/spree/zone_spec.rb +32 -8
  281. data/spec/spec_helper.rb +3 -0
  282. data/spec/support/concerns/{adjustment_source_spec.rb → adjustment_source.rb} +0 -0
  283. data/spec/support/concerns/{default_price_spec.rb → default_price.rb} +9 -0
  284. data/spec/validators/db_maximum_length_validator_spec.rb +22 -0
  285. data/spree_core.gemspec +5 -6
  286. metadata +99 -36
  287. data/CHANGELOG.md +0 -4
  288. data/app/models/concerns/spree/number_generator.rb +0 -39
  289. data/spec/models/spree/validations/db_maximum_length_validator_spec.rb +0 -24
@@ -1,8 +1,14 @@
1
1
  module Spree
2
2
  class PaymentMethod < Spree::Base
3
3
  acts_as_paranoid
4
- DISPLAY = [:both, :front_end, :back_end]
5
- default_scope { where(deleted_at: nil) }
4
+ acts_as_list
5
+
6
+ DISPLAY = [:both, :front_end, :back_end].freeze
7
+
8
+ scope :active, -> { where(active: true) }
9
+ scope :available, -> { active.where(display_on: [:front_end, :back_end, :both]) }
10
+ scope :available_on_front_end, -> { active.where(display_on: [:front_end, :both]) }
11
+ scope :available_on_back_end, -> { active.where(display_on: [:back_end, :both]) }
6
12
 
7
13
  validates :name, presence: true
8
14
 
@@ -24,17 +30,6 @@ module Spree
24
30
  raise ::NotImplementedError, 'You must implement payment_source_class method for this gateway.'
25
31
  end
26
32
 
27
- def self.available(display_on = 'both')
28
- all.select do |p|
29
- p.active &&
30
- (p.display_on == display_on.to_s || p.display_on.blank?)
31
- end
32
- end
33
-
34
- def self.active?
35
- where(type: self.to_s, active: true).count > 0
36
- end
37
-
38
33
  def method_type
39
34
  type.demodulize.downcase
40
35
  end
@@ -68,5 +63,9 @@ module Spree
68
63
  def cancel(response)
69
64
  raise ::NotImplementedError, 'You must implement cancel method for this payment method.'
70
65
  end
66
+
67
+ def store_credit?
68
+ self.class == Spree::PaymentMethod::StoreCredit
69
+ end
71
70
  end
72
71
  end
@@ -0,0 +1,130 @@
1
+ module Spree
2
+ class PaymentMethod::StoreCredit < PaymentMethod
3
+ def payment_source_class
4
+ ::Spree::StoreCredit
5
+ end
6
+
7
+ def can_capture?(payment)
8
+ ['checkout', 'pending'].include?(payment.state)
9
+ end
10
+
11
+ def can_void?(payment)
12
+ payment.pending?
13
+ end
14
+
15
+ def authorize(amount_in_cents, store_credit, gateway_options = {})
16
+ if store_credit.nil?
17
+ ActiveMerchant::Billing::Response.new(false, Spree.t('store_credit_payment_method.unable_to_find'), {}, {})
18
+ else
19
+ action = -> (store_credit) do
20
+ store_credit.authorize(
21
+ amount_in_cents / 100.0.to_d,
22
+ gateway_options[:currency],
23
+ action_originator: gateway_options[:originator]
24
+ )
25
+ end
26
+ handle_action_call(store_credit, action, :authorize)
27
+ end
28
+ end
29
+
30
+ def capture(amount_in_cents, auth_code, gateway_options = {})
31
+ action = -> (store_credit) do
32
+ store_credit.capture(
33
+ amount_in_cents / 100.0.to_d,
34
+ auth_code,
35
+ gateway_options[:currency],
36
+ action_originator: gateway_options[:originator]
37
+ )
38
+ end
39
+ handle_action(action, :capture, auth_code)
40
+ end
41
+
42
+ def purchase(amount_in_cents, store_credit, gateway_options = {})
43
+ eligible_events = store_credit.store_credit_events.where(
44
+ amount: amount_in_cents / 100.0.to_d,
45
+ action: Spree::StoreCredit::ELIGIBLE_ACTION
46
+ )
47
+ event = eligible_events.detect do |eligible_event|
48
+ store_credit.store_credit_events.where(authorization_code: eligible_event.authorization_code).
49
+ where.not(action: Spree::StoreCredit::ELIGIBLE_ACTION).empty?
50
+ end
51
+
52
+ if event.blank?
53
+ ActiveMerchant::Billing::Response.new(false, Spree.t('store_credit_payment_method.unable_to_find'), {}, {})
54
+ else
55
+ capture(amount_in_cents, event.authorization_code, gateway_options)
56
+ end
57
+ end
58
+
59
+ def void(auth_code, gateway_options = {})
60
+ action = -> (store_credit) do
61
+ store_credit.void(auth_code, action_originator: gateway_options[:originator])
62
+ end
63
+ handle_action(action, :void, auth_code)
64
+ end
65
+
66
+ def credit(amount_in_cents, auth_code, gateway_options)
67
+ action = -> (store_credit) do
68
+ currency = gateway_options[:currency] || store_credit.currency
69
+ originator = gateway_options[:originator]
70
+
71
+ store_credit.credit(amount_in_cents / 100.0.to_d, auth_code, currency, action_originator: originator)
72
+ end
73
+
74
+ handle_action(action, :credit, auth_code)
75
+ end
76
+
77
+ def cancel(auth_code)
78
+ store_credit_event = StoreCreditEvent.find_by(authorization_code: auth_code,
79
+ action: Spree::StoreCredit::CAPTURE_ACTION)
80
+ store_credit = store_credit_event.try(:store_credit)
81
+
82
+ if !store_credit_event || !store_credit
83
+ handle_action(nil, :cancel, false)
84
+ else
85
+ action = -> (store_credit) do
86
+ store_credit.credit(store_credit_event.amount, auth_code, store_credit.currency)
87
+ end
88
+ handle_action(action, :cancel, auth_code)
89
+ end
90
+ end
91
+
92
+ def source_required?
93
+ true
94
+ end
95
+
96
+ private
97
+
98
+ def handle_action_call(store_credit, action, action_name, auth_code = nil)
99
+ store_credit.with_lock do
100
+ if response = action.call(store_credit)
101
+ # note that we only need to return the auth code on an 'auth', but it's innocuous to always return
102
+ ActiveMerchant::Billing::Response.new(
103
+ true,
104
+ Spree.t('store_credit_payment_method.successful_action', action: action_name),
105
+ {},
106
+ authorization: auth_code || response
107
+ )
108
+ else
109
+ ActiveMerchant::Billing::Response.new(false, store_credit.errors.full_messages.join, {}, {})
110
+ end
111
+ end
112
+ end
113
+
114
+ def handle_action(action, action_name, auth_code)
115
+ # Find first event with provided auth_code
116
+ store_credit = StoreCreditEvent.find_by_authorization_code(auth_code).try(:store_credit)
117
+
118
+ if store_credit.nil?
119
+ ActiveMerchant::Billing::Response.new(
120
+ false,
121
+ Spree.t('store_credit_payment_method.unable_to_find_for_action', auth_code: auth_code, action: action_name),
122
+ {},
123
+ {}
124
+ )
125
+ else
126
+ handle_action_call(store_credit, action, action_name, auth_code)
127
+ end
128
+ end
129
+ end
130
+ end
@@ -1,5 +1,5 @@
1
1
  class Spree::Preference < Spree::Base
2
2
  serialize :value
3
3
 
4
- validates :key, presence: true, uniqueness: true
4
+ validates :key, presence: true, uniqueness: { allow_blank: true }
5
5
  end
@@ -1,12 +1,15 @@
1
1
  module Spree
2
2
  class Price < Spree::Base
3
+ include VatPriceCalculation
4
+
3
5
  acts_as_paranoid
4
6
 
5
7
  MAXIMUM_AMOUNT = BigDecimal('99_999_999.99')
6
8
 
7
9
  belongs_to :variant, class_name: 'Spree::Variant', inverse_of: :prices, touch: true
8
10
 
9
- validate :check_price
11
+ before_validation :ensure_currency
12
+
10
13
  validates :amount, allow_nil: true, numericality: {
11
14
  greater_than_or_equal_to: 0,
12
15
  less_than_or_equal_to: MAXIMUM_AMOUNT
@@ -21,12 +24,19 @@ module Spree
21
24
  Spree::Money.new(amount || 0, { currency: currency })
22
25
  end
23
26
 
24
- def price
25
- amount
27
+ def amount=(amount)
28
+ self[:amount] = Spree::LocalizedNumber.parse(amount)
29
+ end
30
+
31
+ alias_attribute :price, :amount
32
+
33
+ def price_including_vat_for(price_options)
34
+ options = price_options.merge(tax_category: variant.tax_category)
35
+ gross_amount(price, options)
26
36
  end
27
37
 
28
- def price=(price)
29
- self[:amount] = Spree::LocalizedNumber.parse(price)
38
+ def display_price_including_vat_for(price_options)
39
+ Spree::Money.new(price_including_vat_for(price_options), currency: currency)
30
40
  end
31
41
 
32
42
  # Remove variant default_scope `deleted_at: nil`
@@ -36,7 +46,7 @@ module Spree
36
46
 
37
47
  private
38
48
 
39
- def check_price
49
+ def ensure_currency
40
50
  self.currency ||= Spree::Config[:currency]
41
51
  end
42
52
  end
@@ -32,7 +32,15 @@ module Spree
32
32
 
33
33
  has_many :classifications, dependent: :delete_all, inverse_of: :product
34
34
  has_many :taxons, through: :classifications, before_remove: :remove_taxon
35
- has_and_belongs_to_many :promotion_rules, join_table: :spree_products_promotion_rules
35
+
36
+ has_many :product_promotion_rules, class_name: 'Spree::ProductPromotionRule'
37
+ has_many :promotion_rules, through: :product_promotion_rules, class_name: 'Spree::PromotionRule'
38
+
39
+ has_many :promotions, through: :promotion_rules, class_name: 'Spree::Promotion'
40
+
41
+ has_many :possible_promotions, -> { advertised.active }, through: :promotion_rules,
42
+ class_name: 'Spree::Promotion',
43
+ source: :promotion
36
44
 
37
45
  belongs_to :tax_category, class_name: 'Spree::TaxCategory'
38
46
  belongs_to :shipping_category, class_name: 'Spree::ShippingCategory', inverse_of: :products
@@ -43,12 +51,12 @@ module Spree
43
51
  class_name: 'Spree::Variant'
44
52
 
45
53
  has_many :variants,
46
- -> { where(is_master: false).order("#{::Spree::Variant.quoted_table_name}.position ASC") },
54
+ -> { where(is_master: false).order(:position) },
47
55
  inverse_of: :product,
48
56
  class_name: 'Spree::Variant'
49
57
 
50
58
  has_many :variants_including_master,
51
- -> { order("#{::Spree::Variant.quoted_table_name}.position ASC") },
59
+ -> { order(:position) },
52
60
  inverse_of: :product,
53
61
  class_name: 'Spree::Variant',
54
62
  dependent: :destroy
@@ -60,16 +68,13 @@ module Spree
60
68
  has_many :line_items, through: :variants_including_master
61
69
  has_many :orders, through: :line_items
62
70
 
63
- delegate_belongs_to :master, :sku, :price, :currency, :display_amount, :display_price, :weight, :height, :width, :depth, :is_master, :has_default_price?, :cost_currency, :price_in, :amount_in
71
+ delegate_belongs_to :master, :sku, :price, :currency, :display_amount, :display_price, :weight, :height, :width, :depth,
72
+ :is_master, :has_default_price?, :cost_currency, :price_in, :amount_in, :cost_price, :images
64
73
 
65
- delegate_belongs_to :master, :cost_price
66
-
67
- delegate :images, to: :master, prefix: true
68
- alias_method :images, :master_images
74
+ alias_method :master_images, :images
69
75
 
70
76
  has_many :variant_images, -> { order(:position) }, source: :images, through: :variants_including_master
71
77
 
72
- after_create :set_master_variant_defaults
73
78
  after_create :add_associations_from_prototype
74
79
  after_create :build_variants_from_option_values_hash, if: :option_values_hash
75
80
 
@@ -86,11 +91,15 @@ module Spree
86
91
  before_validation :normalize_slug, on: :update
87
92
  before_validation :validate_master
88
93
 
89
- validates :meta_keywords, length: { maximum: 255 }
90
- validates :meta_title, length: { maximum: 255 }
91
- validates :name, presence: true
92
- validates :price, presence: true, if: proc { Spree::Config[:require_master_price] }
93
- validates :shipping_category_id, presence: true
94
+ with_options length: { maximum: 255 }, allow_blank: true do
95
+ validates :meta_keywords
96
+ validates :meta_title
97
+ end
98
+ with_options presence: true do
99
+ validates :name, :shipping_category
100
+ validates :price, if: proc { Spree::Config[:require_master_price] }
101
+ end
102
+
94
103
  validates :slug, length: { minimum: 3 }, allow_blank: true, uniqueness: true
95
104
 
96
105
  attr_accessor :option_values_hash
@@ -108,11 +117,7 @@ module Spree
108
117
  end
109
118
 
110
119
  def tax_category
111
- if self[:tax_category_id].nil?
112
- TaxCategory.where(is_default: true).first
113
- else
114
- TaxCategory.find(self[:tax_category_id])
115
- end
120
+ super || TaxCategory.find_by(is_default: true)
116
121
  end
117
122
 
118
123
  # Adding properties and option types on creation based on a chosen prototype
@@ -124,9 +129,10 @@ module Spree
124
129
  # Ensures option_types and product_option_types exist for keys in option_values_hash
125
130
  def ensure_option_types_exist_for_values_hash
126
131
  return if option_values_hash.nil?
127
- option_values_hash.keys.map(&:to_i).each do |id|
128
- self.option_type_ids << id unless option_type_ids.include?(id)
129
- product_option_types.create(option_type_id: id) unless product_option_types.pluck(:option_type_id).include?(id)
132
+ required_option_type_ids = option_values_hash.keys.map(&:to_i)
133
+ missing_option_type_ids = required_option_type_ids - option_type_ids
134
+ missing_option_type_ids.each do |id|
135
+ product_option_types.create(option_type_id: id)
130
136
  end
131
137
  end
132
138
 
@@ -148,7 +154,20 @@ module Spree
148
154
  # deleted products and products with nil or future available_on date
149
155
  # are not available
150
156
  def available?
151
- !(available_on.nil? || available_on.future?) && !deleted?
157
+ !(available_on.nil? || available_on.future?) && !deleted? && !discontinued?
158
+ end
159
+
160
+ def discontinue!
161
+ update_attribute(:discontinue_on, Time.current)
162
+ end
163
+
164
+ def discontinued?
165
+ !!discontinue_on && discontinue_on <= Time.current
166
+ end
167
+
168
+ # determine if any variant (including master) can be supplied
169
+ def can_supply?
170
+ variants_including_master.any?(&:can_supply?)
152
171
  end
153
172
 
154
173
  # split variants list into hash which shows mapping of opt value onto matching variants
@@ -159,11 +178,10 @@ module Spree
159
178
  end
160
179
 
161
180
  def self.like_any(fields, values)
162
- where fields.map { |field|
163
- values.map { |value|
164
- arel_table[field].matches("%#{value}%")
165
- }.inject(:or)
166
- }.inject(:or)
181
+ conditions = fields.product(values).map do |(field, value)|
182
+ arel_table[field].matches("%#{value}%")
183
+ end
184
+ where conditions.inject(:or)
167
185
  end
168
186
 
169
187
  # Suitable for displaying only variants that has at least one option value.
@@ -184,29 +202,19 @@ module Spree
184
202
  end
185
203
 
186
204
  def property(property_name)
187
- return nil unless prop = properties.find_by(name: property_name)
188
- product_properties.find_by(property: prop).try(:value)
205
+ product_properties.joins(:property).find_by(spree_properties: { name: property_name }).try(:value)
189
206
  end
190
207
 
191
- def set_property(property_name, property_value)
208
+ def set_property(property_name, property_value, property_presentation = property_name)
192
209
  ActiveRecord::Base.transaction do
193
210
  # Works around spree_i18n #301
194
- property = if Property.exists?(name: property_name)
195
- Property.where(name: property_name).first
196
- else
197
- Property.create(name: property_name, presentation: property_name)
198
- end
211
+ property = Property.create_with(presentation: property_presentation).find_or_create_by(name: property_name)
199
212
  product_property = ProductProperty.where(product: self, property: property).first_or_initialize
200
213
  product_property.value = property_value
201
214
  product_property.save!
202
215
  end
203
216
  end
204
217
 
205
- def possible_promotions
206
- promotion_ids = promotion_rules.map(&:promotion_id).uniq
207
- Spree::Promotion.advertised.where(id: promotion_ids).reject(&:expired?)
208
- end
209
-
210
218
  def total_on_hand
211
219
  if any_variants_not_track_inventory?
212
220
  Float::INFINITY
@@ -219,7 +227,7 @@ module Spree
219
227
  # which would make AR's default finder return nil.
220
228
  # This is a stopgap for that little problem.
221
229
  def master
222
- super || variants_including_master.with_deleted.where(is_master: true).first
230
+ super || variants_including_master.with_deleted.find_by(is_master: true)
223
231
  end
224
232
 
225
233
  private
@@ -250,7 +258,7 @@ module Spree
250
258
  values = values.inject(values.shift) { |memo, value| memo.product(value).map(&:flatten) }
251
259
 
252
260
  values.each do |ids|
253
- variant = variants.create(
261
+ variants.create(
254
262
  option_value_ids: ids,
255
263
  price: master.price
256
264
  )
@@ -269,7 +277,7 @@ module Spree
269
277
 
270
278
  def punch_slug
271
279
  # punch slug with date prefix to allow reuse of original
272
- update_column :slug, "#{Time.now.to_i}_#{slug}"[0..254] unless frozen?
280
+ update_column :slug, "#{Time.current.to_i}_#{slug}"[0..254] unless frozen?
273
281
  end
274
282
 
275
283
  def update_slug_history
@@ -320,11 +328,6 @@ module Spree
320
328
  end
321
329
  end
322
330
 
323
- # ensures the master variant is flagged as such
324
- def set_master_variant_defaults
325
- master.is_master = true
326
- end
327
-
328
331
  # Try building a slug based on the following fields in increasing order of specificity.
329
332
  def slug_candidates
330
333
  [
@@ -186,14 +186,19 @@ module Spree
186
186
  where("#{Product.quoted_table_name}.deleted_at IS NULL or #{Product.quoted_table_name}.deleted_at >= ?", Time.zone.now)
187
187
  end
188
188
 
189
+ add_search_scope :not_discontinued do
190
+ where("#{Product.quoted_table_name}.discontinue_on IS NULL or #{Product.quoted_table_name}.discontinue_on >= ?", Time.zone.now)
191
+ end
192
+
189
193
  # Can't use add_search_scope for this as it needs a default argument
190
194
  def self.available(available_on = nil, currency = nil)
191
- joins(:master => :prices).where("#{Product.quoted_table_name}.available_on <= ?", available_on || Time.now)
195
+ available_on ||= Time.current
196
+ not_discontinued.joins(master: :prices).where("#{Product.quoted_table_name}.available_on <= ?", available_on).uniq
192
197
  end
193
198
  search_scopes << :available
194
199
 
195
200
  def self.active(currency = nil)
196
- not_deleted.available(nil, currency)
201
+ not_discontinued.available(nil, currency)
197
202
  end
198
203
  search_scopes << :active
199
204