spree_core 6.0.0.beta4 → 6.0.0.rc2

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 (575) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +5 -5
  3. data/app/helpers/spree/images_helper.rb +0 -57
  4. data/app/jobs/spree/base_job.rb +21 -0
  5. data/app/jobs/spree/carts/reap_expired_job.rb +2 -2
  6. data/app/jobs/spree/collections/regenerate_time_based_job.rb +6 -3
  7. data/app/jobs/spree/orders/finalize_stale_drafts_job.rb +8 -5
  8. data/app/jobs/spree/payments/cancel_unused_sessions_job.rb +61 -0
  9. data/app/jobs/spree/search_provider/index_job.rb +4 -0
  10. data/app/jobs/spree/seller_payouts/sweep_seller_job.rb +2 -0
  11. data/app/jobs/spree/uploads/purge_unattached_job.rb +49 -0
  12. data/app/mailers/spree/admin_user_mailer.rb +4 -19
  13. data/app/mailers/spree/base_mailer.rb +97 -13
  14. data/app/mailers/spree/email_template_mailer.rb +27 -0
  15. data/app/mailers/spree/export_mailer.rb +3 -4
  16. data/app/mailers/spree/import_mailer.rb +3 -4
  17. data/app/mailers/spree/invitation_mailer.rb +24 -7
  18. data/app/mailers/spree/seller_user_mailer.rb +4 -16
  19. data/app/mailers/spree/webhook_mailer.rb +2 -2
  20. data/app/models/concerns/spree/acted_by.rb +1 -1
  21. data/app/models/concerns/spree/admin_user_methods.rb +15 -1
  22. data/app/models/concerns/spree/api_typed.rb +91 -0
  23. data/app/models/concerns/spree/calculated_adjustments.rb +41 -13
  24. data/app/models/concerns/spree/calculator/currency_amounts.rb +1 -1
  25. data/app/models/concerns/spree/calculator/tiers.rb +63 -0
  26. data/app/models/concerns/spree/canonical_decimals.rb +61 -0
  27. data/app/models/concerns/spree/customer_methods.rb +23 -18
  28. data/app/models/concerns/spree/derived_number.rb +11 -13
  29. data/app/models/concerns/spree/email_template_identity.rb +52 -0
  30. data/app/models/concerns/spree/parameterizable_name.rb +2 -0
  31. data/app/models/concerns/spree/payment_session_transitions.rb +8 -2
  32. data/app/models/concerns/spree/post_sale/taxation.rb +54 -0
  33. data/app/models/concerns/spree/post_sale/taxed_line.rb +51 -0
  34. data/app/models/concerns/spree/preference_schema/json_schema.rb +139 -0
  35. data/app/models/concerns/spree/preference_schema.rb +62 -32
  36. data/app/models/concerns/spree/product_scopes.rb +16 -2
  37. data/app/models/concerns/spree/provider_listing.rb +41 -0
  38. data/app/models/concerns/spree/publishable.rb +82 -14
  39. data/app/models/concerns/spree/purchase/addresses.rb +23 -10
  40. data/app/models/concerns/spree/purchase/currency.rb +1 -1
  41. data/app/models/concerns/spree/purchase/deprecated_aliases.rb +87 -0
  42. data/app/models/concerns/spree/purchase/gift_cards.rb +10 -4
  43. data/app/models/concerns/spree/purchase/locale.rb +1 -1
  44. data/app/models/concerns/spree/purchase/payment_processing.rb +8 -4
  45. data/app/models/concerns/spree/purchase/purchase_order.rb +9 -4
  46. data/app/models/concerns/spree/purchase/store_credits.rb +36 -1
  47. data/app/models/concerns/spree/purchase/totals.rb +49 -8
  48. data/app/models/concerns/spree/ransackable_attributes.rb +46 -0
  49. data/app/models/concerns/spree/receivable.rb +1 -1
  50. data/app/models/concerns/spree/searchable.rb +0 -4
  51. data/app/models/concerns/spree/secret_preferences.rb +59 -0
  52. data/app/models/concerns/spree/signed_id_attachments.rb +54 -0
  53. data/app/models/concerns/spree/single_store_resource.rb +1 -1
  54. data/app/models/concerns/spree/stores/email_branding.rb +32 -0
  55. data/app/models/concerns/spree/stores/markets.rb +5 -2
  56. data/app/models/concerns/spree/type_labels.rb +38 -0
  57. data/app/models/concerns/spree/typed_adjustment_line.rb +5 -6
  58. data/app/models/concerns/spree/typed_associations.rb +12 -18
  59. data/app/models/concerns/spree/vat_price_calculation.rb +5 -6
  60. data/app/models/spree/address.rb +12 -2
  61. data/app/models/spree/adjusters/largest_remainder.rb +14 -1
  62. data/app/models/spree/adjusters/promotion.rb +4 -19
  63. data/app/models/spree/allowed_origin.rb +3 -1
  64. data/app/models/spree/api_key.rb +5 -6
  65. data/app/models/spree/authentication/strategies/oidc_strategy.rb +5 -5
  66. data/app/models/spree/base.rb +99 -22
  67. data/app/models/spree/calculator/flat_percent_item_total.rb +2 -4
  68. data/app/models/spree/calculator/flat_rate.rb +3 -5
  69. data/app/models/spree/calculator/flexi_rate.rb +4 -6
  70. data/app/models/spree/calculator/percent_on_line_item.rb +2 -2
  71. data/app/models/spree/calculator/price_sack.rb +5 -7
  72. data/app/models/spree/calculator/shipping/digital_delivery.rb +3 -5
  73. data/app/models/spree/calculator/shipping/flat_percent_item_total.rb +7 -7
  74. data/app/models/spree/calculator/shipping/flat_rate.rb +5 -7
  75. data/app/models/spree/calculator/shipping/flexi_rate.rb +4 -6
  76. data/app/models/spree/calculator/shipping/per_item.rb +3 -5
  77. data/app/models/spree/calculator/shipping/price_sack.rb +5 -7
  78. data/app/models/spree/calculator/tiered_flat_rate.rb +7 -24
  79. data/app/models/spree/calculator/tiered_percent.rb +7 -25
  80. data/app/models/spree/cart.rb +78 -18
  81. data/app/models/spree/catalog.rb +1 -0
  82. data/app/models/spree/catalog_price.rb +0 -7
  83. data/app/models/spree/catalog_price_tier.rb +0 -8
  84. data/app/models/spree/category.rb +3 -1
  85. data/app/models/spree/channel/gating.rb +3 -2
  86. data/app/models/spree/channel.rb +6 -4
  87. data/app/models/spree/claim.rb +22 -1
  88. data/app/models/spree/claim_line_item.rb +41 -6
  89. data/app/models/spree/collection.rb +2 -1
  90. data/app/models/spree/collection_rule.rb +5 -21
  91. data/app/models/spree/commission_line.rb +1 -1
  92. data/app/models/spree/commission_rate.rb +7 -4
  93. data/app/models/spree/commission_rate_value.rb +1 -1
  94. data/app/models/spree/commission_rule.rb +4 -11
  95. data/app/models/spree/commission_rules/category_rule.rb +2 -5
  96. data/app/models/spree/commission_rules/item_total_rule.rb +2 -2
  97. data/app/models/spree/commission_rules/product_rule.rb +1 -1
  98. data/app/models/spree/commission_rules/seller_rule.rb +2 -5
  99. data/app/models/spree/company.rb +1 -0
  100. data/app/models/spree/company_invitation.rb +1 -0
  101. data/app/models/spree/consent_record.rb +0 -1
  102. data/app/models/spree/credit_card.rb +1 -1
  103. data/app/models/spree/current.rb +23 -0
  104. data/app/models/spree/custom_field.rb +2 -2
  105. data/app/models/spree/custom_field_definition.rb +20 -35
  106. data/app/models/spree/custom_fields/boolean.rb +1 -1
  107. data/app/models/spree/custom_fields/json.rb +1 -1
  108. data/app/models/spree/customer_group.rb +1 -0
  109. data/app/models/spree/data_feed/google.rb +0 -2
  110. data/app/models/spree/data_request.rb +2 -1
  111. data/app/models/spree/delivery_method.rb +36 -14
  112. data/app/models/spree/delivery_method_rule.rb +4 -11
  113. data/app/models/spree/delivery_method_rules/channel_rule.rb +2 -5
  114. data/app/models/spree/delivery_method_rules/item_total_rule.rb +2 -2
  115. data/app/models/spree/delivery_profile.rb +1 -0
  116. data/app/models/spree/delivery_rate.rb +7 -8
  117. data/app/models/spree/delivery_rate_provider/base.rb +1 -0
  118. data/app/models/spree/delivery_zone.rb +1 -2
  119. data/app/models/spree/delivery_zone_member.rb +4 -4
  120. data/app/models/spree/digital_asset.rb +7 -0
  121. data/app/models/spree/digital_asset_provider/base.rb +2 -0
  122. data/app/models/spree/digital_link.rb +2 -5
  123. data/app/models/spree/discount.rb +1 -1
  124. data/app/models/spree/email_template.rb +25 -0
  125. data/app/models/spree/email_template_draft.rb +13 -0
  126. data/app/models/spree/email_template_revision.rb +17 -0
  127. data/app/models/spree/email_templates/entry.rb +118 -0
  128. data/app/models/spree/email_templates/sample_record.rb +16 -0
  129. data/app/models/spree/emails/branding.rb +80 -0
  130. data/app/models/spree/exchange.rb +16 -1
  131. data/app/models/spree/exchange_line_item.rb +121 -5
  132. data/app/models/spree/export.rb +0 -1
  133. data/app/models/spree/exports/products.rb +10 -1
  134. data/app/models/spree/exports/report.rb +3 -3
  135. data/app/models/spree/fee.rb +4 -2
  136. data/app/models/spree/file_upload.rb +155 -0
  137. data/app/models/spree/fulfillment.rb +66 -30
  138. data/app/models/spree/fulfillment_changer.rb +2 -2
  139. data/app/models/spree/fulfillment_group.rb +6 -1
  140. data/app/models/spree/fulfillment_item.rb +0 -4
  141. data/app/models/spree/fulfillment_provider/base.rb +1 -0
  142. data/app/models/spree/gateway/bogus.rb +6 -0
  143. data/app/models/spree/gateway/custom_payment_source_method.rb +5 -0
  144. data/app/models/spree/gateway.rb +6 -2
  145. data/app/models/spree/gift_card.rb +18 -8
  146. data/app/models/spree/gift_card_batch.rb +3 -5
  147. data/app/models/spree/import.rb +5 -7
  148. data/app/models/spree/import_mapping.rb +1 -2
  149. data/app/models/spree/import_row.rb +2 -2
  150. data/app/models/spree/import_schemas/products.rb +1 -0
  151. data/app/models/spree/integration.rb +9 -19
  152. data/app/models/spree/invitation.rb +5 -5
  153. data/app/models/spree/line_item.rb +15 -4
  154. data/app/models/spree/locale.rb +11 -8
  155. data/app/models/spree/market.rb +8 -2
  156. data/app/models/spree/media.rb +1 -9
  157. data/app/models/spree/newsletter_subscriber.rb +4 -0
  158. data/app/models/spree/option_type.rb +2 -1
  159. data/app/models/spree/option_value.rb +2 -1
  160. data/app/models/spree/order.rb +60 -142
  161. data/app/models/spree/order_group.rb +18 -4
  162. data/app/models/spree/order_inventory.rb +10 -5
  163. data/app/models/spree/order_promotion.rb +1 -1
  164. data/app/models/spree/order_routing/has_strategy_preference.rb +1 -1
  165. data/app/models/spree/order_routing/strategy/base.rb +4 -2
  166. data/app/models/spree/order_routing/strategy/rules.rb +1 -1
  167. data/app/models/spree/order_routing_rule.rb +6 -22
  168. data/app/models/spree/package_type.rb +2 -2
  169. data/app/models/spree/payment/gateway_options.rb +15 -53
  170. data/app/models/spree/payment/processing.rb +2 -4
  171. data/app/models/spree/payment.rb +21 -13
  172. data/app/models/spree/payment_method/check.rb +2 -0
  173. data/app/models/spree/payment_method/session_instrumentation.rb +1 -0
  174. data/app/models/spree/payment_method/store_credit.rb +15 -13
  175. data/app/models/spree/payment_method.rb +78 -6
  176. data/app/models/spree/payment_session.rb +5 -1
  177. data/app/models/spree/payment_setup_session.rb +1 -0
  178. data/app/models/spree/payout_provider/base.rb +3 -1
  179. data/app/models/spree/pickup_point_provider/base.rb +2 -0
  180. data/app/models/spree/policy.rb +1 -0
  181. data/app/models/spree/price.rb +17 -14
  182. data/app/models/spree/price_adjustment_tier.rb +4 -0
  183. data/app/models/spree/price_list.rb +14 -9
  184. data/app/models/spree/price_rules/channel_rule.rb +3 -6
  185. data/app/models/spree/price_rules/customer_group_rule.rb +3 -6
  186. data/app/models/spree/price_rules/market_rule.rb +2 -5
  187. data/app/models/spree/price_rules/user_rule.rb +1 -3
  188. data/app/models/spree/product/channels.rb +9 -2
  189. data/app/models/spree/product.rb +54 -30
  190. data/app/models/spree/product_submission.rb +0 -12
  191. data/app/models/spree/product_type.rb +1 -1
  192. data/app/models/spree/promotion/actions/create_line_items.rb +28 -18
  193. data/app/models/spree/promotion/rules/category.rb +1 -5
  194. data/app/models/spree/promotion/rules/channel.rb +2 -9
  195. data/app/models/spree/promotion/rules/country.rb +3 -8
  196. data/app/models/spree/promotion/rules/currency.rb +1 -5
  197. data/app/models/spree/promotion/rules/customer_group.rb +2 -9
  198. data/app/models/spree/promotion/rules/first_order.rb +0 -4
  199. data/app/models/spree/promotion/rules/item_total.rb +6 -10
  200. data/app/models/spree/promotion/rules/market.rb +2 -9
  201. data/app/models/spree/promotion/rules/one_use_per_user.rb +0 -4
  202. data/app/models/spree/promotion/rules/option_value.rb +2 -6
  203. data/app/models/spree/promotion/rules/product.rb +1 -5
  204. data/app/models/spree/promotion/rules/user.rb +0 -4
  205. data/app/models/spree/promotion/rules/user_logged_in.rb +0 -4
  206. data/app/models/spree/promotion.rb +36 -6
  207. data/app/models/spree/promotion_action.rb +7 -14
  208. data/app/models/spree/promotion_action_line_item.rb +1 -1
  209. data/app/models/spree/promotion_handler/coupon.rb +59 -27
  210. data/app/models/spree/promotion_rule.rb +7 -14
  211. data/app/models/spree/purchase_order.rb +3 -0
  212. data/app/models/spree/quantity_rule.rb +1 -1
  213. data/app/models/spree/refund.rb +41 -18
  214. data/app/models/spree/return.rb +31 -2
  215. data/app/models/spree/return_line_item.rb +65 -14
  216. data/app/models/spree/role.rb +7 -7
  217. data/app/models/spree/saved_report.rb +2 -1
  218. data/app/models/spree/search_provider/base.rb +4 -0
  219. data/app/models/spree/seller.rb +10 -2
  220. data/app/models/spree/seller_payout.rb +8 -0
  221. data/app/models/spree/seller_requirement.rb +5 -12
  222. data/app/models/spree/seller_requirement_custom_field.rb +1 -2
  223. data/app/models/spree/seller_requirement_submission.rb +3 -2
  224. data/app/models/spree/seller_requirements/accept_terms.rb +2 -3
  225. data/app/models/spree/seller_transfer.rb +6 -0
  226. data/app/models/spree/shipping_label.rb +2 -0
  227. data/app/models/spree/stock/availability_validator.rb +2 -2
  228. data/app/models/spree/stock/estimator.rb +1 -1
  229. data/app/models/spree/stock/inventory_unit_builder.rb +1 -1
  230. data/app/models/spree/stock/packer.rb +2 -2
  231. data/app/models/spree/stock/splitter/weight.rb +1 -1
  232. data/app/models/spree/stock_level/custom_events.rb +6 -0
  233. data/app/models/spree/stock_level.rb +3 -1
  234. data/app/models/spree/stock_location.rb +1 -0
  235. data/app/models/spree/stock_movement.rb +1 -1
  236. data/app/models/spree/stock_receipt_item.rb +1 -1
  237. data/app/models/spree/stock_reservation.rb +2 -2
  238. data/app/models/spree/stock_transfer.rb +6 -2
  239. data/app/models/spree/store.rb +70 -19
  240. data/app/models/spree/store_credit.rb +13 -13
  241. data/app/models/spree/store_credit_event.rb +5 -5
  242. data/app/models/spree/supplier.rb +1 -0
  243. data/app/models/spree/tax_category.rb +1 -0
  244. data/app/models/spree/tax_exemption_certificate.rb +3 -0
  245. data/app/models/spree/tax_identifier.rb +3 -1
  246. data/app/models/spree/tax_line.rb +53 -3
  247. data/app/models/spree/tax_provider/base.rb +71 -14
  248. data/app/models/spree/tax_provider/internal.rb +70 -41
  249. data/app/models/spree/tax_provider/recorded_share.rb +114 -0
  250. data/app/models/spree/tax_rate.rb +30 -8
  251. data/app/models/spree/taxon.rb +1 -1
  252. data/app/models/spree/taxonomy.rb +0 -1
  253. data/app/models/spree/update_status.rb +25 -0
  254. data/app/models/spree/variant.rb +10 -12
  255. data/app/models/spree/variant_media.rb +1 -1
  256. data/app/models/spree/webhook_delivery.rb +1 -0
  257. data/app/models/spree/webhook_endpoint.rb +46 -13
  258. data/app/models/spree/wishlist.rb +1 -6
  259. data/app/models/spree/wishlist_item/custom_events.rb +6 -0
  260. data/app/presenters/spree/csv/customer_presenter.rb +1 -1
  261. data/app/presenters/spree/csv/newsletter_subscriber_presenter.rb +1 -1
  262. data/app/presenters/spree/csv/order_line_item_presenter.rb +10 -6
  263. data/app/presenters/spree/csv/price_list_price_presenter.rb +3 -8
  264. data/app/presenters/spree/csv/product_variant_presenter.rb +59 -20
  265. data/app/presenters/spree/csv/purchase_order_item_presenter.rb +1 -3
  266. data/app/serializers/spree/emails/base_serializer.rb +14 -0
  267. data/app/serializers/spree/emails/export_serializer.rb +13 -0
  268. data/app/serializers/spree/emails/import_serializer.rb +19 -0
  269. data/app/serializers/spree/emails/invitation_serializer.rb +28 -0
  270. data/app/serializers/spree/emails/store_serializer.rb +31 -0
  271. data/app/serializers/spree/emails/user_serializer.rb +12 -0
  272. data/app/serializers/spree/emails/webhook_endpoint_serializer.rb +16 -0
  273. data/app/services/spree/carts/create.rb +8 -2
  274. data/app/services/spree/carts/destroy.rb +2 -2
  275. data/app/services/spree/carts/remove_out_of_stock_items.rb +2 -2
  276. data/app/services/spree/carts/split_by_seller.rb +1 -1
  277. data/app/services/spree/carts/update.rb +22 -8
  278. data/app/services/spree/catalogs/set_product_terms.rb +1 -1
  279. data/app/services/spree/categories/add_products.rb +13 -31
  280. data/app/services/spree/categories/remove_products.rb +12 -51
  281. data/app/services/spree/checkout/advance.rb +1 -4
  282. data/app/services/spree/collections/add_products.rb +10 -23
  283. data/app/services/spree/collections/remove_products.rb +7 -43
  284. data/app/services/spree/coupon_codes/bulk_generate.rb +8 -14
  285. data/app/services/spree/data_requests/create.rb +1 -1
  286. data/app/services/spree/deliveries/destroy.rb +2 -2
  287. data/app/services/spree/email_templates/check.rb +58 -0
  288. data/app/services/spree/email_templates/no_sample_record.rb +7 -0
  289. data/app/services/spree/email_templates/preview.rb +99 -0
  290. data/app/services/spree/emails/file_system.rb +23 -0
  291. data/app/services/spree/emails/filters.rb +84 -0
  292. data/app/services/spree/emails/liquid_context.rb +48 -0
  293. data/app/services/spree/emails/rendered_email.rb +14 -0
  294. data/app/services/spree/emails/renderer.rb +125 -0
  295. data/app/services/spree/emails/template.rb +51 -0
  296. data/app/services/spree/emails/template_data.rb +23 -0
  297. data/app/services/spree/emails/template_resolver.rb +109 -0
  298. data/app/services/spree/emails/text_converter.rb +72 -0
  299. data/app/services/spree/emails/translation_inliner.rb +134 -0
  300. data/app/services/spree/imports/row_processors/price_list_price.rb +10 -10
  301. data/app/services/spree/imports/row_processors/product_variant.rb +77 -34
  302. data/app/services/spree/imports/row_processors/purchase_order.rb +13 -13
  303. data/app/services/spree/locales.rb +43 -10
  304. data/app/services/spree/newsletter/subscribe.rb +8 -12
  305. data/app/services/spree/orders/build_fulfillments.rb +3 -2
  306. data/app/services/spree/orders/create.rb +5 -4
  307. data/app/services/spree/orders/discounts/create.rb +14 -11
  308. data/app/services/spree/orders/discounts/destroy.rb +1 -0
  309. data/app/services/spree/orders/discounts/update.rb +1 -0
  310. data/app/services/spree/orders/fees/create.rb +1 -0
  311. data/app/services/spree/orders/fees/destroy.rb +1 -0
  312. data/app/services/spree/orders/fees/update.rb +1 -0
  313. data/app/services/spree/orders/remove_line_item.rb +13 -3
  314. data/app/services/spree/orders/update.rb +10 -6
  315. data/app/services/spree/orders/update_item.rb +6 -3
  316. data/app/services/spree/orders/update_statuses.rb +3 -1
  317. data/app/services/spree/prices/bulk_upsert.rb +32 -25
  318. data/app/services/spree/product_memberships.rb +61 -0
  319. data/app/services/spree/returns/eligibility_validator.rb +1 -1
  320. data/app/services/spree/search_provider/custom_field_schema.rb +2 -2
  321. data/app/services/spree/seeds/commission_rates.rb +1 -1
  322. data/app/services/spree/seeds/digital_delivery.rb +1 -1
  323. data/app/services/spree/seeds/payment_methods.rb +2 -2
  324. data/app/services/spree/seeds/saved_reports.rb +3 -3
  325. data/app/services/spree/seeds/stores.rb +1 -1
  326. data/app/services/spree/sellers/requirements.rb +1 -2
  327. data/app/services/spree/shipping_labels/confirm_refund.rb +1 -1
  328. data/app/services/spree/shipping_labels/store_file.rb +3 -3
  329. data/app/services/spree/stock_levels/correct.rb +3 -3
  330. data/app/services/spree/stock_reservations/reserve.rb +2 -3
  331. data/app/services/spree/store_credits/apply.rb +15 -13
  332. data/app/services/spree/stores/provision_defaults.rb +23 -25
  333. data/app/services/spree/update_check.rb +160 -0
  334. data/app/services/spree/uploads/assign_stores.rb +92 -0
  335. data/app/subscribers/spree/seller_onboarding_subscriber.rb +1 -1
  336. data/app/validators/spree/attachment_content_type_validator.rb +19 -2
  337. data/app/validators/spree/locale_codes_validator.rb +22 -0
  338. data/app/views/layouts/spree/base_mailer.html.erb +2 -43
  339. data/app/views/layouts/spree/base_mailer.liquid +79 -0
  340. data/app/views/spree/admin_user_mailer/password_reset_email.liquid +14 -0
  341. data/app/views/spree/export_mailer/export_done.liquid +12 -0
  342. data/app/views/spree/import_mailer/import_done.liquid +15 -0
  343. data/app/views/spree/invitation_mailer/invitation_accepted.liquid +9 -0
  344. data/app/views/spree/invitation_mailer/invitation_email.liquid +13 -0
  345. data/app/views/spree/seller_user_mailer/password_reset_email.liquid +14 -0
  346. data/app/views/spree/shared/_mailer_button.html.erb +4 -5
  347. data/app/views/spree/shared/_mailer_hero.html.erb +6 -5
  348. data/app/views/spree/shared/_summary_row.liquid +8 -0
  349. data/app/views/spree/webhook_mailer/endpoint_disabled.liquid +21 -0
  350. data/app/workflows/spree/carts/add_item.rb +4 -4
  351. data/app/workflows/spree/carts/complete.rb +13 -3
  352. data/app/workflows/spree/carts/merge.rb +1 -1
  353. data/app/workflows/spree/carts/recalculate_totals.rb +5 -0
  354. data/app/workflows/spree/carts/upsert_items.rb +18 -9
  355. data/app/workflows/spree/claims/approve.rb +5 -22
  356. data/app/workflows/spree/claims/cancel.rb +5 -21
  357. data/app/workflows/spree/claims/create.rb +15 -0
  358. data/app/workflows/spree/claims/deny.rb +7 -0
  359. data/app/workflows/spree/claims/resolve.rb +34 -31
  360. data/app/workflows/spree/customers/anonymize.rb +3 -4
  361. data/app/workflows/spree/customers/create.rb +1 -1
  362. data/app/workflows/spree/data_requests/fulfill.rb +2 -2
  363. data/app/workflows/spree/deliveries/update_tracking.rb +1 -1
  364. data/app/workflows/spree/email_templates/discard_draft.rb +20 -0
  365. data/app/workflows/spree/email_templates/publish.rb +70 -0
  366. data/app/workflows/spree/email_templates/restore_revision.rb +33 -0
  367. data/app/workflows/spree/email_templates/revert.rb +29 -0
  368. data/app/workflows/spree/email_templates/save_draft.rb +85 -0
  369. data/app/workflows/spree/exchanges/approve.rb +5 -22
  370. data/app/workflows/spree/exchanges/cancel.rb +5 -21
  371. data/app/workflows/spree/exchanges/create.rb +6 -0
  372. data/app/workflows/spree/exchanges/fulfill.rb +75 -51
  373. data/app/workflows/spree/exchanges/receive.rb +6 -65
  374. data/app/workflows/spree/fulfillments/cancel.rb +1 -1
  375. data/app/workflows/spree/fulfillments/create.rb +53 -49
  376. data/app/workflows/spree/fulfillments/fulfill.rb +23 -27
  377. data/app/workflows/spree/fulfillments/mark_delivered.rb +1 -1
  378. data/app/workflows/spree/fulfillments/replacements.rb +59 -0
  379. data/app/workflows/spree/gift_cards/apply.rb +2 -2
  380. data/app/workflows/spree/orders/add_item.rb +5 -1
  381. data/app/workflows/spree/orders/cancel.rb +3 -3
  382. data/app/workflows/spree/orders/complete.rb +16 -8
  383. data/app/workflows/spree/payments/capture.rb +20 -5
  384. data/app/workflows/spree/payments/process.rb +5 -3
  385. data/app/workflows/spree/post_sale/approval.rb +35 -0
  386. data/app/workflows/spree/post_sale/cancellation.rb +44 -0
  387. data/app/workflows/spree/post_sale/receipt.rb +92 -0
  388. data/app/workflows/spree/price_lists/update.rb +4 -4
  389. data/app/workflows/spree/products/nested_attributes.rb +1 -1
  390. data/app/workflows/spree/purchase_orders/cancel.rb +1 -1
  391. data/app/workflows/spree/purchase_orders/close.rb +3 -31
  392. data/app/workflows/spree/purchase_orders/create.rb +3 -1
  393. data/app/workflows/spree/purchase_orders/mark_draft.rb +2 -2
  394. data/app/workflows/spree/purchase_orders/mark_ordered.rb +2 -2
  395. data/app/workflows/spree/purchase_orders/receive.rb +1 -1
  396. data/app/workflows/spree/purchase_orders/update.rb +5 -34
  397. data/app/workflows/spree/receivables/closing_short.rb +46 -0
  398. data/app/workflows/spree/receivables/draft_editing.rb +48 -0
  399. data/app/workflows/spree/refunds/create.rb +5 -2
  400. data/app/workflows/spree/refunds/order_payments.rb +14 -2
  401. data/app/workflows/spree/refunds/tax_credit.rb +67 -0
  402. data/app/workflows/spree/returns/approve.rb +5 -22
  403. data/app/workflows/spree/returns/cancel.rb +5 -22
  404. data/app/workflows/spree/returns/create.rb +8 -0
  405. data/app/workflows/spree/returns/receive.rb +5 -76
  406. data/app/workflows/spree/returns/refund.rb +31 -8
  407. data/app/workflows/spree/seller_requirement_submissions/create.rb +1 -2
  408. data/app/workflows/spree/seller_transfers/reverse.rb +28 -1
  409. data/app/workflows/spree/shipping_labels/purchase.rb +8 -8
  410. data/app/workflows/spree/shipping_labels/record.rb +7 -7
  411. data/app/workflows/spree/shipping_labels/refund.rb +2 -2
  412. data/app/workflows/spree/stock_receipts/recording.rb +4 -4
  413. data/app/workflows/spree/stock_transfers/cancel.rb +2 -2
  414. data/app/workflows/spree/stock_transfers/close.rb +3 -30
  415. data/app/workflows/spree/stock_transfers/create.rb +3 -1
  416. data/app/workflows/spree/stock_transfers/mark_draft.rb +1 -1
  417. data/app/workflows/spree/stock_transfers/mark_in_transit.rb +3 -3
  418. data/app/workflows/spree/stock_transfers/mark_ready.rb +2 -2
  419. data/app/workflows/spree/stock_transfers/receive.rb +1 -1
  420. data/app/workflows/spree/stock_transfers/update.rb +5 -33
  421. data/config/currency_exponents.json +180 -0
  422. data/config/i18n-tasks.yml +32 -18
  423. data/config/initializers/alba.rb +7 -0
  424. data/config/language_names.csv +264 -0
  425. data/config/locales/ar.yml +811 -0
  426. data/config/locales/be.yml +855 -0
  427. data/config/locales/bg.yml +723 -0
  428. data/config/locales/ca.yml +689 -0
  429. data/config/locales/content_locales.rb +9 -0
  430. data/config/locales/cs.yml +855 -0
  431. data/config/locales/da.yml +771 -0
  432. data/config/locales/de-CH.yml +248 -0
  433. data/config/locales/de.yml +835 -0
  434. data/config/locales/el.yml +769 -0
  435. data/config/locales/en.yml +47 -505
  436. data/config/locales/es-CL.yml +301 -0
  437. data/config/locales/es-EC.yml +186 -0
  438. data/config/locales/es-MX.yml +334 -0
  439. data/config/locales/es.yml +831 -0
  440. data/config/locales/et.yml +644 -0
  441. data/config/locales/fa.yml +709 -0
  442. data/config/locales/fi.yml +771 -0
  443. data/config/locales/fr.yml +830 -0
  444. data/config/locales/hu.yml +751 -0
  445. data/config/locales/id.yml +756 -0
  446. data/config/locales/it.yml +833 -0
  447. data/config/locales/ja.yml +726 -0
  448. data/config/locales/km.yml +759 -0
  449. data/config/locales/ko.yml +575 -0
  450. data/config/locales/ku.yml +753 -0
  451. data/config/locales/lt.yml +821 -0
  452. data/config/locales/lv.yml +685 -0
  453. data/config/locales/nb.yml +824 -0
  454. data/config/locales/ne.yml +823 -0
  455. data/config/locales/nl.yml +826 -0
  456. data/config/locales/pl.yml +835 -0
  457. data/config/locales/pt-BR.yml +547 -0
  458. data/config/locales/pt.yml +636 -0
  459. data/config/locales/ro.yml +750 -0
  460. data/config/locales/ru.yml +834 -0
  461. data/config/locales/sk.yml +680 -0
  462. data/config/locales/sl-SI.yml +600 -0
  463. data/config/locales/sv.yml +827 -0
  464. data/config/locales/th.yml +745 -0
  465. data/config/locales/tr.yml +777 -0
  466. data/config/locales/uk.yml +836 -0
  467. data/config/locales/vi.yml +768 -0
  468. data/config/locales/zh-CN.yml +786 -0
  469. data/config/locales/zh-TW.yml +834 -0
  470. data/db/migrate/20260925000001_add_replacement_to_spree_fulfillment_items.rb +5 -0
  471. data/db/migrate/20261001000001_add_post_sale_tax.rb +30 -0
  472. data/db/migrate/20261001000001_convert_preferences_to_json.rb +166 -0
  473. data/db/migrate/20261001000001_create_spree_email_templates.rb +52 -0
  474. data/db/migrate/20261009000001_change_money_columns_to_decimal_19_4.rb +116 -0
  475. data/db/migrate/20261010000001_add_store_id_to_active_storage_blobs.rb +12 -0
  476. data/lib/generators/spree/dummy/dummy_generator.rb +0 -9
  477. data/lib/generators/spree/dummy/templates/rails/test.rb +26 -0
  478. data/lib/spree/checkout/default_requirements.rb +37 -13
  479. data/lib/spree/core/configuration.rb +19 -1
  480. data/lib/spree/core/controller_helpers/store.rb +1 -1
  481. data/lib/spree/core/dependencies.rb +5 -4
  482. data/lib/spree/core/emails/escaped_output.rb +60 -0
  483. data/lib/spree/core/engine.rb +72 -165
  484. data/lib/spree/core/permissions/default_catalog.rb +5 -0
  485. data/lib/spree/core/preferences/invalid_preferences.rb +18 -0
  486. data/lib/spree/core/preferences/json_conversion.rb +186 -0
  487. data/lib/spree/core/preferences/masking.rb +55 -2
  488. data/lib/spree/core/preferences/preferable.rb +343 -103
  489. data/lib/spree/core/preferences/preferable_class_methods.rb +188 -136
  490. data/lib/spree/core/previews/import_preview.rb +10 -0
  491. data/lib/spree/core/ransack_configuration.rb +13 -2
  492. data/lib/spree/core/tax/provider_error.rb +37 -0
  493. data/lib/spree/core/version.rb +1 -1
  494. data/lib/spree/core.rb +91 -373
  495. data/lib/spree/emails/editable_templates.rb +82 -0
  496. data/lib/spree/events/catalog.rb +189 -0
  497. data/lib/spree/events.rb +21 -1
  498. data/lib/spree/hooks.rb +0 -17
  499. data/lib/spree/i18n.rb +32 -15
  500. data/lib/spree/iso_data.rb +7 -0
  501. data/lib/spree/localized_number.rb +6 -0
  502. data/lib/spree/migrations.rb +4 -1
  503. data/lib/spree/money.rb +196 -15
  504. data/lib/spree/reporting/adapters/base.rb +7 -6
  505. data/lib/spree/reporting/adapters/live.rb +6 -6
  506. data/lib/spree/reporting/default_vocabulary.rb +22 -10
  507. data/lib/spree/reporting/hydration.rb +1 -1
  508. data/lib/spree/reporting/query.rb +8 -0
  509. data/lib/spree/reporting/registry.rb +13 -0
  510. data/lib/spree/reporting/schema.rb +10 -10
  511. data/lib/spree/testing_support/editable_email_templates.rb +61 -0
  512. data/lib/spree/testing_support/emails.rb +22 -0
  513. data/lib/spree/testing_support/factories/cart_factory.rb +1 -1
  514. data/lib/spree/testing_support/factories/email_template_factory.rb +28 -0
  515. data/lib/spree/testing_support/factories/order_factory.rb +2 -2
  516. data/lib/spree/testing_support/factories/product_factory.rb +9 -0
  517. data/lib/spree/testing_support/factories/refund_factory.rb +1 -1
  518. data/lib/spree/testing_support/factories/stock_factory.rb +1 -1
  519. data/lib/spree/testing_support/factories/variant_factory.rb +1 -1
  520. data/lib/spree/testing_support/i18n.rb +3 -94
  521. data/lib/spree/testing_support/lifecycle_events.rb +1 -1
  522. data/lib/spree/testing_support/order_walkthrough.rb +1 -1
  523. data/lib/spree/testing_support/preferences.rb +0 -5
  524. data/lib/spree/testing_support/store.rb +9 -1
  525. data/lib/spree/upgrades/5_6_to_6_0/manifest.yml +67 -1
  526. data/lib/spree/uploads.rb +101 -0
  527. data/lib/tasks/categories_and_collections.rake +3 -3
  528. data/lib/tasks/core.rake +6 -4
  529. data/lib/tasks/markets.rake +2 -1
  530. data/lib/tasks/media.rake +3 -3
  531. data/lib/tasks/migrate_users_to_customers.rake +23 -18
  532. data/lib/tasks/money.rake +46 -0
  533. data/lib/tasks/package_types.rake +1 -1
  534. data/lib/tasks/preferences_json.rake +101 -0
  535. data/lib/tasks/seller_sample_data.rake +2 -2
  536. data/lib/tasks/store_settings.rake +13 -6
  537. data/lib/tasks/typed_adjustments_migration.rake +1 -1
  538. data/lib/tasks/uploads.rake +19 -0
  539. data/spec/fixtures/mailer_views/custom_views_probe_mailer/welcome_email.html.erb +4 -0
  540. metadata +219 -46
  541. data/app/jobs/spree/products/touch_categories_job.rb +0 -15
  542. data/app/jobs/spree/products/touch_taxons_job.rb +0 -13
  543. data/app/models/acts_as_taggable_on/tag_decorator.rb +0 -10
  544. data/app/models/concerns/spree/has_image_alt_text.rb +0 -18
  545. data/app/models/spree/base_analytics_event_handler.rb +0 -54
  546. data/app/models/spree/invitations/store.rb +0 -6
  547. data/app/models/spree/preference.rb +0 -14
  548. data/app/models/spree/promotion_handler/page.rb +0 -25
  549. data/app/models/spree/promotion_handler/promotion_duplicator.rb +0 -60
  550. data/app/models/spree/stock/differentiator.rb +0 -45
  551. data/app/presenters/spree/filters/options_presenter.rb +0 -47
  552. data/app/services/spree/carts/empty.rb +0 -42
  553. data/app/services/spree/country_to_timezone.rb +0 -273
  554. data/app/services/spree/payments/create.rb +0 -92
  555. data/app/validators/spree/url_validator.rb +0 -23
  556. data/app/views/active_storage/blobs/_blob.html.erb +0 -14
  557. data/app/views/spree/admin_user_mailer/password_reset_email.html.erb +0 -8
  558. data/app/views/spree/export_mailer/export_done.html.erb +0 -7
  559. data/app/views/spree/import_mailer/import_done.html.erb +0 -12
  560. data/app/views/spree/invitation_mailer/invitation_accepted.html.erb +0 -4
  561. data/app/views/spree/invitation_mailer/invitation_email.html.erb +0 -12
  562. data/app/views/spree/seller_user_mailer/password_reset_email.html.erb +0 -8
  563. data/app/views/spree/shared/_base_mailer_footer.html.erb +0 -19
  564. data/app/views/spree/shared/_base_mailer_header.html.erb +0 -8
  565. data/app/views/spree/shared/_base_mailer_stylesheets.html.erb +0 -399
  566. data/app/views/spree/shared/_mailer_logo.html.erb +0 -41
  567. data/app/views/spree/shared/_purchased_items_styles.html.erb +0 -119
  568. data/app/views/spree/webhook_mailer/endpoint_disabled.html.erb +0 -51
  569. data/app/views/spree/webhook_mailer/endpoint_disabled.text.erb +0 -10
  570. data/app/workflows/spree/fulfillments/purchase_label.rb +0 -40
  571. data/app/workflows/spree/returns/purchase_label.rb +0 -32
  572. data/lib/spree/analytics.rb +0 -11
  573. data/lib/spree/core/preferences/scoped_store.rb +0 -34
  574. data/lib/spree/core/preferences/store.rb +0 -105
  575. data/lib/spree/translation_migrations.rb +0 -52
@@ -38,8 +38,17 @@ FactoryBot.define do
38
38
  price { 19.99 }
39
39
  compare_at_price { nil }
40
40
  currency { nil }
41
+ published_at { nil }
42
+ unpublished_at { nil }
41
43
  end
42
44
 
45
+ # The deprecated +available_on=+/+discontinue_on=+ setters warn, so the
46
+ # dates go straight onto the legacy columns, which code still reads and
47
+ # which seed the default-channel publication created below.
48
+ after(:build) do |product, evaluator|
49
+ product[:available_on] = evaluator.published_at if evaluator.published_at
50
+ product[:discontinue_on] = evaluator.unpublished_at if evaluator.unpublished_at
51
+ end
43
52
  before(:create) do |_product|
44
53
  create(:stock_location) unless Spree::StockLocation.any?
45
54
  end
@@ -4,7 +4,7 @@ FactoryBot.define do
4
4
  factory :refund, class: Spree::Refund do
5
5
  amount { 100.00 }
6
6
  transaction_id { generate(:refund_transaction_id) }
7
- association(:payment, state: 'completed')
7
+ association(:payment, status: 'completed')
8
8
  association(:reason, factory: :refund_reason)
9
9
  end
10
10
 
@@ -20,7 +20,7 @@ FactoryBot.define do
20
20
 
21
21
  after(:build) do |package, evaluator|
22
22
  evaluator.variants_contents.each do |variant, count|
23
- package.add_multiple build_list(:inventory_unit, count, variant: variant)
23
+ package.add_multiple build_list(:fulfillment_item, count, variant: variant)
24
24
  end
25
25
  end
26
26
 
@@ -45,7 +45,7 @@ FactoryBot.define do
45
45
  product { |p| p.association(:product) }
46
46
 
47
47
  factory :with_image_variant do
48
- images { create_list(:image, 1) }
48
+ images { create_list(:media, 1) }
49
49
  end
50
50
 
51
51
  trait :with_no_price do
@@ -1,94 +1,3 @@
1
- # This file exists solely to test whether or not there are missing translations
2
- # within the code that Spree's test suite covers.
3
- #
4
- # If there is a translation referenced which has no corresponding key within the
5
- # .yml file, then there will be a message output at the end of the suite showing
6
- # that.
7
- #
8
- # If there is a translation within the locale file which *isn't* used in the
9
- # test, this will also be shown at the end of the suite run.
10
- module Spree
11
- class << self
12
- attr_accessor :used_translations, :missing_translation_messages,
13
- :unused_translations, :unused_translation_messages
14
- alias normal_t t
15
- end
16
-
17
- def self.t(*args)
18
- original_args = args.dup
19
- options = args.extract_options!
20
- self.used_translations ||= []
21
- [*args.first].each do |translation_key|
22
- key = ([*options[:scope]] << translation_key).join('.')
23
- self.used_translations << key
24
- end
25
- normal_t(*original_args)
26
- end
27
-
28
- def self.check_missing_translations
29
- self.missing_translation_messages = []
30
- self.used_translations ||= []
31
- used_translations.map { |a| a.split('.') }.each do |translation_keys|
32
- root = translations
33
- processed_keys = []
34
- translation_keys.each do |key|
35
- root = root.fetch(key.to_sym)
36
- processed_keys << key.to_sym
37
- rescue KeyError
38
- error = "#{(processed_keys << key).join('.')} (#{I18n.locale})"
39
- unless Spree.missing_translation_messages.include?(error)
40
- Spree.missing_translation_messages << error
41
- end
42
- end
43
- end
44
- end
45
-
46
- def self.check_unused_translations
47
- self.used_translations ||= []
48
- self.unused_translation_messages = []
49
- self.unused_translations = []
50
- load_translations(translations)
51
- translation_diff = unused_translations - used_translations
52
- translation_diff.each do |translation|
53
- Spree.unused_translation_messages << "#{translation} (#{I18n.locale})"
54
- end
55
- end
56
-
57
- private
58
-
59
- def self.load_translations(hash, root = [])
60
- hash.each do |k, v|
61
- if v.is_a?(Hash)
62
- load_translations(v, root.dup << k)
63
- else
64
- key = (root + [k]).join('.')
65
- unused_translations << key
66
- end
67
- end
68
- end
69
-
70
- def self.translations
71
- @translations ||= I18n.backend.send(:translations)[I18n.locale][:spree]
72
- end
73
- end
74
-
75
- RSpec.configure do |config|
76
- # Need to check here again because this is used in i18n_spec too.
77
- if ENV['CHECK_TRANSLATIONS']
78
- config.after :suite do
79
- Spree.check_missing_translations
80
- if Spree.missing_translation_messages.any?
81
- puts "\nThere are missing translations within Spree:"
82
- puts Spree.missing_translation_messages.sort
83
- exit(1)
84
- end
85
-
86
- Spree.check_unused_translations
87
- if Spree.unused_translation_messages.any?
88
- puts "\nThere are unused translations within Spree:"
89
- puts Spree.unused_translation_messages.sort
90
- exit(1)
91
- end
92
- end
93
- end
94
- end
1
+ # Deprecated: Spree's specs now catch missing keys through Rails'
2
+ # config.i18n.raise_on_missing_translations. Removed in Spree 6.1.
3
+ Spree::Deprecation.warn("spree/testing_support/i18n is deprecated and does nothing; set config.i18n.raise_on_missing_translations = true in your test environment instead. It will be removed in Spree 6.1.") if defined?(Spree::Deprecation)
@@ -21,7 +21,7 @@ shared_examples_for 'lifecycle events' do |factory: nil, event_prefix: nil|
21
21
  expect(record).to receive(:publish_event).with("#{lifecycle_event_prefix}.updated")
22
22
  allow(record).to receive(:publish_event).with(anything)
23
23
 
24
- # update_attribute, not update!: Spree::Shipment#update! shadows the
24
+ # update_attribute, not update!: Spree::Fulfillment#update! shadows the
25
25
  # Active Record method with the legacy state-recalculation API.
26
26
  Timecop.travel(1.minute.from_now) do
27
27
  record.update_attribute(:updated_at, Time.current)
@@ -21,7 +21,7 @@ class OrderWalkthrough
21
21
 
22
22
  # A delivery method must exist for rates to be displayed on checkout page
23
23
  unless Spree::DeliveryMethod.exists?
24
- FactoryBot.create(:shipping_method).tap do |delivery_method|
24
+ FactoryBot.create(:delivery_method).tap do |delivery_method|
25
25
  delivery_method.calculator.preferred_amount = 10
26
26
  delivery_method.calculator.preferred_currency = store.default_currency
27
27
  delivery_method.calculator.save
@@ -18,11 +18,6 @@ module Spree
18
18
  yield(config) if block_given?
19
19
  end
20
20
 
21
- def assert_preference_unset(preference)
22
- find("#preferences_#{preference}")['checked'].should be false
23
- Spree::Config[preference].should be false
24
- end
25
-
26
21
  # Stubs store-scoped commerce settings for the duration of one example.
27
22
  #
28
23
  # stub_store_preferences(track_inventory_levels: false)
@@ -29,6 +29,15 @@ RSpec.configure do |config|
29
29
  end
30
30
  end
31
31
 
32
+ # Reloaded before each example rather than after: the after hooks run
33
+ # inside the example's transaction, so a reload there still sees the
34
+ # example's writes, and the next example would start from values the
35
+ # database no longer holds. Setting a preference to such a stale value is
36
+ # no change to Rails, so it would never be saved.
37
+ config.before(:each) do
38
+ @default_store&.reload unless self.class.metadata[:without_global_store]
39
+ end
40
+
32
41
  config.after(:each) do
33
42
  unless self.class.metadata[:without_global_store]
34
43
  @default_store&.products = []
@@ -43,7 +52,6 @@ RSpec.configure do |config|
43
52
  @default_store.association(:default_market).reset if @default_store.association_cached?(:default_market)
44
53
  @default_store.association(:markets).reset if @default_store.association_cached?(:markets)
45
54
  @default_store.remove_instance_variable(:@has_markets) if @default_store.instance_variable_defined?(:@has_markets)
46
- @default_store.reload
47
55
  end
48
56
  end
49
57
  end
@@ -35,6 +35,22 @@ steps:
35
35
 
36
36
  Idempotent, and a no-op on an already-consolidated schema.
37
37
 
38
+ - id: preferences_json
39
+ name: "Check for preferences still stored as YAML"
40
+ task: "spree:upgrade:preferences_json"
41
+ notes: |
42
+ Spree 6.0 stores preferences as JSON instead of YAML, and keeps secrets
43
+ (preferences declared as passwords, such as gateway API keys) in a
44
+ separate secret_preferences column. The migration converts every row and
45
+ moves every secret itself, so a normal db:migrate needs nothing from this
46
+ step.
47
+
48
+ This is a safety net: it converts any row still holding YAML, and moves
49
+ the secrets of a class that was not loaded when the migration ran, such
50
+ as one from an extension gem installed afterwards.
51
+
52
+ Idempotent, and a no-op once everything is converted.
53
+
38
54
  - id: migrate_media_class_names
39
55
  name: "Rewrite stored Spree::Asset and Spree::Image class names"
40
56
  task: "spree:migrate_media_class_names"
@@ -149,7 +165,9 @@ steps:
149
165
  that has_secure_password reads directly only when unpeppered; the task
150
166
  aborts if Devise.pepper is set. Because Devise is gone in 6.0 a former
151
167
  pepper can't be introspected, so when Devise is absent the task aborts until
152
- you confirm none was used with CONFIRM_NO_PEPPER=true. Peppered / SSO /
168
+ you confirm none was used with CONFIRM_NO_PEPPER=true. The check is skipped
169
+ when there are no passwords to copy (e.g. an empty spree_users table), so
170
+ new projects pass without the flag. Peppered / SSO /
153
171
  non-bcrypt installs keep a custom customer_class instead (see the 5.6-to-6.0
154
172
  upgrade guide) and skip this step.
155
173
 
@@ -288,6 +306,20 @@ steps:
288
306
  with one statement scoped to the rows still missing a code, so an
289
307
  interrupted run picks up where it stopped.
290
308
 
309
+ - id: migrate_checkout_zones
310
+ name: "Convert checkout zones to markets"
311
+ task: "spree:markets:migrate_checkout_zones"
312
+ notes: |
313
+ Markets replace the store's checkout zone in Spree 6. Stores that have
314
+ no market yet get a default one covering their checkout zone's
315
+ countries, or their default country when no zone was set, and the
316
+ checkout zone is cleared.
317
+
318
+ Run before migrate_tax_zones, which matches zone price rules against a
319
+ store's markets, and before backfill_order_markets, which assigns each
320
+ store's default market to its orders. Idempotent: stores that already
321
+ have a market are skipped.
322
+
291
323
  - id: migrate_promotion_option_value_rules
292
324
  name: "Rewrite Option Value promotion rules to stable option value ids"
293
325
  task: "spree:upgrade:migrate_promotion_option_value_rules"
@@ -715,3 +747,37 @@ steps:
715
747
  predates the upgrade.
716
748
 
717
749
  Idempotent, and safe to re-run at any time to repair drift.
750
+
751
+ - id: encrypt_secret_preferences
752
+ name: "Encrypt gateway and integration secrets"
753
+ task: "spree:upgrade:encrypt_secret_preferences"
754
+ notes: |
755
+ The 6.0 migration moves existing secrets into the secret_preferences
756
+ column in plain text, so it never needs encryption keys. They stay
757
+ readable and are encrypted the next time their record is saved. This step
758
+ encrypts them all now.
759
+
760
+ Skips with a note when Active Record encryption keys are not configured;
761
+ see the encryption section of the upgrade guide to add them, then run
762
+ this step again.
763
+
764
+ Idempotent.
765
+
766
+ - id: assign_upload_stores
767
+ name: "Record which store owns every uploaded file"
768
+ task: "spree:uploads:assign_stores"
769
+ notes: |
770
+ Spree 6.0 records the store that owns every uploaded file, so a file from
771
+ one store cannot be attached in another and storage can be measured and
772
+ cleaned up per store. New files get their store when they are uploaded;
773
+ this gives existing files the store of the record they are attached to.
774
+
775
+ Files attached in more than one store are listed and left unchanged for
776
+ you to resolve. Files attached to nothing keep no store and are left
777
+ alone: the daily purge of never-attached uploads
778
+ (Spree::Uploads::PurgeUnattachedJob, see the upgrade guide) only
779
+ deletes files that belong to a store.
780
+
781
+ Run AFTER the steps that create or move attachments (media, categories,
782
+ returns), so their files are covered. Idempotent — files that already
783
+ have a store are skipped.
@@ -0,0 +1,101 @@
1
+ module Spree
2
+ # Store ownership of uploaded files (docs/plans/6.0-uploads-and-file-ownership.md).
3
+ #
4
+ # Every `ActiveStorage::Blob` records the store that owns it, and attaching a
5
+ # blob to a record of another store is reported. Spree 6.0 only logs; 6.1
6
+ # refuses, so one release of logs finds every path that still creates blobs
7
+ # without a store.
8
+ module Uploads
9
+ # How long an upload reference (`signed_id`) stays attachable.
10
+ SIGNED_ID_EXPIRY = 1.day
11
+
12
+ # Mints an expiring upload reference for +blob+.
13
+ #
14
+ # @param blob [ActiveStorage::Blob]
15
+ # @return [String] an opaque reference clients pass back unchanged
16
+ def self.signed_id_for(blob)
17
+ blob.signed_id(expires_in: SIGNED_ID_EXPIRY)
18
+ end
19
+
20
+ # The store owning +record+, or nil for records that belong to no store
21
+ # (admin users, customers).
22
+ #
23
+ # @param record [ActiveRecord::Base]
24
+ # @return [Integer, String, nil]
25
+ def self.store_id_for(record)
26
+ return record.id if record.is_a?(Spree::Store)
27
+ # A processed rendition belongs to whoever owns its original.
28
+ return record.blob&.store_id if record.is_a?(ActiveStorage::VariantRecord)
29
+ return record.store_id if record.has_attribute?(:store_id)
30
+
31
+ record.try(:store)&.id
32
+ rescue Module::DelegationError
33
+ # An owner whose parent is gone (a digital asset on a deleted variant)
34
+ # has no store to compare against.
35
+ nil
36
+ end
37
+
38
+ module BlobStoreOwnership
39
+ extend ActiveSupport::Concern
40
+
41
+ included do
42
+ before_create :assign_current_store
43
+ end
44
+
45
+ private
46
+
47
+ # Reads the store a request or job declared, never the default-store
48
+ # fallback: guessing the default would hide exactly the creation paths
49
+ # the 6.0 logs are meant to find.
50
+ def assign_current_store
51
+ return unless has_attribute?(:store_id)
52
+
53
+ self.store_id ||= Spree::Current.attributes[:store]&.id
54
+ end
55
+ end
56
+
57
+ module AttachmentStoreCheck
58
+ extend ActiveSupport::Concern
59
+
60
+ included do
61
+ before_validation :adopt_owner_store, on: :create
62
+ after_create :check_blob_store
63
+ end
64
+
65
+ private
66
+
67
+ # A file Spree just made from bytes it holds (an export, a purchased
68
+ # label) is created in the same call that attaches it, so it simply
69
+ # takes its owner's store, written with the file itself.
70
+ def adopt_owner_store
71
+ return unless blob&.new_record? && blob.has_attribute?(:store_id) && record
72
+
73
+ blob.store_id ||= owner_store_id
74
+ end
75
+
76
+ def check_blob_store
77
+ return unless blob&.has_attribute?(:store_id) && record
78
+ return if owner_store_id.nil?
79
+
80
+ if blob.store_id.nil?
81
+ report_store_mismatch('has no store')
82
+ elsif blob.store_id.to_s != owner_store_id.to_s
83
+ report_store_mismatch("belongs to store #{blob.store_id}")
84
+ end
85
+ end
86
+
87
+ def owner_store_id
88
+ return @owner_store_id if defined?(@owner_store_id)
89
+
90
+ @owner_store_id = Spree::Uploads.store_id_for(record)
91
+ end
92
+
93
+ def report_store_mismatch(reason)
94
+ Rails.logger.warn(
95
+ "[Spree] Attached file #{blob.id} #{reason}, but #{record_type}##{record_id} (#{name}) " \
96
+ "belongs to store #{owner_store_id}. Spree 6.1 refuses this attachment."
97
+ )
98
+ end
99
+ end
100
+ end
101
+ end
@@ -80,7 +80,7 @@ namespace :spree do
80
80
  # otherwise purge/destroy: ActionText description, images, custom fields, slugs.
81
81
  move_polymorphic.call(ActionText::RichText, :record_type, :record_id, category, collection)
82
82
  move_polymorphic.call(ActiveStorage::Attachment, :record_type, :record_id, category, collection, name: %w[image square_image])
83
- move_polymorphic.call(Spree::Metafield, :resource_type, :resource_id, category, collection)
83
+ move_polymorphic.call(Spree::CustomField, :resource_type, :resource_id, category, collection)
84
84
  move_polymorphic.call(FriendlyId::Slug, :sluggable_type, :sluggable_id, category, collection)
85
85
 
86
86
  # Rules -> collection_rules (remap the STI type; insert_all bypasses the
@@ -169,8 +169,8 @@ namespace :spree do
169
169
  # category rows that still hold the pre-6.0 names.
170
170
  puts 'Backfilling renamed class-name strings...'
171
171
  [
172
- [Spree::Metafield, :resource_type, 'Spree::Taxon', 'Spree::Category'],
173
- [Spree::MetafieldDefinition, :resource_type, 'Spree::Taxon', 'Spree::Category'],
172
+ [Spree::CustomField, :resource_type, 'Spree::Taxon', 'Spree::Category'],
173
+ [Spree::CustomFieldDefinition, :resource_type, 'Spree::Taxon', 'Spree::Category'],
174
174
  [ActiveStorage::Attachment, :record_type, 'Spree::Taxon', 'Spree::Category'],
175
175
  # description is an ActionText field (translates :description, backend: :action_text);
176
176
  # surviving categories keep their rich-text description without this.
data/lib/tasks/core.rake CHANGED
@@ -26,14 +26,16 @@ namespace :core do
26
26
 
27
27
  desc 'Migrate newsletter subscribers'
28
28
  task migrate_newsletter_subscribers: :environment do |_t, _args|
29
- Spree.customer_class.where(accepts_email_marketing: true).in_batches(of: 500) do |user_batch|
30
- subscriber_attributes = user_batch.pluck(:id, :email, :updated_at).map do |id, email, updated_at|
29
+ store_id = Spree::Store.default&.id
30
+
31
+ Spree.customer_class.where(accepts_email_marketing: true).in_batches(of: 500) do |customer_batch|
32
+ subscriber_attributes = customer_batch.pluck(:id, :email, :updated_at).map do |id, email, updated_at|
31
33
  {
32
34
  email: Spree::NewsletterSubscriber.new(email: email).email, # normalized email
33
- user_id: id,
35
+ customer_id: id,
34
36
  verified_at: updated_at,
35
37
  verification_token: nil,
36
- store_id: Spree::Store.default&.id,
38
+ store_id: store_id,
37
39
  updated_at: DateTime.current,
38
40
  created_at: DateTime.current
39
41
  }
@@ -31,7 +31,8 @@ namespace :spree do
31
31
  market = store.markets.create!(
32
32
  name: primary_country&.name || 'Default',
33
33
  currency: iso_country&.currency_code || store.read_attribute(:default_currency) || 'USD',
34
- default_locale: iso_country&.languages_official&.first || store.read_attribute(:default_locale) || 'en',
34
+ default_locale: Spree::Locales.listed(iso_country&.languages_official&.first) ||
35
+ Spree::Locales.listed(store.read_attribute(:default_locale)) || 'en',
35
36
  default: true,
36
37
  countries: countries
37
38
  )
data/lib/tasks/media.rake CHANGED
@@ -31,9 +31,9 @@ namespace :spree do
31
31
  # Subquery (not pluck) so the product set doesn't materialize in Ruby —
32
32
  # important for catalogs with millions of products.
33
33
  variant_product_ids = Spree::Variant
34
- .joins("INNER JOIN #{Spree::Asset.table_name} ON " \
35
- "#{Spree::Asset.table_name}.viewable_id = #{Spree::Variant.table_name}.id " \
36
- "AND #{Spree::Asset.table_name}.viewable_type = 'Spree::Variant'")
34
+ .joins("INNER JOIN #{Spree::Media.table_name} ON " \
35
+ "#{Spree::Media.table_name}.viewable_id = #{Spree::Variant.table_name}.id " \
36
+ "AND #{Spree::Media.table_name}.viewable_type = 'Spree::Variant'")
37
37
  .select(:product_id)
38
38
 
39
39
  relation = Spree::Product.where(id: variant_product_ids)
@@ -16,7 +16,8 @@ namespace :spree do
16
16
  configured — Devise's +encrypted_password+ is a plain bcrypt digest, the
17
17
  exact format +has_secure_password+ reads. The task aborts if a pepper is
18
18
  detected; when Devise is absent (so a former pepper can't be introspected)
19
- it aborts until you confirm none was used with CONFIRM_NO_PEPPER=true.
19
+ it aborts until you confirm none was used with CONFIRM_NO_PEPPER=true. The
20
+ check is skipped when there are no passwords to copy.
20
21
  Source rows with a blank email, or whose email is already taken by a
21
22
  different customer, abort the task with their ids — reconcile them or pass
22
23
  SKIP_INVALID_ROWS=true to skip and continue. The legacy source table is
@@ -38,10 +39,29 @@ namespace :spree do
38
39
  next
39
40
  end
40
41
 
42
+ customer_model = Spree.customer_class
43
+ admin_model = Spree.admin_user_class
44
+
45
+ # Read the legacy table by name only — the Devise-era Spree::User /
46
+ # Spree::LegacyUser models are gone in 6.0. Inherit from Spree.base_class
47
+ # so the source shares the customer table's connection (the id-exclusion
48
+ # subquery below spans both).
49
+ source = Class.new(Spree.base_class) { self.table_name = source_table }
50
+
51
+ pending = source.where.not(id: customer_model.select(:id))
52
+ admin_pending = if admin_model.column_names.include?('encrypted_password')
53
+ admin_model.where(password_digest: [nil, '']).where.not(encrypted_password: [nil, ''])
54
+ else
55
+ admin_model.none
56
+ end
57
+
41
58
  # Peppered digests can't be verified by has_secure_password. Refuse rather
42
59
  # than silently produce accounts nobody can sign into. Once Devise is gone
43
60
  # a former pepper can't be introspected, so require an explicit all-clear.
44
- if defined?(Devise) && Devise.respond_to?(:pepper) && Devise.pepper.present?
61
+ # With no digest to copy (e.g. an empty legacy table) there is nothing to protect.
62
+ if pending.none? && admin_pending.none?
63
+ puts " No customer or admin passwords to copy — skipping the Devise pepper check."
64
+ elsif defined?(Devise) && Devise.respond_to?(:pepper) && Devise.pepper.present?
45
65
  abort " Devise.pepper is configured — bcrypt digests can't be copied into password_digest. " \
46
66
  "Use the custom-model path (keep the table, alias password_digest) or force password resets."
47
67
  elsif !defined?(Devise) && ENV['CONFIRM_NO_PEPPER'] != 'true'
@@ -51,15 +71,6 @@ namespace :spree do
51
71
  "used no pepper, or use the custom-model path / force password resets."
52
72
  end
53
73
 
54
- customer_model = Spree.customer_class
55
- admin_model = Spree.admin_user_class
56
-
57
- # Read the legacy table by name only — the Devise-era Spree::User /
58
- # Spree::LegacyUser models are gone in 6.0. Inherit from Spree.base_class
59
- # so the source shares the customer table's connection (the id-exclusion
60
- # subquery below spans both).
61
- source = Class.new(Spree.base_class) { self.table_name = source_table }
62
-
63
74
  # Copy only columns both tables share; map the digest column separately.
64
75
  direct_columns = customer_model.column_names & source.column_names
65
76
  copy_digest_from_encrypted = source.column_names.include?('encrypted_password') &&
@@ -75,7 +86,6 @@ namespace :spree do
75
86
  # the whole batch on the unique-email index.
76
87
  # Either kind aborts the task with the offending ids unless the operator
77
88
  # passes SKIP_INVALID_ROWS=true to skip them and continue.
78
- pending = source.where.not(id: customer_model.select(:id))
79
89
  blank_email_ids = pending.where("email IS NULL OR email = ''").pluck(:id)
80
90
  # Compare case-insensitively to match Spree::Customer's uniqueness
81
91
  # (case_sensitive: false) — emails are stored case-preserved, so a legacy
@@ -140,12 +150,7 @@ namespace :spree do
140
150
 
141
151
  # Admins stay in spree_admin_users (in place) — backfill the digest from
142
152
  # the legacy column when present.
143
- admin_backfilled = 0
144
- if admin_model.column_names.include?('encrypted_password')
145
- admin_backfilled = admin_model.where(password_digest: [nil, '']).
146
- where.not(encrypted_password: [nil, '']).
147
- update_all('password_digest = encrypted_password')
148
- end
153
+ admin_backfilled = admin_pending.update_all('password_digest = encrypted_password')
149
154
 
150
155
  puts
151
156
  puts " Customers copied: #{copied} (skipped #{invalid_ids.size} invalid row(s): " \
@@ -0,0 +1,46 @@
1
+ namespace :spree do
2
+ namespace :money do
3
+ desc <<~DESC
4
+ Lists payment capture events in currencies without two decimals (yen,
5
+ dinar, ...) and writes nothing (Spree 6.0).
6
+
7
+ Before 6.0 a partial or manual capture recorded its amount in the wrong
8
+ unit for these currencies: a 1000 JPY capture was recorded as 100000,
9
+ a 1.500 KWD capture as 0.150. A purchase (authorize and capture in one)
10
+ recorded the right amount, and the two cannot be told apart from the
11
+ row, so every event is listed with both readings: as recorded, and
12
+ corrected as if the capture flow wrote it. Compare them with the
13
+ gateway's own records before changing anything.
14
+ DESC
15
+ task audit_capture_events: :environment do
16
+ listed = 0
17
+ # The wrong records were written with the Money gem's exponents as they
18
+ # were before 6.0, which differ from ISO 4217 for a few currencies.
19
+ legacy_exponents = { 'HUF' => 0, 'MGA' => 1, 'MRO' => 1, 'MRU' => 1 }
20
+ exponent_for = ->(code) { legacy_exponents.fetch(code) { Spree::Money::Rounding.precision(code) } }
21
+ codes = (Spree::Money::Rounding::EXPONENTS.keys | legacy_exponents.keys).reject { |code| exponent_for.(code) == 2 }
22
+ in_codes = [Spree::Order, Spree::Cart, Spree::OrderGroup].map { |owner| owner.arel_table[:currency].in(codes) }.reduce(:or)
23
+
24
+ Spree::PaymentCaptureEvent.left_joins(payment: [:order, :cart, :order_group]).where(in_codes)
25
+ .includes(payment: [:order, :cart, :order_group]).find_each do |event|
26
+ payment = event.payment
27
+ next if payment.nil?
28
+
29
+ exponent = exponent_for.(payment.currency.to_s.upcase)
30
+
31
+ corrected = event.amount * (10**exponent) / 100
32
+ puts [
33
+ event.prefixed_id,
34
+ payment.number,
35
+ payment.currency,
36
+ "recorded=#{Spree::Money::Rounding.format(event.amount, payment.currency)}",
37
+ "if_from_capture=#{Spree::Money::Rounding.format(corrected, payment.currency)}",
38
+ "payment_amount=#{Spree::Money::Rounding.format(payment.amount, payment.currency)}"
39
+ ].join("\t")
40
+ listed += 1
41
+ end
42
+
43
+ puts " #{listed} capture event(s) in currencies without two decimals. Nothing was changed."
44
+ end
45
+ end
46
+ end
@@ -43,7 +43,7 @@ namespace :spree do
43
43
  end
44
44
 
45
45
  store.package_types.create!(
46
- name: Spree.t('package_types.default_name'),
46
+ name: I18n.t('spree.package_types.default_name'),
47
47
  kind: 'box',
48
48
  default: true,
49
49
  weight: weight.positive? ? weight : nil,