spree_core 6.0.0.beta3 → 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
@@ -4,11 +4,10 @@ module Spree
4
4
  # (Mobility-backed content such as product names, descriptions, taxons,
5
5
  # option values, etc.).
6
6
  #
7
- # This is deliberately **independent of the admin/storefront UI translation
8
- # set** (`Spree.available_locales`, which reflects which `spree_i18n` UI
9
- # bundles happen to be installed). In a headless setup the storefront ships
10
- # its own UI translations, so the languages a merchant can store content in
11
- # must not be constrained by which admin chrome translations exist.
7
+ # This is deliberately **independent of the languages Spree ships
8
+ # translations for** (`Spree.available_locales`). In a headless setup the
9
+ # storefront ships its own UI translations, so the languages a merchant can
10
+ # store content in must not be constrained by Spree's own translations.
12
11
  #
13
12
  # Codes follow BCP-47 casing (lowercase language, uppercase region —
14
13
  # `pt-BR`, `zh-CN`) so the browser's `Intl.DisplayNames` resolves a clean
@@ -26,7 +26,7 @@ module Spree
26
26
  end
27
27
  end
28
28
 
29
- subscriber.publish_event('newsletter_subscriber.subscription_requested', subscription_requested_payload) unless subscriber.verified?
29
+ publish_subscription_requested unless subscriber.verified?
30
30
  subscriber
31
31
  end
32
32
 
@@ -34,17 +34,13 @@ module Spree
34
34
 
35
35
  attr_reader :email, :current_user, :current_store, :redirect_url
36
36
 
37
- def subscription_requested_payload
38
- payload = {
39
- id: subscriber.prefixed_id,
40
- email: subscriber.email,
41
- verification_token: subscriber.verification_token,
42
- unsubscribe_token: subscriber.generate_token_for(:unsubscribe),
43
- store_id: current_store.prefixed_id,
44
- customer_id: subscriber.customer&.prefixed_id
45
- }
46
- payload[:redirect_url] = redirect_url if redirect_url.present?
47
- payload
37
+ def publish_subscription_requested
38
+ event_name = 'newsletter_subscriber.subscription_requested'
39
+ payload = subscriber.event_payload_for(
40
+ event_name,
41
+ include_verification_token: true, store: current_store, redirect_url: redirect_url.presence
42
+ )
43
+ subscriber.publish_event(event_name, payload)
48
44
  end
49
45
 
50
46
  def upsert_subscriber
@@ -17,13 +17,14 @@ module Spree
17
17
  class BuildFulfillments
18
18
  prepend Spree::ServiceModule::Base
19
19
 
20
- def call(order:)
20
+ # @param keep_selection [Boolean] false when the destination changed
21
+ def call(order:, keep_selection: true)
21
22
  return success(order) if order.completed?
22
23
  return success(order) unless order.ship_address_id.present?
23
24
  return success(order) unless order.line_items.any?
24
25
  return success(order) unless order.delivery_step_required?
25
26
 
26
- order.rebuild_fulfillments!
27
+ order.rebuild_fulfillments!(keep_selection: keep_selection)
27
28
  order.create_shipment_tax_charge!
28
29
  order.set_fulfillments_cost
29
30
  order.apply_free_shipping_promotions
@@ -49,7 +49,7 @@ module Spree
49
49
  # ActiveStorage raises a message-less error for that, which would
50
50
  # otherwise be an unhandled 500 on the Admin API.
51
51
  order ||= @store.orders.new
52
- order.errors.add(:po_document, :po_document_upload_incomplete, message: Spree.t(:po_document_upload_incomplete))
52
+ order.errors.add(:po_document, :po_document_upload_incomplete, message: I18n.t('spree.po_document_upload_incomplete'))
53
53
  failure(order, order.errors.full_messages.to_sentence)
54
54
  end
55
55
 
@@ -170,6 +170,7 @@ module Spree
170
170
  coupon_code: @params[:coupon_code]
171
171
  }
172
172
  order.coupon_code = nil
173
+ handler.release_stale_coupon_codes
173
174
  end
174
175
  end
175
176
  end
@@ -18,16 +18,19 @@ module Spree
18
18
  # @return [Spree::ServiceModule::Result] value is the created rows
19
19
  def call(order:, label:, value:, value_type: 'flat', line_item: nil)
20
20
  value = BigDecimal(value.to_s)
21
- return failure(nil, Spree.t('errors.messages.discount_value_must_be_positive')) unless value.positive?
22
- return failure(nil, Spree.t('errors.messages.discount_value_type_invalid')) unless %w[flat percent].include?(value_type)
21
+ return failure(nil, I18n.t('spree.errors.messages.discount_value_must_be_positive')) unless value.positive?
22
+ return failure(nil, I18n.t('spree.errors.messages.discount_value_type_invalid')) unless %w[flat percent].include?(value_type)
23
23
 
24
24
  rows = order.with_lock do
25
25
  created = line_item ? [line_item_row(order, line_item, label, value, value_type)].compact : distributed_rows(order, label, value, value_type)
26
- Spree.order_recalculate_totals_workflow.call(order: order) if created.any?
26
+ if created.any?
27
+ Spree.order_recalculate_totals_workflow.call(order: order)
28
+ order.update_statuses!
29
+ end
27
30
  created
28
31
  end
29
32
 
30
- return failure(nil, Spree.t('errors.messages.discount_has_no_effect')) if rows.empty?
33
+ return failure(nil, I18n.t('spree.errors.messages.discount_has_no_effect')) if rows.empty?
31
34
 
32
35
  success(rows)
33
36
  end
@@ -15,6 +15,7 @@ module Spree
15
15
  order.with_lock do
16
16
  discount.destroy!
17
17
  Spree.order_recalculate_totals_workflow.call(order: order)
18
+ order.update_statuses!
18
19
  end
19
20
 
20
21
  success(discount)
@@ -18,6 +18,7 @@ module Spree
18
18
  next false unless discount.update(attributes)
19
19
 
20
20
  Spree.order_recalculate_totals_workflow.call(order: order)
21
+ order.update_statuses!
21
22
  true
22
23
  end
23
24
 
@@ -17,6 +17,7 @@ module Spree
17
17
  next false unless fee.save
18
18
 
19
19
  Spree.order_recalculate_totals_workflow.call(order: order)
20
+ order.update_statuses!
20
21
  true
21
22
  end
22
23
 
@@ -12,6 +12,7 @@ module Spree
12
12
  order.with_lock do
13
13
  fee.destroy!
14
14
  Spree.order_recalculate_totals_workflow.call(order: order)
15
+ order.update_statuses!
15
16
  end
16
17
 
17
18
  success(fee)
@@ -14,6 +14,7 @@ module Spree
14
14
  next false unless fee.update(attributes)
15
15
 
16
16
  Spree.order_recalculate_totals_workflow.call(order: order)
17
+ order.update_statuses!
17
18
  true
18
19
  end
19
20
 
@@ -1,12 +1,22 @@
1
1
  module Spree
2
2
  module Orders
3
- # Draft-order twin of the cart service — order: is the canonical keyword
4
- # on this side, delegating onto the shared implementation.
3
+ # Draft-order twin of the cart service. Removing an item is an upsert with
4
+ # quantity zero, so the removal passes the same 'orders.upsert_items.validate'
5
+ # hook every other draft-order item edit does.
5
6
  class RemoveLineItem
6
7
  prepend Spree::ServiceModule::Base
7
8
 
9
+ # @param order [Spree::Order]
10
+ # @param line_item [Spree::LineItem]
11
+ # @param options [Hash] accepted for signature compatibility and ignored
12
+ # @return [Spree::ServiceModule::Result] value is the removed line item
8
13
  def call(order:, line_item:, options: {})
9
- Spree::Carts::RemoveLineItem.call(cart: order, line_item: line_item, options: options)
14
+ result = Spree.order_upsert_items_workflow.call(
15
+ order: order,
16
+ items: [{ variant_id: line_item.variant_id, quantity: 0 }]
17
+ )
18
+
19
+ result.success? ? success(line_item) : failure(line_item, result.error)
10
20
  end
11
21
  end
12
22
  end
@@ -15,7 +15,10 @@ module Spree
15
15
  items_param = @params.delete(:items)
16
16
  address_params = extract_address_params
17
17
 
18
- ApplicationRecord.transaction do
18
+ # requires_new: the rescue below sits outside the block, so joining a
19
+ # caller's open transaction (the API's order lock) would let it commit
20
+ # the half-applied edit.
21
+ ApplicationRecord.transaction(requires_new: true) do
19
22
  ship_address_id_before = @order.ship_address_id
20
23
  assign_addresses(address_params)
21
24
 
@@ -25,11 +28,12 @@ module Spree
25
28
 
26
29
  process_items(items_param) if items_param
27
30
 
28
- if items_param || @order.ship_address_id != ship_address_id_before
29
- build_fulfillments
30
- end
31
+ # An address row edited in place keeps its id, so an edit to the fields a quote reads counts too.
32
+ destination_changed = @order.ship_address_id != ship_address_id_before || @order.ship_address&.saved_change_to_destination?
33
+ build_fulfillments(keep_selection: !destination_changed) if items_param || destination_changed
31
34
 
32
35
  @order.recalculate_totals!
36
+ @order.update_statuses!
33
37
  end
34
38
 
35
39
  success(@order.reload)
@@ -70,8 +74,8 @@ module Spree
70
74
  propagate_step_failure!(result, fallback: 'Failed to update items on order') if result.failure?
71
75
  end
72
76
 
73
- def build_fulfillments
74
- result = Spree::Orders::BuildFulfillments.call(order: @order)
77
+ def build_fulfillments(keep_selection:)
78
+ result = Spree::Orders::BuildFulfillments.call(order: @order, keep_selection: keep_selection)
75
79
  propagate_step_failure!(result, fallback: 'Failed to build shipments') if result.failure?
76
80
  end
77
81
 
@@ -21,7 +21,7 @@ module Spree
21
21
  price_provided = !price.equal?(PRICE_NOT_PROVIDED)
22
22
 
23
23
  if price_provided && order.completed?
24
- return reject_price(line_item, :price_override_not_allowed, Spree.t('cart_line_item.price_override_not_allowed'))
24
+ return reject_price(line_item, :price_override_not_allowed, I18n.t('spree.cart_line_item.price_override_not_allowed'))
25
25
  end
26
26
 
27
27
  attributes = {}
@@ -30,7 +30,7 @@ module Spree
30
30
 
31
31
  if price_provided && !price.nil?
32
32
  manual_price = parse_manual_price(price)
33
- return reject_price(line_item, :invalid_price, Spree.t('cart_line_item.invalid_price')) if manual_price.nil?
33
+ return reject_price(line_item, :invalid_price, I18n.t('spree.cart_line_item.invalid_price')) if manual_price.nil?
34
34
 
35
35
  attributes[:price] = manual_price
36
36
  attributes[:price_source] = Spree::LineItem::MANUAL_PRICE_SOURCE
@@ -47,7 +47,10 @@ module Spree
47
47
  ActiveRecord::Base.transaction do
48
48
  return failure(line_item) unless line_item.update(attributes)
49
49
 
50
- Spree::Orders::Recalculate.call(order: order, line_item: line_item) if quantity_changing || price_provided
50
+ if quantity_changing || price_provided
51
+ Spree::Orders::Recalculate.call(order: order, line_item: line_item)
52
+ order.update_statuses!
53
+ end
51
54
  end
52
55
 
53
56
  success(line_item)
@@ -4,7 +4,9 @@ module Spree
4
4
  # Derive-then-persist: statuses are recomputed from payment/refund/
5
5
  # fulfillment records and stored in indexed columns so admin filtering
6
6
  # keeps working. Triggered from payment/refund/fulfillment/return event
7
- # subscribers — never inline from controllers.
7
+ # subscribers, and by each edit service that moves an order's total
8
+ # (fees, discounts, items) once it has re-summed — no event announces
9
+ # that change. Never inline from controllers.
8
10
  class UpdateStatuses
9
11
  prepend Spree::ServiceModule::Base
10
12
 
@@ -0,0 +1,61 @@
1
+ module Spree
2
+ # Bulk adding and removing products on categories or collections. The
3
+ # including service names the join model (`membership_class`), the grouping
4
+ # it belongs to (`group_class`, `group_key`) and refreshes the grouping's
5
+ # own counters in `refresh_group_counters`.
6
+ module ProductMemberships
7
+ private
8
+
9
+ def add_memberships(groups, products)
10
+ rows = groups.pluck(:id).flat_map do |group_id|
11
+ position = membership_class.where(group_key => group_id).count
12
+ products.pluck(:id).map do |product_id|
13
+ {
14
+ group_key => group_id, product_id: product_id, position: (position += 1),
15
+ created_at: Time.current, updated_at: Time.current
16
+ }
17
+ end
18
+ end
19
+ membership_class.insert_all(rows)
20
+
21
+ refresh_after_membership_change(groups.pluck(:id), products)
22
+ end
23
+
24
+ # Removes the products, then re-packs the remaining positions from 1.
25
+ def remove_memberships(groups, products)
26
+ group_ids = groups.pluck(:id)
27
+
28
+ ApplicationRecord.transaction do
29
+ membership_class.where(group_key => group_ids, product_id: products.pluck(:id)).delete_all
30
+
31
+ rows = group_ids.flat_map do |group_id|
32
+ membership_class.where(group_key => group_id).order(:position).pluck(:product_id).map.with_index(1) do |product_id, position|
33
+ {
34
+ group_key => group_id, product_id: product_id, position: position,
35
+ created_at: Time.current, updated_at: Time.current
36
+ }
37
+ end
38
+ end
39
+ membership_class.upsert_all(rows, unique_by: ([group_key, :product_id] unless Spree.mysql?)) if rows.any?
40
+ end
41
+
42
+ refresh_after_membership_change(group_ids, products)
43
+ end
44
+
45
+ # Bulk writes skip the join model's callbacks, so counters, caches and the
46
+ # search index are refreshed here.
47
+ def refresh_after_membership_change(group_ids, products)
48
+ product_ids = products.pluck(:id)
49
+ product_ids.each { |id| Spree::Product.reset_counters(id, membership_class.model_name.element.pluralize.to_sym) }
50
+ refresh_group_counters(group_ids)
51
+
52
+ Spree::Product.where(id: product_ids).touch_all
53
+ products.each(&:enqueue_search_index)
54
+ group_class.where(id: group_ids).touch_all
55
+ end
56
+
57
+ def group_key
58
+ group_class.model_name.element.foreign_key.to_sym
59
+ end
60
+ end
61
+ end
@@ -32,7 +32,7 @@ module Spree
32
32
  workflow.errors.add(
33
33
  :base,
34
34
  :outside_return_window,
35
- message: Spree.t('return_errors.outside_window', days: window)
35
+ message: I18n.t('spree.return_errors.outside_window', days: window)
36
36
  )
37
37
  workflow.reject!
38
38
  end
@@ -142,9 +142,9 @@ module Spree
142
142
  def sort_option_label(name, field_type, direction)
143
143
  suffix =
144
144
  if field_type == 'number'
145
- direction == :asc ? Spree.t(:sort_low_to_high) : Spree.t(:sort_high_to_low)
145
+ direction == :asc ? I18n.t('spree.sort_low_to_high') : I18n.t('spree.sort_high_to_low')
146
146
  else
147
- direction == :asc ? Spree.t(:sort_a_to_z) : Spree.t(:sort_z_to_a)
147
+ direction == :asc ? I18n.t('spree.sort_a_to_z') : I18n.t('spree.sort_z_to_a')
148
148
  end
149
149
  "#{name} (#{suffix})"
150
150
  end
@@ -35,7 +35,7 @@ module Spree
35
35
  return if store.commission_rates.exists?(code: DEFAULT_CODE)
36
36
 
37
37
  rate = store.commission_rates.create!(
38
- name: Spree.t('seed.commission_rates.marketplace_default'),
38
+ name: I18n.t('spree.seed.commission_rates.marketplace_default'),
39
39
  code: DEFAULT_CODE,
40
40
  kind: 'percentage',
41
41
  value: 0,
@@ -12,7 +12,7 @@ module Spree
12
12
  profile = Spree::DeliveryProfiles::Digital.find_by(store: store) ||
13
13
  Spree::DeliveryProfiles::Digital.create!(store: store, name: I18n.t('spree.seed.delivery_profiles.digital'))
14
14
 
15
- digital_delivery_method = Spree::DeliveryMethod.find_or_initialize_by(name: Spree.t('digital.digital_delivery'), store: store)
15
+ digital_delivery_method = Spree::DeliveryMethod.find_or_initialize_by(name: I18n.t('spree.digital.digital_delivery'), store: store)
16
16
 
17
17
  digital_delivery_method.delivery_profile = profile
18
18
  digital_delivery_method.storefront_visible = true
@@ -12,8 +12,8 @@ module Spree
12
12
  )
13
13
  return if payment_method.persisted?
14
14
 
15
- payment_method.name = Spree.t(:store_credit_name)
16
- payment_method.description = Spree.t(:store_credit_name)
15
+ payment_method.name = I18n.t('spree.store_credit_name')
16
+ payment_method.description = I18n.t('spree.store_credit_name')
17
17
  payment_method.active = true
18
18
  payment_method.save!
19
19
  end
@@ -86,8 +86,8 @@ module Spree
86
86
  next if known_names(report[:key]).intersect?(existing)
87
87
 
88
88
  store.saved_reports.create!(
89
- name: Spree.t("reporting.seeds.#{report[:key]}.name"),
90
- description: Spree.t("reporting.seeds.#{report[:key]}.description"),
89
+ name: I18n.t("spree.reporting.seeds.#{report[:key]}.name"),
90
+ description: I18n.t("spree.reporting.seeds.#{report[:key]}.description"),
91
91
  query: report[:query],
92
92
  seeded: true
93
93
  )
@@ -98,7 +98,7 @@ module Spree
98
98
  def known_names(key)
99
99
  @known_names ||= {}
100
100
  @known_names[key] ||= Spree.available_locales.filter_map do |locale|
101
- Spree.t("reporting.seeds.#{key}.name", locale: locale, default: nil)&.downcase
101
+ I18n.t("spree.reporting.seeds.#{key}.name", locale: locale, default: nil)&.downcase
102
102
  end.to_set
103
103
  end
104
104
  end
@@ -61,8 +61,7 @@ module Spree
61
61
  def record_blocking(errors)
62
62
  blocking.each do |requirement|
63
63
  errors.add(:requirements, :incomplete,
64
- message: Spree.t('seller_requirements.incomplete', name: requirement.name,
65
- default: "#{requirement.name} is not complete"))
64
+ message: I18n.t('spree.seller_requirements.incomplete', name: requirement.name))
66
65
  end
67
66
  end
68
67
 
@@ -14,7 +14,7 @@ module Spree
14
14
  return success(shipping_label) if shipping_label.refunded?
15
15
 
16
16
  unless shipping_label.refund_requested?
17
- shipping_label.errors.add(:base, :not_refund_requested, message: Spree.t('shipping_labels.errors.not_refund_requested'))
17
+ shipping_label.errors.add(:base, :not_refund_requested, message: I18n.t('spree.shipping_labels.errors.not_refund_requested'))
18
18
  return failure(shipping_label)
19
19
  end
20
20
 
@@ -27,7 +27,7 @@ module Spree
27
27
 
28
28
  url = shipping_label.file_url
29
29
  if url.blank?
30
- shipping_label.errors.add(:file, :no_file_url, message: Spree.t('shipping_labels.errors.no_file_url'))
30
+ shipping_label.errors.add(:file, :no_file_url, message: I18n.t('spree.shipping_labels.errors.no_file_url'))
31
31
  return failure(shipping_label)
32
32
  end
33
33
 
@@ -38,13 +38,13 @@ module Spree
38
38
  # ordinary outcome here, not an exception the caller should handle:
39
39
  # the purchase behind it is already a fact, and the job retries.
40
40
  shipping_label.errors.add(:file, :file_fetch_failed, status: e.message,
41
- message: Spree.t('shipping_labels.errors.file_fetch_failed', status: e.message))
41
+ message: I18n.t('spree.shipping_labels.errors.file_fetch_failed', status: e.message))
42
42
  return failure(shipping_label)
43
43
  end
44
44
 
45
45
  unless response.is_a?(Net::HTTPSuccess)
46
46
  shipping_label.errors.add(:file, :file_fetch_failed, status: response.code,
47
- message: Spree.t('shipping_labels.errors.file_fetch_failed', status: response.code))
47
+ message: I18n.t('spree.shipping_labels.errors.file_fetch_failed', status: response.code))
48
48
  return failure(shipping_label)
49
49
  end
50
50
 
@@ -24,13 +24,13 @@ module Spree
24
24
  # @return [Spree::ServiceModule::Result] the corrected stock level
25
25
  def call(stock_level:, count_on_hand: nil, adjustment: nil, reason: nil)
26
26
  if count_on_hand.present? && adjustment.present?
27
- return failure(stock_level, Spree.t('stock_level.errors.adjustment_exclusive_with_count_on_hand'))
27
+ return failure(stock_level, I18n.t('spree.stock_level.errors.adjustment_exclusive_with_count_on_hand'))
28
28
  end
29
29
 
30
30
  target = parse(count_on_hand)
31
31
  delta = parse(adjustment)
32
- return failure(stock_level, Spree.t('stock_level.errors.count_on_hand_not_an_integer')) if count_on_hand.present? && target.nil?
33
- return failure(stock_level, Spree.t('stock_level.errors.adjustment_not_an_integer')) if adjustment.present? && delta.nil?
32
+ return failure(stock_level, I18n.t('spree.stock_level.errors.count_on_hand_not_an_integer')) if count_on_hand.present? && target.nil?
33
+ return failure(stock_level, I18n.t('spree.stock_level.errors.adjustment_not_an_integer')) if adjustment.present? && delta.nil?
34
34
 
35
35
  # Locked around the read: the delta is worked out from the count this
36
36
  # caller first saw, so two admins correcting the same level at once
@@ -36,9 +36,8 @@ module Spree
36
36
  if available < line_item.quantity
37
37
  raise InsufficientStockError.new(
38
38
  line_item,
39
- Spree.t(
40
- :insufficient_stock_for_reservation,
41
- default: '%{item} has only %{available} available',
39
+ I18n.t(
40
+ 'spree.insufficient_stock_for_reservation',
42
41
  item: line_item.variant.name,
43
42
  available: [available, 0].max
44
43
  )
@@ -13,7 +13,10 @@ module Spree
13
13
  # credit payment for a negative amount.
14
14
  remaining_total = [amount ? [amount, @order.outstanding_balance].min : @order.outstanding_balance, 0].max
15
15
 
16
- return failure(nil, Spree.t(:error_user_does_not_have_any_store_credits)) unless @order.customer&.store_credits&.any?
16
+ # Mirrors Spree::GiftCards::Apply, which refuses a gift card once store
17
+ # credit is in use: the two are never combined on one order.
18
+ return failure(nil, I18n.t('spree.store_credit_using_gift_card_error')) if @order.gift_card.present?
19
+ return failure(nil, I18n.t('spree.error_user_does_not_have_any_store_credits')) unless spendable_store_credits.exists?
17
20
 
18
21
  ApplicationRecord.transaction do
19
22
  existing = @order.payments.store_credits.where(status: :checkout)
@@ -40,12 +43,10 @@ module Spree
40
43
  # creating unnecessary invalid payment records on every recalculation.
41
44
  def update_existing_payments(payments, remaining_total)
42
45
  payments.each do |payment|
43
- credit = payment.source
44
- available = credit.amount_remaining + payment.amount
45
- new_amount = [available, remaining_total].min
46
+ new_amount = store_credit_amount(payment.source, remaining_total)
46
47
 
47
48
  if new_amount.positive?
48
- payment.update_column(:amount, new_amount)
49
+ payment.update_store_credit_amount!(new_amount)
49
50
  remaining_total -= new_amount
50
51
  else
51
52
  payment.invalidate!
@@ -53,19 +54,14 @@ module Spree
53
54
  end
54
55
 
55
56
  # If there's still remaining total, apply from additional store credits
56
- apply_store_credits(remaining_total) if remaining_total.positive?
57
+ apply_store_credits(remaining_total, except: payments.map(&:source_id)) if remaining_total.positive?
57
58
  end
58
59
 
59
- def apply_store_credits(remaining_total)
60
+ def apply_store_credits(remaining_total, except: [])
60
61
  payment_method = Spree::PaymentMethod::StoreCredit.available.first
61
62
  raise 'Store credit payment method could not be found' unless payment_method
62
63
 
63
- # Only credits in the order's own currency: an older credit in another
64
- # currency would be drawn against first and, being unusable, either
65
- # overshoot the allowed amount or persist a payment that fails later
66
- # at authorization. This matches what the order already counts as
67
- # available (Purchase::StoreCredits#total_available_store_credit).
68
- @order.customer.store_credits.for_store(@order.store).where(currency: @order.currency).oldest_first.each do |credit|
64
+ spendable_store_credits.where.not(id: except).oldest_first.each do |credit|
69
65
  break unless remaining_total.positive?
70
66
  next if credit.amount_remaining.zero?
71
67
 
@@ -75,6 +71,12 @@ module Spree
75
71
  end
76
72
  end
77
73
 
74
+ def spendable_store_credits
75
+ return Spree::StoreCredit.none unless @order.customer
76
+
77
+ @order.customer.store_credits.without_gift_card.for_store(@order.store).where(currency: @order.currency)
78
+ end
79
+
78
80
  def create_store_credit_payment(payment_method, credit, amount)
79
81
  @order.payments.create!(
80
82
  source: credit,
@@ -59,18 +59,17 @@ module Spree
59
59
  # translations. Only the *derived* default is filtered: falling back to
60
60
  # a country's official language that Spree has no strings for would set
61
61
  # a storefront to a language with nothing behind it, and it is not one
62
- # the setup screen ever offers. Installs without spree_i18n know only
63
- # English, so filtering there would flatten every country to it.
62
+ # the setup screen ever offers.
64
63
  def resolve_locale(requested, country)
65
64
  return requested if requested.present?
66
65
 
67
66
  derived = country.default_locale.presence
68
67
  return 'en' if derived.blank?
69
68
 
70
- translated = Spree.available_locales.map { |locale| locale.to_s.split('-').first }.uniq
71
- return derived if translated.size <= 1 || translated.include?(derived)
72
-
73
- (country.official_locales & translated).first || 'en'
69
+ locales = Spree.available_locales
70
+ Spree.locale_for_language(derived, locales) ||
71
+ country.official_locales.lazy.filter_map { |language| Spree.locale_for_language(language, locales) }.first ||
72
+ 'en'
74
73
  end
75
74
 
76
75
  # The store's own country/currency/locale columns are not the source of
@@ -118,12 +117,12 @@ module Spree
118
117
  end
119
118
 
120
119
  # Match on identity alone: folding the other attributes into the finder
121
- # made re-running fail once anything edited them.
120
+ # made re-running fail once anything edited them. The default flag is
121
+ # that identity; the name is translated and may have been edited.
122
122
  def provision_stock_location
123
- # first_party, so a seller who happened to name a location the same
124
- # thing is never adopted as the store's own.
125
- location = store.stock_locations.first_party.
126
- where(name: Spree.t(:default_stock_location_name)).first_or_initialize
123
+ # first_party, so a seller's location is never adopted as the store's own.
124
+ location = store.stock_locations.first_party.where(default: true).
125
+ first_or_initialize(name: I18n.t('spree.default_stock_location_name'))
127
126
  location.propagate_all_variants = false if location.new_record?
128
127
  location.country_code = country.iso
129
128
  if location.persisted? && location.will_save_change_to_country_code?
@@ -206,7 +205,7 @@ module Spree
206
205
  weight_unit = store.preferred_weight_unit
207
206
 
208
207
  store.package_types.create!(
209
- name: Spree.t('package_types.default_name'),
208
+ name: I18n.t('spree.package_types.default_name'),
210
209
  kind: 'box',
211
210
  default: true,
212
211
  length: metric ? 30 : 12,
@@ -237,10 +236,12 @@ module Spree
237
236
  end
238
237
  return if store.stock_locations.first_party.where(pickup_enabled: true).none?
239
238
 
240
- delivery_method = store.delivery_methods.where(name: Spree.t('pickup.store_pickup')).first_or_initialize
239
+ # Matched by its provider, not its translated (and editable) name.
240
+ delivery_method = store.delivery_methods.first_party.
241
+ where(fulfillment_provider: 'Spree::FulfillmentProvider::Pickup').
242
+ first_or_initialize(name: I18n.t('spree.pickup.store_pickup'))
241
243
  delivery_method.delivery_profile = profile
242
244
  delivery_method.storefront_visible = true
243
- delivery_method.fulfillment_provider = 'Spree::FulfillmentProvider::Pickup'
244
245
  delivery_method.calculator ||= Spree::Calculator::Shipping::FlatRate.new
245
246
  delivery_method.calculator.preferences = { amount: 0, currency: currency }
246
247
  delivery_method.save!
@@ -26,7 +26,7 @@ module Spree
26
26
  seller = invitation.resource
27
27
  return unless seller.is_a?(Spree::Seller)
28
28
 
29
- Spree::Sellers::StartOnboarding.call(seller: seller)
29
+ Spree.seller_start_onboarding_workflow.call(seller: seller)
30
30
  end
31
31
  end
32
32
  end