spree_core 6.0.0.beta4 → 6.0.0.rc1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (495) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +2 -2
  3. data/app/helpers/spree/images_helper.rb +0 -57
  4. data/app/jobs/spree/base_job.rb +21 -0
  5. data/app/jobs/spree/carts/reap_expired_job.rb +2 -2
  6. data/app/jobs/spree/collections/regenerate_time_based_job.rb +6 -3
  7. data/app/jobs/spree/orders/finalize_stale_drafts_job.rb +8 -5
  8. data/app/jobs/spree/payments/cancel_unused_sessions_job.rb +61 -0
  9. data/app/jobs/spree/seller_payouts/sweep_seller_job.rb +2 -0
  10. data/app/mailers/spree/admin_user_mailer.rb +4 -19
  11. data/app/mailers/spree/base_mailer.rb +96 -13
  12. data/app/mailers/spree/email_template_mailer.rb +27 -0
  13. data/app/mailers/spree/export_mailer.rb +3 -4
  14. data/app/mailers/spree/import_mailer.rb +3 -4
  15. data/app/mailers/spree/invitation_mailer.rb +24 -7
  16. data/app/mailers/spree/seller_user_mailer.rb +4 -16
  17. data/app/mailers/spree/webhook_mailer.rb +2 -2
  18. data/app/models/concerns/spree/acted_by.rb +1 -1
  19. data/app/models/concerns/spree/admin_user_methods.rb +13 -1
  20. data/app/models/concerns/spree/api_typed.rb +91 -0
  21. data/app/models/concerns/spree/calculator/tiers.rb +76 -0
  22. data/app/models/concerns/spree/customer_methods.rb +15 -14
  23. data/app/models/concerns/spree/email_template_identity.rb +52 -0
  24. data/app/models/concerns/spree/parameterizable_name.rb +2 -0
  25. data/app/models/concerns/spree/payment_session_transitions.rb +8 -2
  26. data/app/models/concerns/spree/post_sale/taxation.rb +54 -0
  27. data/app/models/concerns/spree/post_sale/taxed_line.rb +51 -0
  28. data/app/models/concerns/spree/preference_schema.rb +4 -12
  29. data/app/models/concerns/spree/product_scopes.rb +11 -1
  30. data/app/models/concerns/spree/provider_listing.rb +41 -0
  31. data/app/models/concerns/spree/publishable.rb +82 -14
  32. data/app/models/concerns/spree/purchase/addresses.rb +23 -10
  33. data/app/models/concerns/spree/purchase/currency.rb +1 -1
  34. data/app/models/concerns/spree/purchase/deprecated_aliases.rb +87 -0
  35. data/app/models/concerns/spree/purchase/gift_cards.rb +10 -4
  36. data/app/models/concerns/spree/purchase/locale.rb +1 -1
  37. data/app/models/concerns/spree/purchase/payment_processing.rb +7 -3
  38. data/app/models/concerns/spree/purchase/purchase_order.rb +2 -2
  39. data/app/models/concerns/spree/purchase/totals.rb +49 -1
  40. data/app/models/concerns/spree/receivable.rb +1 -1
  41. data/app/models/concerns/spree/searchable.rb +0 -4
  42. data/app/models/concerns/spree/secret_preferences.rb +59 -0
  43. data/app/models/concerns/spree/single_store_resource.rb +1 -1
  44. data/app/models/concerns/spree/stores/email_branding.rb +31 -0
  45. data/app/models/concerns/spree/type_labels.rb +38 -0
  46. data/app/models/concerns/spree/typed_adjustment_line.rb +1 -1
  47. data/app/models/concerns/spree/typed_associations.rb +6 -0
  48. data/app/models/spree/address.rb +11 -1
  49. data/app/models/spree/adjusters/promotion.rb +1 -15
  50. data/app/models/spree/allowed_origin.rb +0 -1
  51. data/app/models/spree/api_key.rb +5 -6
  52. data/app/models/spree/authentication/strategies/oidc_strategy.rb +5 -5
  53. data/app/models/spree/base.rb +70 -22
  54. data/app/models/spree/calculator/flat_percent_item_total.rb +1 -3
  55. data/app/models/spree/calculator/flat_rate.rb +1 -3
  56. data/app/models/spree/calculator/flexi_rate.rb +1 -3
  57. data/app/models/spree/calculator/percent_on_line_item.rb +1 -1
  58. data/app/models/spree/calculator/price_sack.rb +1 -3
  59. data/app/models/spree/calculator/shipping/digital_delivery.rb +1 -3
  60. data/app/models/spree/calculator/shipping/flat_percent_item_total.rb +1 -3
  61. data/app/models/spree/calculator/shipping/flat_rate.rb +1 -3
  62. data/app/models/spree/calculator/shipping/flexi_rate.rb +1 -3
  63. data/app/models/spree/calculator/shipping/per_item.rb +1 -3
  64. data/app/models/spree/calculator/shipping/price_sack.rb +1 -3
  65. data/app/models/spree/calculator/tiered_flat_rate.rb +6 -23
  66. data/app/models/spree/calculator/tiered_percent.rb +7 -25
  67. data/app/models/spree/cart.rb +78 -18
  68. data/app/models/spree/channel/gating.rb +1 -1
  69. data/app/models/spree/channel.rb +4 -4
  70. data/app/models/spree/claim.rb +21 -1
  71. data/app/models/spree/claim_line_item.rb +41 -6
  72. data/app/models/spree/collection.rb +0 -1
  73. data/app/models/spree/collection_rule.rb +5 -21
  74. data/app/models/spree/commission_line.rb +1 -1
  75. data/app/models/spree/commission_rate.rb +2 -3
  76. data/app/models/spree/commission_rate_value.rb +1 -1
  77. data/app/models/spree/commission_rule.rb +4 -11
  78. data/app/models/spree/commission_rules/product_rule.rb +1 -1
  79. data/app/models/spree/company_invitation.rb +1 -0
  80. data/app/models/spree/consent_record.rb +0 -1
  81. data/app/models/spree/credit_card.rb +1 -1
  82. data/app/models/spree/current.rb +23 -0
  83. data/app/models/spree/custom_field.rb +2 -2
  84. data/app/models/spree/custom_field_definition.rb +9 -23
  85. data/app/models/spree/custom_fields/boolean.rb +1 -1
  86. data/app/models/spree/custom_fields/json.rb +1 -1
  87. data/app/models/spree/data_feed/google.rb +0 -2
  88. data/app/models/spree/data_request.rb +1 -1
  89. data/app/models/spree/delivery_method.rb +30 -13
  90. data/app/models/spree/delivery_method_rule.rb +4 -11
  91. data/app/models/spree/delivery_rate.rb +4 -5
  92. data/app/models/spree/delivery_rate_provider/base.rb +1 -0
  93. data/app/models/spree/delivery_zone.rb +0 -1
  94. data/app/models/spree/delivery_zone_member.rb +4 -4
  95. data/app/models/spree/digital_asset.rb +7 -0
  96. data/app/models/spree/digital_asset_provider/base.rb +2 -0
  97. data/app/models/spree/digital_link.rb +2 -5
  98. data/app/models/spree/discount.rb +1 -1
  99. data/app/models/spree/email_template.rb +25 -0
  100. data/app/models/spree/email_template_draft.rb +13 -0
  101. data/app/models/spree/email_template_revision.rb +17 -0
  102. data/app/models/spree/email_templates/entry.rb +118 -0
  103. data/app/models/spree/email_templates/sample_record.rb +16 -0
  104. data/app/models/spree/emails/branding.rb +80 -0
  105. data/app/models/spree/exchange.rb +15 -1
  106. data/app/models/spree/exchange_line_item.rb +121 -5
  107. data/app/models/spree/export.rb +0 -1
  108. data/app/models/spree/exports/products.rb +10 -1
  109. data/app/models/spree/exports/report.rb +1 -1
  110. data/app/models/spree/fee.rb +4 -2
  111. data/app/models/spree/fulfillment.rb +53 -27
  112. data/app/models/spree/fulfillment_changer.rb +2 -2
  113. data/app/models/spree/fulfillment_item.rb +0 -4
  114. data/app/models/spree/fulfillment_provider/base.rb +1 -0
  115. data/app/models/spree/gateway/bogus.rb +4 -0
  116. data/app/models/spree/gateway/custom_payment_source_method.rb +5 -0
  117. data/app/models/spree/gateway.rb +5 -1
  118. data/app/models/spree/gift_card.rb +12 -1
  119. data/app/models/spree/gift_card_batch.rb +0 -1
  120. data/app/models/spree/import.rb +2 -7
  121. data/app/models/spree/import_mapping.rb +1 -2
  122. data/app/models/spree/import_row.rb +2 -2
  123. data/app/models/spree/import_schemas/products.rb +1 -0
  124. data/app/models/spree/integration.rb +8 -18
  125. data/app/models/spree/invitation.rb +5 -5
  126. data/app/models/spree/line_item.rb +13 -2
  127. data/app/models/spree/locale.rb +11 -8
  128. data/app/models/spree/market.rb +1 -0
  129. data/app/models/spree/newsletter_subscriber.rb +3 -0
  130. data/app/models/spree/order.rb +56 -138
  131. data/app/models/spree/order_group.rb +11 -2
  132. data/app/models/spree/order_inventory.rb +10 -5
  133. data/app/models/spree/order_promotion.rb +1 -1
  134. data/app/models/spree/order_routing/has_strategy_preference.rb +1 -1
  135. data/app/models/spree/order_routing/strategy/base.rb +4 -2
  136. data/app/models/spree/order_routing/strategy/rules.rb +1 -1
  137. data/app/models/spree/order_routing_rule.rb +6 -22
  138. data/app/models/spree/package_type.rb +1 -2
  139. data/app/models/spree/payment/gateway_options.rb +14 -52
  140. data/app/models/spree/payment/processing.rb +2 -4
  141. data/app/models/spree/payment.rb +20 -5
  142. data/app/models/spree/payment_method/session_instrumentation.rb +1 -0
  143. data/app/models/spree/payment_method/store_credit.rb +4 -4
  144. data/app/models/spree/payment_method.rb +36 -4
  145. data/app/models/spree/payment_session.rb +5 -1
  146. data/app/models/spree/payment_setup_session.rb +1 -0
  147. data/app/models/spree/payout_provider/base.rb +3 -1
  148. data/app/models/spree/pickup_point_provider/base.rb +2 -0
  149. data/app/models/spree/price_list.rb +1 -2
  150. data/app/models/spree/price_rules/channel_rule.rb +1 -1
  151. data/app/models/spree/price_rules/customer_group_rule.rb +1 -1
  152. data/app/models/spree/product.rb +31 -7
  153. data/app/models/spree/product_submission.rb +0 -12
  154. data/app/models/spree/product_type.rb +0 -1
  155. data/app/models/spree/promotion/actions/create_line_items.rb +27 -17
  156. data/app/models/spree/promotion/rules/category.rb +0 -4
  157. data/app/models/spree/promotion/rules/channel.rb +0 -4
  158. data/app/models/spree/promotion/rules/country.rb +0 -4
  159. data/app/models/spree/promotion/rules/currency.rb +0 -4
  160. data/app/models/spree/promotion/rules/customer_group.rb +0 -4
  161. data/app/models/spree/promotion/rules/first_order.rb +0 -4
  162. data/app/models/spree/promotion/rules/item_total.rb +4 -8
  163. data/app/models/spree/promotion/rules/market.rb +0 -4
  164. data/app/models/spree/promotion/rules/one_use_per_user.rb +0 -4
  165. data/app/models/spree/promotion/rules/option_value.rb +0 -3
  166. data/app/models/spree/promotion/rules/product.rb +0 -4
  167. data/app/models/spree/promotion/rules/user.rb +0 -4
  168. data/app/models/spree/promotion/rules/user_logged_in.rb +0 -4
  169. data/app/models/spree/promotion.rb +35 -6
  170. data/app/models/spree/promotion_action.rb +7 -14
  171. data/app/models/spree/promotion_action_line_item.rb +1 -1
  172. data/app/models/spree/promotion_handler/coupon.rb +59 -27
  173. data/app/models/spree/promotion_rule.rb +7 -14
  174. data/app/models/spree/purchase_order.rb +1 -0
  175. data/app/models/spree/quantity_rule.rb +1 -1
  176. data/app/models/spree/refund.rb +28 -3
  177. data/app/models/spree/return.rb +30 -2
  178. data/app/models/spree/return_line_item.rb +65 -14
  179. data/app/models/spree/role.rb +7 -7
  180. data/app/models/spree/saved_report.rb +1 -1
  181. data/app/models/spree/seller.rb +2 -1
  182. data/app/models/spree/seller_payout.rb +7 -0
  183. data/app/models/spree/seller_requirement.rb +4 -12
  184. data/app/models/spree/seller_requirement_custom_field.rb +1 -2
  185. data/app/models/spree/seller_requirement_submission.rb +2 -2
  186. data/app/models/spree/seller_requirements/accept_terms.rb +2 -3
  187. data/app/models/spree/seller_transfer.rb +5 -0
  188. data/app/models/spree/shipping_label.rb +1 -0
  189. data/app/models/spree/stock/availability_validator.rb +2 -2
  190. data/app/models/spree/stock/inventory_unit_builder.rb +1 -1
  191. data/app/models/spree/stock/packer.rb +2 -2
  192. data/app/models/spree/stock/splitter/weight.rb +1 -1
  193. data/app/models/spree/stock_level/custom_events.rb +6 -0
  194. data/app/models/spree/stock_level.rb +1 -1
  195. data/app/models/spree/stock_movement.rb +1 -1
  196. data/app/models/spree/stock_receipt_item.rb +1 -1
  197. data/app/models/spree/stock_reservation.rb +2 -2
  198. data/app/models/spree/stock_transfer.rb +4 -2
  199. data/app/models/spree/store.rb +33 -7
  200. data/app/models/spree/store_credit.rb +8 -9
  201. data/app/models/spree/store_credit_event.rb +5 -5
  202. data/app/models/spree/tax_exemption_certificate.rb +2 -0
  203. data/app/models/spree/tax_identifier.rb +3 -1
  204. data/app/models/spree/tax_line.rb +53 -3
  205. data/app/models/spree/tax_provider/base.rb +71 -14
  206. data/app/models/spree/tax_provider/internal.rb +59 -33
  207. data/app/models/spree/tax_provider/recorded_share.rb +114 -0
  208. data/app/models/spree/tax_rate.rb +1 -2
  209. data/app/models/spree/taxonomy.rb +0 -1
  210. data/app/models/spree/variant.rb +4 -3
  211. data/app/models/spree/variant_media.rb +1 -1
  212. data/app/models/spree/webhook_endpoint.rb +10 -13
  213. data/app/models/spree/wishlist.rb +1 -6
  214. data/app/models/spree/wishlist_item/custom_events.rb +6 -0
  215. data/app/presenters/spree/csv/customer_presenter.rb +1 -1
  216. data/app/presenters/spree/csv/newsletter_subscriber_presenter.rb +1 -1
  217. data/app/presenters/spree/csv/order_line_item_presenter.rb +1 -1
  218. data/app/presenters/spree/csv/product_variant_presenter.rb +42 -12
  219. data/app/serializers/spree/emails/base_serializer.rb +14 -0
  220. data/app/serializers/spree/emails/export_serializer.rb +13 -0
  221. data/app/serializers/spree/emails/import_serializer.rb +19 -0
  222. data/app/serializers/spree/emails/invitation_serializer.rb +28 -0
  223. data/app/serializers/spree/emails/store_serializer.rb +31 -0
  224. data/app/serializers/spree/emails/user_serializer.rb +12 -0
  225. data/app/serializers/spree/emails/webhook_endpoint_serializer.rb +16 -0
  226. data/app/services/spree/carts/create.rb +8 -2
  227. data/app/services/spree/carts/destroy.rb +2 -2
  228. data/app/services/spree/carts/remove_out_of_stock_items.rb +2 -2
  229. data/app/services/spree/carts/split_by_seller.rb +1 -1
  230. data/app/services/spree/carts/update.rb +19 -5
  231. data/app/services/spree/catalogs/set_product_terms.rb +1 -1
  232. data/app/services/spree/categories/add_products.rb +13 -31
  233. data/app/services/spree/categories/remove_products.rb +12 -51
  234. data/app/services/spree/checkout/advance.rb +1 -4
  235. data/app/services/spree/collections/add_products.rb +10 -23
  236. data/app/services/spree/collections/remove_products.rb +7 -43
  237. data/app/services/spree/coupon_codes/bulk_generate.rb +8 -14
  238. data/app/services/spree/data_requests/create.rb +1 -1
  239. data/app/services/spree/deliveries/destroy.rb +2 -2
  240. data/app/services/spree/email_templates/check.rb +58 -0
  241. data/app/services/spree/email_templates/no_sample_record.rb +7 -0
  242. data/app/services/spree/email_templates/preview.rb +99 -0
  243. data/app/services/spree/emails/file_system.rb +23 -0
  244. data/app/services/spree/emails/filters.rb +84 -0
  245. data/app/services/spree/emails/liquid_context.rb +48 -0
  246. data/app/services/spree/emails/rendered_email.rb +14 -0
  247. data/app/services/spree/emails/renderer.rb +125 -0
  248. data/app/services/spree/emails/template.rb +51 -0
  249. data/app/services/spree/emails/template_data.rb +23 -0
  250. data/app/services/spree/emails/template_resolver.rb +109 -0
  251. data/app/services/spree/emails/text_converter.rb +72 -0
  252. data/app/services/spree/emails/translation_inliner.rb +134 -0
  253. data/app/services/spree/imports/row_processors/price_list_price.rb +10 -10
  254. data/app/services/spree/imports/row_processors/product_variant.rb +65 -34
  255. data/app/services/spree/imports/row_processors/purchase_order.rb +13 -13
  256. data/app/services/spree/locales.rb +4 -5
  257. data/app/services/spree/newsletter/subscribe.rb +8 -12
  258. data/app/services/spree/orders/build_fulfillments.rb +3 -2
  259. data/app/services/spree/orders/create.rb +2 -1
  260. data/app/services/spree/orders/discounts/create.rb +7 -4
  261. data/app/services/spree/orders/discounts/destroy.rb +1 -0
  262. data/app/services/spree/orders/discounts/update.rb +1 -0
  263. data/app/services/spree/orders/fees/create.rb +1 -0
  264. data/app/services/spree/orders/fees/destroy.rb +1 -0
  265. data/app/services/spree/orders/fees/update.rb +1 -0
  266. data/app/services/spree/orders/remove_line_item.rb +13 -3
  267. data/app/services/spree/orders/update.rb +10 -6
  268. data/app/services/spree/orders/update_item.rb +6 -3
  269. data/app/services/spree/orders/update_statuses.rb +3 -1
  270. data/app/services/spree/product_memberships.rb +61 -0
  271. data/app/services/spree/returns/eligibility_validator.rb +1 -1
  272. data/app/services/spree/search_provider/custom_field_schema.rb +2 -2
  273. data/app/services/spree/seeds/commission_rates.rb +1 -1
  274. data/app/services/spree/seeds/digital_delivery.rb +1 -1
  275. data/app/services/spree/seeds/payment_methods.rb +2 -2
  276. data/app/services/spree/seeds/saved_reports.rb +3 -3
  277. data/app/services/spree/sellers/requirements.rb +1 -2
  278. data/app/services/spree/shipping_labels/confirm_refund.rb +1 -1
  279. data/app/services/spree/shipping_labels/store_file.rb +3 -3
  280. data/app/services/spree/stock_levels/correct.rb +3 -3
  281. data/app/services/spree/stock_reservations/reserve.rb +2 -3
  282. data/app/services/spree/store_credits/apply.rb +15 -13
  283. data/app/services/spree/stores/provision_defaults.rb +15 -14
  284. data/app/subscribers/spree/seller_onboarding_subscriber.rb +1 -1
  285. data/app/views/layouts/spree/base_mailer.html.erb +2 -43
  286. data/app/views/layouts/spree/base_mailer.liquid +79 -0
  287. data/app/views/spree/admin_user_mailer/password_reset_email.liquid +14 -0
  288. data/app/views/spree/export_mailer/export_done.liquid +12 -0
  289. data/app/views/spree/import_mailer/import_done.liquid +15 -0
  290. data/app/views/spree/invitation_mailer/invitation_accepted.liquid +9 -0
  291. data/app/views/spree/invitation_mailer/invitation_email.liquid +13 -0
  292. data/app/views/spree/seller_user_mailer/password_reset_email.liquid +14 -0
  293. data/app/views/spree/shared/_mailer_button.html.erb +4 -5
  294. data/app/views/spree/shared/_mailer_hero.html.erb +6 -5
  295. data/app/views/spree/shared/_summary_row.liquid +8 -0
  296. data/app/views/spree/webhook_mailer/endpoint_disabled.liquid +21 -0
  297. data/app/workflows/spree/carts/add_item.rb +4 -4
  298. data/app/workflows/spree/carts/complete.rb +13 -3
  299. data/app/workflows/spree/carts/merge.rb +1 -1
  300. data/app/workflows/spree/carts/recalculate_totals.rb +5 -0
  301. data/app/workflows/spree/carts/upsert_items.rb +18 -9
  302. data/app/workflows/spree/claims/approve.rb +5 -22
  303. data/app/workflows/spree/claims/cancel.rb +5 -21
  304. data/app/workflows/spree/claims/create.rb +15 -0
  305. data/app/workflows/spree/claims/deny.rb +7 -0
  306. data/app/workflows/spree/claims/resolve.rb +34 -31
  307. data/app/workflows/spree/customers/anonymize.rb +3 -4
  308. data/app/workflows/spree/customers/create.rb +1 -1
  309. data/app/workflows/spree/data_requests/fulfill.rb +2 -2
  310. data/app/workflows/spree/deliveries/update_tracking.rb +1 -1
  311. data/app/workflows/spree/email_templates/discard_draft.rb +20 -0
  312. data/app/workflows/spree/email_templates/publish.rb +70 -0
  313. data/app/workflows/spree/email_templates/restore_revision.rb +33 -0
  314. data/app/workflows/spree/email_templates/revert.rb +29 -0
  315. data/app/workflows/spree/email_templates/save_draft.rb +85 -0
  316. data/app/workflows/spree/exchanges/approve.rb +5 -22
  317. data/app/workflows/spree/exchanges/cancel.rb +5 -21
  318. data/app/workflows/spree/exchanges/create.rb +6 -0
  319. data/app/workflows/spree/exchanges/fulfill.rb +75 -51
  320. data/app/workflows/spree/exchanges/receive.rb +6 -65
  321. data/app/workflows/spree/fulfillments/cancel.rb +1 -1
  322. data/app/workflows/spree/fulfillments/create.rb +51 -47
  323. data/app/workflows/spree/fulfillments/fulfill.rb +23 -27
  324. data/app/workflows/spree/fulfillments/mark_delivered.rb +1 -1
  325. data/app/workflows/spree/fulfillments/replacements.rb +59 -0
  326. data/app/workflows/spree/gift_cards/apply.rb +2 -2
  327. data/app/workflows/spree/orders/add_item.rb +5 -1
  328. data/app/workflows/spree/orders/cancel.rb +3 -3
  329. data/app/workflows/spree/orders/complete.rb +16 -8
  330. data/app/workflows/spree/payments/process.rb +2 -2
  331. data/app/workflows/spree/post_sale/approval.rb +35 -0
  332. data/app/workflows/spree/post_sale/cancellation.rb +44 -0
  333. data/app/workflows/spree/post_sale/receipt.rb +92 -0
  334. data/app/workflows/spree/products/nested_attributes.rb +1 -1
  335. data/app/workflows/spree/purchase_orders/cancel.rb +1 -1
  336. data/app/workflows/spree/purchase_orders/close.rb +3 -31
  337. data/app/workflows/spree/purchase_orders/mark_draft.rb +2 -2
  338. data/app/workflows/spree/purchase_orders/mark_ordered.rb +2 -2
  339. data/app/workflows/spree/purchase_orders/receive.rb +1 -1
  340. data/app/workflows/spree/purchase_orders/update.rb +5 -34
  341. data/app/workflows/spree/receivables/closing_short.rb +46 -0
  342. data/app/workflows/spree/receivables/draft_editing.rb +48 -0
  343. data/app/workflows/spree/refunds/create.rb +5 -2
  344. data/app/workflows/spree/refunds/order_payments.rb +14 -2
  345. data/app/workflows/spree/refunds/tax_credit.rb +67 -0
  346. data/app/workflows/spree/returns/approve.rb +5 -22
  347. data/app/workflows/spree/returns/cancel.rb +5 -22
  348. data/app/workflows/spree/returns/create.rb +8 -0
  349. data/app/workflows/spree/returns/receive.rb +5 -76
  350. data/app/workflows/spree/returns/refund.rb +31 -8
  351. data/app/workflows/spree/seller_requirement_submissions/create.rb +1 -2
  352. data/app/workflows/spree/seller_transfers/reverse.rb +28 -1
  353. data/app/workflows/spree/shipping_labels/purchase.rb +8 -8
  354. data/app/workflows/spree/shipping_labels/record.rb +5 -5
  355. data/app/workflows/spree/shipping_labels/refund.rb +2 -2
  356. data/app/workflows/spree/stock_receipts/recording.rb +4 -4
  357. data/app/workflows/spree/stock_transfers/cancel.rb +2 -2
  358. data/app/workflows/spree/stock_transfers/close.rb +3 -30
  359. data/app/workflows/spree/stock_transfers/mark_draft.rb +1 -1
  360. data/app/workflows/spree/stock_transfers/mark_in_transit.rb +3 -3
  361. data/app/workflows/spree/stock_transfers/mark_ready.rb +2 -2
  362. data/app/workflows/spree/stock_transfers/receive.rb +1 -1
  363. data/app/workflows/spree/stock_transfers/update.rb +5 -33
  364. data/config/i18n-tasks.yml +32 -18
  365. data/config/initializers/alba.rb +7 -0
  366. data/config/language_names.csv +264 -0
  367. data/config/locales/ar.yml +811 -0
  368. data/config/locales/be.yml +855 -0
  369. data/config/locales/bg.yml +723 -0
  370. data/config/locales/ca.yml +689 -0
  371. data/config/locales/cs.yml +855 -0
  372. data/config/locales/da.yml +771 -0
  373. data/config/locales/de-CH.yml +248 -0
  374. data/config/locales/de.yml +835 -0
  375. data/config/locales/el.yml +769 -0
  376. data/config/locales/en.yml +37 -503
  377. data/config/locales/es-CL.yml +301 -0
  378. data/config/locales/es-EC.yml +186 -0
  379. data/config/locales/es-MX.yml +334 -0
  380. data/config/locales/es.yml +831 -0
  381. data/config/locales/et.yml +644 -0
  382. data/config/locales/fa.yml +709 -0
  383. data/config/locales/fi.yml +771 -0
  384. data/config/locales/fr.yml +830 -0
  385. data/config/locales/hu.yml +751 -0
  386. data/config/locales/id.yml +756 -0
  387. data/config/locales/it.yml +833 -0
  388. data/config/locales/ja.yml +726 -0
  389. data/config/locales/km.yml +759 -0
  390. data/config/locales/ko.yml +575 -0
  391. data/config/locales/ku.yml +753 -0
  392. data/config/locales/lt.yml +821 -0
  393. data/config/locales/lv.yml +685 -0
  394. data/config/locales/nb.yml +824 -0
  395. data/config/locales/ne.yml +823 -0
  396. data/config/locales/nl.yml +826 -0
  397. data/config/locales/pl.yml +835 -0
  398. data/config/locales/pt-BR.yml +547 -0
  399. data/config/locales/pt.yml +636 -0
  400. data/config/locales/ro.yml +750 -0
  401. data/config/locales/ru.yml +834 -0
  402. data/config/locales/sk.yml +680 -0
  403. data/config/locales/sl-SI.yml +600 -0
  404. data/config/locales/sv.yml +827 -0
  405. data/config/locales/th.yml +745 -0
  406. data/config/locales/tr.yml +777 -0
  407. data/config/locales/uk.yml +836 -0
  408. data/config/locales/vi.yml +768 -0
  409. data/config/locales/zh-CN.yml +786 -0
  410. data/config/locales/zh-TW.yml +834 -0
  411. data/db/migrate/20260925000001_add_replacement_to_spree_fulfillment_items.rb +5 -0
  412. data/db/migrate/20261001000001_add_post_sale_tax.rb +30 -0
  413. data/db/migrate/20261001000001_convert_preferences_to_json.rb +166 -0
  414. data/db/migrate/20261001000001_create_spree_email_templates.rb +52 -0
  415. data/lib/generators/spree/dummy/dummy_generator.rb +0 -9
  416. data/lib/generators/spree/dummy/templates/rails/test.rb +7 -0
  417. data/lib/spree/checkout/default_requirements.rb +37 -13
  418. data/lib/spree/core/configuration.rb +1 -1
  419. data/lib/spree/core/controller_helpers/store.rb +1 -1
  420. data/lib/spree/core/dependencies.rb +5 -4
  421. data/lib/spree/core/emails/escaped_output.rb +60 -0
  422. data/lib/spree/core/engine.rb +48 -167
  423. data/lib/spree/core/permissions/default_catalog.rb +5 -0
  424. data/lib/spree/core/preferences/json_conversion.rb +182 -0
  425. data/lib/spree/core/preferences/masking.rb +4 -1
  426. data/lib/spree/core/preferences/preferable.rb +137 -83
  427. data/lib/spree/core/preferences/preferable_class_methods.rb +81 -146
  428. data/lib/spree/core/previews/import_preview.rb +10 -0
  429. data/lib/spree/core/tax/provider_error.rb +37 -0
  430. data/lib/spree/core/version.rb +1 -1
  431. data/lib/spree/core.rb +85 -370
  432. data/lib/spree/emails/editable_templates.rb +82 -0
  433. data/lib/spree/events/catalog.rb +189 -0
  434. data/lib/spree/events.rb +21 -1
  435. data/lib/spree/hooks.rb +0 -17
  436. data/lib/spree/i18n.rb +32 -15
  437. data/lib/spree/reporting/adapters/live.rb +6 -6
  438. data/lib/spree/reporting/default_vocabulary.rb +22 -10
  439. data/lib/spree/reporting/hydration.rb +1 -1
  440. data/lib/spree/reporting/query.rb +8 -0
  441. data/lib/spree/reporting/schema.rb +10 -10
  442. data/lib/spree/testing_support/editable_email_templates.rb +61 -0
  443. data/lib/spree/testing_support/emails.rb +22 -0
  444. data/lib/spree/testing_support/factories/cart_factory.rb +1 -1
  445. data/lib/spree/testing_support/factories/email_template_factory.rb +28 -0
  446. data/lib/spree/testing_support/factories/order_factory.rb +2 -2
  447. data/lib/spree/testing_support/factories/refund_factory.rb +1 -1
  448. data/lib/spree/testing_support/factories/stock_factory.rb +1 -1
  449. data/lib/spree/testing_support/factories/variant_factory.rb +1 -1
  450. data/lib/spree/testing_support/i18n.rb +3 -94
  451. data/lib/spree/testing_support/lifecycle_events.rb +1 -1
  452. data/lib/spree/testing_support/order_walkthrough.rb +1 -1
  453. data/lib/spree/testing_support/preferences.rb +0 -5
  454. data/lib/spree/testing_support/store.rb +9 -1
  455. data/lib/spree/upgrades/5_6_to_6_0/manifest.yml +45 -0
  456. data/lib/tasks/categories_and_collections.rake +3 -3
  457. data/lib/tasks/core.rake +6 -4
  458. data/lib/tasks/media.rake +3 -3
  459. data/lib/tasks/package_types.rake +1 -1
  460. data/lib/tasks/preferences_json.rake +101 -0
  461. data/spec/fixtures/mailer_views/custom_views_probe_mailer/welcome_email.html.erb +4 -0
  462. metadata +188 -44
  463. data/app/models/acts_as_taggable_on/tag_decorator.rb +0 -10
  464. data/app/models/concerns/spree/has_image_alt_text.rb +0 -18
  465. data/app/models/spree/base_analytics_event_handler.rb +0 -54
  466. data/app/models/spree/invitations/store.rb +0 -6
  467. data/app/models/spree/preference.rb +0 -14
  468. data/app/models/spree/promotion_handler/page.rb +0 -25
  469. data/app/models/spree/promotion_handler/promotion_duplicator.rb +0 -60
  470. data/app/models/spree/stock/differentiator.rb +0 -45
  471. data/app/presenters/spree/filters/options_presenter.rb +0 -47
  472. data/app/services/spree/carts/empty.rb +0 -42
  473. data/app/services/spree/country_to_timezone.rb +0 -273
  474. data/app/services/spree/payments/create.rb +0 -92
  475. data/app/validators/spree/url_validator.rb +0 -23
  476. data/app/views/active_storage/blobs/_blob.html.erb +0 -14
  477. data/app/views/spree/admin_user_mailer/password_reset_email.html.erb +0 -8
  478. data/app/views/spree/export_mailer/export_done.html.erb +0 -7
  479. data/app/views/spree/import_mailer/import_done.html.erb +0 -12
  480. data/app/views/spree/invitation_mailer/invitation_accepted.html.erb +0 -4
  481. data/app/views/spree/invitation_mailer/invitation_email.html.erb +0 -12
  482. data/app/views/spree/seller_user_mailer/password_reset_email.html.erb +0 -8
  483. data/app/views/spree/shared/_base_mailer_footer.html.erb +0 -19
  484. data/app/views/spree/shared/_base_mailer_header.html.erb +0 -8
  485. data/app/views/spree/shared/_base_mailer_stylesheets.html.erb +0 -399
  486. data/app/views/spree/shared/_mailer_logo.html.erb +0 -41
  487. data/app/views/spree/shared/_purchased_items_styles.html.erb +0 -119
  488. data/app/views/spree/webhook_mailer/endpoint_disabled.html.erb +0 -51
  489. data/app/views/spree/webhook_mailer/endpoint_disabled.text.erb +0 -10
  490. data/app/workflows/spree/fulfillments/purchase_label.rb +0 -40
  491. data/app/workflows/spree/returns/purchase_label.rb +0 -32
  492. data/lib/spree/analytics.rb +0 -11
  493. data/lib/spree/core/preferences/scoped_store.rb +0 -34
  494. data/lib/spree/core/preferences/store.rb +0 -105
  495. data/lib/spree/translation_migrations.rb +0 -52
@@ -23,24 +23,20 @@ class Spree::Base < ApplicationRecord
23
23
  # rather than assigning, so extensions don't clobber each other.
24
24
  class_attribute :additional_permitted_attributes, instance_writer: false, default: [].freeze
25
25
 
26
+ # The Seller API's counterpart, kept separate because sellers write a
27
+ # narrower set than operators: an attribute an extension makes writable for
28
+ # admins stays admin-only unless it is also declared here.
29
+ #
30
+ # Spree::Product.additional_seller_permitted_attributes += [:brand_id]
31
+ class_attribute :additional_seller_permitted_attributes, instance_writer: false, default: [].freeze
32
+
26
33
  # Backfills preferences added to the class after this row was last saved, so
27
- # a reader never sees nil for a newly defined preference. Assigning
28
- # unconditionally would dirty every record on load: `preferences` is a
29
- # YAML-serialized Hash, so the dirty check compares the serialized string and
30
- # the merge reorders keys even when nothing changed — which is enough to make
31
- # `with_lock` refuse the record ("unpersisted changes").
34
+ # a reader never sees nil for a newly defined preference. Only missing keys
35
+ # are assigned: assigning unconditionally would dirty every record on load,
36
+ # which is enough to make `with_lock` refuse the record ("unpersisted
37
+ # changes").
32
38
  after_initialize do
33
- if has_attribute?(:preferences) && !preferences.nil?
34
- missing = default_preferences.except(*preferences.keys)
35
- self.preferences = preferences.merge(missing) if missing.any?
36
- end
37
- end
38
-
39
- # only for backwards compatibility with Kaminari
40
- if defined?(Kaminari) && Kaminari.config.page_method_name != :page
41
- def self.page(num)
42
- send Kaminari.config.page_method_name, num
43
- end
39
+ backfill_default_preferences if has_attribute?(:preferences) && !preferences.nil?
44
40
  end
45
41
 
46
42
  self.abstract_class = true
@@ -115,9 +111,10 @@ class Spree::Base < ApplicationRecord
115
111
  # read through the parent (a plain `Spree::Export` row, a factory that sets
116
112
  # `type` as an attribute, a query on the base relation).
117
113
  #
118
- # Resolves against `available_types` where the class defines it, so an
119
- # unrecognized column value passes through untouched rather than being
120
- # constantized.
114
+ # Resolves against the class's type registry where it has one, so an
115
+ # unrecognized column value (an extension no longer installed) is never
116
+ # constantized — it is shortened the same way instead, never sent as a class
117
+ # name.
121
118
  #
122
119
  # @param type [String, nil] value of the STI `type` column
123
120
  # @return [String]
@@ -125,9 +122,33 @@ class Spree::Base < ApplicationRecord
125
122
  return api_type if type.blank?
126
123
 
127
124
  type = type.to_s
128
- return type unless respond_to?(:available_types)
125
+ api_type_registry.find { |klass| klass.to_s == type }&.api_type || type.demodulize.underscore
126
+ end
127
+
128
+ # Inverse of {.api_type_for}: the STI `type` column value for a wire
129
+ # shorthand, or nil when no registered subclass answers to it. Lookup is
130
+ # registry-driven, so a request can never name a class outside the family.
131
+ #
132
+ # @param api_type [String, nil] e.g. `"orders"`
133
+ # @return [String, nil] e.g. `"Spree::Exports::Orders"`
134
+ def self.class_name_for_api_type(api_type)
135
+ return nil if api_type.blank?
129
136
 
130
- available_types.find { |klass| klass.to_s == type }&.api_type || type
137
+ api_type_registry.find { |klass| klass.api_type == api_type.to_s }&.to_s
138
+ end
139
+
140
+ # The subclasses a typed family accepts — `registered_subclasses` for the
141
+ # preference-schema families, `available_types` for exports and imports.
142
+ #
143
+ # @return [Array<Class>]
144
+ def self.api_type_registry
145
+ if subclass_registry
146
+ registered_subclasses
147
+ elsif respond_to?(:available_types)
148
+ Array(available_types)
149
+ else
150
+ []
151
+ end
131
152
  end
132
153
 
133
154
  # Shorthand for a *polymorphic* `*_type` column (`owner_type`,
@@ -138,10 +159,37 @@ class Spree::Base < ApplicationRecord
138
159
  #
139
160
  # @param type [String, Class, nil] value of the polymorphic type column
140
161
  # @return [String, nil]
162
+ #
163
+ # The configured customer and admin user classes always answer `customer`
164
+ # and `admin_user`, so a host app's own `User` class does not change the
165
+ # contract.
141
166
  def self.polymorphic_api_type(type)
142
167
  return nil if type.blank?
143
168
 
144
- type.to_s.demodulize.underscore
169
+ type = type.to_s
170
+ return 'customer' if type == Spree.customer_class(constantize: false)
171
+ return 'admin_user' if type == Spree.admin_user_class(constantize: false)
172
+
173
+ type.demodulize.underscore
174
+ end
175
+
176
+ # Inverse of {.polymorphic_api_type}: the class name a polymorphic `*_type`
177
+ # column stores for a wire shorthand. Matches against `candidates` when the
178
+ # caller knows what the column may reference (always prefer that); otherwise
179
+ # against the configured customer and admin user classes, then the
180
+ # `Spree::` model of that name. Never constantizes the input.
181
+ #
182
+ # @param api_type [String, nil] e.g. `"purchase_order"`
183
+ # @param candidates [Array<Class, String>, nil] classes the column may hold
184
+ # @return [String, nil] e.g. `"Spree::PurchaseOrder"`
185
+ def self.polymorphic_type_for(api_type, candidates = nil)
186
+ token = api_type.to_s
187
+ return nil unless token.match?(/\A[a-z][a-z0-9_]*\z/)
188
+
189
+ candidates ||= [Spree.customer_class(constantize: false),
190
+ Spree.admin_user_class(constantize: false),
191
+ "Spree::#{token.camelize}"]
192
+ candidates.map(&:to_s).find { |name| polymorphic_api_type(name) == token }
145
193
  end
146
194
 
147
195
  # Prefixed id for a polymorphic reference, encoded from the columns without
@@ -1,11 +1,9 @@
1
- require_dependency 'spree/calculator'
2
-
3
1
  module Spree
4
2
  class Calculator::FlatPercentItemTotal < Calculator
5
3
  preference :flat_percent, :decimal, default: 0
6
4
 
7
5
  def self.description
8
- Spree.t(:flat_percent)
6
+ I18n.t('spree.flat_percent')
9
7
  end
10
8
 
11
9
  def compute(object)
@@ -1,5 +1,3 @@
1
- require_dependency 'spree/calculator'
2
-
3
1
  module Spree
4
2
  class Calculator::FlatRate < Calculator
5
3
  preference :amount, :decimal, default: 0
@@ -7,7 +5,7 @@ module Spree
7
5
  preference :apply_only_on_full_priced_items, :boolean, default: false
8
6
 
9
7
  def self.description
10
- Spree.t(:flat_rate_per_order)
8
+ I18n.t('spree.flat_rate_per_order')
11
9
  end
12
10
 
13
11
  def compute(object = nil)
@@ -1,5 +1,3 @@
1
- require_dependency 'spree/calculator'
2
-
3
1
  module Spree
4
2
  class Calculator::FlexiRate < Calculator
5
3
  preference :first_item, :decimal, default: 0.0
@@ -9,7 +7,7 @@ module Spree
9
7
  preference :apply_only_on_full_priced_items, :boolean, default: false
10
8
 
11
9
  def self.description
12
- Spree.t(:flexible_rate)
10
+ I18n.t('spree.flexible_rate')
13
11
  end
14
12
 
15
13
  def self.available?(_object)
@@ -5,7 +5,7 @@ module Spree
5
5
  preference :apply_only_on_full_priced_items, :boolean, default: false
6
6
 
7
7
  def self.description
8
- Spree.t(:percent_per_item)
8
+ I18n.t('spree.percent_per_item')
9
9
  end
10
10
 
11
11
  def compute(object)
@@ -1,5 +1,3 @@
1
- require_dependency 'spree/calculator'
2
-
3
1
  module Spree
4
2
  class Calculator::PriceSack < Calculator
5
3
  preference :minimal_amount, :decimal, default: 0
@@ -8,7 +6,7 @@ module Spree
8
6
  preference :currency, :string, default: -> { Spree::Store.default.default_currency }
9
7
 
10
8
  def self.description
11
- Spree.t(:price_sack)
9
+ I18n.t('spree.price_sack')
12
10
  end
13
11
 
14
12
  # as object we always get line items, as calculable we have Coupon, ShippingMethod
@@ -1,5 +1,3 @@
1
- require_dependency 'spree/shipping_calculator'
2
-
3
1
  module Spree
4
2
  module Calculator::Shipping
5
3
  class DigitalDelivery < ShippingCalculator
@@ -9,7 +7,7 @@ module Spree
9
7
  preference :currency, :string, default: -> { Spree::Store.default.default_currency }
10
8
 
11
9
  def self.description
12
- Spree.t('digital.digital_delivery')
10
+ I18n.t('spree.digital.digital_delivery')
13
11
  end
14
12
 
15
13
  def compute_package(package = nil)
@@ -1,12 +1,10 @@
1
- require_dependency 'spree/shipping_calculator'
2
-
3
1
  module Spree
4
2
  module Calculator::Shipping
5
3
  class FlatPercentItemTotal < ShippingCalculator
6
4
  preference :flat_percent, :decimal, default: 0
7
5
 
8
6
  def self.description
9
- Spree.t(:flat_percent)
7
+ I18n.t('spree.flat_percent')
10
8
  end
11
9
 
12
10
  def compute_package(package)
@@ -1,5 +1,3 @@
1
- require_dependency 'spree/shipping_calculator'
2
-
3
1
  module Spree
4
2
  module Calculator::Shipping
5
3
  class FlatRate < ShippingCalculator
@@ -18,7 +16,7 @@ module Spree
18
16
  preference :maximum_weight, :decimal, default: nil, nullable: true, deprecated: 'Use a Spree::DeliveryMethodRules rule on the delivery method instead; removed in Spree 6.1.'
19
17
 
20
18
  def self.description
21
- Spree.t(:shipping_flat_rate_per_order)
19
+ I18n.t('spree.shipping_flat_rate_per_order')
22
20
  end
23
21
 
24
22
  def compute_package(package)
@@ -1,5 +1,3 @@
1
- require_dependency 'spree/shipping_calculator'
2
-
3
1
  module Spree
4
2
  module Calculator::Shipping
5
3
  class FlexiRate < ShippingCalculator
@@ -15,7 +13,7 @@ module Spree
15
13
  end
16
14
 
17
15
  def self.description
18
- Spree.t(:shipping_flexible_rate)
16
+ I18n.t('spree.shipping_flexible_rate')
19
17
  end
20
18
 
21
19
  def compute_package(package)
@@ -1,5 +1,3 @@
1
- require_dependency 'spree/shipping_calculator'
2
-
3
1
  module Spree
4
2
  module Calculator::Shipping
5
3
  class PerItem < ShippingCalculator
@@ -9,7 +7,7 @@ module Spree
9
7
  preference :currency, :string, default: -> { Spree::Store.default.default_currency }
10
8
 
11
9
  def self.description
12
- Spree.t(:shipping_flat_rate_per_item)
10
+ I18n.t('spree.shipping_flat_rate_per_item')
13
11
  end
14
12
 
15
13
  def compute_package(package)
@@ -1,5 +1,3 @@
1
- require_dependency 'spree/shipping_calculator'
2
-
3
1
  module Spree
4
2
  module Calculator::Shipping
5
3
  class PriceSack < ShippingCalculator
@@ -15,7 +13,7 @@ module Spree
15
13
  end
16
14
 
17
15
  def self.description
18
- Spree.t(:shipping_price_sack)
16
+ I18n.t('spree.shipping_price_sack')
19
17
  end
20
18
 
21
19
  def compute_package(package)
@@ -1,42 +1,25 @@
1
- require_dependency 'spree/calculator'
2
-
3
1
  module Spree
4
2
  class Calculator::TieredFlatRate < Calculator
3
+ include Spree::Calculator::Tiers
4
+
5
5
  preference :base_amount, :decimal, default: 0
6
6
  preference :currency, :string, default: -> { Spree::Store.default.default_currency }
7
- preference :tiers, :hash, default: {}
8
-
9
- before_validation do
10
- # Convert tier values to decimals. Strings don't do us much good.
11
- if preferred_tiers.is_a?(Hash)
12
- self.preferred_tiers = Hash[*preferred_tiers.flatten.map(&:to_f)]
13
- end
14
- end
15
-
16
- validate :preferred_tiers_content
17
7
 
18
8
  def self.description
19
- Spree.t(:tiered_flat_rate)
9
+ I18n.t('spree.tiered_flat_rate')
20
10
  end
21
11
 
22
12
  def compute(object = nil)
23
13
  return 0 unless object&.currency.present?
24
14
  return 0 unless preferred_currency.casecmp(object.currency.upcase).zero?
25
15
 
26
- base, amount = preferred_tiers.sort.reverse.detect { |b, _| object.amount >= b }
27
- amount || preferred_base_amount
16
+ tier_value_for(object.amount) || preferred_base_amount
28
17
  end
29
18
 
30
19
  private
31
20
 
32
- def preferred_tiers_content
33
- if preferred_tiers.is_a? Hash
34
- unless preferred_tiers.keys.all? { |k| k.is_a?(Numeric) && k > 0 }
35
- errors.add(:base, :keys_should_be_positive_number)
36
- end
37
- else
38
- errors.add(:preferred_tiers, :should_be_hash)
39
- end
21
+ def validate_tier_values(values)
22
+ errors.add(:preferred_tiers, :values_should_be_number) unless values.all? { |value| value && value >= 0 }
40
23
  end
41
24
  end
42
25
  end
@@ -1,45 +1,27 @@
1
- require_dependency 'spree/calculator'
2
-
3
1
  module Spree
4
2
  class Calculator::TieredPercent < Calculator
5
- preference :base_percent, :decimal, default: 0
6
- preference :tiers, :hash, default: {}
3
+ include Spree::Calculator::Tiers
7
4
 
8
- before_validation do
9
- # Convert tier values to decimals. Strings don't do us much good.
10
- if preferred_tiers.is_a?(Hash)
11
- self.preferred_tiers = Hash[*preferred_tiers.flatten.map(&:to_f)]
12
- end
13
- end
5
+ preference :base_percent, :decimal, default: 0
14
6
 
15
7
  validates :preferred_base_percent, numericality: {
16
8
  greater_than_or_equal_to: 0,
17
9
  less_than_or_equal_to: 100
18
10
  }
19
- validate :preferred_tiers_content
20
11
 
21
12
  def self.description
22
- Spree.t(:tiered_percent)
13
+ I18n.t('spree.tiered_percent')
23
14
  end
24
15
 
25
16
  def compute(object)
26
- base, percent = preferred_tiers.sort.reverse.detect { |b, _| object.amount >= b }
27
- (object.amount * (percent || preferred_base_percent) / 100).round(2)
17
+ percent = tier_value_for(object.amount) || preferred_base_percent
18
+ (object.amount * percent / 100).round(2)
28
19
  end
29
20
 
30
21
  private
31
22
 
32
- def preferred_tiers_content
33
- if preferred_tiers.is_a? Hash
34
- unless preferred_tiers.keys.all? { |k| k.is_a?(Numeric) && k > 0 }
35
- errors.add(:base, :keys_should_be_positive_number)
36
- end
37
- unless preferred_tiers.values.all? { |k| k.is_a?(Numeric) && k >= 0 && k <= 100 }
38
- errors.add(:base, :values_should_be_percent)
39
- end
40
- else
41
- errors.add(:preferred_tiers, :should_be_hash)
42
- end
23
+ def validate_tier_values(values)
24
+ errors.add(:preferred_tiers, :values_should_be_percent) unless values.all? { |value| value&.between?(0, 100) }
43
25
  end
44
26
  end
45
27
  end
@@ -36,6 +36,7 @@ module Spree
36
36
  include Spree::Purchase::Validations
37
37
  include Spree::Purchase::Totals
38
38
  include Spree::Purchase::Lifecycle
39
+ include Spree::Purchase::DeprecatedAliases
39
40
 
40
41
  # Concurrency is manual (the API's OrderLock semantics — compare
41
42
  # client-sent version, 409 on mismatch); Rails auto-locking must not
@@ -54,16 +55,7 @@ module Spree
54
55
  :discount_total, :fee_total, :delivery_total, :total, :payment_total, :outstanding_balance,
55
56
  :tax_total, :pre_tax_item_amount, :pre_tax_total, :amount_due
56
57
 
57
- alias display_promo_total display_discount_total
58
- alias display_ship_total display_delivery_total
59
- alias_attribute :ship_total, :delivery_total
60
-
61
58
  belongs_to :customer, class_name: "::#{Spree.customer_class}", optional: true
62
- # Order-parity aliases — shared Cart/Order code (services, Purchase
63
- # concerns) reads #user; the cart column is customer_id.
64
- alias_method :user, :customer
65
- alias_method :user=, :customer=
66
- alias_attribute :user_id, :customer_id
67
59
  # Codes are stored stripped + lowercased so lookups stay case-insensitive;
68
60
  # normalizes also applies to finder values.
69
61
  normalizes :coupon_code, with: ->(code) { code.to_s.strip.downcase.presence }
@@ -87,6 +79,8 @@ module Spree
87
79
  end
88
80
  end
89
81
  has_many :fulfillment_items, through: :fulfillments, class_name: 'Spree::FulfillmentItem'
82
+ # Legacy name (5.x carts were orders) — removed in 6.1
83
+ has_many :shipments, -> { order(:created_at, :id) }, class_name: 'Spree::Fulfillment', inverse_of: :cart, deprecated: true
90
84
  has_many :order_promotions, class_name: 'Spree::OrderPromotion', inverse_of: :cart, dependent: :destroy
91
85
  has_many :promotions, through: :order_promotions, class_name: 'Spree::Promotion'
92
86
  has_many :payments, class_name: 'Spree::Payment', inverse_of: :cart, dependent: :destroy
@@ -167,10 +161,18 @@ module Spree
167
161
 
168
162
  # Idempotent delivery-proposal rebuild — replaces the destructive
169
163
  # order-side create_proposed_fulfillments. Open fulfillments are rebuilt from
170
- # the current items/address; nothing here touches a completed cart.
171
- def rebuild_fulfillments!
164
+ # the current items/address; nothing here touches a completed cart. Until
165
+ # deliveries can be proposed the cart gets none: preselecting the pickup
166
+ # rate that is all a destination-less quote finds would choose pickup for
167
+ # a customer who had not been offered anything.
168
+ #
169
+ # @param keep_selection [Boolean] false when the destination changed: a
170
+ # choice made for one address is not a choice for another
171
+ def rebuild_fulfillments!(keep_selection: true)
172
172
  return if completed?
173
173
 
174
+ previous_selections = keep_selection ? fulfillments.selected_rates_by_stock_location : {}
175
+
174
176
  discounts.for_fulfillments.delete_all
175
177
  tax_lines.for_fulfillments.delete_all
176
178
  fees.for_fulfillments.delete_all
@@ -179,6 +181,7 @@ module Spree
179
181
  DeliveryRate.where(fulfillment_id: fulfillment_ids).delete_all
180
182
  fulfillments.delete_all
181
183
  fulfillment_items.reset
184
+ return fulfillments.reload unless can_propose_deliveries?
182
185
 
183
186
  # Appended rather than assigned: setting `cart` on each proposal already
184
187
  # files it under this cart's `fulfillments` (the association is
@@ -188,6 +191,7 @@ module Spree
188
191
  Spree::Stock::Coordinator.new(self).fulfillments.each do |fulfillment|
189
192
  fulfillment.address_id = ship_address_id
190
193
  fulfillment.order = nil
194
+ fulfillment.carry_over_selection(previous_selections.fetch(fulfillment.stock_location_id, []))
191
195
  fulfillments << fulfillment
192
196
  end
193
197
  prune_undeliverable_fulfillments!
@@ -206,7 +210,7 @@ module Spree
206
210
  self.warnings |= affected_line_items.map do |line_item|
207
211
  {
208
212
  code: 'delivery_unavailable',
209
- message: Spree.t('cart_line_item.delivery_unavailable', li_name: line_item.name),
213
+ message: I18n.t('spree.cart_line_item.delivery_unavailable', li_name: line_item.name),
210
214
  line_item_id: line_item.prefixed_id
211
215
  }
212
216
  end
@@ -214,7 +218,7 @@ module Spree
214
218
 
215
219
  def ensure_available_delivery_rates
216
220
  if fulfillments.empty? || fulfillments.any? { |fulfillment| fulfillment.delivery_rates.blank? }
217
- errors.add(:base, :items_cannot_be_shipped, message: Spree.t(:items_cannot_be_shipped))
221
+ errors.add(:base, :items_cannot_be_shipped, message: I18n.t('spree.items_cannot_be_shipped'))
218
222
  return false
219
223
  end
220
224
  true
@@ -229,7 +233,6 @@ module Spree
229
233
  updated_at: Time.current
230
234
  )
231
235
  end
232
- alias set_shipments_cost set_fulfillments_cost
233
236
 
234
237
  def ensure_updated_fulfillments
235
238
  rebuild_fulfillments! unless completed?
@@ -238,7 +241,11 @@ module Spree
238
241
  # Re-prices, re-taxes and rebuilds delivery proposals — the
239
242
  # recalculation-on-write replacement for transition-triggered rebuilds.
240
243
  # Called by Carts::Update after address/market changes.
241
- def recalculate_for_address_change!
244
+ #
245
+ # @param keep_selection [Boolean] true when the parcel still goes where it
246
+ # did (a market change, a corrected phone number), so the chosen
247
+ # delivery is kept and quoted again
248
+ def recalculate_for_address_change!(keep_selection: false)
242
249
  # One provider round for the whole cart, and persisted: a per-item loop
243
250
  # would ask an external pricing system once per line, and nothing here
244
251
  # saves the line items — rebuild_fulfillments! and recalculate_totals!
@@ -251,8 +258,7 @@ module Spree
251
258
  raise Spree::Pricing::PriceResolution::ProviderUnavailable, result.error.to_s if result.failure?
252
259
 
253
260
  Spree::Carts::PriceItems.apply(result.value)
254
- rebuild_fulfillments!
255
- set_fulfillments_cost
261
+ rebuild_fulfillments!(keep_selection: keep_selection)
256
262
  recalculate_totals!
257
263
  end
258
264
 
@@ -269,11 +275,27 @@ module Spree
269
275
  reload
270
276
  end
271
277
 
278
+ # The variants this cart's buyer may order: the channel's live
279
+ # publications, narrowed to the catalogs the cart's company, customer
280
+ # group or channel resolve to. Status, stock and currency are left to the
281
+ # add and checkout checks, which explain a refusal rather than reading as
282
+ # not found.
283
+ #
284
+ # @return [ActiveRecord::Relation<Spree::Variant>]
285
+ def orderable_variants
286
+ products = Spree.products_for_context_service.call(
287
+ store: store, channel: channel, customer: customer, company: resolved_company,
288
+ base: store.products.not_discontinued_on(channel)
289
+ ).value
290
+
291
+ Spree::Variant.for_products(products)
292
+ end
293
+
272
294
  # Removes out-of-stock/discontinued items and populates warnings
273
295
  # (mirrors Order#remove_out_of_stock_items!).
274
296
  def remove_out_of_stock_items!
275
297
  existing_warnings = warnings
276
- result = Spree::Carts::RemoveOutOfStockItems.call(cart: self)
298
+ result = Spree.cart_remove_out_of_stock_items_service.call(cart: self)
277
299
  return self unless result.success?
278
300
 
279
301
  cart, _messages, new_warnings = result.value
@@ -281,5 +303,43 @@ module Spree
281
303
  cart
282
304
  end
283
305
 
306
+ # Whether this cart shows a batch coupon code it does not hold — used,
307
+ # taken by another cart or order, or given up by one — and so can never
308
+ # discount it.
309
+ #
310
+ # @return [Boolean]
311
+ def coupon_code_unavailable?
312
+ code = read_attribute(:coupon_code)
313
+ return false if code.blank?
314
+
315
+ record = Spree::CouponCode.where(promotion_id: store.promotions.select(:id)).find_by(code: code)
316
+ record.present? && record.cart_id != id
317
+ end
318
+
319
+ # Drops an entered batch coupon code this cart no longer holds, and warns
320
+ # the shopper, who otherwise sees the discount vanish.
321
+ #
322
+ # @return [Spree::Cart]
323
+ def remove_unavailable_coupon_code!
324
+ return self unless coupon_code_unavailable?
325
+
326
+ # The lock refuses unsaved attributes and reloads the cart, so this
327
+ # request's warnings are set aside and put back.
328
+ existing_warnings = warnings
329
+ clear_attribute_changes([:warnings])
330
+ removed = with_lock do
331
+ # Checked under the lock, as the completion claim itself is: a
332
+ # completing cart's code sits on its draft order while the payment
333
+ # runs, and a failed payment hands it back.
334
+ next false if completion_claimed? || !coupon_code_unavailable?
335
+
336
+ Spree.coupon_handler.new(self, enable_gift_cards: false).remove(read_attribute(:coupon_code))
337
+ true
338
+ end
339
+ self.warnings = existing_warnings
340
+ self.warnings |= [{ code: 'coupon_code_unavailable', message: I18n.t('spree.coupon_code_unavailable') }] if removed
341
+ self
342
+ end
343
+
284
344
  end
285
345
  end
@@ -58,7 +58,7 @@ module Spree
58
58
  return if STOREFRONT_ACCESS.include?(value.to_s)
59
59
 
60
60
  errors.add(
61
- :preferred_storefront_access, :invalid_storefront_access, message: Spree.t(:invalid_storefront_access, scope: [:errors, :messages], default: 'is not a valid storefront access level')
61
+ :preferred_storefront_access, :invalid_storefront_access, message: I18n.t('spree.errors.messages.invalid_storefront_access')
62
62
  )
63
63
  end
64
64
  end
@@ -14,9 +14,9 @@ module Spree
14
14
  include Spree::Channel::Gating
15
15
 
16
16
  # Empty -> falls back to the Store-level preference.
17
- preference :order_routing_strategy, :string, default: nil
17
+ preference :order_routing_strategy, :string, default: nil,
18
+ parse_on_set: ->(value) { Spree::ApiTyped.class_name_for(Spree.order_routing.strategies, value) }
18
19
 
19
- belongs_to :store, class_name: 'Spree::Store'
20
20
 
21
21
  has_many :orders, class_name: 'Spree::Order', inverse_of: :channel, dependent: :nullify
22
22
  has_many :order_routing_rules, class_name: 'Spree::OrderRoutingRule', dependent: :destroy
@@ -163,7 +163,7 @@ module Spree
163
163
  return unless default?
164
164
  return if destroyed_by_association.present?
165
165
 
166
- errors.add(:base, :cannot_delete_default_channel, message: Spree.t('errors.messages.cannot_delete_default_channel'))
166
+ errors.add(:base, :cannot_delete_default_channel, message: I18n.t('spree.errors.messages.cannot_delete_default_channel'))
167
167
  throw :abort
168
168
  end
169
169
 
@@ -176,7 +176,7 @@ module Spree
176
176
  return unless api_keys.active.exists?
177
177
  return if destroyed_by_association.present?
178
178
 
179
- errors.add(:base, :cannot_delete_channel_with_active_api_keys, message: Spree.t('errors.messages.cannot_delete_channel_with_active_api_keys'))
179
+ errors.add(:base, :cannot_delete_channel_with_active_api_keys, message: I18n.t('spree.errors.messages.cannot_delete_channel_with_active_api_keys'))
180
180
  throw :abort
181
181
  end
182
182
 
@@ -16,8 +16,10 @@ module Spree
16
16
  include Spree::HasStatus
17
17
  include Spree::HasCustomFields
18
18
  include Spree::Metadata
19
+ include Spree::PostSale::Taxation
19
20
 
20
21
  publishes_lifecycle_events
22
+ publishes_events :approved, :canceled, :denied, :opened, :resolved
21
23
 
22
24
  has_status :open, :approved, :resolved, :denied, :canceled, default: :open
23
25
 
@@ -25,7 +27,6 @@ module Spree
25
27
  # stays closed — an unrecognised value would silently do nothing.
26
28
  RESOLUTIONS = %w[refund replacement refund_and_replacement].freeze
27
29
 
28
- belongs_to :store, class_name: 'Spree::Store'
29
30
  belongs_to :order, class_name: 'Spree::Order', inverse_of: :claims
30
31
  belongs_to :reason, class_name: 'Spree::ClaimReason', optional: true, inverse_of: :claims
31
32
  acted_by :created_by
@@ -59,6 +60,21 @@ module Spree
59
60
  end
60
61
  end
61
62
 
63
+ # The tax inside {#refunded_line_amounts}, for the lines whose tax is
64
+ # recorded: each line's refund carries its tax in proportion to what the
65
+ # customer paid for it.
66
+ #
67
+ # @return [Hash{Integer => BigDecimal}] line item id => tax
68
+ def refunded_line_taxes
69
+ claim_line_items.each_with_object(Hash.new(0)) do |line, taxes|
70
+ next unless line.settles_tax? && line.paid_amount.positive?
71
+
72
+ taxes[line.line_item_id] += Spree::Money::Rounding.to_currency(
73
+ line.tax_total * line.refund_amount.to_d / line.paid_amount, currency
74
+ )
75
+ end
76
+ end
77
+
62
78
  def display_refund_total
63
79
  Spree::Money.new(refund_total, currency: currency)
64
80
  end
@@ -66,5 +82,9 @@ module Spree
66
82
  def replacement_line_items
67
83
  claim_line_items.select(&:send_replacement)
68
84
  end
85
+
86
+ def taxed_lines
87
+ claim_line_items
88
+ end
69
89
  end
70
90
  end