spree_core 6.0.0.beta4 → 6.0.0.rc2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (575) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +5 -5
  3. data/app/helpers/spree/images_helper.rb +0 -57
  4. data/app/jobs/spree/base_job.rb +21 -0
  5. data/app/jobs/spree/carts/reap_expired_job.rb +2 -2
  6. data/app/jobs/spree/collections/regenerate_time_based_job.rb +6 -3
  7. data/app/jobs/spree/orders/finalize_stale_drafts_job.rb +8 -5
  8. data/app/jobs/spree/payments/cancel_unused_sessions_job.rb +61 -0
  9. data/app/jobs/spree/search_provider/index_job.rb +4 -0
  10. data/app/jobs/spree/seller_payouts/sweep_seller_job.rb +2 -0
  11. data/app/jobs/spree/uploads/purge_unattached_job.rb +49 -0
  12. data/app/mailers/spree/admin_user_mailer.rb +4 -19
  13. data/app/mailers/spree/base_mailer.rb +97 -13
  14. data/app/mailers/spree/email_template_mailer.rb +27 -0
  15. data/app/mailers/spree/export_mailer.rb +3 -4
  16. data/app/mailers/spree/import_mailer.rb +3 -4
  17. data/app/mailers/spree/invitation_mailer.rb +24 -7
  18. data/app/mailers/spree/seller_user_mailer.rb +4 -16
  19. data/app/mailers/spree/webhook_mailer.rb +2 -2
  20. data/app/models/concerns/spree/acted_by.rb +1 -1
  21. data/app/models/concerns/spree/admin_user_methods.rb +15 -1
  22. data/app/models/concerns/spree/api_typed.rb +91 -0
  23. data/app/models/concerns/spree/calculated_adjustments.rb +41 -13
  24. data/app/models/concerns/spree/calculator/currency_amounts.rb +1 -1
  25. data/app/models/concerns/spree/calculator/tiers.rb +63 -0
  26. data/app/models/concerns/spree/canonical_decimals.rb +61 -0
  27. data/app/models/concerns/spree/customer_methods.rb +23 -18
  28. data/app/models/concerns/spree/derived_number.rb +11 -13
  29. data/app/models/concerns/spree/email_template_identity.rb +52 -0
  30. data/app/models/concerns/spree/parameterizable_name.rb +2 -0
  31. data/app/models/concerns/spree/payment_session_transitions.rb +8 -2
  32. data/app/models/concerns/spree/post_sale/taxation.rb +54 -0
  33. data/app/models/concerns/spree/post_sale/taxed_line.rb +51 -0
  34. data/app/models/concerns/spree/preference_schema/json_schema.rb +139 -0
  35. data/app/models/concerns/spree/preference_schema.rb +62 -32
  36. data/app/models/concerns/spree/product_scopes.rb +16 -2
  37. data/app/models/concerns/spree/provider_listing.rb +41 -0
  38. data/app/models/concerns/spree/publishable.rb +82 -14
  39. data/app/models/concerns/spree/purchase/addresses.rb +23 -10
  40. data/app/models/concerns/spree/purchase/currency.rb +1 -1
  41. data/app/models/concerns/spree/purchase/deprecated_aliases.rb +87 -0
  42. data/app/models/concerns/spree/purchase/gift_cards.rb +10 -4
  43. data/app/models/concerns/spree/purchase/locale.rb +1 -1
  44. data/app/models/concerns/spree/purchase/payment_processing.rb +8 -4
  45. data/app/models/concerns/spree/purchase/purchase_order.rb +9 -4
  46. data/app/models/concerns/spree/purchase/store_credits.rb +36 -1
  47. data/app/models/concerns/spree/purchase/totals.rb +49 -8
  48. data/app/models/concerns/spree/ransackable_attributes.rb +46 -0
  49. data/app/models/concerns/spree/receivable.rb +1 -1
  50. data/app/models/concerns/spree/searchable.rb +0 -4
  51. data/app/models/concerns/spree/secret_preferences.rb +59 -0
  52. data/app/models/concerns/spree/signed_id_attachments.rb +54 -0
  53. data/app/models/concerns/spree/single_store_resource.rb +1 -1
  54. data/app/models/concerns/spree/stores/email_branding.rb +32 -0
  55. data/app/models/concerns/spree/stores/markets.rb +5 -2
  56. data/app/models/concerns/spree/type_labels.rb +38 -0
  57. data/app/models/concerns/spree/typed_adjustment_line.rb +5 -6
  58. data/app/models/concerns/spree/typed_associations.rb +12 -18
  59. data/app/models/concerns/spree/vat_price_calculation.rb +5 -6
  60. data/app/models/spree/address.rb +12 -2
  61. data/app/models/spree/adjusters/largest_remainder.rb +14 -1
  62. data/app/models/spree/adjusters/promotion.rb +4 -19
  63. data/app/models/spree/allowed_origin.rb +3 -1
  64. data/app/models/spree/api_key.rb +5 -6
  65. data/app/models/spree/authentication/strategies/oidc_strategy.rb +5 -5
  66. data/app/models/spree/base.rb +99 -22
  67. data/app/models/spree/calculator/flat_percent_item_total.rb +2 -4
  68. data/app/models/spree/calculator/flat_rate.rb +3 -5
  69. data/app/models/spree/calculator/flexi_rate.rb +4 -6
  70. data/app/models/spree/calculator/percent_on_line_item.rb +2 -2
  71. data/app/models/spree/calculator/price_sack.rb +5 -7
  72. data/app/models/spree/calculator/shipping/digital_delivery.rb +3 -5
  73. data/app/models/spree/calculator/shipping/flat_percent_item_total.rb +7 -7
  74. data/app/models/spree/calculator/shipping/flat_rate.rb +5 -7
  75. data/app/models/spree/calculator/shipping/flexi_rate.rb +4 -6
  76. data/app/models/spree/calculator/shipping/per_item.rb +3 -5
  77. data/app/models/spree/calculator/shipping/price_sack.rb +5 -7
  78. data/app/models/spree/calculator/tiered_flat_rate.rb +7 -24
  79. data/app/models/spree/calculator/tiered_percent.rb +7 -25
  80. data/app/models/spree/cart.rb +78 -18
  81. data/app/models/spree/catalog.rb +1 -0
  82. data/app/models/spree/catalog_price.rb +0 -7
  83. data/app/models/spree/catalog_price_tier.rb +0 -8
  84. data/app/models/spree/category.rb +3 -1
  85. data/app/models/spree/channel/gating.rb +3 -2
  86. data/app/models/spree/channel.rb +6 -4
  87. data/app/models/spree/claim.rb +22 -1
  88. data/app/models/spree/claim_line_item.rb +41 -6
  89. data/app/models/spree/collection.rb +2 -1
  90. data/app/models/spree/collection_rule.rb +5 -21
  91. data/app/models/spree/commission_line.rb +1 -1
  92. data/app/models/spree/commission_rate.rb +7 -4
  93. data/app/models/spree/commission_rate_value.rb +1 -1
  94. data/app/models/spree/commission_rule.rb +4 -11
  95. data/app/models/spree/commission_rules/category_rule.rb +2 -5
  96. data/app/models/spree/commission_rules/item_total_rule.rb +2 -2
  97. data/app/models/spree/commission_rules/product_rule.rb +1 -1
  98. data/app/models/spree/commission_rules/seller_rule.rb +2 -5
  99. data/app/models/spree/company.rb +1 -0
  100. data/app/models/spree/company_invitation.rb +1 -0
  101. data/app/models/spree/consent_record.rb +0 -1
  102. data/app/models/spree/credit_card.rb +1 -1
  103. data/app/models/spree/current.rb +23 -0
  104. data/app/models/spree/custom_field.rb +2 -2
  105. data/app/models/spree/custom_field_definition.rb +20 -35
  106. data/app/models/spree/custom_fields/boolean.rb +1 -1
  107. data/app/models/spree/custom_fields/json.rb +1 -1
  108. data/app/models/spree/customer_group.rb +1 -0
  109. data/app/models/spree/data_feed/google.rb +0 -2
  110. data/app/models/spree/data_request.rb +2 -1
  111. data/app/models/spree/delivery_method.rb +36 -14
  112. data/app/models/spree/delivery_method_rule.rb +4 -11
  113. data/app/models/spree/delivery_method_rules/channel_rule.rb +2 -5
  114. data/app/models/spree/delivery_method_rules/item_total_rule.rb +2 -2
  115. data/app/models/spree/delivery_profile.rb +1 -0
  116. data/app/models/spree/delivery_rate.rb +7 -8
  117. data/app/models/spree/delivery_rate_provider/base.rb +1 -0
  118. data/app/models/spree/delivery_zone.rb +1 -2
  119. data/app/models/spree/delivery_zone_member.rb +4 -4
  120. data/app/models/spree/digital_asset.rb +7 -0
  121. data/app/models/spree/digital_asset_provider/base.rb +2 -0
  122. data/app/models/spree/digital_link.rb +2 -5
  123. data/app/models/spree/discount.rb +1 -1
  124. data/app/models/spree/email_template.rb +25 -0
  125. data/app/models/spree/email_template_draft.rb +13 -0
  126. data/app/models/spree/email_template_revision.rb +17 -0
  127. data/app/models/spree/email_templates/entry.rb +118 -0
  128. data/app/models/spree/email_templates/sample_record.rb +16 -0
  129. data/app/models/spree/emails/branding.rb +80 -0
  130. data/app/models/spree/exchange.rb +16 -1
  131. data/app/models/spree/exchange_line_item.rb +121 -5
  132. data/app/models/spree/export.rb +0 -1
  133. data/app/models/spree/exports/products.rb +10 -1
  134. data/app/models/spree/exports/report.rb +3 -3
  135. data/app/models/spree/fee.rb +4 -2
  136. data/app/models/spree/file_upload.rb +155 -0
  137. data/app/models/spree/fulfillment.rb +66 -30
  138. data/app/models/spree/fulfillment_changer.rb +2 -2
  139. data/app/models/spree/fulfillment_group.rb +6 -1
  140. data/app/models/spree/fulfillment_item.rb +0 -4
  141. data/app/models/spree/fulfillment_provider/base.rb +1 -0
  142. data/app/models/spree/gateway/bogus.rb +6 -0
  143. data/app/models/spree/gateway/custom_payment_source_method.rb +5 -0
  144. data/app/models/spree/gateway.rb +6 -2
  145. data/app/models/spree/gift_card.rb +18 -8
  146. data/app/models/spree/gift_card_batch.rb +3 -5
  147. data/app/models/spree/import.rb +5 -7
  148. data/app/models/spree/import_mapping.rb +1 -2
  149. data/app/models/spree/import_row.rb +2 -2
  150. data/app/models/spree/import_schemas/products.rb +1 -0
  151. data/app/models/spree/integration.rb +9 -19
  152. data/app/models/spree/invitation.rb +5 -5
  153. data/app/models/spree/line_item.rb +15 -4
  154. data/app/models/spree/locale.rb +11 -8
  155. data/app/models/spree/market.rb +8 -2
  156. data/app/models/spree/media.rb +1 -9
  157. data/app/models/spree/newsletter_subscriber.rb +4 -0
  158. data/app/models/spree/option_type.rb +2 -1
  159. data/app/models/spree/option_value.rb +2 -1
  160. data/app/models/spree/order.rb +60 -142
  161. data/app/models/spree/order_group.rb +18 -4
  162. data/app/models/spree/order_inventory.rb +10 -5
  163. data/app/models/spree/order_promotion.rb +1 -1
  164. data/app/models/spree/order_routing/has_strategy_preference.rb +1 -1
  165. data/app/models/spree/order_routing/strategy/base.rb +4 -2
  166. data/app/models/spree/order_routing/strategy/rules.rb +1 -1
  167. data/app/models/spree/order_routing_rule.rb +6 -22
  168. data/app/models/spree/package_type.rb +2 -2
  169. data/app/models/spree/payment/gateway_options.rb +15 -53
  170. data/app/models/spree/payment/processing.rb +2 -4
  171. data/app/models/spree/payment.rb +21 -13
  172. data/app/models/spree/payment_method/check.rb +2 -0
  173. data/app/models/spree/payment_method/session_instrumentation.rb +1 -0
  174. data/app/models/spree/payment_method/store_credit.rb +15 -13
  175. data/app/models/spree/payment_method.rb +78 -6
  176. data/app/models/spree/payment_session.rb +5 -1
  177. data/app/models/spree/payment_setup_session.rb +1 -0
  178. data/app/models/spree/payout_provider/base.rb +3 -1
  179. data/app/models/spree/pickup_point_provider/base.rb +2 -0
  180. data/app/models/spree/policy.rb +1 -0
  181. data/app/models/spree/price.rb +17 -14
  182. data/app/models/spree/price_adjustment_tier.rb +4 -0
  183. data/app/models/spree/price_list.rb +14 -9
  184. data/app/models/spree/price_rules/channel_rule.rb +3 -6
  185. data/app/models/spree/price_rules/customer_group_rule.rb +3 -6
  186. data/app/models/spree/price_rules/market_rule.rb +2 -5
  187. data/app/models/spree/price_rules/user_rule.rb +1 -3
  188. data/app/models/spree/product/channels.rb +9 -2
  189. data/app/models/spree/product.rb +54 -30
  190. data/app/models/spree/product_submission.rb +0 -12
  191. data/app/models/spree/product_type.rb +1 -1
  192. data/app/models/spree/promotion/actions/create_line_items.rb +28 -18
  193. data/app/models/spree/promotion/rules/category.rb +1 -5
  194. data/app/models/spree/promotion/rules/channel.rb +2 -9
  195. data/app/models/spree/promotion/rules/country.rb +3 -8
  196. data/app/models/spree/promotion/rules/currency.rb +1 -5
  197. data/app/models/spree/promotion/rules/customer_group.rb +2 -9
  198. data/app/models/spree/promotion/rules/first_order.rb +0 -4
  199. data/app/models/spree/promotion/rules/item_total.rb +6 -10
  200. data/app/models/spree/promotion/rules/market.rb +2 -9
  201. data/app/models/spree/promotion/rules/one_use_per_user.rb +0 -4
  202. data/app/models/spree/promotion/rules/option_value.rb +2 -6
  203. data/app/models/spree/promotion/rules/product.rb +1 -5
  204. data/app/models/spree/promotion/rules/user.rb +0 -4
  205. data/app/models/spree/promotion/rules/user_logged_in.rb +0 -4
  206. data/app/models/spree/promotion.rb +36 -6
  207. data/app/models/spree/promotion_action.rb +7 -14
  208. data/app/models/spree/promotion_action_line_item.rb +1 -1
  209. data/app/models/spree/promotion_handler/coupon.rb +59 -27
  210. data/app/models/spree/promotion_rule.rb +7 -14
  211. data/app/models/spree/purchase_order.rb +3 -0
  212. data/app/models/spree/quantity_rule.rb +1 -1
  213. data/app/models/spree/refund.rb +41 -18
  214. data/app/models/spree/return.rb +31 -2
  215. data/app/models/spree/return_line_item.rb +65 -14
  216. data/app/models/spree/role.rb +7 -7
  217. data/app/models/spree/saved_report.rb +2 -1
  218. data/app/models/spree/search_provider/base.rb +4 -0
  219. data/app/models/spree/seller.rb +10 -2
  220. data/app/models/spree/seller_payout.rb +8 -0
  221. data/app/models/spree/seller_requirement.rb +5 -12
  222. data/app/models/spree/seller_requirement_custom_field.rb +1 -2
  223. data/app/models/spree/seller_requirement_submission.rb +3 -2
  224. data/app/models/spree/seller_requirements/accept_terms.rb +2 -3
  225. data/app/models/spree/seller_transfer.rb +6 -0
  226. data/app/models/spree/shipping_label.rb +2 -0
  227. data/app/models/spree/stock/availability_validator.rb +2 -2
  228. data/app/models/spree/stock/estimator.rb +1 -1
  229. data/app/models/spree/stock/inventory_unit_builder.rb +1 -1
  230. data/app/models/spree/stock/packer.rb +2 -2
  231. data/app/models/spree/stock/splitter/weight.rb +1 -1
  232. data/app/models/spree/stock_level/custom_events.rb +6 -0
  233. data/app/models/spree/stock_level.rb +3 -1
  234. data/app/models/spree/stock_location.rb +1 -0
  235. data/app/models/spree/stock_movement.rb +1 -1
  236. data/app/models/spree/stock_receipt_item.rb +1 -1
  237. data/app/models/spree/stock_reservation.rb +2 -2
  238. data/app/models/spree/stock_transfer.rb +6 -2
  239. data/app/models/spree/store.rb +70 -19
  240. data/app/models/spree/store_credit.rb +13 -13
  241. data/app/models/spree/store_credit_event.rb +5 -5
  242. data/app/models/spree/supplier.rb +1 -0
  243. data/app/models/spree/tax_category.rb +1 -0
  244. data/app/models/spree/tax_exemption_certificate.rb +3 -0
  245. data/app/models/spree/tax_identifier.rb +3 -1
  246. data/app/models/spree/tax_line.rb +53 -3
  247. data/app/models/spree/tax_provider/base.rb +71 -14
  248. data/app/models/spree/tax_provider/internal.rb +70 -41
  249. data/app/models/spree/tax_provider/recorded_share.rb +114 -0
  250. data/app/models/spree/tax_rate.rb +30 -8
  251. data/app/models/spree/taxon.rb +1 -1
  252. data/app/models/spree/taxonomy.rb +0 -1
  253. data/app/models/spree/update_status.rb +25 -0
  254. data/app/models/spree/variant.rb +10 -12
  255. data/app/models/spree/variant_media.rb +1 -1
  256. data/app/models/spree/webhook_delivery.rb +1 -0
  257. data/app/models/spree/webhook_endpoint.rb +46 -13
  258. data/app/models/spree/wishlist.rb +1 -6
  259. data/app/models/spree/wishlist_item/custom_events.rb +6 -0
  260. data/app/presenters/spree/csv/customer_presenter.rb +1 -1
  261. data/app/presenters/spree/csv/newsletter_subscriber_presenter.rb +1 -1
  262. data/app/presenters/spree/csv/order_line_item_presenter.rb +10 -6
  263. data/app/presenters/spree/csv/price_list_price_presenter.rb +3 -8
  264. data/app/presenters/spree/csv/product_variant_presenter.rb +59 -20
  265. data/app/presenters/spree/csv/purchase_order_item_presenter.rb +1 -3
  266. data/app/serializers/spree/emails/base_serializer.rb +14 -0
  267. data/app/serializers/spree/emails/export_serializer.rb +13 -0
  268. data/app/serializers/spree/emails/import_serializer.rb +19 -0
  269. data/app/serializers/spree/emails/invitation_serializer.rb +28 -0
  270. data/app/serializers/spree/emails/store_serializer.rb +31 -0
  271. data/app/serializers/spree/emails/user_serializer.rb +12 -0
  272. data/app/serializers/spree/emails/webhook_endpoint_serializer.rb +16 -0
  273. data/app/services/spree/carts/create.rb +8 -2
  274. data/app/services/spree/carts/destroy.rb +2 -2
  275. data/app/services/spree/carts/remove_out_of_stock_items.rb +2 -2
  276. data/app/services/spree/carts/split_by_seller.rb +1 -1
  277. data/app/services/spree/carts/update.rb +22 -8
  278. data/app/services/spree/catalogs/set_product_terms.rb +1 -1
  279. data/app/services/spree/categories/add_products.rb +13 -31
  280. data/app/services/spree/categories/remove_products.rb +12 -51
  281. data/app/services/spree/checkout/advance.rb +1 -4
  282. data/app/services/spree/collections/add_products.rb +10 -23
  283. data/app/services/spree/collections/remove_products.rb +7 -43
  284. data/app/services/spree/coupon_codes/bulk_generate.rb +8 -14
  285. data/app/services/spree/data_requests/create.rb +1 -1
  286. data/app/services/spree/deliveries/destroy.rb +2 -2
  287. data/app/services/spree/email_templates/check.rb +58 -0
  288. data/app/services/spree/email_templates/no_sample_record.rb +7 -0
  289. data/app/services/spree/email_templates/preview.rb +99 -0
  290. data/app/services/spree/emails/file_system.rb +23 -0
  291. data/app/services/spree/emails/filters.rb +84 -0
  292. data/app/services/spree/emails/liquid_context.rb +48 -0
  293. data/app/services/spree/emails/rendered_email.rb +14 -0
  294. data/app/services/spree/emails/renderer.rb +125 -0
  295. data/app/services/spree/emails/template.rb +51 -0
  296. data/app/services/spree/emails/template_data.rb +23 -0
  297. data/app/services/spree/emails/template_resolver.rb +109 -0
  298. data/app/services/spree/emails/text_converter.rb +72 -0
  299. data/app/services/spree/emails/translation_inliner.rb +134 -0
  300. data/app/services/spree/imports/row_processors/price_list_price.rb +10 -10
  301. data/app/services/spree/imports/row_processors/product_variant.rb +77 -34
  302. data/app/services/spree/imports/row_processors/purchase_order.rb +13 -13
  303. data/app/services/spree/locales.rb +43 -10
  304. data/app/services/spree/newsletter/subscribe.rb +8 -12
  305. data/app/services/spree/orders/build_fulfillments.rb +3 -2
  306. data/app/services/spree/orders/create.rb +5 -4
  307. data/app/services/spree/orders/discounts/create.rb +14 -11
  308. data/app/services/spree/orders/discounts/destroy.rb +1 -0
  309. data/app/services/spree/orders/discounts/update.rb +1 -0
  310. data/app/services/spree/orders/fees/create.rb +1 -0
  311. data/app/services/spree/orders/fees/destroy.rb +1 -0
  312. data/app/services/spree/orders/fees/update.rb +1 -0
  313. data/app/services/spree/orders/remove_line_item.rb +13 -3
  314. data/app/services/spree/orders/update.rb +10 -6
  315. data/app/services/spree/orders/update_item.rb +6 -3
  316. data/app/services/spree/orders/update_statuses.rb +3 -1
  317. data/app/services/spree/prices/bulk_upsert.rb +32 -25
  318. data/app/services/spree/product_memberships.rb +61 -0
  319. data/app/services/spree/returns/eligibility_validator.rb +1 -1
  320. data/app/services/spree/search_provider/custom_field_schema.rb +2 -2
  321. data/app/services/spree/seeds/commission_rates.rb +1 -1
  322. data/app/services/spree/seeds/digital_delivery.rb +1 -1
  323. data/app/services/spree/seeds/payment_methods.rb +2 -2
  324. data/app/services/spree/seeds/saved_reports.rb +3 -3
  325. data/app/services/spree/seeds/stores.rb +1 -1
  326. data/app/services/spree/sellers/requirements.rb +1 -2
  327. data/app/services/spree/shipping_labels/confirm_refund.rb +1 -1
  328. data/app/services/spree/shipping_labels/store_file.rb +3 -3
  329. data/app/services/spree/stock_levels/correct.rb +3 -3
  330. data/app/services/spree/stock_reservations/reserve.rb +2 -3
  331. data/app/services/spree/store_credits/apply.rb +15 -13
  332. data/app/services/spree/stores/provision_defaults.rb +23 -25
  333. data/app/services/spree/update_check.rb +160 -0
  334. data/app/services/spree/uploads/assign_stores.rb +92 -0
  335. data/app/subscribers/spree/seller_onboarding_subscriber.rb +1 -1
  336. data/app/validators/spree/attachment_content_type_validator.rb +19 -2
  337. data/app/validators/spree/locale_codes_validator.rb +22 -0
  338. data/app/views/layouts/spree/base_mailer.html.erb +2 -43
  339. data/app/views/layouts/spree/base_mailer.liquid +79 -0
  340. data/app/views/spree/admin_user_mailer/password_reset_email.liquid +14 -0
  341. data/app/views/spree/export_mailer/export_done.liquid +12 -0
  342. data/app/views/spree/import_mailer/import_done.liquid +15 -0
  343. data/app/views/spree/invitation_mailer/invitation_accepted.liquid +9 -0
  344. data/app/views/spree/invitation_mailer/invitation_email.liquid +13 -0
  345. data/app/views/spree/seller_user_mailer/password_reset_email.liquid +14 -0
  346. data/app/views/spree/shared/_mailer_button.html.erb +4 -5
  347. data/app/views/spree/shared/_mailer_hero.html.erb +6 -5
  348. data/app/views/spree/shared/_summary_row.liquid +8 -0
  349. data/app/views/spree/webhook_mailer/endpoint_disabled.liquid +21 -0
  350. data/app/workflows/spree/carts/add_item.rb +4 -4
  351. data/app/workflows/spree/carts/complete.rb +13 -3
  352. data/app/workflows/spree/carts/merge.rb +1 -1
  353. data/app/workflows/spree/carts/recalculate_totals.rb +5 -0
  354. data/app/workflows/spree/carts/upsert_items.rb +18 -9
  355. data/app/workflows/spree/claims/approve.rb +5 -22
  356. data/app/workflows/spree/claims/cancel.rb +5 -21
  357. data/app/workflows/spree/claims/create.rb +15 -0
  358. data/app/workflows/spree/claims/deny.rb +7 -0
  359. data/app/workflows/spree/claims/resolve.rb +34 -31
  360. data/app/workflows/spree/customers/anonymize.rb +3 -4
  361. data/app/workflows/spree/customers/create.rb +1 -1
  362. data/app/workflows/spree/data_requests/fulfill.rb +2 -2
  363. data/app/workflows/spree/deliveries/update_tracking.rb +1 -1
  364. data/app/workflows/spree/email_templates/discard_draft.rb +20 -0
  365. data/app/workflows/spree/email_templates/publish.rb +70 -0
  366. data/app/workflows/spree/email_templates/restore_revision.rb +33 -0
  367. data/app/workflows/spree/email_templates/revert.rb +29 -0
  368. data/app/workflows/spree/email_templates/save_draft.rb +85 -0
  369. data/app/workflows/spree/exchanges/approve.rb +5 -22
  370. data/app/workflows/spree/exchanges/cancel.rb +5 -21
  371. data/app/workflows/spree/exchanges/create.rb +6 -0
  372. data/app/workflows/spree/exchanges/fulfill.rb +75 -51
  373. data/app/workflows/spree/exchanges/receive.rb +6 -65
  374. data/app/workflows/spree/fulfillments/cancel.rb +1 -1
  375. data/app/workflows/spree/fulfillments/create.rb +53 -49
  376. data/app/workflows/spree/fulfillments/fulfill.rb +23 -27
  377. data/app/workflows/spree/fulfillments/mark_delivered.rb +1 -1
  378. data/app/workflows/spree/fulfillments/replacements.rb +59 -0
  379. data/app/workflows/spree/gift_cards/apply.rb +2 -2
  380. data/app/workflows/spree/orders/add_item.rb +5 -1
  381. data/app/workflows/spree/orders/cancel.rb +3 -3
  382. data/app/workflows/spree/orders/complete.rb +16 -8
  383. data/app/workflows/spree/payments/capture.rb +20 -5
  384. data/app/workflows/spree/payments/process.rb +5 -3
  385. data/app/workflows/spree/post_sale/approval.rb +35 -0
  386. data/app/workflows/spree/post_sale/cancellation.rb +44 -0
  387. data/app/workflows/spree/post_sale/receipt.rb +92 -0
  388. data/app/workflows/spree/price_lists/update.rb +4 -4
  389. data/app/workflows/spree/products/nested_attributes.rb +1 -1
  390. data/app/workflows/spree/purchase_orders/cancel.rb +1 -1
  391. data/app/workflows/spree/purchase_orders/close.rb +3 -31
  392. data/app/workflows/spree/purchase_orders/create.rb +3 -1
  393. data/app/workflows/spree/purchase_orders/mark_draft.rb +2 -2
  394. data/app/workflows/spree/purchase_orders/mark_ordered.rb +2 -2
  395. data/app/workflows/spree/purchase_orders/receive.rb +1 -1
  396. data/app/workflows/spree/purchase_orders/update.rb +5 -34
  397. data/app/workflows/spree/receivables/closing_short.rb +46 -0
  398. data/app/workflows/spree/receivables/draft_editing.rb +48 -0
  399. data/app/workflows/spree/refunds/create.rb +5 -2
  400. data/app/workflows/spree/refunds/order_payments.rb +14 -2
  401. data/app/workflows/spree/refunds/tax_credit.rb +67 -0
  402. data/app/workflows/spree/returns/approve.rb +5 -22
  403. data/app/workflows/spree/returns/cancel.rb +5 -22
  404. data/app/workflows/spree/returns/create.rb +8 -0
  405. data/app/workflows/spree/returns/receive.rb +5 -76
  406. data/app/workflows/spree/returns/refund.rb +31 -8
  407. data/app/workflows/spree/seller_requirement_submissions/create.rb +1 -2
  408. data/app/workflows/spree/seller_transfers/reverse.rb +28 -1
  409. data/app/workflows/spree/shipping_labels/purchase.rb +8 -8
  410. data/app/workflows/spree/shipping_labels/record.rb +7 -7
  411. data/app/workflows/spree/shipping_labels/refund.rb +2 -2
  412. data/app/workflows/spree/stock_receipts/recording.rb +4 -4
  413. data/app/workflows/spree/stock_transfers/cancel.rb +2 -2
  414. data/app/workflows/spree/stock_transfers/close.rb +3 -30
  415. data/app/workflows/spree/stock_transfers/create.rb +3 -1
  416. data/app/workflows/spree/stock_transfers/mark_draft.rb +1 -1
  417. data/app/workflows/spree/stock_transfers/mark_in_transit.rb +3 -3
  418. data/app/workflows/spree/stock_transfers/mark_ready.rb +2 -2
  419. data/app/workflows/spree/stock_transfers/receive.rb +1 -1
  420. data/app/workflows/spree/stock_transfers/update.rb +5 -33
  421. data/config/currency_exponents.json +180 -0
  422. data/config/i18n-tasks.yml +32 -18
  423. data/config/initializers/alba.rb +7 -0
  424. data/config/language_names.csv +264 -0
  425. data/config/locales/ar.yml +811 -0
  426. data/config/locales/be.yml +855 -0
  427. data/config/locales/bg.yml +723 -0
  428. data/config/locales/ca.yml +689 -0
  429. data/config/locales/content_locales.rb +9 -0
  430. data/config/locales/cs.yml +855 -0
  431. data/config/locales/da.yml +771 -0
  432. data/config/locales/de-CH.yml +248 -0
  433. data/config/locales/de.yml +835 -0
  434. data/config/locales/el.yml +769 -0
  435. data/config/locales/en.yml +47 -505
  436. data/config/locales/es-CL.yml +301 -0
  437. data/config/locales/es-EC.yml +186 -0
  438. data/config/locales/es-MX.yml +334 -0
  439. data/config/locales/es.yml +831 -0
  440. data/config/locales/et.yml +644 -0
  441. data/config/locales/fa.yml +709 -0
  442. data/config/locales/fi.yml +771 -0
  443. data/config/locales/fr.yml +830 -0
  444. data/config/locales/hu.yml +751 -0
  445. data/config/locales/id.yml +756 -0
  446. data/config/locales/it.yml +833 -0
  447. data/config/locales/ja.yml +726 -0
  448. data/config/locales/km.yml +759 -0
  449. data/config/locales/ko.yml +575 -0
  450. data/config/locales/ku.yml +753 -0
  451. data/config/locales/lt.yml +821 -0
  452. data/config/locales/lv.yml +685 -0
  453. data/config/locales/nb.yml +824 -0
  454. data/config/locales/ne.yml +823 -0
  455. data/config/locales/nl.yml +826 -0
  456. data/config/locales/pl.yml +835 -0
  457. data/config/locales/pt-BR.yml +547 -0
  458. data/config/locales/pt.yml +636 -0
  459. data/config/locales/ro.yml +750 -0
  460. data/config/locales/ru.yml +834 -0
  461. data/config/locales/sk.yml +680 -0
  462. data/config/locales/sl-SI.yml +600 -0
  463. data/config/locales/sv.yml +827 -0
  464. data/config/locales/th.yml +745 -0
  465. data/config/locales/tr.yml +777 -0
  466. data/config/locales/uk.yml +836 -0
  467. data/config/locales/vi.yml +768 -0
  468. data/config/locales/zh-CN.yml +786 -0
  469. data/config/locales/zh-TW.yml +834 -0
  470. data/db/migrate/20260925000001_add_replacement_to_spree_fulfillment_items.rb +5 -0
  471. data/db/migrate/20261001000001_add_post_sale_tax.rb +30 -0
  472. data/db/migrate/20261001000001_convert_preferences_to_json.rb +166 -0
  473. data/db/migrate/20261001000001_create_spree_email_templates.rb +52 -0
  474. data/db/migrate/20261009000001_change_money_columns_to_decimal_19_4.rb +116 -0
  475. data/db/migrate/20261010000001_add_store_id_to_active_storage_blobs.rb +12 -0
  476. data/lib/generators/spree/dummy/dummy_generator.rb +0 -9
  477. data/lib/generators/spree/dummy/templates/rails/test.rb +26 -0
  478. data/lib/spree/checkout/default_requirements.rb +37 -13
  479. data/lib/spree/core/configuration.rb +19 -1
  480. data/lib/spree/core/controller_helpers/store.rb +1 -1
  481. data/lib/spree/core/dependencies.rb +5 -4
  482. data/lib/spree/core/emails/escaped_output.rb +60 -0
  483. data/lib/spree/core/engine.rb +72 -165
  484. data/lib/spree/core/permissions/default_catalog.rb +5 -0
  485. data/lib/spree/core/preferences/invalid_preferences.rb +18 -0
  486. data/lib/spree/core/preferences/json_conversion.rb +186 -0
  487. data/lib/spree/core/preferences/masking.rb +55 -2
  488. data/lib/spree/core/preferences/preferable.rb +343 -103
  489. data/lib/spree/core/preferences/preferable_class_methods.rb +188 -136
  490. data/lib/spree/core/previews/import_preview.rb +10 -0
  491. data/lib/spree/core/ransack_configuration.rb +13 -2
  492. data/lib/spree/core/tax/provider_error.rb +37 -0
  493. data/lib/spree/core/version.rb +1 -1
  494. data/lib/spree/core.rb +91 -373
  495. data/lib/spree/emails/editable_templates.rb +82 -0
  496. data/lib/spree/events/catalog.rb +189 -0
  497. data/lib/spree/events.rb +21 -1
  498. data/lib/spree/hooks.rb +0 -17
  499. data/lib/spree/i18n.rb +32 -15
  500. data/lib/spree/iso_data.rb +7 -0
  501. data/lib/spree/localized_number.rb +6 -0
  502. data/lib/spree/migrations.rb +4 -1
  503. data/lib/spree/money.rb +196 -15
  504. data/lib/spree/reporting/adapters/base.rb +7 -6
  505. data/lib/spree/reporting/adapters/live.rb +6 -6
  506. data/lib/spree/reporting/default_vocabulary.rb +22 -10
  507. data/lib/spree/reporting/hydration.rb +1 -1
  508. data/lib/spree/reporting/query.rb +8 -0
  509. data/lib/spree/reporting/registry.rb +13 -0
  510. data/lib/spree/reporting/schema.rb +10 -10
  511. data/lib/spree/testing_support/editable_email_templates.rb +61 -0
  512. data/lib/spree/testing_support/emails.rb +22 -0
  513. data/lib/spree/testing_support/factories/cart_factory.rb +1 -1
  514. data/lib/spree/testing_support/factories/email_template_factory.rb +28 -0
  515. data/lib/spree/testing_support/factories/order_factory.rb +2 -2
  516. data/lib/spree/testing_support/factories/product_factory.rb +9 -0
  517. data/lib/spree/testing_support/factories/refund_factory.rb +1 -1
  518. data/lib/spree/testing_support/factories/stock_factory.rb +1 -1
  519. data/lib/spree/testing_support/factories/variant_factory.rb +1 -1
  520. data/lib/spree/testing_support/i18n.rb +3 -94
  521. data/lib/spree/testing_support/lifecycle_events.rb +1 -1
  522. data/lib/spree/testing_support/order_walkthrough.rb +1 -1
  523. data/lib/spree/testing_support/preferences.rb +0 -5
  524. data/lib/spree/testing_support/store.rb +9 -1
  525. data/lib/spree/upgrades/5_6_to_6_0/manifest.yml +67 -1
  526. data/lib/spree/uploads.rb +101 -0
  527. data/lib/tasks/categories_and_collections.rake +3 -3
  528. data/lib/tasks/core.rake +6 -4
  529. data/lib/tasks/markets.rake +2 -1
  530. data/lib/tasks/media.rake +3 -3
  531. data/lib/tasks/migrate_users_to_customers.rake +23 -18
  532. data/lib/tasks/money.rake +46 -0
  533. data/lib/tasks/package_types.rake +1 -1
  534. data/lib/tasks/preferences_json.rake +101 -0
  535. data/lib/tasks/seller_sample_data.rake +2 -2
  536. data/lib/tasks/store_settings.rake +13 -6
  537. data/lib/tasks/typed_adjustments_migration.rake +1 -1
  538. data/lib/tasks/uploads.rake +19 -0
  539. data/spec/fixtures/mailer_views/custom_views_probe_mailer/welcome_email.html.erb +4 -0
  540. metadata +219 -46
  541. data/app/jobs/spree/products/touch_categories_job.rb +0 -15
  542. data/app/jobs/spree/products/touch_taxons_job.rb +0 -13
  543. data/app/models/acts_as_taggable_on/tag_decorator.rb +0 -10
  544. data/app/models/concerns/spree/has_image_alt_text.rb +0 -18
  545. data/app/models/spree/base_analytics_event_handler.rb +0 -54
  546. data/app/models/spree/invitations/store.rb +0 -6
  547. data/app/models/spree/preference.rb +0 -14
  548. data/app/models/spree/promotion_handler/page.rb +0 -25
  549. data/app/models/spree/promotion_handler/promotion_duplicator.rb +0 -60
  550. data/app/models/spree/stock/differentiator.rb +0 -45
  551. data/app/presenters/spree/filters/options_presenter.rb +0 -47
  552. data/app/services/spree/carts/empty.rb +0 -42
  553. data/app/services/spree/country_to_timezone.rb +0 -273
  554. data/app/services/spree/payments/create.rb +0 -92
  555. data/app/validators/spree/url_validator.rb +0 -23
  556. data/app/views/active_storage/blobs/_blob.html.erb +0 -14
  557. data/app/views/spree/admin_user_mailer/password_reset_email.html.erb +0 -8
  558. data/app/views/spree/export_mailer/export_done.html.erb +0 -7
  559. data/app/views/spree/import_mailer/import_done.html.erb +0 -12
  560. data/app/views/spree/invitation_mailer/invitation_accepted.html.erb +0 -4
  561. data/app/views/spree/invitation_mailer/invitation_email.html.erb +0 -12
  562. data/app/views/spree/seller_user_mailer/password_reset_email.html.erb +0 -8
  563. data/app/views/spree/shared/_base_mailer_footer.html.erb +0 -19
  564. data/app/views/spree/shared/_base_mailer_header.html.erb +0 -8
  565. data/app/views/spree/shared/_base_mailer_stylesheets.html.erb +0 -399
  566. data/app/views/spree/shared/_mailer_logo.html.erb +0 -41
  567. data/app/views/spree/shared/_purchased_items_styles.html.erb +0 -119
  568. data/app/views/spree/webhook_mailer/endpoint_disabled.html.erb +0 -51
  569. data/app/views/spree/webhook_mailer/endpoint_disabled.text.erb +0 -10
  570. data/app/workflows/spree/fulfillments/purchase_label.rb +0 -40
  571. data/app/workflows/spree/returns/purchase_label.rb +0 -32
  572. data/lib/spree/analytics.rb +0 -11
  573. data/lib/spree/core/preferences/scoped_store.rb +0 -34
  574. data/lib/spree/core/preferences/store.rb +0 -105
  575. data/lib/spree/translation_migrations.rb +0 -52
@@ -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
@@ -14,7 +14,7 @@ module Spree
14
14
  s.customer_support_email = 'support@example.com'
15
15
  s.default_currency = 'USD'
16
16
  s.default_country_code = 'US'
17
- s.default_locale = I18n.locale
17
+ s.default_locale = Spree::Locales.listed(I18n.locale) || 'en'
18
18
  end.save!
19
19
  end
20
20
  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,
@@ -18,8 +18,9 @@ module Spree
18
18
 
19
19
  # @param store [Spree::Store] the store to provision, already persisted
20
20
  # @param country [Spree::Country] where the shop ships from
21
- # @param locale [String, nil] storefront locale; defaults to the
22
- # country's own language when Spree translates it, then English
21
+ # @param locale [String, nil] storefront locale, a code from
22
+ # `Spree::Locales::ALL` (another spelling or region moves to the listed
23
+ # one); defaults to the country's own language, then English
23
24
  # @param currency [String, nil] ISO 4217 code; defaults to the country's
24
25
  # own currency. Merchants who ship from one country and price in
25
26
  # another currency pass it explicitly.
@@ -55,22 +56,17 @@ module Spree
55
56
  found&.iso_code || country.default_currency.presence || 'USD'
56
57
  end
57
58
 
58
- # An explicit locale is honored as given — a host app may ship its own
59
- # translations. Only the *derived* default is filtered: falling back to
60
- # a country's official language that Spree has no strings for would set
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.
59
+ # A store's locale is a content locale, so it comes from
60
+ # `Spree::Locales::ALL` whether or not Spree ships translations for it:
61
+ # the storefront and dashboard carry their own. An explicit locale is
62
+ # kept when it is listed, or moved to its listed language (`en-IE`
63
+ # becomes `en`); one Spree cannot place, or none, gives the country's
64
+ # first official language Spree lists.
64
65
  def resolve_locale(requested, country)
65
- return requested if requested.present?
66
+ listed = Spree::Locales.listed(requested)
67
+ return listed if listed
66
68
 
67
- derived = country.default_locale.presence
68
- return 'en' if derived.blank?
69
-
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
+ [country.default_locale, *country.official_locales].lazy.filter_map { |language| Spree::Locales.for_language(language) }.first || 'en'
74
70
  end
75
71
 
76
72
  # The store's own country/currency/locale columns are not the source of
@@ -118,12 +114,12 @@ module Spree
118
114
  end
119
115
 
120
116
  # Match on identity alone: folding the other attributes into the finder
121
- # made re-running fail once anything edited them.
117
+ # made re-running fail once anything edited them. The default flag is
118
+ # that identity; the name is translated and may have been edited.
122
119
  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
120
+ # first_party, so a seller's location is never adopted as the store's own.
121
+ location = store.stock_locations.first_party.where(default: true).
122
+ first_or_initialize(name: I18n.t('spree.default_stock_location_name'))
127
123
  location.propagate_all_variants = false if location.new_record?
128
124
  location.country_code = country.iso
129
125
  if location.persisted? && location.will_save_change_to_country_code?
@@ -206,7 +202,7 @@ module Spree
206
202
  weight_unit = store.preferred_weight_unit
207
203
 
208
204
  store.package_types.create!(
209
- name: Spree.t('package_types.default_name'),
205
+ name: I18n.t('spree.package_types.default_name'),
210
206
  kind: 'box',
211
207
  default: true,
212
208
  length: metric ? 30 : 12,
@@ -237,10 +233,12 @@ module Spree
237
233
  end
238
234
  return if store.stock_locations.first_party.where(pickup_enabled: true).none?
239
235
 
240
- delivery_method = store.delivery_methods.where(name: Spree.t('pickup.store_pickup')).first_or_initialize
236
+ # Matched by its provider, not its translated (and editable) name.
237
+ delivery_method = store.delivery_methods.first_party.
238
+ where(fulfillment_provider: 'Spree::FulfillmentProvider::Pickup').
239
+ first_or_initialize(name: I18n.t('spree.pickup.store_pickup'))
241
240
  delivery_method.delivery_profile = profile
242
241
  delivery_method.storefront_visible = true
243
- delivery_method.fulfillment_provider = 'Spree::FulfillmentProvider::Pickup'
244
242
  delivery_method.calculator ||= Spree::Calculator::Shipping::FlatRate.new
245
243
  delivery_method.calculator.preferences = { amount: 0, currency: currency }
246
244
  delivery_method.save!
@@ -259,7 +257,7 @@ module Spree
259
257
  calculator = delivery_method.calculator
260
258
  next unless calculator.respond_to?(:preferred_currency)
261
259
  next if calculator.preferred_currency == currency
262
- next unless calculator.preferences[:amount].to_f.zero?
260
+ next unless Spree::Money::Rounding.blank_or_zero?(calculator.preferences[:amount])
263
261
 
264
262
  calculator.update!(preferences: calculator.preferences.merge(currency: currency))
265
263
  end
@@ -0,0 +1,160 @@
1
+ require 'net/http'
2
+
3
+ module Spree
4
+ # Asks spreecloud.io which Spree releases are newer than the running one,
5
+ # and reports usage with the same request. The answer is cached for a day,
6
+ # so at most one admin request a day waits on it, for no more than the two
7
+ # one-second timeouts. A failed check is cached for an hour, so a passing
8
+ # outage costs one slow request an hour and never hides a release for long.
9
+ #
10
+ # The +version+, +environment+, +url+ and +install_id+ query keys are what
11
+ # Spree 5 sent, and the endpoint still serves 5.x installs with them. They
12
+ # are never renamed; new keys only ever go beside them.
13
+ class UpdateCheck
14
+ ENDPOINT = 'https://spreecloud.io/updates.json'.freeze
15
+ RELEASES_URL = 'https://github.com/spree/spree/releases/tag/'.freeze
16
+ RELEASE_TAG = /\Av\d+(\.[0-9a-z]+)*\z/
17
+ CACHE_TTL = 1.day
18
+ FAILURE_TTL = 1.hour
19
+ OPEN_TIMEOUT = 1
20
+ READ_TIMEOUT = 1
21
+ # The read timeout applies to each read, not the whole response, so a
22
+ # slowly trickling answer needs an overall deadline as well.
23
+ TOTAL_TIMEOUT = 2
24
+ # Order-of-magnitude buckets: enough to tell a hobby install from a large
25
+ # merchant, never an exact figure.
26
+ BUCKETS = {
27
+ 0..0 => '0',
28
+ 1..9 => '1-9',
29
+ 10..99 => '10-99',
30
+ 100..999 => '100-999',
31
+ 1_000..9_999 => '1k-9k',
32
+ 10_000..99_999 => '10k-99k'
33
+ }.freeze
34
+ LARGEST_BUCKET = '100k+'.freeze
35
+
36
+ class << self
37
+ # @return [Boolean] false when the operator turned the check off
38
+ def enabled?
39
+ Spree::Config.update_check_enabled && !env_flag?('SPREE_UPDATE_CHECK_DISABLED')
40
+ end
41
+
42
+ # When false, the check still runs but sends the running version only.
43
+ #
44
+ # @return [Boolean]
45
+ def telemetry_enabled?
46
+ Spree::Config.telemetry_enabled &&
47
+ !env_flag?('SPREE_TELEMETRY_DISABLED') &&
48
+ !env_flag?('DO_NOT_TRACK')
49
+ end
50
+
51
+ # The cached answer, fetched from the endpoint when there is none.
52
+ #
53
+ # @return [Spree::UpdateStatus]
54
+ def status
55
+ return Spree::UpdateStatus.new(current_version: Spree.version, enabled: false) unless enabled?
56
+
57
+ result = Rails.cache.read(cache_key) || refresh
58
+
59
+ Spree::UpdateStatus.new(current_version: Spree.version, checked_at: result[:checked_at], releases: result[:releases])
60
+ end
61
+
62
+ # @return [Hash] the query string the endpoint receives
63
+ def query
64
+ return { version: Spree.version } unless telemetry_enabled?
65
+
66
+ {
67
+ version: Spree.version,
68
+ environment: Rails.env,
69
+ url: Spree::Store.default&.storefront_url,
70
+ install_id: Spree.install_id,
71
+ ruby: RUBY_VERSION,
72
+ rails: Rails.version,
73
+ database: ActiveRecord::Base.connection_db_config.adapter,
74
+ gems: Gem.loaded_specs.keys.grep(/\Aspree_/).sort.join(','),
75
+ dashboard: dashboard_served?,
76
+ stores: bucket(Spree::Store.count),
77
+ products: bucket(Spree::Product.count),
78
+ orders: bucket(Spree::Order.complete.count),
79
+ sellers: seller_bucket
80
+ }.compact
81
+ end
82
+
83
+ private
84
+
85
+ def refresh
86
+ releases = fetch_releases
87
+ result = { releases: releases || [], checked_at: Time.current }
88
+ Rails.cache.write(cache_key, result, expires_in: releases ? CACHE_TTL : FAILURE_TTL)
89
+ result
90
+ end
91
+
92
+ # Nil when the check failed, for whatever reason: an update notice is
93
+ # never worth failing the admin's request over, including a database
94
+ # error while counting for the usage fields.
95
+ def fetch_releases
96
+ uri = URI(ENDPOINT)
97
+ uri.query = URI.encode_www_form(query)
98
+
99
+ response = Timeout.timeout(TOTAL_TIMEOUT) do
100
+ Net::HTTP.start(uri.host, uri.port, use_ssl: true, max_retries: 0,
101
+ open_timeout: OPEN_TIMEOUT, read_timeout: READ_TIMEOUT) do |http|
102
+ http.get(uri.request_uri)
103
+ end
104
+ end
105
+ return unless response.is_a?(Net::HTTPSuccess)
106
+
107
+ newer_releases(JSON.parse(response.body))
108
+ rescue StandardError => e
109
+ Rails.error.report(e, handled: true, severity: :warning, source: 'spree.update_check')
110
+ nil
111
+ end
112
+
113
+ # The endpoint already filters by version; this does it again and
114
+ # builds each link from the tag, so whatever comes back over the wire
115
+ # never reaches the dashboard as a URL. Release candidates are only
116
+ # offered to installs already running one.
117
+ def newer_releases(payload)
118
+ current = Gem::Version.new(Spree.version)
119
+
120
+ Array(payload).filter_map { |release|
121
+ tag = release.is_a?(Hash) ? release['tag_name'].to_s : ''
122
+ next unless tag.match?(RELEASE_TAG)
123
+
124
+ version = Gem::Version.new(tag.delete_prefix('v'))
125
+ next unless version > current
126
+ next if version.prerelease? && !current.prerelease?
127
+
128
+ { version: version.to_s, url: "#{RELEASES_URL}#{tag}", published_at: parse_time(release['published_at']) }
129
+ }.sort_by { |release| Gem::Version.new(release[:version]) }.reverse
130
+ end
131
+
132
+ def parse_time(value)
133
+ Time.iso8601(value.to_s)
134
+ rescue ArgumentError
135
+ nil
136
+ end
137
+
138
+ def bucket(count)
139
+ BUCKETS.find { |range, _| range.cover?(count) }&.last || LARGEST_BUCKET
140
+ end
141
+
142
+ def seller_bucket
143
+ count = Spree::Seller.count
144
+ bucket(count) if count.positive?
145
+ end
146
+
147
+ def dashboard_served?
148
+ (defined?(Spree::Dashboard) && Spree::Dashboard.dist_path.present?) || Spree::Config.dashboard_url.present?
149
+ end
150
+
151
+ def env_flag?(name)
152
+ ActiveModel::Type::Boolean.new.cast(ENV[name].presence) == true
153
+ end
154
+
155
+ def cache_key
156
+ "spree/update_check/#{Spree.version}"
157
+ end
158
+ end
159
+ end
160
+ end
@@ -0,0 +1,92 @@
1
+ module Spree
2
+ module Uploads
3
+ # Gives every attached file that has no store the store of the record it
4
+ # is attached to (the 6.0 upgrade, `spree:uploads:assign_stores`).
5
+ #
6
+ # Owners with a `store_id` column are handled with one set-based update per
7
+ # attachment type; the rest (a variant's digital file, a seller's
8
+ # submission) ask {Spree::Uploads.store_id_for} record by record. Files
9
+ # attached nowhere keep no store, and the daily purge leaves them alone.
10
+ #
11
+ # Safe to re-run: only files without a store are touched.
12
+ class AssignStores
13
+ prepend Spree::ServiceModule::Base
14
+
15
+ BATCH_SIZE = 1_000
16
+
17
+ # @return [Spree::ServiceModule::Result] a hash with `assigned`, the
18
+ # number of files given a store, and `conflicts`, the ids of files
19
+ # attached to records of more than one store, which are reported and
20
+ # left for a person to resolve
21
+ def call
22
+ assigned = record_types.sum do |record_type|
23
+ owner_class = record_type.safe_constantize
24
+ next 0 if owner_class.nil?
25
+
26
+ # Renditions and previews are blobs themselves: a set-based update
27
+ # would join the table it updates, which MySQL refuses.
28
+ if owner_class.column_names.include?('store_id') && !record_type.start_with?('ActiveStorage::')
29
+ assign_from_store_column(record_type, owner_class)
30
+ else
31
+ assign_record_by_record(record_type)
32
+ end
33
+ end
34
+
35
+ success(assigned: assigned, conflicts: conflicting_blob_ids)
36
+ end
37
+
38
+ private
39
+
40
+ # Renditions and previews take their store from their original file, so
41
+ # they go last, once the originals have one.
42
+ def record_types
43
+ ActiveStorage::Attachment.distinct.pluck(:record_type).
44
+ sort_by { |record_type| [record_type.start_with?('ActiveStorage::') ? 1 : 0, record_type] }
45
+ end
46
+
47
+ # Correlated subqueries rather than UPDATE … FROM, which MySQL and SQLite
48
+ # spell differently. Several attachments of one file in different stores
49
+ # pick one; {#conflicting_blob_ids} reports them.
50
+ def assign_from_store_column(record_type, owner_class)
51
+ attachments_table = ActiveStorage::Attachment.table_name
52
+ owners_table = owner_class.table_name
53
+ owner_store = ActiveStorage::Attachment.
54
+ joins("INNER JOIN #{owners_table} ON #{owners_table}.id = #{attachments_table}.record_id").
55
+ where(record_type: record_type).
56
+ where("#{attachments_table}.blob_id = #{ActiveStorage::Blob.table_name}.id").
57
+ where.not(owners_table => { store_id: nil })
58
+
59
+ ActiveStorage::Blob.where(store_id: nil).
60
+ where(id: ActiveStorage::Attachment.where(record_type: record_type).select(:blob_id)).
61
+ where(owner_store.arel.exists).
62
+ update_all(["store_id = (#{owner_store.select("#{owners_table}.store_id").limit(1).to_sql})"])
63
+ end
64
+
65
+ # One update per store and batch rather than per file.
66
+ def assign_record_by_record(record_type)
67
+ unassigned = ActiveStorage::Attachment.joins(:blob).
68
+ where(record_type: record_type, ActiveStorage::Blob.table_name => { store_id: nil })
69
+
70
+ unassigned.preload(:record).in_batches(of: BATCH_SIZE).sum do |batch|
71
+ batch.select(&:record).group_by { |attachment| Spree::Uploads.store_id_for(attachment.record) }.sum do |store_id, owned|
72
+ next 0 if store_id.nil?
73
+
74
+ ActiveStorage::Blob.where(id: owned.map(&:blob_id), store_id: nil).update_all(store_id: store_id)
75
+ end
76
+ end
77
+ end
78
+
79
+ def conflicting_blob_ids
80
+ resolvable_types = record_types.select(&:safe_constantize)
81
+ shared_blob_ids = ActiveStorage::Attachment.where(record_type: resolvable_types).group(:blob_id).having('COUNT(*) > 1').pluck(:blob_id)
82
+
83
+ shared_blob_ids.each_slice(BATCH_SIZE).flat_map do |blob_ids|
84
+ ActiveStorage::Attachment.where(blob_id: blob_ids, record_type: resolvable_types).includes(:record).group_by(&:blob_id).filter_map do |blob_id, blob_attachments|
85
+ store_ids = blob_attachments.filter_map { |attachment| attachment.record && Spree::Uploads.store_id_for(attachment.record) }
86
+ blob_id if store_ids.map(&:to_s).uniq.size > 1
87
+ end
88
+ end
89
+ end
90
+ end
91
+ end
92
+ end
@@ -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
@@ -44,6 +44,16 @@ module Spree
44
44
  # identified outright and never reaches the tie-break at all.
45
45
  CONTAINER_CONTENT_TYPES = %w[application/zip application/x-ole-storage].freeze
46
46
 
47
+ # Plain-text formats carry no signature at all, so the bytes can only prove
48
+ # what they are not. Marcel names anything it recognises — HTML, XML, SVG,
49
+ # scripts with a shebang, executables — and those never reach this
50
+ # tie-break. What is left is accepted under a text extension only when it
51
+ # is free of the control bytes every binary format carries. No particular
52
+ # encoding is required, since spreadsheet tools still export in legacy ones.
53
+ TEXT_CONTENT_TYPES = %w[text/csv].freeze
54
+ UNIDENTIFIED_CONTENT_TYPES = %w[application/octet-stream text/plain].freeze
55
+ BINARY_BYTES = /[\x00-\x08\x0B\x0E-\x1F\x7F]/n
56
+
47
57
  # A misspelled or forgotten option would otherwise leave this validator
48
58
  # silently accepting everything, on the one path whose whole purpose is
49
59
  # refusing hostile files. An allowlist that resolves to empty at runtime is
@@ -116,9 +126,16 @@ module Spree
116
126
  # read, which would hand the decision back to whoever uploaded the file.
117
127
  detected = Marcel::MimeType.for(StringIO.new(head))
118
128
  return true if permitted.include?(detected)
129
+ return permitted_plain_text?(blob, head, permitted) if UNIDENTIFIED_CONTENT_TYPES.include?(detected)
119
130
  return false unless CONTAINER_CONTENT_TYPES.include?(detected)
120
131
 
121
- permitted.include?(office_type_from_extension(blob))
132
+ permitted.include?(type_from_extension(blob))
133
+ end
134
+
135
+ def permitted_plain_text?(blob, head, permitted)
136
+ text_type = type_from_extension(blob)
137
+
138
+ TEXT_CONTENT_TYPES.include?(text_type) && permitted.include?(text_type) && !head.b.match?(BINARY_BYTES)
122
139
  end
123
140
 
124
141
  # Asks the extension alone, rather than re-reading the bytes with a filename
@@ -129,7 +146,7 @@ module Spree
129
146
  # bytes we read.
130
147
  #
131
148
  # @return [String, nil]
132
- def office_type_from_extension(blob)
149
+ def type_from_extension(blob)
133
150
  filename = blob.filename.to_s
134
151
  return if File.extname(filename).blank?
135
152
 
@@ -0,0 +1,22 @@
1
+ module Spree
2
+ # Rejects locale codes `Spree::Locales::ALL` does not list, in a single code
3
+ # or a comma-separated list, naming the ones it refuses. Reads the stored
4
+ # column, since a store answers `default_locale` from its default market,
5
+ # and checks only an attribute that is changing, so a record saved before
6
+ # the list was enforced can still be edited.
7
+ #
8
+ # @example
9
+ # validates_with Spree::LocaleCodesValidator, attributes: %i[default_locale supported_locales]
10
+ class LocaleCodesValidator < ActiveModel::Validator
11
+ def validate(record)
12
+ Array(options[:attributes]).each do |attribute|
13
+ next unless record.will_save_change_to_attribute?(attribute)
14
+
15
+ unlisted = Spree::Locales.unlisted(record.read_attribute(attribute))
16
+ next if unlisted.empty?
17
+
18
+ record.errors.add(attribute, I18n.t('spree.locales_not_listed', codes: unlisted.join(', ')))
19
+ end
20
+ end
21
+ end
22
+ end