spree_core 5.2.6 → 5.3.0.rc1

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.
Files changed (294) hide show
  1. checksums.yaml +4 -4
  2. data/app/helpers/spree/addresses_helper.rb +1 -23
  3. data/app/helpers/spree/base_helper.rb +25 -77
  4. data/app/helpers/spree/currency_helper.rb +2 -2
  5. data/app/helpers/spree/images_helper.rb +5 -0
  6. data/app/helpers/spree/mail_helper.rb +1 -1
  7. data/app/helpers/spree/products_helper.rb +1 -1
  8. data/app/jobs/spree/events/subscriber_job.rb +58 -0
  9. data/app/jobs/spree/products/refresh_metrics_job.rb +14 -0
  10. data/app/jobs/spree/products/touch_taxons_job.rb +0 -1
  11. data/app/jobs/spree/variants/touch_job.rb +9 -0
  12. data/app/models/concerns/spree/adjustment_source.rb +8 -0
  13. data/app/models/concerns/spree/parameterizable_name.rb +1 -1
  14. data/app/models/concerns/spree/product_scopes.rb +54 -8
  15. data/app/models/concerns/spree/publishable.rb +253 -0
  16. data/app/models/concerns/spree/unique_name.rb +1 -1
  17. data/app/models/concerns/spree/user_methods.rb +21 -1
  18. data/app/models/spree/ability.rb +3 -3
  19. data/app/models/spree/address.rb +0 -3
  20. data/app/models/spree/adjustable/promotion_accumulator.rb +1 -1
  21. data/app/models/spree/adjustment.rb +32 -6
  22. data/app/models/spree/asset.rb +6 -3
  23. data/app/models/spree/base.rb +3 -1
  24. data/app/models/spree/classification.rb +2 -2
  25. data/app/models/spree/credit_card.rb +0 -3
  26. data/app/models/spree/current.rb +24 -3
  27. data/app/models/spree/custom_domain.rb +1 -1
  28. data/app/models/spree/customer_group.rb +94 -0
  29. data/app/models/spree/customer_group_user.rb +16 -0
  30. data/app/models/spree/customer_return.rb +2 -3
  31. data/app/models/spree/digital.rb +2 -4
  32. data/app/models/spree/digital_link.rb +2 -3
  33. data/app/models/spree/event.rb +104 -0
  34. data/app/models/spree/export.rb +7 -1
  35. data/app/models/spree/gift_card.rb +13 -1
  36. data/app/models/spree/gift_card_batch.rb +3 -1
  37. data/app/models/spree/image.rb +20 -5
  38. data/app/models/spree/import.rb +17 -12
  39. data/app/models/spree/import_row.rb +13 -26
  40. data/app/models/spree/inventory_unit.rb +0 -3
  41. data/app/models/spree/invitation.rb +12 -6
  42. data/app/models/spree/line_item.rb +57 -4
  43. data/app/models/spree/newsletter_subscriber.rb +2 -0
  44. data/app/models/spree/option_type.rb +2 -5
  45. data/app/models/spree/option_value.rb +1 -4
  46. data/app/models/spree/order/checkout.rb +7 -2
  47. data/app/models/spree/order/emails.rb +3 -11
  48. data/app/models/spree/order/store_credit.rb +5 -1
  49. data/app/models/spree/order.rb +80 -31
  50. data/app/models/spree/order_updater.rb +28 -9
  51. data/app/models/spree/payment/custom_events.rb +37 -0
  52. data/app/models/spree/payment.rb +13 -4
  53. data/app/models/spree/payment_capture_event.rb +0 -4
  54. data/app/models/spree/payment_method.rb +1 -1
  55. data/app/models/spree/permission_sets/default_customer.rb +3 -3
  56. data/app/models/spree/policy.rb +1 -8
  57. data/app/models/spree/post.rb +2 -7
  58. data/app/models/spree/post_category.rb +2 -0
  59. data/app/models/spree/price.rb +8 -6
  60. data/app/models/spree/price_list.rb +263 -0
  61. data/app/models/spree/price_rule.rb +26 -0
  62. data/app/models/spree/price_rules/customer_group_rule.rb +21 -0
  63. data/app/models/spree/price_rules/user_rule.rb +19 -0
  64. data/app/models/spree/price_rules/volume_rule.rb +21 -0
  65. data/app/models/spree/price_rules/zone_rule.rb +19 -0
  66. data/app/models/spree/product.rb +117 -76
  67. data/app/models/spree/product_property.rb +2 -2
  68. data/app/models/spree/promotion/rules/customer_group.rb +22 -0
  69. data/app/models/spree/promotion/rules/user.rb +2 -2
  70. data/app/models/spree/promotion.rb +3 -4
  71. data/app/models/spree/promotion_handler/coupon.rb +3 -3
  72. data/app/models/spree/property.rb +3 -6
  73. data/app/models/spree/prototype.rb +0 -3
  74. data/app/models/spree/refund.rb +2 -3
  75. data/app/models/spree/reimbursement.rb +5 -3
  76. data/app/models/spree/report.rb +7 -1
  77. data/app/models/spree/reports/products_performance.rb +1 -1
  78. data/app/models/spree/reports/sales_total.rb +1 -1
  79. data/app/models/spree/return_authorization.rb +7 -3
  80. data/app/models/spree/return_item.rb +15 -4
  81. data/app/models/spree/shipment/custom_events.rb +47 -0
  82. data/app/models/spree/shipment.rb +12 -5
  83. data/app/models/spree/shipping_category.rb +0 -3
  84. data/app/models/spree/shipping_method.rb +0 -3
  85. data/app/models/spree/stock/quantifier.rb +1 -1
  86. data/app/models/spree/stock_item.rb +2 -0
  87. data/app/models/spree/stock_location.rb +0 -3
  88. data/app/models/spree/stock_movement/custom_events.rb +44 -0
  89. data/app/models/spree/stock_movement.rb +3 -0
  90. data/app/models/spree/stock_transfer.rb +2 -3
  91. data/app/models/spree/store.rb +64 -92
  92. data/app/models/spree/store_credit.rb +3 -4
  93. data/app/models/spree/store_product.rb +14 -0
  94. data/app/models/spree/subscriber.rb +186 -0
  95. data/app/models/spree/tax_category.rb +0 -4
  96. data/app/models/spree/tax_rate.rb +0 -3
  97. data/app/models/spree/taxon.rb +3 -36
  98. data/app/models/spree/taxonomy.rb +0 -3
  99. data/app/models/spree/variant.rb +114 -21
  100. data/app/models/spree/webhook_delivery.rb +60 -0
  101. data/app/models/spree/webhook_endpoint.rb +53 -0
  102. data/app/models/spree/wished_item.rb +2 -4
  103. data/app/models/spree/wishlist.rb +2 -3
  104. data/app/models/spree/zone.rb +0 -9
  105. data/app/paginators/spree/shared/paginate.rb +4 -0
  106. data/app/serializers/spree/events/asset_serializer.rb +22 -0
  107. data/app/serializers/spree/events/base_serializer.rb +61 -0
  108. data/app/serializers/spree/events/customer_return_serializer.rb +20 -0
  109. data/app/serializers/spree/events/digital_link_serializer.rb +20 -0
  110. data/app/serializers/spree/events/digital_serializer.rb +18 -0
  111. data/app/serializers/spree/events/export_serializer.rb +22 -0
  112. data/app/serializers/spree/events/gift_card_batch_serializer.rb +24 -0
  113. data/app/serializers/spree/events/gift_card_serializer.rb +29 -0
  114. data/app/serializers/spree/events/image_serializer.rb +9 -0
  115. data/app/serializers/spree/events/import_row_serializer.rb +23 -0
  116. data/app/serializers/spree/events/import_serializer.rb +24 -0
  117. data/app/serializers/spree/events/invitation_serializer.rb +28 -0
  118. data/app/serializers/spree/events/line_item_serializer.rb +31 -0
  119. data/app/serializers/spree/events/newsletter_subscriber_serializer.rb +21 -0
  120. data/app/serializers/spree/events/order_serializer.rb +39 -0
  121. data/app/serializers/spree/events/payment_serializer.rb +24 -0
  122. data/app/serializers/spree/events/post_category_serializer.rb +20 -0
  123. data/app/serializers/spree/events/post_serializer.rb +26 -0
  124. data/app/serializers/spree/events/price_serializer.rb +22 -0
  125. data/app/serializers/spree/events/product_serializer.rb +24 -0
  126. data/app/serializers/spree/events/promotion_serializer.rb +32 -0
  127. data/app/serializers/spree/events/refund_serializer.rb +23 -0
  128. data/app/serializers/spree/events/reimbursement_serializer.rb +22 -0
  129. data/app/serializers/spree/events/report_serializer.rb +23 -0
  130. data/app/serializers/spree/events/return_authorization_serializer.rb +22 -0
  131. data/app/serializers/spree/events/return_item_serializer.rb +27 -0
  132. data/app/serializers/spree/events/shipment_serializer.rb +24 -0
  133. data/app/serializers/spree/events/stock_item_serializer.rb +22 -0
  134. data/app/serializers/spree/events/stock_movement_serializer.rb +22 -0
  135. data/app/serializers/spree/events/stock_transfer_serializer.rb +22 -0
  136. data/app/serializers/spree/events/store_credit_serializer.rb +30 -0
  137. data/app/serializers/spree/events/user_serializer.rb +18 -0
  138. data/app/serializers/spree/events/variant_serializer.rb +34 -0
  139. data/app/serializers/spree/events/wished_item_serializer.rb +20 -0
  140. data/app/serializers/spree/events/wishlist_serializer.rb +22 -0
  141. data/app/services/spree/addresses/update.rb +1 -1
  142. data/app/services/spree/cart/add_item.rb +1 -1
  143. data/app/services/spree/coupon_codes/coupon_codes_handler.rb +2 -1
  144. data/app/services/spree/data_feeds/google/required_attributes.rb +4 -4
  145. data/app/services/spree/newsletter/verify.rb +5 -0
  146. data/app/services/spree/products/auto_match_taxons.rb +1 -1
  147. data/app/services/spree/seeds/all.rb +1 -1
  148. data/app/services/spree/taxons/add_products.rb +8 -4
  149. data/app/services/spree/taxons/regenerate_products.rb +8 -0
  150. data/app/services/spree/taxons/remove_products.rb +12 -7
  151. data/app/subscribers/spree/event_log_subscriber.rb +64 -0
  152. data/app/subscribers/spree/export_subscriber.rb +26 -0
  153. data/app/subscribers/spree/invitation_email_subscriber.rb +40 -0
  154. data/app/subscribers/spree/product_metrics_subscriber.rb +29 -0
  155. data/app/subscribers/spree/report_subscriber.rb +26 -0
  156. data/config/locales/en.yml +126 -0
  157. data/db/migrate/20251110120000_create_spree_price_lists.rb +22 -0
  158. data/db/migrate/20251110120001_create_spree_price_rules.rb +13 -0
  159. data/db/migrate/20251110120002_add_price_list_id_to_spree_prices.rb +6 -0
  160. data/db/migrate/20251110120003_add_price_list_id_to_spree_line_items.rb +5 -0
  161. data/db/migrate/20251201172118_fix_indexes_on_friendly_id_slugs.rb +8 -0
  162. data/db/migrate/20251214000001_create_spree_webhook_endpoints.rb +19 -0
  163. data/db/migrate/20251214000002_create_spree_webhook_deliveries.rb +23 -0
  164. data/db/migrate/20251222000000_add_performance_indexes_to_spree_adjustments.rb +25 -0
  165. data/db/migrate/20260112000000_fix_spree_prices_unique_indexes.rb +33 -0
  166. data/db/migrate/20260115120000_create_spree_customer_groups.rb +14 -0
  167. data/db/migrate/20260115120001_create_spree_customer_group_users.rb +14 -0
  168. data/db/migrate/20260117140831_remove_not_null_constraint_from_policy_name.rb +5 -0
  169. data/db/migrate/20260118120000_add_statistics_to_store_products.rb +11 -0
  170. data/db/migrate/20260119120000_add_counter_caches_to_spree_products.rb +9 -0
  171. data/db/migrate/20260119170000_add_counter_caches_to_spree_taxons.rb +9 -0
  172. data/db/migrate/20260120120000_add_image_count_to_spree_variants.rb +9 -0
  173. data/lib/generators/spree/dummy/dummy_generator.rb +14 -2
  174. data/lib/spree/core/configuration.rb +1 -0
  175. data/lib/spree/core/controller_helpers/auth.rb +0 -15
  176. data/lib/spree/core/controller_helpers/currency.rb +13 -9
  177. data/lib/spree/core/controller_helpers/search.rb +1 -1
  178. data/lib/spree/core/controller_helpers/store.rb +5 -1
  179. data/lib/spree/core/engine.rb +61 -78
  180. data/lib/spree/core/importer/order.rb +1 -1
  181. data/lib/spree/core/importer/product.rb +1 -1
  182. data/lib/spree/core/preferences/preferable.rb +14 -1
  183. data/lib/spree/core/pricing/context.rb +63 -0
  184. data/lib/spree/core/pricing/resolver.rb +129 -0
  185. data/lib/spree/core/search/base.rb +1 -1
  186. data/lib/spree/core/token_generator.rb +1 -1
  187. data/lib/spree/core/version.rb +1 -1
  188. data/lib/spree/core.rb +42 -47
  189. data/lib/spree/events/adapters/active_support_notifications.rb +112 -0
  190. data/lib/spree/events/adapters/base.rb +193 -0
  191. data/lib/spree/events/registry.rb +99 -0
  192. data/lib/spree/events.rb +240 -0
  193. data/lib/spree/permitted_attributes.rb +13 -2
  194. data/lib/spree/testing_support/common_rake.rb +68 -35
  195. data/lib/spree/testing_support/factories/customer_group_factory.rb +6 -0
  196. data/lib/spree/testing_support/factories/customer_group_user_factory.rb +6 -0
  197. data/lib/spree/testing_support/factories/price_factory.rb +4 -0
  198. data/lib/spree/testing_support/factories/price_list_factory.rb +34 -0
  199. data/lib/spree/testing_support/factories/price_rule_factory.rb +49 -0
  200. data/lib/spree/testing_support/factories/promotion_rule_factory.rb +12 -0
  201. data/lib/spree/testing_support/factories/stock_item_factory.rb +6 -4
  202. data/lib/spree/testing_support/factories/store_product_factory.rb +6 -0
  203. data/lib/spree/testing_support/factories/taxon_factory.rb +0 -1
  204. data/lib/spree/testing_support/factories/webhook_delivery_factory.rb +48 -0
  205. data/lib/spree/testing_support/factories/webhook_endpoint_factory.rb +22 -0
  206. data/lib/spree/testing_support/lifecycle_events.rb +38 -0
  207. data/lib/spree/testing_support/store.rb +4 -2
  208. data/lib/spree/webhooks.rb +22 -0
  209. data/lib/tasks/products.rake +40 -0
  210. data/lib/tasks/taxons.rake +19 -0
  211. data/lib/tasks/variants.rake +18 -0
  212. metadata +112 -114
  213. data/app/jobs/spree/themes/duplicate_components_job.rb +0 -59
  214. data/app/jobs/spree/themes/screenshot_job.rb +0 -81
  215. data/app/models/concerns/spree/has_page_links.rb +0 -53
  216. data/app/models/spree/page.rb +0 -188
  217. data/app/models/spree/page_block.rb +0 -73
  218. data/app/models/spree/page_blocks/buttons.rb +0 -29
  219. data/app/models/spree/page_blocks/heading.rb +0 -18
  220. data/app/models/spree/page_blocks/image.rb +0 -20
  221. data/app/models/spree/page_blocks/link.rb +0 -21
  222. data/app/models/spree/page_blocks/mega_nav.rb +0 -33
  223. data/app/models/spree/page_blocks/mega_nav_with_subcategories.rb +0 -32
  224. data/app/models/spree/page_blocks/metafields.rb +0 -18
  225. data/app/models/spree/page_blocks/nav.rb +0 -15
  226. data/app/models/spree/page_blocks/newsletter_form.rb +0 -18
  227. data/app/models/spree/page_blocks/products/brand.rb +0 -15
  228. data/app/models/spree/page_blocks/products/buy_buttons.rb +0 -24
  229. data/app/models/spree/page_blocks/products/description.rb +0 -18
  230. data/app/models/spree/page_blocks/products/price.rb +0 -18
  231. data/app/models/spree/page_blocks/products/quantity_selector.rb +0 -20
  232. data/app/models/spree/page_blocks/products/share.rb +0 -8
  233. data/app/models/spree/page_blocks/products/title.rb +0 -19
  234. data/app/models/spree/page_blocks/products/variant_picker.rb +0 -13
  235. data/app/models/spree/page_blocks/subheading.rb +0 -17
  236. data/app/models/spree/page_blocks/text.rb +0 -16
  237. data/app/models/spree/page_link.rb +0 -60
  238. data/app/models/spree/page_section.rb +0 -222
  239. data/app/models/spree/page_sections/announcement_bar.rb +0 -28
  240. data/app/models/spree/page_sections/breadcrumbs.rb +0 -12
  241. data/app/models/spree/page_sections/collection_banner.rb +0 -18
  242. data/app/models/spree/page_sections/custom_code.rb +0 -11
  243. data/app/models/spree/page_sections/featured_posts.rb +0 -45
  244. data/app/models/spree/page_sections/featured_product.rb +0 -50
  245. data/app/models/spree/page_sections/featured_taxon.rb +0 -90
  246. data/app/models/spree/page_sections/featured_taxons.rb +0 -45
  247. data/app/models/spree/page_sections/footer.rb +0 -101
  248. data/app/models/spree/page_sections/header.rb +0 -62
  249. data/app/models/spree/page_sections/image_banner.rb +0 -55
  250. data/app/models/spree/page_sections/image_with_text.rb +0 -65
  251. data/app/models/spree/page_sections/main_password_footer.rb +0 -18
  252. data/app/models/spree/page_sections/main_password_header.rb +0 -20
  253. data/app/models/spree/page_sections/newsletter.rb +0 -54
  254. data/app/models/spree/page_sections/page_title.rb +0 -19
  255. data/app/models/spree/page_sections/post_details.rb +0 -19
  256. data/app/models/spree/page_sections/post_grid.rb +0 -19
  257. data/app/models/spree/page_sections/product_details.rb +0 -53
  258. data/app/models/spree/page_sections/product_grid.rb +0 -13
  259. data/app/models/spree/page_sections/related_products.rb +0 -58
  260. data/app/models/spree/page_sections/rich_text.rb +0 -31
  261. data/app/models/spree/page_sections/taxon_banner.rb +0 -18
  262. data/app/models/spree/page_sections/taxon_grid.rb +0 -17
  263. data/app/models/spree/page_sections/video.rb +0 -107
  264. data/app/models/spree/pages/account.rb +0 -19
  265. data/app/models/spree/pages/cart.rb +0 -19
  266. data/app/models/spree/pages/checkout.rb +0 -15
  267. data/app/models/spree/pages/custom.rb +0 -38
  268. data/app/models/spree/pages/homepage.rb +0 -72
  269. data/app/models/spree/pages/login.rb +0 -19
  270. data/app/models/spree/pages/password.rb +0 -59
  271. data/app/models/spree/pages/post.rb +0 -27
  272. data/app/models/spree/pages/post_list.rb +0 -36
  273. data/app/models/spree/pages/product_details.rb +0 -30
  274. data/app/models/spree/pages/search_results.rb +0 -43
  275. data/app/models/spree/pages/shop_all.rb +0 -40
  276. data/app/models/spree/pages/taxon.rb +0 -29
  277. data/app/models/spree/pages/taxon_list.rb +0 -41
  278. data/app/models/spree/pages/wishlist.rb +0 -15
  279. data/app/models/spree/theme.rb +0 -233
  280. data/app/models/spree/themes/default.rb +0 -97
  281. data/app/services/spree/taxons/touch_featured_sections.rb +0 -21
  282. data/db/migrate/20250120094216_create_page_builder_models.rb +0 -78
  283. data/db/migrate/20250305121352_remove_page_builder_indices.rb +0 -11
  284. data/db/migrate/20250825175217_add_missing_page_builder_indexes.rb +0 -7
  285. data/db/migrate/20250913130044_add_page_links_counter_cache_to_spree_stores.rb +0 -10
  286. data/lib/generators/spree/dummy/templates/initializers/devise.rb +0 -3
  287. data/lib/generators/spree/install/install_generator.rb +0 -219
  288. data/lib/generators/spree/install/templates/config/initializers/spree.rb +0 -126
  289. data/lib/spree/core/webhooks.rb +0 -21
  290. data/lib/spree/testing_support/factories/page_block_factory.rb +0 -22
  291. data/lib/spree/testing_support/factories/page_factory.rb +0 -33
  292. data/lib/spree/testing_support/factories/page_link_factory.rb +0 -7
  293. data/lib/spree/testing_support/factories/page_section_factory.rb +0 -27
  294. data/lib/spree/testing_support/factories/theme_factory.rb +0 -14
@@ -0,0 +1,104 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Spree
4
+ # Represents an event in the Spree event system.
5
+ #
6
+ # Events are immutable objects that carry information about something
7
+ # that happened in the system. They contain:
8
+ # - An id (UUID)
9
+ # - A name (e.g., 'order.complete', 'product.create')
10
+ # - A store_id (the store where the event originated)
11
+ # - A payload (serialized data about the event)
12
+ # - Metadata (contextual information like spree_version)
13
+ #
14
+ # @example Creating an event
15
+ # event = Spree::Event.new(
16
+ # name: 'order.complete',
17
+ # payload: order.serializable_hash
18
+ # )
19
+ #
20
+ # @example Accessing event data
21
+ # event.id # => "550e8400-e29b-41d4-a716-446655440000"
22
+ # event.name # => 'order.complete'
23
+ # event.store_id # => 1
24
+ # event.payload # => { 'id' => 1, 'number' => 'R123456' }
25
+ # event.created_at # => 2024-01-15 10:30:00 UTC
26
+ #
27
+ class Event
28
+ include ActiveModel::Model
29
+ include ActiveModel::Attributes
30
+ include ActiveModel::Serializers::JSON
31
+
32
+ attribute :id, :string, default: -> { SecureRandom.uuid }
33
+ attribute :name, :string
34
+ attribute :store_id, :integer, default: -> { Spree::Current.store&.id }
35
+ attribute :payload, default: -> { {}.freeze }
36
+ attribute :metadata, default: -> { { 'spree_version' => Spree.version }.freeze }
37
+ attribute :created_at, :datetime, default: -> { Time.current }
38
+
39
+ validates :name, presence: true
40
+ validates :store_id, presence: true
41
+
42
+ def name=(value)
43
+ super(value.to_s.freeze) if value
44
+ end
45
+
46
+ def payload=(value)
47
+ super((value || {}).deep_stringify_keys.freeze)
48
+ end
49
+
50
+ def metadata=(value)
51
+ base = { 'spree_version' => Spree.version }
52
+ super(base.merge((value || {}).deep_stringify_keys).freeze)
53
+ end
54
+
55
+ # Returns the store where the event originated
56
+ # @return [Spree::Store, nil]
57
+ def store
58
+ return nil if store_id.blank?
59
+
60
+ @store ||= Spree::Store.find_by(id: store_id)
61
+ end
62
+
63
+ # Returns the resource type from the event name
64
+ # @return [String] The resource type (e.g., 'order' from 'order.complete')
65
+ def resource_type
66
+ @resource_type ||= name.to_s.split('.').first
67
+ end
68
+
69
+ # Returns the action from the event name
70
+ # @return [String] The action (e.g., 'complete' from 'order.complete')
71
+ def action
72
+ @action ||= name.to_s.split('.').drop(1).join('.')
73
+ end
74
+
75
+ # Checks if the event matches a pattern
76
+ # Supports wildcards: 'order.*' matches 'order.complete', 'order.cancel'
77
+ # @param pattern [String] The pattern to match against
78
+ # @return [Boolean]
79
+ def matches?(pattern)
80
+ self.class.matches?(name, pattern)
81
+ end
82
+
83
+ # Class method to check if an event name matches a pattern
84
+ # @param event_name [String] The event name
85
+ # @param pattern [String] The pattern (supports * wildcard)
86
+ # @return [Boolean]
87
+ def self.matches?(event_name, pattern)
88
+ return true if pattern == '*'
89
+
90
+ if pattern.include?('*')
91
+ regex = Regexp.new("^#{Regexp.escape(pattern).gsub('\*', '.*')}$")
92
+ event_name.match?(regex)
93
+ else
94
+ event_name == pattern
95
+ end
96
+ end
97
+
98
+ alias_method :to_h, :attributes
99
+
100
+ def inspect
101
+ "#<Spree::Event id=#{id.inspect} name=#{name.inspect} store_id=#{store_id.inspect} created_at=#{created_at&.iso8601}>"
102
+ end
103
+ end
104
+ end
@@ -11,6 +11,12 @@ module Spree
11
11
 
12
12
  include Spree::Core::NumberGenerator.new(prefix: 'EF')
13
13
 
14
+ publishes_lifecycle_events
15
+
16
+ # Set event prefix for all Export subclasses
17
+ # This ensures Spree::Exports::Products publishes 'export.create' not 'products.create'
18
+ self.event_prefix = 'export'
19
+
14
20
  #
15
21
  # Associations
16
22
  #
@@ -44,7 +50,7 @@ module Spree
44
50
  before_validation :set_default_format, on: :create
45
51
  before_validation :normalize_search_params, on: :create, if: -> { search_params.present? }
46
52
  before_create :clear_search_params, if: -> { record_selection == 'all' }
47
- after_commit :generate_async, on: :create
53
+ # NOTE: generate_async is now handled by Spree::ExportSubscriber listening to 'export.create' event
48
54
 
49
55
  #
50
56
  # Virtual attributes
@@ -5,6 +5,8 @@ module Spree
5
5
  include Spree::Metafields
6
6
  include Spree::Security::GiftCards if defined?(Spree::Security::GiftCards)
7
7
 
8
+ publishes_lifecycle_events
9
+
8
10
  #
9
11
  # State machine
10
12
  #
@@ -18,11 +20,13 @@ module Spree
18
20
  transition partially_redeemed: :redeemed
19
21
  end
20
22
  after_transition to: :redeemed, do: :after_redeem
23
+ after_transition to: :redeemed, do: :publish_gift_card_redeemed_event
21
24
 
22
25
  event :partial_redeem do
23
26
  transition active: :partially_redeemed
24
27
  transition partially_redeemed: :partially_redeemed
25
28
  end
29
+ after_transition to: :partially_redeemed, do: :publish_gift_card_partially_redeemed_event
26
30
  end
27
31
 
28
32
  #
@@ -60,7 +64,7 @@ module Spree
60
64
  self.whitelisted_ransackable_associations = %w[users orders batch]
61
65
  self.whitelisted_ransackable_scopes = %w[active expired redeemed partially_redeemed]
62
66
 
63
- auto_strip_attributes :code
67
+ normalizes :code, with: ->(value) { value&.to_s&.squish&.presence }
64
68
 
65
69
  #
66
70
  # Callbacks
@@ -156,6 +160,14 @@ module Spree
156
160
  update!(redeemed_at: Time.current)
157
161
  end
158
162
 
163
+ def publish_gift_card_redeemed_event
164
+ publish_event('gift_card.redeemed')
165
+ end
166
+
167
+ def publish_gift_card_partially_redeemed_event
168
+ publish_event('gift_card.partially_redeemed')
169
+ end
170
+
159
171
  def ensure_can_be_deleted
160
172
  return if can_be_deleted?
161
173
 
@@ -3,6 +3,8 @@ module Spree
3
3
  extend DisplayMoney
4
4
  include Spree::SingleStoreResource
5
5
 
6
+ publishes_lifecycle_events
7
+
6
8
  #
7
9
  # Associations
8
10
  #
@@ -24,7 +26,7 @@ module Spree
24
26
  before_validation :set_currency
25
27
  after_create :generate_gift_cards
26
28
 
27
- auto_strip_attributes :prefix
29
+ normalizes :prefix, with: ->(value) { value&.to_s&.squish&.presence }
28
30
 
29
31
  money_methods :amount
30
32
 
@@ -6,6 +6,9 @@ module Spree
6
6
 
7
7
  after_commit :touch_product_variants, if: :should_touch_product_variants?, on: :update
8
8
 
9
+ after_create :increment_viewable_image_count
10
+ after_destroy :decrement_viewable_image_count
11
+
9
12
  # In Rails 5.x class constants are being undefined/redefined during the code reloading process
10
13
  # in a rails development environment, after which the actual ruby objects stored in those class constants
11
14
  # are no longer equal (subclass == self) what causes error ActiveRecord::SubclassNotFound
@@ -62,12 +65,24 @@ module Spree
62
65
  end
63
66
 
64
67
  def should_touch_product_variants?
65
- return false unless viewable.is_a?(Spree::Variant)
66
- return false unless viewable.is_master?
67
- return false unless viewable.product.has_variants?
68
- return false unless saved_change_to_position?
68
+ viewable.is_a?(Spree::Variant) &&
69
+ viewable.is_master? &&
70
+ viewable.product.has_variants? &&
71
+ saved_change_to_position?
72
+ end
73
+
74
+ def increment_viewable_image_count
75
+ return unless viewable.is_a?(Spree::Variant)
76
+
77
+ Spree::Variant.increment_counter(:image_count, viewable_id)
78
+ Spree::Product.increment_counter(:total_image_count, viewable.product_id)
79
+ end
80
+
81
+ def decrement_viewable_image_count
82
+ return unless viewable.is_a?(Spree::Variant)
69
83
 
70
- true
84
+ Spree::Variant.decrement_counter(:image_count, viewable_id)
85
+ Spree::Product.decrement_counter(:total_image_count, viewable.product_id)
71
86
  end
72
87
  end
73
88
  end
@@ -7,6 +7,12 @@ module Spree
7
7
 
8
8
  include Spree::Core::NumberGenerator.new(prefix: 'IM')
9
9
 
10
+ publishes_lifecycle_events
11
+
12
+ # Set event prefix for all Import subclasses
13
+ # This ensures Spree::Imports::Products publishes 'import.create' not 'products.create'
14
+ self.event_prefix = 'import'
15
+
10
16
  #
11
17
  # Associations
12
18
  #
@@ -56,8 +62,9 @@ module Spree
56
62
  event :complete do
57
63
  transition from: :processing, to: :completed
58
64
  end
59
- after_transition to: :completed, do: :send_import_completed_email
60
- after_transition to: :completed, do: :update_loader_in_import_view
65
+ after_transition to: :completed, do: :publish_import_completed_event
66
+ # NOTE: send_import_completed_email and update_loader_in_import_view
67
+ # are now handled by Spree::Admin::ImportSubscriber listening to 'import.completed' event
61
68
 
62
69
  event :fail do
63
70
  transition to: :failed
@@ -159,8 +166,8 @@ module Spree
159
166
  "#{Spree.t(type.demodulize.pluralize.downcase)} #{number}"
160
167
  end
161
168
 
162
- def send_import_completed_email
163
- # Spree::ImportMailer.import_done(self).deliver_later
169
+ def publish_import_completed_event
170
+ publish_event('import.completed')
164
171
  end
165
172
 
166
173
  # Returns the headers of the csv file
@@ -205,13 +212,11 @@ module Spree
205
212
  # Returns the store for the import
206
213
  # @return [Spree::Store]
207
214
  def store
208
- owner.is_a?(Spree::Store) ? owner : owner.respond_to?(:store) ? owner.store : Spree::Store.default
209
- end
210
-
211
- def update_loader_in_import_view
212
- return unless defined?(broadcast_update_to)
213
-
214
- broadcast_update_to "import_#{id}_loader", target: 'loader', partial: 'spree/admin/imports/loader', locals: { import: self }
215
+ if owner.is_a?(Spree::Store)
216
+ owner
217
+ else
218
+ owner.respond_to?(:store) ? owner.store : Spree::Store.default
219
+ end
215
220
  end
216
221
 
217
222
  # Returns the current ability for the import
@@ -243,7 +248,7 @@ module Spree
243
248
  def model_class
244
249
  klass = "Spree::#{to_s.demodulize.singularize}".safe_constantize
245
250
 
246
- raise NameError, "Missing model class for #{to_s}" unless klass
251
+ raise NameError, "Missing model class for #{self}" unless klass
247
252
 
248
253
  klass
249
254
  end
@@ -1,5 +1,8 @@
1
1
  module Spree
2
2
  class ImportRow < Spree.base_class
3
+ # Set event prefix for ImportRow
4
+ self.event_prefix = 'import_row'
5
+
3
6
  #
4
7
  # Associations
5
8
  #
@@ -24,15 +27,16 @@ module Spree
24
27
  event :fail do
25
28
  transition to: :failed
26
29
  end
27
- after_transition to: :failed, do: :add_row_to_import_view
28
- after_transition to: :failed, do: :update_footer_in_import_view
30
+ after_transition to: :failed, do: :publish_import_row_failed_event
31
+ # NOTE: add_row_to_import_view and update_footer_in_import_view
32
+ # are now handled by Spree::Admin::ImportRowSubscriber
29
33
 
30
34
  event :complete do
31
35
  transition to: :completed
32
36
  end
33
-
34
- after_transition to: :completed, do: :add_row_to_import_view
35
- after_transition to: :completed, do: :update_footer_in_import_view
37
+ after_transition to: :completed, do: :publish_import_row_completed_event
38
+ # NOTE: add_row_to_import_view and update_footer_in_import_view
39
+ # are now handled by Spree::Admin::ImportRowSubscriber
36
40
  end
37
41
 
38
42
  #
@@ -81,29 +85,12 @@ module Spree
81
85
  fail!
82
86
  end
83
87
 
84
- def add_row_to_import_view
85
- return unless defined?(broadcast_append_to)
86
-
87
- # we need to render this partial with store context to properly generate image URLs
88
- with_store_context do
89
- broadcast_append_to "import_#{import.id}_rows", target: 'rows', partial: 'spree/admin/imports/row', locals: { row: self, import: import }
90
- end
91
- end
92
-
93
- def update_footer_in_import_view
94
- return unless defined?(broadcast_replace_to)
95
-
96
- broadcast_replace_to "import_#{import_id}_footer", target: 'footer', partial: 'spree/admin/imports/footer', locals: { import: import }
88
+ def publish_import_row_completed_event
89
+ publish_event('import_row.completed')
97
90
  end
98
91
 
99
- private
100
-
101
- def with_store_context
102
- Spree::Current.store = store
103
- Rails.application.routes.default_url_options[:host] = store.url_or_custom_domain
104
- yield
105
- ensure
106
- Spree::Current.reset if defined?(Spree::Current)
92
+ def publish_import_row_failed_event
93
+ publish_event('import_row.failed')
107
94
  end
108
95
  end
109
96
  end
@@ -1,9 +1,6 @@
1
1
  module Spree
2
2
  class InventoryUnit < Spree.base_class
3
3
  extend Spree::DisplayMoney
4
- if defined?(Spree::Webhooks::HasWebhooks)
5
- include Spree::Webhooks::HasWebhooks
6
- end
7
4
 
8
5
  with_options inverse_of: :inventory_units do
9
6
  belongs_to :variant, -> { with_deleted }, class_name: 'Spree::Variant'
@@ -46,6 +46,7 @@ module Spree
46
46
  transition pending: :accepted
47
47
  end
48
48
  after_transition to: :accepted, do: :after_accept
49
+ after_transition to: :accepted, do: :publish_invitation_accepted_event
49
50
  end
50
51
 
51
52
  #
@@ -53,7 +54,7 @@ module Spree
53
54
  #
54
55
  after_initialize :set_defaults, if: :new_record?
55
56
  before_validation :set_invitee_from_email, on: :create
56
- after_create :send_invitation_email, unless: :skip_email
57
+ after_create :publish_invitation_created_event, unless: :skip_email
57
58
 
58
59
  # returns the store for the invitation
59
60
  # if the resource is a store, return the resource
@@ -80,7 +81,7 @@ module Spree
80
81
  def resend!
81
82
  return if expired? || deleted? || accepted?
82
83
 
83
- send_invitation_email
84
+ publish_event('invitation.resent')
84
85
  end
85
86
 
86
87
  private
@@ -89,15 +90,20 @@ module Spree
89
90
  def after_accept
90
91
  create_role_user
91
92
  set_accepted_at
92
- send_acceptance_notification
93
93
  end
94
94
 
95
- def send_invitation_email
96
- Spree::InvitationMailer.invitation_email(self).deliver_later
95
+ def publish_invitation_accepted_event
96
+ publish_event('invitation.accepted')
97
97
  end
98
98
 
99
+ def publish_invitation_created_event
100
+ publish_event('invitation.created')
101
+ end
102
+
103
+ # This method is kept for backwards compatibility.
104
+ # Email sending is now handled by the InvitationEmailSubscriber via the 'invitation.accept' event.
99
105
  def send_acceptance_notification
100
- Spree::InvitationMailer.invitation_accepted(self).deliver_later
106
+ # no-op - email is sent via event subscriber
101
107
  end
102
108
 
103
109
  def set_defaults
@@ -2,9 +2,8 @@ module Spree
2
2
  class LineItem < Spree.base_class
3
3
  include Spree::Metafields
4
4
  include Spree::Metadata
5
- if defined?(Spree::Webhooks::HasWebhooks)
6
- include Spree::Webhooks::HasWebhooks
7
- end
5
+
6
+ publishes_lifecycle_events
8
7
 
9
8
  attribute :quantity, :integer, default: 1
10
9
 
@@ -15,6 +14,7 @@ module Spree
15
14
  belongs_to :variant, -> { with_deleted }, class_name: 'Spree::Variant'
16
15
  end
17
16
  belongs_to :tax_category, -> { with_deleted }, class_name: 'Spree::TaxCategory'
17
+ belongs_to :price_list, class_name: 'Spree::PriceList', optional: true
18
18
 
19
19
  has_one :product, -> { with_deleted }, class_name: 'Spree::Product', through: :variant
20
20
 
@@ -72,9 +72,11 @@ module Spree
72
72
  end
73
73
 
74
74
  def update_price
75
- currency_price = variant.price_in(order.currency)
75
+ context = Spree::Pricing::Context.from_order(variant, order, quantity: quantity)
76
+ currency_price = variant.price_for(context)
76
77
 
77
78
  self.price = currency_price.price_including_vat_for(tax_zone: tax_zone) if currency_price.present?
79
+ self.price_list_id = currency_price.price_list_id if currency_price.present?
78
80
  end
79
81
 
80
82
  def copy_tax_category
@@ -95,16 +97,25 @@ module Spree
95
97
  price - (promo_total.abs / quantity)
96
98
  end
97
99
 
100
+ # Returns the amount (price * quantity) of the line item
101
+ #
102
+ # @return [BigDecimal]
98
103
  def amount
99
104
  price * quantity
100
105
  end
101
106
 
107
+ # Returns the compare at amount (compare at price * quantity) of the line item
108
+ #
109
+ # @return [BigDecimal]
102
110
  def compare_at_amount
103
111
  (variant.compare_at_amount_in(currency) || 0) * quantity
104
112
  end
105
113
 
106
114
  alias subtotal amount
107
115
 
116
+ # Returns the taxable amount (amount + taxable adjustment total) of the line item
117
+ #
118
+ # @return [BigDecimal]
108
119
  def taxable_amount
109
120
  amount + taxable_adjustment_total
110
121
  end
@@ -119,6 +130,9 @@ module Spree
119
130
  alias discounted_money display_discounted_amount
120
131
  alias discounted_amount taxable_amount
121
132
 
133
+ # Returns the final amount of the line item
134
+ #
135
+ # @return [BigDecimal]
122
136
  def final_amount
123
137
  amount + adjustment_total
124
138
  end
@@ -133,10 +147,16 @@ module Spree
133
147
  alias total final_amount
134
148
  alias money display_total
135
149
 
150
+ # Returns true if the line item has sufficient stock
151
+ #
152
+ # @return [Boolean]
136
153
  def sufficient_stock?
137
154
  can_supply? quantity
138
155
  end
139
156
 
157
+ # Returns true if the line item has insufficient stock
158
+ #
159
+ # @return [Boolean]
140
160
  def insufficient_stock?
141
161
  !sufficient_stock?
142
162
  end
@@ -200,10 +220,34 @@ module Spree
200
220
  @maximum_quantity ||= variant.backorderable? ? Spree::DatabaseTypeUtilities.maximum_value_for(:integer) : variant.total_on_hand
201
221
  end
202
222
 
223
+ # Returns true if the line item variant has digital assets
224
+ #
225
+ # @return [Boolean]
203
226
  def with_digital_assets?
204
227
  variant.with_digital_assets?
205
228
  end
206
229
 
230
+ # Recalculates and persists the price based on the current quantity and pricing context
231
+ # This is used for volume-based pricing and other price list rules
232
+ # @return [void]
233
+ def recalculate_price
234
+ context = Spree::Pricing::Context.from_order(variant, order, quantity: quantity)
235
+ currency_price = variant.price_for(context)
236
+
237
+ return unless currency_price.present?
238
+
239
+ new_price = currency_price.price_including_vat_for(tax_zone: tax_zone)
240
+
241
+ return unless new_price.present?
242
+
243
+ new_price_list_id = currency_price.price_list_id
244
+
245
+ # Only update if price or price list changed
246
+ if new_price != price || new_price_list_id != price_list_id
247
+ update_columns(price: new_price, price_list_id: new_price_list_id, updated_at: Time.current)
248
+ end
249
+ end
250
+
207
251
  private
208
252
 
209
253
  def ensure_valid_quantity
@@ -239,11 +283,20 @@ module Spree
239
283
 
240
284
  def update_adjustments
241
285
  if saved_change_to_quantity?
286
+ recalculate_price if should_update_price? && !previously_new_record?
242
287
  recalculate_adjustments
243
288
  update_tax_charge # Called to ensure pre_tax_amount is updated.
244
289
  end
245
290
  end
246
291
 
292
+ # Returns true if the price should be updated when quantity changes
293
+ # Override this method to customize when prices should be recalculated
294
+ # By default, prices are not updated after an order is completed
295
+ # @return [Boolean]
296
+ def should_update_price?
297
+ !order.completed?
298
+ end
299
+
247
300
  def recalculate_adjustments
248
301
  Spree::Adjustable::AdjustmentsUpdater.update(self)
249
302
  end
@@ -5,6 +5,8 @@ module Spree
5
5
  include Spree::NewsletterSubscriber::Emails
6
6
  include Spree::Metafields
7
7
 
8
+ publishes_lifecycle_events
9
+
8
10
  has_secure_token :verification_token
9
11
 
10
12
  #
@@ -7,15 +7,12 @@ module Spree
7
7
  include Spree::Metafields
8
8
  include Spree::Metadata
9
9
  include Spree::TranslatableResource
10
- if defined?(Spree::Webhooks::HasWebhooks)
11
- include Spree::Webhooks::HasWebhooks
12
- end
13
10
 
14
11
  TRANSLATABLE_FIELDS = %i[presentation].freeze
15
12
  translates(*TRANSLATABLE_FIELDS, column_fallback: !Spree.always_use_translations?)
16
13
 
17
14
  self::Translation.class_eval do
18
- auto_strip_attributes :presentation
15
+ normalizes :presentation, with: ->(value) { value&.to_s&.squish&.presence }
19
16
  end
20
17
 
21
18
  #
@@ -62,7 +59,7 @@ module Spree
62
59
 
63
60
  # legacy, name itself is now parameterized before saving
64
61
  def filter_param
65
- Spree::Deprecation.warn('Spree::OptionType#filter_param is deprecated and will be removed in Spree 6. Please use Spree::OptionType#name instead.')
62
+ Spree::Deprecation.warn('Spree::OptionType#filter_param is deprecated and will be removed in Spree 5.5. Please use Spree::OptionType#name instead.')
66
63
  name.parameterize
67
64
  end
68
65
 
@@ -4,15 +4,12 @@ module Spree
4
4
  include Spree::Metafields
5
5
  include Spree::Metadata
6
6
  include Spree::TranslatableResource
7
- if defined?(Spree::Webhooks::HasWebhooks)
8
- include Spree::Webhooks::HasWebhooks
9
- end
10
7
 
11
8
  TRANSLATABLE_FIELDS = %i[presentation].freeze
12
9
  translates(*TRANSLATABLE_FIELDS, column_fallback: !Spree.always_use_translations?)
13
10
 
14
11
  self::Translation.class_eval do
15
- auto_strip_attributes :presentation
12
+ normalizes :presentation, with: ->(value) { value&.to_s&.squish&.presence }
16
13
  end
17
14
 
18
15
  #
@@ -118,11 +118,12 @@ module Spree
118
118
  before_transition to: :resumed, do: proc { |order| order.state_machine_resumed = true }
119
119
 
120
120
  after_transition to: :complete, do: :finalize!
121
- after_transition to: :complete, do: :use_all_coupon_codes
122
- after_transition to: :complete, do: :redeem_gift_card
123
121
  after_transition to: :resumed, do: :after_resume
124
122
  after_transition to: :canceled, do: :after_cancel
123
+ after_transition to: :complete, do: :use_all_coupon_codes
124
+ after_transition to: :complete, do: :redeem_gift_card
125
125
  after_transition to: :complete, do: :subscribe_to_newsletter
126
+ after_transition to: :complete, do: :publish_order_completed_event
126
127
 
127
128
  after_transition from: any - :cart, to: any - [:confirm, :complete] do |order|
128
129
  order.update_totals
@@ -133,6 +134,10 @@ module Spree
133
134
  alias_method :save_state, :save
134
135
  end
135
136
 
137
+ def publish_order_completed_event
138
+ publish_event('order.completed')
139
+ end
140
+
136
141
  def subscribe_to_newsletter
137
142
  return unless accept_marketing?
138
143
 
@@ -4,10 +4,7 @@ module Spree
4
4
  extend ActiveSupport::Concern
5
5
 
6
6
  def deliver_order_confirmation_email
7
- # you can overwrite this method in your application / extension to send out the confirmation email
8
- # or use `spree_emails` gem
9
- # YourEmailVendor.deliver_order_confirmation_email(id) # `id` = ID of the Order being sent, you can also pass the entire Order object using `self`
10
- # update_column(:confirmation_delivered, true) # if you would like to mark that the email was sent
7
+ Spree::Deprecation.warn('Spree::Order#deliver_order_confirmation_email is deprecated and will be removed in Spree 5.5. Please create a Subscriber for order.completed event.')
11
8
  end
12
9
 
13
10
  # If you would like to also send confirmation email to store owner(s)
@@ -16,16 +13,11 @@ module Spree
16
13
  end
17
14
 
18
15
  def deliver_store_owner_order_notification_email
19
- # you can overwrite this method in your application / extension to send out the confirmation email
20
- # or use `spree_emails` gem
21
- # YourEmailVendor.deliver_store_owner_notification_email(id) # `id` = ID of the Order being sent, you can also pass the entire Order object using `self`
22
- # update_column(:store_owner_notification_delivered, true) # if you would like to mark that the email was sent
16
+ Spree::Deprecation.warn('Spree::Order#deliver_store_owner_order_notification_email is deprecated and will be removed in Spree 5.5. Please create a Subscriber for order.completed event.')
23
17
  end
24
18
 
25
19
  def send_cancel_email
26
- # you can overwrite this method in your application / extension to send out the confirmation email
27
- # or use `spree_emails` gem
28
- # YourEmailVendor.deliver_cancel_email(id) # `id` = ID of the Order being sent, you can also pass the entire Order object using `self`
20
+ Spree::Deprecation.warn('Spree::Order#send_cancel_email is deprecated and will be removed in Spree 5.5. Please create a Subscriber for order.canceled event.')
29
21
  end
30
22
  end
31
23
  end