spree_core 6.0.0.beta4 → 6.0.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 (495) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +2 -2
  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/seller_payouts/sweep_seller_job.rb +2 -0
  10. data/app/mailers/spree/admin_user_mailer.rb +4 -19
  11. data/app/mailers/spree/base_mailer.rb +96 -13
  12. data/app/mailers/spree/email_template_mailer.rb +27 -0
  13. data/app/mailers/spree/export_mailer.rb +3 -4
  14. data/app/mailers/spree/import_mailer.rb +3 -4
  15. data/app/mailers/spree/invitation_mailer.rb +24 -7
  16. data/app/mailers/spree/seller_user_mailer.rb +4 -16
  17. data/app/mailers/spree/webhook_mailer.rb +2 -2
  18. data/app/models/concerns/spree/acted_by.rb +1 -1
  19. data/app/models/concerns/spree/admin_user_methods.rb +13 -1
  20. data/app/models/concerns/spree/api_typed.rb +91 -0
  21. data/app/models/concerns/spree/calculator/tiers.rb +76 -0
  22. data/app/models/concerns/spree/customer_methods.rb +15 -14
  23. data/app/models/concerns/spree/email_template_identity.rb +52 -0
  24. data/app/models/concerns/spree/parameterizable_name.rb +2 -0
  25. data/app/models/concerns/spree/payment_session_transitions.rb +8 -2
  26. data/app/models/concerns/spree/post_sale/taxation.rb +54 -0
  27. data/app/models/concerns/spree/post_sale/taxed_line.rb +51 -0
  28. data/app/models/concerns/spree/preference_schema.rb +4 -12
  29. data/app/models/concerns/spree/product_scopes.rb +11 -1
  30. data/app/models/concerns/spree/provider_listing.rb +41 -0
  31. data/app/models/concerns/spree/publishable.rb +82 -14
  32. data/app/models/concerns/spree/purchase/addresses.rb +23 -10
  33. data/app/models/concerns/spree/purchase/currency.rb +1 -1
  34. data/app/models/concerns/spree/purchase/deprecated_aliases.rb +87 -0
  35. data/app/models/concerns/spree/purchase/gift_cards.rb +10 -4
  36. data/app/models/concerns/spree/purchase/locale.rb +1 -1
  37. data/app/models/concerns/spree/purchase/payment_processing.rb +7 -3
  38. data/app/models/concerns/spree/purchase/purchase_order.rb +2 -2
  39. data/app/models/concerns/spree/purchase/totals.rb +49 -1
  40. data/app/models/concerns/spree/receivable.rb +1 -1
  41. data/app/models/concerns/spree/searchable.rb +0 -4
  42. data/app/models/concerns/spree/secret_preferences.rb +59 -0
  43. data/app/models/concerns/spree/single_store_resource.rb +1 -1
  44. data/app/models/concerns/spree/stores/email_branding.rb +31 -0
  45. data/app/models/concerns/spree/type_labels.rb +38 -0
  46. data/app/models/concerns/spree/typed_adjustment_line.rb +1 -1
  47. data/app/models/concerns/spree/typed_associations.rb +6 -0
  48. data/app/models/spree/address.rb +11 -1
  49. data/app/models/spree/adjusters/promotion.rb +1 -15
  50. data/app/models/spree/allowed_origin.rb +0 -1
  51. data/app/models/spree/api_key.rb +5 -6
  52. data/app/models/spree/authentication/strategies/oidc_strategy.rb +5 -5
  53. data/app/models/spree/base.rb +70 -22
  54. data/app/models/spree/calculator/flat_percent_item_total.rb +1 -3
  55. data/app/models/spree/calculator/flat_rate.rb +1 -3
  56. data/app/models/spree/calculator/flexi_rate.rb +1 -3
  57. data/app/models/spree/calculator/percent_on_line_item.rb +1 -1
  58. data/app/models/spree/calculator/price_sack.rb +1 -3
  59. data/app/models/spree/calculator/shipping/digital_delivery.rb +1 -3
  60. data/app/models/spree/calculator/shipping/flat_percent_item_total.rb +1 -3
  61. data/app/models/spree/calculator/shipping/flat_rate.rb +1 -3
  62. data/app/models/spree/calculator/shipping/flexi_rate.rb +1 -3
  63. data/app/models/spree/calculator/shipping/per_item.rb +1 -3
  64. data/app/models/spree/calculator/shipping/price_sack.rb +1 -3
  65. data/app/models/spree/calculator/tiered_flat_rate.rb +6 -23
  66. data/app/models/spree/calculator/tiered_percent.rb +7 -25
  67. data/app/models/spree/cart.rb +78 -18
  68. data/app/models/spree/channel/gating.rb +1 -1
  69. data/app/models/spree/channel.rb +4 -4
  70. data/app/models/spree/claim.rb +21 -1
  71. data/app/models/spree/claim_line_item.rb +41 -6
  72. data/app/models/spree/collection.rb +0 -1
  73. data/app/models/spree/collection_rule.rb +5 -21
  74. data/app/models/spree/commission_line.rb +1 -1
  75. data/app/models/spree/commission_rate.rb +2 -3
  76. data/app/models/spree/commission_rate_value.rb +1 -1
  77. data/app/models/spree/commission_rule.rb +4 -11
  78. data/app/models/spree/commission_rules/product_rule.rb +1 -1
  79. data/app/models/spree/company_invitation.rb +1 -0
  80. data/app/models/spree/consent_record.rb +0 -1
  81. data/app/models/spree/credit_card.rb +1 -1
  82. data/app/models/spree/current.rb +23 -0
  83. data/app/models/spree/custom_field.rb +2 -2
  84. data/app/models/spree/custom_field_definition.rb +9 -23
  85. data/app/models/spree/custom_fields/boolean.rb +1 -1
  86. data/app/models/spree/custom_fields/json.rb +1 -1
  87. data/app/models/spree/data_feed/google.rb +0 -2
  88. data/app/models/spree/data_request.rb +1 -1
  89. data/app/models/spree/delivery_method.rb +30 -13
  90. data/app/models/spree/delivery_method_rule.rb +4 -11
  91. data/app/models/spree/delivery_rate.rb +4 -5
  92. data/app/models/spree/delivery_rate_provider/base.rb +1 -0
  93. data/app/models/spree/delivery_zone.rb +0 -1
  94. data/app/models/spree/delivery_zone_member.rb +4 -4
  95. data/app/models/spree/digital_asset.rb +7 -0
  96. data/app/models/spree/digital_asset_provider/base.rb +2 -0
  97. data/app/models/spree/digital_link.rb +2 -5
  98. data/app/models/spree/discount.rb +1 -1
  99. data/app/models/spree/email_template.rb +25 -0
  100. data/app/models/spree/email_template_draft.rb +13 -0
  101. data/app/models/spree/email_template_revision.rb +17 -0
  102. data/app/models/spree/email_templates/entry.rb +118 -0
  103. data/app/models/spree/email_templates/sample_record.rb +16 -0
  104. data/app/models/spree/emails/branding.rb +80 -0
  105. data/app/models/spree/exchange.rb +15 -1
  106. data/app/models/spree/exchange_line_item.rb +121 -5
  107. data/app/models/spree/export.rb +0 -1
  108. data/app/models/spree/exports/products.rb +10 -1
  109. data/app/models/spree/exports/report.rb +1 -1
  110. data/app/models/spree/fee.rb +4 -2
  111. data/app/models/spree/fulfillment.rb +53 -27
  112. data/app/models/spree/fulfillment_changer.rb +2 -2
  113. data/app/models/spree/fulfillment_item.rb +0 -4
  114. data/app/models/spree/fulfillment_provider/base.rb +1 -0
  115. data/app/models/spree/gateway/bogus.rb +4 -0
  116. data/app/models/spree/gateway/custom_payment_source_method.rb +5 -0
  117. data/app/models/spree/gateway.rb +5 -1
  118. data/app/models/spree/gift_card.rb +12 -1
  119. data/app/models/spree/gift_card_batch.rb +0 -1
  120. data/app/models/spree/import.rb +2 -7
  121. data/app/models/spree/import_mapping.rb +1 -2
  122. data/app/models/spree/import_row.rb +2 -2
  123. data/app/models/spree/import_schemas/products.rb +1 -0
  124. data/app/models/spree/integration.rb +8 -18
  125. data/app/models/spree/invitation.rb +5 -5
  126. data/app/models/spree/line_item.rb +13 -2
  127. data/app/models/spree/locale.rb +11 -8
  128. data/app/models/spree/market.rb +1 -0
  129. data/app/models/spree/newsletter_subscriber.rb +3 -0
  130. data/app/models/spree/order.rb +56 -138
  131. data/app/models/spree/order_group.rb +11 -2
  132. data/app/models/spree/order_inventory.rb +10 -5
  133. data/app/models/spree/order_promotion.rb +1 -1
  134. data/app/models/spree/order_routing/has_strategy_preference.rb +1 -1
  135. data/app/models/spree/order_routing/strategy/base.rb +4 -2
  136. data/app/models/spree/order_routing/strategy/rules.rb +1 -1
  137. data/app/models/spree/order_routing_rule.rb +6 -22
  138. data/app/models/spree/package_type.rb +1 -2
  139. data/app/models/spree/payment/gateway_options.rb +14 -52
  140. data/app/models/spree/payment/processing.rb +2 -4
  141. data/app/models/spree/payment.rb +20 -5
  142. data/app/models/spree/payment_method/session_instrumentation.rb +1 -0
  143. data/app/models/spree/payment_method/store_credit.rb +4 -4
  144. data/app/models/spree/payment_method.rb +36 -4
  145. data/app/models/spree/payment_session.rb +5 -1
  146. data/app/models/spree/payment_setup_session.rb +1 -0
  147. data/app/models/spree/payout_provider/base.rb +3 -1
  148. data/app/models/spree/pickup_point_provider/base.rb +2 -0
  149. data/app/models/spree/price_list.rb +1 -2
  150. data/app/models/spree/price_rules/channel_rule.rb +1 -1
  151. data/app/models/spree/price_rules/customer_group_rule.rb +1 -1
  152. data/app/models/spree/product.rb +31 -7
  153. data/app/models/spree/product_submission.rb +0 -12
  154. data/app/models/spree/product_type.rb +0 -1
  155. data/app/models/spree/promotion/actions/create_line_items.rb +27 -17
  156. data/app/models/spree/promotion/rules/category.rb +0 -4
  157. data/app/models/spree/promotion/rules/channel.rb +0 -4
  158. data/app/models/spree/promotion/rules/country.rb +0 -4
  159. data/app/models/spree/promotion/rules/currency.rb +0 -4
  160. data/app/models/spree/promotion/rules/customer_group.rb +0 -4
  161. data/app/models/spree/promotion/rules/first_order.rb +0 -4
  162. data/app/models/spree/promotion/rules/item_total.rb +4 -8
  163. data/app/models/spree/promotion/rules/market.rb +0 -4
  164. data/app/models/spree/promotion/rules/one_use_per_user.rb +0 -4
  165. data/app/models/spree/promotion/rules/option_value.rb +0 -3
  166. data/app/models/spree/promotion/rules/product.rb +0 -4
  167. data/app/models/spree/promotion/rules/user.rb +0 -4
  168. data/app/models/spree/promotion/rules/user_logged_in.rb +0 -4
  169. data/app/models/spree/promotion.rb +35 -6
  170. data/app/models/spree/promotion_action.rb +7 -14
  171. data/app/models/spree/promotion_action_line_item.rb +1 -1
  172. data/app/models/spree/promotion_handler/coupon.rb +59 -27
  173. data/app/models/spree/promotion_rule.rb +7 -14
  174. data/app/models/spree/purchase_order.rb +1 -0
  175. data/app/models/spree/quantity_rule.rb +1 -1
  176. data/app/models/spree/refund.rb +28 -3
  177. data/app/models/spree/return.rb +30 -2
  178. data/app/models/spree/return_line_item.rb +65 -14
  179. data/app/models/spree/role.rb +7 -7
  180. data/app/models/spree/saved_report.rb +1 -1
  181. data/app/models/spree/seller.rb +2 -1
  182. data/app/models/spree/seller_payout.rb +7 -0
  183. data/app/models/spree/seller_requirement.rb +4 -12
  184. data/app/models/spree/seller_requirement_custom_field.rb +1 -2
  185. data/app/models/spree/seller_requirement_submission.rb +2 -2
  186. data/app/models/spree/seller_requirements/accept_terms.rb +2 -3
  187. data/app/models/spree/seller_transfer.rb +5 -0
  188. data/app/models/spree/shipping_label.rb +1 -0
  189. data/app/models/spree/stock/availability_validator.rb +2 -2
  190. data/app/models/spree/stock/inventory_unit_builder.rb +1 -1
  191. data/app/models/spree/stock/packer.rb +2 -2
  192. data/app/models/spree/stock/splitter/weight.rb +1 -1
  193. data/app/models/spree/stock_level/custom_events.rb +6 -0
  194. data/app/models/spree/stock_level.rb +1 -1
  195. data/app/models/spree/stock_movement.rb +1 -1
  196. data/app/models/spree/stock_receipt_item.rb +1 -1
  197. data/app/models/spree/stock_reservation.rb +2 -2
  198. data/app/models/spree/stock_transfer.rb +4 -2
  199. data/app/models/spree/store.rb +33 -7
  200. data/app/models/spree/store_credit.rb +8 -9
  201. data/app/models/spree/store_credit_event.rb +5 -5
  202. data/app/models/spree/tax_exemption_certificate.rb +2 -0
  203. data/app/models/spree/tax_identifier.rb +3 -1
  204. data/app/models/spree/tax_line.rb +53 -3
  205. data/app/models/spree/tax_provider/base.rb +71 -14
  206. data/app/models/spree/tax_provider/internal.rb +59 -33
  207. data/app/models/spree/tax_provider/recorded_share.rb +114 -0
  208. data/app/models/spree/tax_rate.rb +1 -2
  209. data/app/models/spree/taxonomy.rb +0 -1
  210. data/app/models/spree/variant.rb +4 -3
  211. data/app/models/spree/variant_media.rb +1 -1
  212. data/app/models/spree/webhook_endpoint.rb +10 -13
  213. data/app/models/spree/wishlist.rb +1 -6
  214. data/app/models/spree/wishlist_item/custom_events.rb +6 -0
  215. data/app/presenters/spree/csv/customer_presenter.rb +1 -1
  216. data/app/presenters/spree/csv/newsletter_subscriber_presenter.rb +1 -1
  217. data/app/presenters/spree/csv/order_line_item_presenter.rb +1 -1
  218. data/app/presenters/spree/csv/product_variant_presenter.rb +42 -12
  219. data/app/serializers/spree/emails/base_serializer.rb +14 -0
  220. data/app/serializers/spree/emails/export_serializer.rb +13 -0
  221. data/app/serializers/spree/emails/import_serializer.rb +19 -0
  222. data/app/serializers/spree/emails/invitation_serializer.rb +28 -0
  223. data/app/serializers/spree/emails/store_serializer.rb +31 -0
  224. data/app/serializers/spree/emails/user_serializer.rb +12 -0
  225. data/app/serializers/spree/emails/webhook_endpoint_serializer.rb +16 -0
  226. data/app/services/spree/carts/create.rb +8 -2
  227. data/app/services/spree/carts/destroy.rb +2 -2
  228. data/app/services/spree/carts/remove_out_of_stock_items.rb +2 -2
  229. data/app/services/spree/carts/split_by_seller.rb +1 -1
  230. data/app/services/spree/carts/update.rb +19 -5
  231. data/app/services/spree/catalogs/set_product_terms.rb +1 -1
  232. data/app/services/spree/categories/add_products.rb +13 -31
  233. data/app/services/spree/categories/remove_products.rb +12 -51
  234. data/app/services/spree/checkout/advance.rb +1 -4
  235. data/app/services/spree/collections/add_products.rb +10 -23
  236. data/app/services/spree/collections/remove_products.rb +7 -43
  237. data/app/services/spree/coupon_codes/bulk_generate.rb +8 -14
  238. data/app/services/spree/data_requests/create.rb +1 -1
  239. data/app/services/spree/deliveries/destroy.rb +2 -2
  240. data/app/services/spree/email_templates/check.rb +58 -0
  241. data/app/services/spree/email_templates/no_sample_record.rb +7 -0
  242. data/app/services/spree/email_templates/preview.rb +99 -0
  243. data/app/services/spree/emails/file_system.rb +23 -0
  244. data/app/services/spree/emails/filters.rb +84 -0
  245. data/app/services/spree/emails/liquid_context.rb +48 -0
  246. data/app/services/spree/emails/rendered_email.rb +14 -0
  247. data/app/services/spree/emails/renderer.rb +125 -0
  248. data/app/services/spree/emails/template.rb +51 -0
  249. data/app/services/spree/emails/template_data.rb +23 -0
  250. data/app/services/spree/emails/template_resolver.rb +109 -0
  251. data/app/services/spree/emails/text_converter.rb +72 -0
  252. data/app/services/spree/emails/translation_inliner.rb +134 -0
  253. data/app/services/spree/imports/row_processors/price_list_price.rb +10 -10
  254. data/app/services/spree/imports/row_processors/product_variant.rb +65 -34
  255. data/app/services/spree/imports/row_processors/purchase_order.rb +13 -13
  256. data/app/services/spree/locales.rb +4 -5
  257. data/app/services/spree/newsletter/subscribe.rb +8 -12
  258. data/app/services/spree/orders/build_fulfillments.rb +3 -2
  259. data/app/services/spree/orders/create.rb +2 -1
  260. data/app/services/spree/orders/discounts/create.rb +7 -4
  261. data/app/services/spree/orders/discounts/destroy.rb +1 -0
  262. data/app/services/spree/orders/discounts/update.rb +1 -0
  263. data/app/services/spree/orders/fees/create.rb +1 -0
  264. data/app/services/spree/orders/fees/destroy.rb +1 -0
  265. data/app/services/spree/orders/fees/update.rb +1 -0
  266. data/app/services/spree/orders/remove_line_item.rb +13 -3
  267. data/app/services/spree/orders/update.rb +10 -6
  268. data/app/services/spree/orders/update_item.rb +6 -3
  269. data/app/services/spree/orders/update_statuses.rb +3 -1
  270. data/app/services/spree/product_memberships.rb +61 -0
  271. data/app/services/spree/returns/eligibility_validator.rb +1 -1
  272. data/app/services/spree/search_provider/custom_field_schema.rb +2 -2
  273. data/app/services/spree/seeds/commission_rates.rb +1 -1
  274. data/app/services/spree/seeds/digital_delivery.rb +1 -1
  275. data/app/services/spree/seeds/payment_methods.rb +2 -2
  276. data/app/services/spree/seeds/saved_reports.rb +3 -3
  277. data/app/services/spree/sellers/requirements.rb +1 -2
  278. data/app/services/spree/shipping_labels/confirm_refund.rb +1 -1
  279. data/app/services/spree/shipping_labels/store_file.rb +3 -3
  280. data/app/services/spree/stock_levels/correct.rb +3 -3
  281. data/app/services/spree/stock_reservations/reserve.rb +2 -3
  282. data/app/services/spree/store_credits/apply.rb +15 -13
  283. data/app/services/spree/stores/provision_defaults.rb +15 -14
  284. data/app/subscribers/spree/seller_onboarding_subscriber.rb +1 -1
  285. data/app/views/layouts/spree/base_mailer.html.erb +2 -43
  286. data/app/views/layouts/spree/base_mailer.liquid +79 -0
  287. data/app/views/spree/admin_user_mailer/password_reset_email.liquid +14 -0
  288. data/app/views/spree/export_mailer/export_done.liquid +12 -0
  289. data/app/views/spree/import_mailer/import_done.liquid +15 -0
  290. data/app/views/spree/invitation_mailer/invitation_accepted.liquid +9 -0
  291. data/app/views/spree/invitation_mailer/invitation_email.liquid +13 -0
  292. data/app/views/spree/seller_user_mailer/password_reset_email.liquid +14 -0
  293. data/app/views/spree/shared/_mailer_button.html.erb +4 -5
  294. data/app/views/spree/shared/_mailer_hero.html.erb +6 -5
  295. data/app/views/spree/shared/_summary_row.liquid +8 -0
  296. data/app/views/spree/webhook_mailer/endpoint_disabled.liquid +21 -0
  297. data/app/workflows/spree/carts/add_item.rb +4 -4
  298. data/app/workflows/spree/carts/complete.rb +13 -3
  299. data/app/workflows/spree/carts/merge.rb +1 -1
  300. data/app/workflows/spree/carts/recalculate_totals.rb +5 -0
  301. data/app/workflows/spree/carts/upsert_items.rb +18 -9
  302. data/app/workflows/spree/claims/approve.rb +5 -22
  303. data/app/workflows/spree/claims/cancel.rb +5 -21
  304. data/app/workflows/spree/claims/create.rb +15 -0
  305. data/app/workflows/spree/claims/deny.rb +7 -0
  306. data/app/workflows/spree/claims/resolve.rb +34 -31
  307. data/app/workflows/spree/customers/anonymize.rb +3 -4
  308. data/app/workflows/spree/customers/create.rb +1 -1
  309. data/app/workflows/spree/data_requests/fulfill.rb +2 -2
  310. data/app/workflows/spree/deliveries/update_tracking.rb +1 -1
  311. data/app/workflows/spree/email_templates/discard_draft.rb +20 -0
  312. data/app/workflows/spree/email_templates/publish.rb +70 -0
  313. data/app/workflows/spree/email_templates/restore_revision.rb +33 -0
  314. data/app/workflows/spree/email_templates/revert.rb +29 -0
  315. data/app/workflows/spree/email_templates/save_draft.rb +85 -0
  316. data/app/workflows/spree/exchanges/approve.rb +5 -22
  317. data/app/workflows/spree/exchanges/cancel.rb +5 -21
  318. data/app/workflows/spree/exchanges/create.rb +6 -0
  319. data/app/workflows/spree/exchanges/fulfill.rb +75 -51
  320. data/app/workflows/spree/exchanges/receive.rb +6 -65
  321. data/app/workflows/spree/fulfillments/cancel.rb +1 -1
  322. data/app/workflows/spree/fulfillments/create.rb +51 -47
  323. data/app/workflows/spree/fulfillments/fulfill.rb +23 -27
  324. data/app/workflows/spree/fulfillments/mark_delivered.rb +1 -1
  325. data/app/workflows/spree/fulfillments/replacements.rb +59 -0
  326. data/app/workflows/spree/gift_cards/apply.rb +2 -2
  327. data/app/workflows/spree/orders/add_item.rb +5 -1
  328. data/app/workflows/spree/orders/cancel.rb +3 -3
  329. data/app/workflows/spree/orders/complete.rb +16 -8
  330. data/app/workflows/spree/payments/process.rb +2 -2
  331. data/app/workflows/spree/post_sale/approval.rb +35 -0
  332. data/app/workflows/spree/post_sale/cancellation.rb +44 -0
  333. data/app/workflows/spree/post_sale/receipt.rb +92 -0
  334. data/app/workflows/spree/products/nested_attributes.rb +1 -1
  335. data/app/workflows/spree/purchase_orders/cancel.rb +1 -1
  336. data/app/workflows/spree/purchase_orders/close.rb +3 -31
  337. data/app/workflows/spree/purchase_orders/mark_draft.rb +2 -2
  338. data/app/workflows/spree/purchase_orders/mark_ordered.rb +2 -2
  339. data/app/workflows/spree/purchase_orders/receive.rb +1 -1
  340. data/app/workflows/spree/purchase_orders/update.rb +5 -34
  341. data/app/workflows/spree/receivables/closing_short.rb +46 -0
  342. data/app/workflows/spree/receivables/draft_editing.rb +48 -0
  343. data/app/workflows/spree/refunds/create.rb +5 -2
  344. data/app/workflows/spree/refunds/order_payments.rb +14 -2
  345. data/app/workflows/spree/refunds/tax_credit.rb +67 -0
  346. data/app/workflows/spree/returns/approve.rb +5 -22
  347. data/app/workflows/spree/returns/cancel.rb +5 -22
  348. data/app/workflows/spree/returns/create.rb +8 -0
  349. data/app/workflows/spree/returns/receive.rb +5 -76
  350. data/app/workflows/spree/returns/refund.rb +31 -8
  351. data/app/workflows/spree/seller_requirement_submissions/create.rb +1 -2
  352. data/app/workflows/spree/seller_transfers/reverse.rb +28 -1
  353. data/app/workflows/spree/shipping_labels/purchase.rb +8 -8
  354. data/app/workflows/spree/shipping_labels/record.rb +5 -5
  355. data/app/workflows/spree/shipping_labels/refund.rb +2 -2
  356. data/app/workflows/spree/stock_receipts/recording.rb +4 -4
  357. data/app/workflows/spree/stock_transfers/cancel.rb +2 -2
  358. data/app/workflows/spree/stock_transfers/close.rb +3 -30
  359. data/app/workflows/spree/stock_transfers/mark_draft.rb +1 -1
  360. data/app/workflows/spree/stock_transfers/mark_in_transit.rb +3 -3
  361. data/app/workflows/spree/stock_transfers/mark_ready.rb +2 -2
  362. data/app/workflows/spree/stock_transfers/receive.rb +1 -1
  363. data/app/workflows/spree/stock_transfers/update.rb +5 -33
  364. data/config/i18n-tasks.yml +32 -18
  365. data/config/initializers/alba.rb +7 -0
  366. data/config/language_names.csv +264 -0
  367. data/config/locales/ar.yml +811 -0
  368. data/config/locales/be.yml +855 -0
  369. data/config/locales/bg.yml +723 -0
  370. data/config/locales/ca.yml +689 -0
  371. data/config/locales/cs.yml +855 -0
  372. data/config/locales/da.yml +771 -0
  373. data/config/locales/de-CH.yml +248 -0
  374. data/config/locales/de.yml +835 -0
  375. data/config/locales/el.yml +769 -0
  376. data/config/locales/en.yml +37 -503
  377. data/config/locales/es-CL.yml +301 -0
  378. data/config/locales/es-EC.yml +186 -0
  379. data/config/locales/es-MX.yml +334 -0
  380. data/config/locales/es.yml +831 -0
  381. data/config/locales/et.yml +644 -0
  382. data/config/locales/fa.yml +709 -0
  383. data/config/locales/fi.yml +771 -0
  384. data/config/locales/fr.yml +830 -0
  385. data/config/locales/hu.yml +751 -0
  386. data/config/locales/id.yml +756 -0
  387. data/config/locales/it.yml +833 -0
  388. data/config/locales/ja.yml +726 -0
  389. data/config/locales/km.yml +759 -0
  390. data/config/locales/ko.yml +575 -0
  391. data/config/locales/ku.yml +753 -0
  392. data/config/locales/lt.yml +821 -0
  393. data/config/locales/lv.yml +685 -0
  394. data/config/locales/nb.yml +824 -0
  395. data/config/locales/ne.yml +823 -0
  396. data/config/locales/nl.yml +826 -0
  397. data/config/locales/pl.yml +835 -0
  398. data/config/locales/pt-BR.yml +547 -0
  399. data/config/locales/pt.yml +636 -0
  400. data/config/locales/ro.yml +750 -0
  401. data/config/locales/ru.yml +834 -0
  402. data/config/locales/sk.yml +680 -0
  403. data/config/locales/sl-SI.yml +600 -0
  404. data/config/locales/sv.yml +827 -0
  405. data/config/locales/th.yml +745 -0
  406. data/config/locales/tr.yml +777 -0
  407. data/config/locales/uk.yml +836 -0
  408. data/config/locales/vi.yml +768 -0
  409. data/config/locales/zh-CN.yml +786 -0
  410. data/config/locales/zh-TW.yml +834 -0
  411. data/db/migrate/20260925000001_add_replacement_to_spree_fulfillment_items.rb +5 -0
  412. data/db/migrate/20261001000001_add_post_sale_tax.rb +30 -0
  413. data/db/migrate/20261001000001_convert_preferences_to_json.rb +166 -0
  414. data/db/migrate/20261001000001_create_spree_email_templates.rb +52 -0
  415. data/lib/generators/spree/dummy/dummy_generator.rb +0 -9
  416. data/lib/generators/spree/dummy/templates/rails/test.rb +7 -0
  417. data/lib/spree/checkout/default_requirements.rb +37 -13
  418. data/lib/spree/core/configuration.rb +1 -1
  419. data/lib/spree/core/controller_helpers/store.rb +1 -1
  420. data/lib/spree/core/dependencies.rb +5 -4
  421. data/lib/spree/core/emails/escaped_output.rb +60 -0
  422. data/lib/spree/core/engine.rb +48 -167
  423. data/lib/spree/core/permissions/default_catalog.rb +5 -0
  424. data/lib/spree/core/preferences/json_conversion.rb +182 -0
  425. data/lib/spree/core/preferences/masking.rb +4 -1
  426. data/lib/spree/core/preferences/preferable.rb +137 -83
  427. data/lib/spree/core/preferences/preferable_class_methods.rb +81 -146
  428. data/lib/spree/core/previews/import_preview.rb +10 -0
  429. data/lib/spree/core/tax/provider_error.rb +37 -0
  430. data/lib/spree/core/version.rb +1 -1
  431. data/lib/spree/core.rb +85 -370
  432. data/lib/spree/emails/editable_templates.rb +82 -0
  433. data/lib/spree/events/catalog.rb +189 -0
  434. data/lib/spree/events.rb +21 -1
  435. data/lib/spree/hooks.rb +0 -17
  436. data/lib/spree/i18n.rb +32 -15
  437. data/lib/spree/reporting/adapters/live.rb +6 -6
  438. data/lib/spree/reporting/default_vocabulary.rb +22 -10
  439. data/lib/spree/reporting/hydration.rb +1 -1
  440. data/lib/spree/reporting/query.rb +8 -0
  441. data/lib/spree/reporting/schema.rb +10 -10
  442. data/lib/spree/testing_support/editable_email_templates.rb +61 -0
  443. data/lib/spree/testing_support/emails.rb +22 -0
  444. data/lib/spree/testing_support/factories/cart_factory.rb +1 -1
  445. data/lib/spree/testing_support/factories/email_template_factory.rb +28 -0
  446. data/lib/spree/testing_support/factories/order_factory.rb +2 -2
  447. data/lib/spree/testing_support/factories/refund_factory.rb +1 -1
  448. data/lib/spree/testing_support/factories/stock_factory.rb +1 -1
  449. data/lib/spree/testing_support/factories/variant_factory.rb +1 -1
  450. data/lib/spree/testing_support/i18n.rb +3 -94
  451. data/lib/spree/testing_support/lifecycle_events.rb +1 -1
  452. data/lib/spree/testing_support/order_walkthrough.rb +1 -1
  453. data/lib/spree/testing_support/preferences.rb +0 -5
  454. data/lib/spree/testing_support/store.rb +9 -1
  455. data/lib/spree/upgrades/5_6_to_6_0/manifest.yml +45 -0
  456. data/lib/tasks/categories_and_collections.rake +3 -3
  457. data/lib/tasks/core.rake +6 -4
  458. data/lib/tasks/media.rake +3 -3
  459. data/lib/tasks/package_types.rake +1 -1
  460. data/lib/tasks/preferences_json.rake +101 -0
  461. data/spec/fixtures/mailer_views/custom_views_probe_mailer/welcome_email.html.erb +4 -0
  462. metadata +188 -44
  463. data/app/models/acts_as_taggable_on/tag_decorator.rb +0 -10
  464. data/app/models/concerns/spree/has_image_alt_text.rb +0 -18
  465. data/app/models/spree/base_analytics_event_handler.rb +0 -54
  466. data/app/models/spree/invitations/store.rb +0 -6
  467. data/app/models/spree/preference.rb +0 -14
  468. data/app/models/spree/promotion_handler/page.rb +0 -25
  469. data/app/models/spree/promotion_handler/promotion_duplicator.rb +0 -60
  470. data/app/models/spree/stock/differentiator.rb +0 -45
  471. data/app/presenters/spree/filters/options_presenter.rb +0 -47
  472. data/app/services/spree/carts/empty.rb +0 -42
  473. data/app/services/spree/country_to_timezone.rb +0 -273
  474. data/app/services/spree/payments/create.rb +0 -92
  475. data/app/validators/spree/url_validator.rb +0 -23
  476. data/app/views/active_storage/blobs/_blob.html.erb +0 -14
  477. data/app/views/spree/admin_user_mailer/password_reset_email.html.erb +0 -8
  478. data/app/views/spree/export_mailer/export_done.html.erb +0 -7
  479. data/app/views/spree/import_mailer/import_done.html.erb +0 -12
  480. data/app/views/spree/invitation_mailer/invitation_accepted.html.erb +0 -4
  481. data/app/views/spree/invitation_mailer/invitation_email.html.erb +0 -12
  482. data/app/views/spree/seller_user_mailer/password_reset_email.html.erb +0 -8
  483. data/app/views/spree/shared/_base_mailer_footer.html.erb +0 -19
  484. data/app/views/spree/shared/_base_mailer_header.html.erb +0 -8
  485. data/app/views/spree/shared/_base_mailer_stylesheets.html.erb +0 -399
  486. data/app/views/spree/shared/_mailer_logo.html.erb +0 -41
  487. data/app/views/spree/shared/_purchased_items_styles.html.erb +0 -119
  488. data/app/views/spree/webhook_mailer/endpoint_disabled.html.erb +0 -51
  489. data/app/views/spree/webhook_mailer/endpoint_disabled.text.erb +0 -10
  490. data/app/workflows/spree/fulfillments/purchase_label.rb +0 -40
  491. data/app/workflows/spree/returns/purchase_label.rb +0 -32
  492. data/lib/spree/analytics.rb +0 -11
  493. data/lib/spree/core/preferences/scoped_store.rb +0 -34
  494. data/lib/spree/core/preferences/store.rb +0 -105
  495. data/lib/spree/translation_migrations.rb +0 -52
@@ -1,36 +1,17 @@
1
- # Preferable allows defining preference accessor methods.
1
+ # Typed model preferences, each a Rails store accessor on the `preferences`
2
+ # JSON column — `secret_preferences` for a `:password` one (see
3
+ # {Spree::SecretPreferences}). Included by `Spree::Base`.
2
4
  #
3
- # A class including Preferable must implement #preferences which should return
4
- # an object responding to .fetch(key), []=(key, val), and .delete(key).
5
- #
6
- # The generated writer method performs typecasting before assignment into the
7
- # preferences object.
8
- #
9
- # Examples:
10
- #
11
- # # Spree::Base includes Preferable and defines preferences as a serialized
12
- # # column.
13
5
  # class Settings < Spree::Base
14
6
  # preference :color, :string, default: 'red'
15
7
  # preference :temperature, :integer, default: 21
16
8
  # end
17
9
  #
18
10
  # s = Settings.new
19
- # s.preferred_color # => 'red'
20
- # s.preferred_temperature # => 21
21
- #
22
- # s.preferred_color = 'blue'
23
- # s.preferred_color # => 'blue'
24
- #
25
- # # Typecasting is performed on assignment
26
- # s.preferred_temperature = '24'
27
- # s.preferred_temperature # => 24
28
- #
29
- # # Modifications have been made to the .preferences hash
30
- # s.preferences #=> {color: 'blue', temperature: 24}
31
- #
32
- # # Save the changes. All handled by activerecord
33
- # s.save!
11
+ # s.preferred_color # => 'red'
12
+ # s.preferred_temperature = '24' # cast on assignment
13
+ # s.preferred_temperature # => 24
14
+ # s.preferences # => { 'color' => 'red', 'temperature' => 24 }
34
15
 
35
16
  require 'spree/core/preferences/preferable_class_methods'
36
17
 
@@ -38,47 +19,56 @@ module Spree::Preferences::Preferable
38
19
  extend ActiveSupport::Concern
39
20
 
40
21
  included do
41
- serialize :preferences, type: Hash, coder: YAML if defined?(serialize)
22
+ serialize :preferences, coder: Spree::Metadata::HashSerializer
42
23
  extend Spree::Preferences::PreferableClassMethods
43
24
  end
44
25
 
26
+ # JSON keeps decimals and times as strings; the declared type turns them
27
+ # back into a BigDecimal and a time. Anything else, or a string that does
28
+ # not parse, comes back as stored.
29
+ #
30
+ # @param value [Object] a value as JSON stores it
31
+ # @param type [Symbol] the preference's declared type
32
+ # @return [Object]
33
+ def self.restore_value(value, type)
34
+ return value unless value.is_a?(String)
35
+
36
+ case type
37
+ when :decimal then BigDecimal(value, exception: false) || value
38
+ when :datetime then Time.zone.iso8601(value)
39
+ else value
40
+ end
41
+ rescue ArgumentError
42
+ value
43
+ end
44
+
45
45
  def get_preference(name)
46
46
  has_preference! name
47
- send self.class.preference_getter_method(name)
47
+ public_send(:"preferred_#{name}")
48
48
  end
49
49
 
50
50
  def set_preference(name, value)
51
51
  has_preference! name
52
- send self.class.preference_setter_method(name), value
52
+ public_send(:"preferred_#{name}=", value)
53
53
  end
54
54
 
55
55
  def preference_type(name)
56
- has_preference! name
57
- send self.class.preference_type_getter_method(name)
56
+ preference_definition(name)[:type]
58
57
  end
59
58
 
60
59
  def preference_default(name)
61
- has_preference! name
62
- send self.class.preference_default_getter_method(name)
60
+ instance_exec(&preference_definition(name)[:default])
63
61
  end
64
62
 
65
63
  def preference_deprecated(name)
66
- has_preference! name
67
- send(self.class.preference_deprecated_getter_method(name))
64
+ preference_definition(name)[:deprecated]
68
65
  end
69
66
 
70
67
  # Whether this preference is written by Spree rather than supplied by the
71
68
  # operator — a webhook secret a provider issues, say. Kept out of the
72
69
  # schema, so nothing offers it as a field to fill in.
73
70
  def preference_internal(name)
74
- has_preference! name
75
- getter = self.class.preference_internal_getter_method(name)
76
- # A preference declared before this option existed has no such reader.
77
- # Treated as not internal rather than raising, so one old declaration
78
- # cannot take a whole class's preference schema down with it.
79
- return false unless respond_to?(getter)
80
-
81
- send(getter)
71
+ preference_definition(name)[:internal]
82
72
  end
83
73
 
84
74
  # The fixed set this preference's value must come from, when it has one.
@@ -87,14 +77,8 @@ module Spree::Preferences::Preferable
87
77
  #
88
78
  # @return [Array, nil]
89
79
  def preference_choices(name)
90
- has_preference! name
91
- getter = self.class.preference_choices_getter_method(name)
92
- # A preference declared before this option existed has no such reader.
93
- # Treated as unconstrained rather than raising, so one old declaration
94
- # cannot take a whole class's preference schema down with it.
95
- return unless respond_to?(getter)
96
-
97
- send(getter)
80
+ choices = preference_definition(name)[:choices]
81
+ choices.respond_to?(:call) ? choices.call : choices
98
82
  end
99
83
 
100
84
  def has_preference!(name)
@@ -102,57 +86,125 @@ module Spree::Preferences::Preferable
102
86
  end
103
87
 
104
88
  def has_preference?(name)
105
- respond_to? self.class.preference_getter_method(name)
89
+ self.class.preference_definitions.key?(name.to_sym)
106
90
  end
107
91
 
108
92
  def defined_preferences
109
- # Only real `preference`-macro definitions carry a `_default` getter —
110
- # the name grep alone would also catch `preferred_*` association writers
111
- # (e.g. Order#preferred_stock_location=).
112
- methods.grep(/\Apreferred_.*=\Z/).filter_map do |pref_method|
113
- name = pref_method.to_s.gsub(/\Apreferred_|=\Z/, '').to_sym
114
- name if respond_to?(self.class.preference_default_getter_method(name))
115
- end
93
+ self.class.preference_definitions.keys
94
+ end
95
+
96
+ def default_preferences
97
+ defined_preferences.index_with { |name| preference_default(name) }
98
+ end
99
+
100
+ # Every preference value, secrets included — what `preferences` alone held
101
+ # before secrets moved to their own column. Declared preferences read
102
+ # through their `preferred_*` reader, so a default applies and decimals and
103
+ # times come back typed rather than in the string form JSON stores.
104
+ #
105
+ # @return [ActiveSupport::HashWithIndifferentAccess]
106
+ def preference_values
107
+ (preferences || {}).to_h.with_indifferent_access.merge(defined_preferences.index_with { |name| get_preference(name) })
116
108
  end
117
109
 
118
- def deprecated_preferences
119
- defined_preferences.each_with_object([]) do |pref_name, array|
120
- deprecated_message = preference_deprecated(pref_name)
121
- array << { name: pref_name, message: deprecated_message } unless deprecated_message.nil?
110
+ # The stored value of a preference, before its default applies. A secret
111
+ # found in `preferences` wins over the encrypted column: it is either a
112
+ # newer value assigned as part of a whole hash, waiting for the save that
113
+ # moves it across, or one the upgrade has not moved yet — the writer always
114
+ # removes that copy, so it is never stale.
115
+ #
116
+ # @param name [Symbol, String]
117
+ # @return [Object] the stored value, or the block's result when nothing is stored
118
+ def stored_preference(name)
119
+ if secret_preference?(name)
120
+ (preferences || {}).fetch(name) { (secret_preferences || {}).fetch(name) { return yield } }
121
+ else
122
+ (preferences || {}).fetch(name) { yield }
122
123
  end
123
124
  end
124
125
 
125
- def default_preferences
126
- Hash[
127
- defined_preferences.map do |preference|
128
- [preference, preference_default(preference)]
129
- end
130
- ]
126
+ # Fills in the declared default of every preference this record has no
127
+ # stored value for, so a preference added to the class after the row was
128
+ # saved never reads as missing and the raw `preferences` hash always holds
129
+ # every value. Secrets are left out: their defaults are read through the
130
+ # `preferred_*` reader, never copied into storage.
131
+ #
132
+ # Filling in a default is not a change to a stored record: a loaded record
133
+ # stays clean, so `with_lock` still accepts it, and the defaults are written
134
+ # with its next real save.
135
+ def backfill_default_preferences
136
+ secrets = self.class.secret_preference_names
137
+ missing = defined_preferences.reject { |name| preferences.key?(name) || secrets.include?(name) }
138
+ return if missing.empty?
139
+
140
+ already_changed = attribute_changed?(:preferences)
141
+ self.preferences = preferences.merge(missing.index_with { |name| stored_default(name) })
142
+ clear_attribute_change(:preferences) if persisted? && !already_changed
131
143
  end
132
144
 
133
- def preferences_of_type(type)
134
- defined_preferences.find_all { |preference| preference_type(preference) == type.to_sym }
145
+ # Names of the preferences the last save changed, secrets included.
146
+ #
147
+ # @return [Array<Symbol>]
148
+ def previously_changed_preference_names
149
+ defined_preferences.select { |name| public_send(:"saved_change_to_preferred_#{name}?") }
135
150
  end
136
151
 
137
- def clear_preferences
138
- preferences.keys.each { |pref| preferences.delete pref }
152
+ private
153
+
154
+ def preference_definition(name)
155
+ has_preference! name
156
+ self.class.preference_definitions[name.to_sym]
139
157
  end
140
158
 
141
- def restore_preferences_for(preference_keys)
142
- preference_keys.each { |pref| preferences[pref] = preference_default(pref) }
159
+ # A declared default in the form JSON stores, as the writer would store it,
160
+ # so a decimal default reads back as a BigDecimal and setting the same
161
+ # amount is not a change.
162
+ def stored_default(name)
163
+ default = preference_default(name)
164
+ type = preference_type(name)
165
+ return default if default.nil? || %i[decimal datetime].exclude?(type)
166
+
167
+ convert_preference_value(default, type, nullable: true).as_json
143
168
  end
144
169
 
145
- def preference_change(name, changes_or_previous_changes)
146
- preference_changes = changes_or_previous_changes.with_indifferent_access.fetch('preferences', [{}, {}])
147
- before_preferences = preference_changes[0] || {}
148
- after_preferences = preference_changes[1] || {}
170
+ # Only a class that stores secrets can declare one, so the names say it all.
171
+ def secret_preference?(name)
172
+ self.class.secret_preference_names.include?(name.to_sym)
173
+ end
149
174
 
150
- return if before_preferences[name] == after_preferences[name]
175
+ def cast_preference(name, value)
176
+ definition = preference_definition(name)
177
+ parse_on_set = definition[:parse_on_set]
178
+ if parse_on_set.is_a?(Proc)
179
+ # Procs that accept more than one arg opt into receiving the owning
180
+ # record so they can scope (e.g. `normalize_id_preference` rejecting
181
+ # cross-store IDs). `arity.abs > 1` covers both the `(value, owner)` and
182
+ # `(value, owner = nil)` shapes.
183
+ value = parse_on_set.arity.abs > 1 ? parse_on_set.call(value, self) : parse_on_set.call(value)
184
+ end
185
+ value = convert_preference_value(value, definition[:type], nullable: definition[:nullable])
186
+ # Decimals and times are kept in the string form JSON stores, so a record
187
+ # read back from the database and one just written compare equal and
188
+ # setting the same value is not a change. The reader restores them.
189
+ value = value.as_json if %i[decimal datetime].include?(definition[:type])
151
190
 
152
- [before_preferences[name], after_preferences[name]]
191
+ Spree::Deprecation.warn("`#{name}` is deprecated. #{definition[:deprecated]}") if definition[:deprecated]
192
+ value
153
193
  end
154
194
 
155
- private
195
+ # Assigns a new hash rather than calling the store accessor's writer, which
196
+ # skips writing nil to a missing key — leaving the default in force when nil
197
+ # was meant. Rails still sees no change when the value is the one stored.
198
+ def write_preference(name, value)
199
+ if secret_preference?(name)
200
+ # A copy assigned as part of a whole hash would otherwise outlive the
201
+ # value written here and be moved over it on save.
202
+ self.preferences = preferences.except(name) if preferences&.key?(name)
203
+ self.secret_preferences = (secret_preferences || {}).with_indifferent_access.merge(name => value)
204
+ else
205
+ self.preferences = (preferences || {}).with_indifferent_access.merge(name => value)
206
+ end
207
+ end
156
208
 
157
209
  def convert_preference_value(value, type, nullable: false)
158
210
  case type
@@ -195,6 +247,8 @@ module Spree::Preferences::Preferable
195
247
  case value.class.to_s
196
248
  when 'Hash'
197
249
  value
250
+ when 'ActionController::Parameters'
251
+ value.to_h
198
252
  when 'String'
199
253
  # only works with hashes whose keys are strings
200
254
  JSON.parse value.gsub('=>', ':')
@@ -221,7 +275,7 @@ module Spree::Preferences::Preferable
221
275
  value.to_time
222
276
  end
223
277
  # Kept as the `yyyy-MM-dd` string it arrived as rather than coerced to a
224
- # Time. Preferences are YAML-serialized, and what a date *means* depends on
278
+ # Time. Preferences are stored as JSON, and what a date *means* depends on
225
279
  # the store's timezone, which this method has no access to: coercing here
226
280
  # would freeze the value against the server's zone and move the operator's
227
281
  # deadline by hours. The reader owns that (see
@@ -1,169 +1,104 @@
1
1
  module Spree::Preferences
2
2
  module PreferableClassMethods
3
- # Declaration order, so an admin form can present preferences the way
4
- # their author grouped them — credentials before the optional settings
5
- # that depend on them. `defined_preferences` reads Ruby's own `methods`,
6
- # whose order is an implementation detail, so it cannot answer this.
7
- def declared_preference_order
8
- @declared_preference_order ||= begin
9
- inherited = superclass.respond_to?(:declared_preference_order) ? superclass.declared_preference_order : []
10
- inherited.dup
3
+ # Bumped by every declaration anywhere, so a class's cached view of its
4
+ # preferences is rebuilt when an ancestor gains one after the class was
5
+ # loaded — a decorator adding a secret to Spree::PaymentMethod, say.
6
+ @declarations = 0
7
+
8
+ class << self
9
+ attr_accessor :declarations
10
+ end
11
+
12
+ # Every preference the class declares, inherited ones included, in
13
+ # declaration order — so an admin form can present them the way their
14
+ # author grouped them.
15
+ #
16
+ # @return [Hash{Symbol => Hash}] name => `{ type:, default:, deprecated:, internal:, choices:, nullable:, parse_on_set: }`
17
+ def preference_definitions
18
+ preference_declarations_cache[:definitions] ||= begin
19
+ inherited = superclass.respond_to?(:preference_definitions) ? superclass.preference_definitions : {}
20
+ inherited.merge(own_preference_definitions).freeze
11
21
  end
12
22
  end
13
23
 
24
+ # Each declared preference's type, known from the declarations alone —
25
+ # without building a record.
26
+ #
27
+ # @return [Hash{Symbol => Symbol}]
28
+ def declared_preference_types
29
+ preference_declarations_cache[:types] ||= preference_definitions.transform_values { |definition| definition[:type] }.freeze
30
+ end
31
+
32
+ # Names of the `:password` preferences, which a model including
33
+ # {Spree::SecretPreferences} stores encrypted in `secret_preferences`.
34
+ #
35
+ # @return [Array<Symbol>]
36
+ def secret_preference_names
37
+ preference_declarations_cache[:secrets] ||= declared_preference_types.filter_map { |name, type| name if type == :password }.freeze
38
+ end
39
+
40
+ # Declares a typed preference with a `preferred_<name>` reader and writer
41
+ # and a `prefers_<name>?` query. On a model, the value lives in the
42
+ # `preferences` column (`secret_preferences` for a `:password` one) as a
43
+ # Rails store accessor, which brings the change methods with it:
44
+ # `preferred_<name>_changed?`, `_change`, `_was`,
45
+ # `saved_change_to_preferred_<name>?` and `preferred_<name>_before_last_save`.
14
46
  def preference(name, type, *args)
15
- declared_preference_order << name.to_sym unless declared_preference_order.include?(name.to_sym)
47
+ name = name.to_sym
48
+ secret = type == :password
49
+ if secret && !include?(Spree::SecretPreferences)
50
+ raise ArgumentError, "#{self.name} declares the secret preference `#{name}` but cannot encrypt it. " \
51
+ 'Include Spree::SecretPreferences and add a `secret_preferences` text column to its table.'
52
+ end
53
+
16
54
  options = args.extract_options!
17
55
  options.assert_valid_keys(:default, :deprecated, :in, :internal, :nullable, :parse_on_set)
18
56
  default = options[:default]
19
57
  default = -> { options[:default] } unless default.is_a?(Proc)
20
- deprecated = options[:deprecated]
21
- internal = options[:internal]
22
- # The fixed set a value must come from. Turns a text box into a picker
23
- # in every admin form, and is what the inclusion validation would have
24
- # told the operator only after a failed save.
25
- choices = options[:in]
26
- nullable = options[:nullable]
27
- parse_on_set = options[:parse_on_set]
28
-
29
- # cache_key will be nil for new objects, then if we check if there
30
- # is a pending preference before going to default
31
- define_method preference_getter_method(name) do
32
- # Rows persisted before the model gained preferences can load with a
33
- # NULL column — treat that as "no stored preferences".
34
- (preferences || {}).fetch(name) do
35
- default.call
36
- end
37
- end
38
-
39
- define_method preference_setter_method(name) do |value|
40
- if parse_on_set.is_a?(Proc)
41
- # Procs that accept more than one arg opt into receiving the
42
- # owning record so they can scope (e.g. `normalize_id_preference`
43
- # rejecting cross-store IDs). `arity.abs > 1` covers both the
44
- # `(value, owner)` and `(value, owner = nil)` shapes.
45
- value = parse_on_set.arity.abs > 1 ? parse_on_set.call(value, self) : parse_on_set.call(value)
46
- end
47
- value = convert_preference_value(value, type, nullable: nullable)
48
- self.preferences = {} if preferences.nil?
49
- preferences[name] = value
50
-
51
- Spree::Deprecation.warn("`#{name}` is deprecated. #{deprecated}") if deprecated
52
-
53
- # If this is an activerecord object, we need to inform
54
- # ActiveRecord::Dirty that this value has changed, since this is an
55
- # in-place update to the preferences hash.
56
- preferences_will_change! if respond_to?(:preferences_will_change!)
57
- end
58
-
59
- define_method preference_default_getter_method(name), &default
60
-
61
- define_method preference_type_getter_method(name) do
62
- type
63
- end
64
-
65
- define_method preference_deprecated_getter_method(name) do
66
- deprecated
67
- end
68
-
69
- # Whether this preference is the system's to write rather than the
70
- # operator's to supply — a value a provider hands back to us, not one
71
- # anybody could know in advance.
72
- define_method preference_internal_getter_method(name) do
73
- internal
74
- end
75
-
76
- define_method preference_choices_getter_method(name) do
77
- choices.respond_to?(:call) ? choices.call : choices
78
- end
79
-
80
- define_method prefers_query_method(name) do
81
- preferences.fetch(name).to_b
82
- end
83
-
84
- define_method preference_change_method(name) do
85
- preference_change(name, changes) if respond_to?(:changes)
86
- end
87
-
88
- define_method preference_was_method(name) do
89
- return unless respond_to?(:changes)
90
-
91
- preference_change(name, changes)&.first || get_preference(name)
92
- end
93
-
94
- define_method preference_changed_method(name) do
95
- respond_to?(:changes) && preference_change(name, changes).present?
96
- end
97
-
98
- define_method preference_previous_change_method(name) do
99
- preference_change(name, previous_changes) if respond_to?(:previous_changes)
58
+ own_preference_definitions[name] = {
59
+ type: type,
60
+ default: default,
61
+ deprecated: options[:deprecated],
62
+ # Whether the system writes the value rather than the operator — a
63
+ # value a provider hands back, kept out of every admin form.
64
+ internal: options[:internal],
65
+ # The fixed set a value must come from; turns a text box into a picker.
66
+ choices: options[:in],
67
+ nullable: options[:nullable],
68
+ parse_on_set: options[:parse_on_set]
69
+ }
70
+ PreferableClassMethods.declarations += 1
71
+
72
+ store_accessor(secret ? :secret_preferences : :preferences, name, prefix: :preferred)
73
+
74
+ # Overrides the store accessor's reader and writer, which treat a missing
75
+ # key as nil; here a missing key means the declared default.
76
+ define_method(:"preferred_#{name}") do
77
+ Spree::Preferences::Preferable.restore_value(stored_preference(name) { stored_default(name) }, type)
100
78
  end
101
79
 
102
- define_method preference_previous_was_method(name) do
103
- return unless respond_to?(:previous_changes)
104
-
105
- preference_change(name, previous_changes)&.first
80
+ define_method(:"preferred_#{name}=") do |value|
81
+ write_preference(name, cast_preference(name, value))
106
82
  end
107
83
 
108
- define_method preference_previous_changed_method(name) do
109
- respond_to?(:previous_changes) && preference_change(name, previous_changes).present?
84
+ define_method(:"prefers_#{name}?") do
85
+ stored_preference(name) { raise KeyError, "key not found: #{name.inspect}" }.to_b
110
86
  end
111
87
  end
112
88
 
113
- def preference_getter_method(name)
114
- "preferred_#{name}".to_sym
115
- end
116
-
117
- def preference_setter_method(name)
118
- "preferred_#{name}=".to_sym
119
- end
120
-
121
- def preference_default_getter_method(name)
122
- "preferred_#{name}_default".to_sym
123
- end
124
-
125
- def preference_deprecated_getter_method(name)
126
- "preferred_#{name}_deprecated".to_sym
127
- end
128
-
129
- def preference_internal_getter_method(name)
130
- "preferred_#{name}_internal".to_sym
131
- end
132
-
133
- def preference_choices_getter_method(name)
134
- "preferred_#{name}_choices".to_sym
135
- end
136
-
137
- def preference_type_getter_method(name)
138
- "preferred_#{name}_type".to_sym
139
- end
140
-
141
- def prefers_query_method(name)
142
- "prefers_#{name}?".to_sym
143
- end
144
-
145
- def preference_change_method(name)
146
- "preferred_#{name}_change".to_sym
147
- end
89
+ private
148
90
 
149
- def preference_was_method(name)
150
- "preferred_#{name}_was".to_sym
91
+ def own_preference_definitions
92
+ @own_preference_definitions ||= {}
151
93
  end
152
94
 
153
- def preference_changed_method(name)
154
- "preferred_#{name}_changed?".to_sym
155
- end
156
-
157
- def preference_previous_change_method(name)
158
- "preferred_#{name}_previous_change".to_sym
159
- end
160
-
161
- def preference_previous_was_method(name)
162
- "preferred_#{name}_previously_was".to_sym
163
- end
164
-
165
- def preference_previous_changed_method(name)
166
- "preferred_#{name}_previously_changed?".to_sym
95
+ def preference_declarations_cache
96
+ generation = PreferableClassMethods.declarations
97
+ unless @preference_declarations_generation == generation
98
+ @preference_declarations_generation = generation
99
+ @preference_declarations_cache = {}
100
+ end
101
+ @preference_declarations_cache
167
102
  end
168
103
  end
169
104
  end
@@ -0,0 +1,10 @@
1
+ require_relative 'preview_data'
2
+
3
+ # Preview Spree import emails at /rails/mailers/spree/import
4
+ class Spree::ImportPreview < ActionMailer::Preview
5
+ include Spree::PreviewData::LocaleParam
6
+
7
+ def import_done
8
+ Spree::ImportMailer.import_done(Spree::Import.where.not(user_id: nil).last)
9
+ end
10
+ end
@@ -0,0 +1,37 @@
1
+ module Spree
2
+ module Tax
3
+ # A tax engine did not produce tax for a sale. Providers raise one of the
4
+ # two subclasses below; core rescues this and refuses the sale rather than
5
+ # completing one whose tax nobody computed — charging nothing because nobody
6
+ # could be asked is the error a merchant discovers in a filing.
7
+ #
8
+ # Split because a customer can only act on one of them. Retrying an outage
9
+ # may work; retrying a postcode the engine rejects never will, and telling
10
+ # someone to try again is worse than telling them nothing.
11
+ #
12
+ # The parallel is {Spree::Pricing::PriceResolution::ProviderUnavailable},
13
+ # which refuses a sale whose prices could not be confirmed. Neither is for a
14
+ # provider a merchant switched off: that is configuration, not failure, and
15
+ # an unconfigured engine simply has no opinion and writes no rows.
16
+ class ProviderError < StandardError
17
+ # @return [String, nil] which engine failed, for the report
18
+ attr_reader :provider_key
19
+
20
+ def initialize(message = nil, provider_key: nil)
21
+ @provider_key = provider_key
22
+ super(message)
23
+ end
24
+ end
25
+
26
+ # The engine could not be asked — unreachable, timed out, or answering with
27
+ # a fault of its own. Nothing is known about the tax, and trying again
28
+ # later may well succeed, so the customer is told to retry.
29
+ class ProviderUnavailable < ProviderError; end
30
+
31
+ # The engine was asked and said no: an address it will not accept, a
32
+ # misconfigured company code, a request it rejects. The service is working,
33
+ # so retrying changes nothing — what the engine said is the useful part and
34
+ # core passes its message on rather than inventing one.
35
+ class CalculationRefused < ProviderError; end
36
+ end
37
+ end
@@ -1,5 +1,5 @@
1
1
  module Spree
2
- VERSION = '6.0.0.beta4'.freeze
2
+ VERSION = '6.0.0.rc1'.freeze
3
3
 
4
4
  def self.version
5
5
  VERSION