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
@@ -9,13 +9,22 @@ module Spree
9
9
  end
10
10
 
11
11
  def variant_includes
12
- [:images, :prices, :stock_levels, :stock_locations, { option_values: [:option_type] }]
12
+ [:images, :prices, { stock_levels: :stock_location, option_values: [:option_type] }]
13
13
  end
14
14
 
15
15
  def multi_line_csv?
16
16
  true
17
17
  end
18
18
 
19
+ # The seller scopes which locations the inventory columns cover; their
20
+ # default, worked out once per export, carries the full row's stock.
21
+ def to_csv_options
22
+ @to_csv_options ||= {
23
+ seller: seller,
24
+ default_stock_location: Spree::StockLocation.owned_by(store_id: store.id, seller_id: seller&.id).active.order_default.first || store.default_stock_location
25
+ }
26
+ end
27
+
19
28
  # when doing full product export, we want to exclude archived products
20
29
  def scope
21
30
  if search_params.nil?
@@ -46,13 +46,13 @@ module Spree
46
46
  result.rows.each do |row|
47
47
  csv << Spree::CSV::FormulaSanitizer.row(
48
48
  row[:dimensions].map { |name, raw| hydration.label(name, raw) } +
49
- query.metrics.map { |m| row[:metrics][m.name][:value] }
49
+ query.metrics.map { |m| m.format_value(row[:metrics][m.name][:value], result.meta[:currency]) }
50
50
  )
51
51
  end
52
- totals = query.metrics.map { |m| result.totals[m.name][:value] }
52
+ totals = query.metrics.map { |m| m.format_value(result.totals[m.name][:value], result.meta[:currency]) }
53
53
  # With a breakdown the totals close the file under a "Total" label in
54
54
  # the first dimension column; without one the totals are the only row.
55
- label = query.dimensions.empty? ? [] : [Spree.t('reporting.export.total')] + Array.new(query.dimensions.size - 1, '')
55
+ label = query.dimensions.empty? ? [] : [I18n.t('spree.reporting.export.total')] + Array.new(query.dimensions.size - 1, '')
56
56
  csv << Spree::CSV::FormulaSanitizer.row(label + totals)
57
57
  end
58
58
  end
@@ -10,8 +10,10 @@ module Spree
10
10
  # Extensions may append their own kinds — the list is validated, not frozen.
11
11
  # `duty` rows are written by a duties provider's adjuster and snapshot the
12
12
  # classification they were calculated from in `metadata` — never re-derive
13
- # a duty from the live catalog.
14
- KINDS = %w[surcharge handling gift_wrap cod payment duty]
13
+ # a duty from the live catalog. `exchange` is what a customer owes when an
14
+ # exchange's replacement costs more, put on the order for the merchant to
15
+ # collect.
16
+ KINDS = %w[surcharge handling gift_wrap cod payment duty exchange]
15
17
 
16
18
  has_prefix_id :fee
17
19
 
@@ -0,0 +1,155 @@
1
+ module Spree
2
+ # A file sent to `POST /files` (docs/plans/6.0-uploads-and-file-ownership.md),
3
+ # before anything uses it.
4
+ #
5
+ # Two ways in, one result. A presigned upload carries only the file's
6
+ # metadata and gets back a storage target the client sends the bytes to; a
7
+ # multipart upload carries the bytes, which are stored here. Either way the
8
+ # result is a blob owned by +store+ and an expiring, opaque `signed_id` the
9
+ # client passes to whichever endpoint uses the file.
10
+ #
11
+ # Never persisted: the blob is the record.
12
+ class FileUpload
13
+ include ActiveModel::Model
14
+ include ActiveModel::Attributes
15
+
16
+ VISIBILITIES = %w[public private].freeze
17
+
18
+ attribute :filename, :string
19
+ attribute :content_type, :string
20
+ attribute :byte_size, :integer
21
+ attribute :checksum, :string
22
+ attribute :visibility, :string, default: 'public'
23
+ # The bytes of a multipart upload (an uploaded file or any IO).
24
+ attribute :file
25
+ attribute :store
26
+ # Narrower limits for a caller with its own rules (a buyer's purchase
27
+ # order document); the installation-wide ones apply otherwise.
28
+ attribute :max_byte_size, :integer
29
+ attribute :allowed_content_types
30
+
31
+ attr_reader :blob, :signed_id, :expires_at
32
+
33
+ validates :store, presence: true
34
+ validates :visibility, inclusion: { in: VISIBILITIES }
35
+ validates :filename, :content_type, presence: true
36
+ validates :checksum, presence: true, unless: :multipart?
37
+ validates :byte_size, numericality: { only_integer: true, greater_than: 0 }
38
+ validate :byte_size_within_limit
39
+ validate :content_type_allowed
40
+
41
+ # The content types a public upload may have. Private uploads accept any
42
+ # type: digital products are ebooks, archives and audio, and each
43
+ # attachment validates its own file.
44
+ #
45
+ # @return [Array<String>]
46
+ def self.public_content_types
47
+ Rails.application.config.active_storage.web_image_content_types +
48
+ Spree::Config.video_content_types +
49
+ %w[text/csv]
50
+ end
51
+
52
+ # @return [Boolean] whether the bytes travel in the request
53
+ def multipart?
54
+ file.respond_to?(:read)
55
+ end
56
+
57
+ # @return [Boolean] whether the file lands on private storage
58
+ def private?
59
+ visibility == 'private'
60
+ end
61
+
62
+ # Validates and creates the blob. A multipart upload is stored here; a
63
+ # presigned one waits for the client to send its bytes to {#upload_target}.
64
+ #
65
+ # @return [Boolean]
66
+ def save
67
+ read_file_facts if multipart?
68
+ return false unless valid?
69
+
70
+ @blob = multipart? ? store_bytes : reserve_blob
71
+ @expires_at = Spree::Uploads::SIGNED_ID_EXPIRY.from_now
72
+ @signed_id = Spree::Uploads.signed_id_for(@blob)
73
+ true
74
+ end
75
+
76
+ # Where a presigned upload's bytes go, or nil once the file is stored.
77
+ #
78
+ # @return [Spree::FileUpload::Target, nil]
79
+ def upload_target
80
+ return if multipart? || blob.nil?
81
+
82
+ Target.new(
83
+ http_method: 'PUT',
84
+ url: blob.service_url_for_direct_upload,
85
+ headers: blob.service_headers_for_direct_upload
86
+ )
87
+ end
88
+
89
+ # The storage target a presigned upload sends its bytes to.
90
+ class Target
91
+ include ActiveModel::Model
92
+ include ActiveModel::Attributes
93
+
94
+ # Not `method`, which every Ruby object already answers.
95
+ attribute :http_method, :string
96
+ attribute :url, :string
97
+ attribute :headers
98
+ end
99
+
100
+ private
101
+
102
+ # The size of a multipart upload comes from the bytes, and so does its type
103
+ # wherever the bytes carry a recognisable signature: Marcel reads the
104
+ # content first, so a renamed executable is not accepted as an image. Only
105
+ # bytes it cannot place fall back to the declared type and the file name.
106
+ def read_file_facts
107
+ self.filename = filename.presence || file.try(:original_filename)
108
+ self.byte_size = io.size
109
+ self.content_type = Marcel::MimeType.for(io, name: filename, declared_type: content_type.presence || file.try(:content_type))
110
+ io.rewind
111
+ end
112
+
113
+ def reserve_blob
114
+ ActiveStorage::Blob.create!(
115
+ filename: filename, byte_size: byte_size, checksum: checksum, content_type: content_type,
116
+ service_name: service_name, store_id: store.id
117
+ )
118
+ end
119
+
120
+ def store_bytes
121
+ ActiveStorage::Blob.build_after_unfurling(
122
+ io: io, filename: filename, content_type: content_type, service_name: service_name, identify: false
123
+ ).tap do |blob|
124
+ blob.store_id = store.id
125
+ blob.save!
126
+ blob.upload_without_unfurling(io)
127
+ end
128
+ end
129
+
130
+ def io
131
+ @io ||= file.respond_to?(:to_io) ? file.to_io : file
132
+ end
133
+
134
+ def service_name
135
+ private? ? Spree.private_storage_service_name : Spree.public_storage_service_name
136
+ end
137
+
138
+ def size_limit
139
+ max_byte_size || (multipart? ? Spree::Config.max_multipart_upload_size : Spree::Config.max_upload_size)
140
+ end
141
+
142
+ def byte_size_within_limit
143
+ return if byte_size.nil? || byte_size <= size_limit
144
+
145
+ errors.add(:byte_size, :less_than_or_equal_to, count: ActiveSupport::NumberHelper.number_to_human_size(size_limit))
146
+ end
147
+
148
+ def content_type_allowed
149
+ allowed = allowed_content_types || (self.class.public_content_types unless private?)
150
+ return if allowed.nil? || content_type.blank? || allowed.include?(content_type)
151
+
152
+ errors.add(:content_type, :inclusion, value: content_type)
153
+ end
154
+ end
155
+ end
@@ -1,5 +1,3 @@
1
- require 'ostruct'
2
-
3
1
  module Spree
4
2
  class Fulfillment < Spree.base_class
5
3
  has_prefix_id :ful
@@ -15,6 +13,9 @@ module Spree
15
13
  include Spree::Fulfillment::CustomEvents
16
14
 
17
15
  publishes_lifecycle_events
16
+ publishes_events :canceled, :delivered, :fulfilled
17
+ publishes_event 'shipment.shipped', deprecated_alias_of: 'fulfillment.fulfilled'
18
+ publishes_event 'shipment.canceled', deprecated_alias_of: 'fulfillment.canceled'
18
19
 
19
20
  with_options inverse_of: :fulfillments do
20
21
  belongs_to :address, class_name: 'Spree::Address', optional: true
@@ -92,6 +93,18 @@ module Spree
92
93
 
93
94
  delegate :store, :currency, to: :owner
94
95
 
96
+ # What a rebuild hands to {#carry_over_selection}, read before the old
97
+ # proposals and their rates are deleted. Grouped rather than keyed by
98
+ # location: one location can ship several proposals (a digital item beside
99
+ # a physical one, stock on hand beside a backorder), each with its own choice.
100
+ #
101
+ # @return [Hash{Integer => Array<Spree::DeliveryRate>}] the selected rates
102
+ # of each stock location's proposals
103
+ def self.selected_rates_by_stock_location
104
+ includes(:selected_delivery_rate).group_by(&:stock_location_id).
105
+ transform_values { |proposals| proposals.filter_map(&:selected_delivery_rate) }
106
+ end
107
+
95
108
  # The exactly-one owner of this fulfillment — the cart during checkout,
96
109
  # the order after completion. New code must read +owner+, never assume
97
110
  # +order+.
@@ -221,7 +234,7 @@ module Spree
221
234
  #
222
235
  # @return [String, Spree::Money]
223
236
  def display_cost(**options)
224
- return Spree.t('delivery_rates.quoted_after_review') if unpriced?
237
+ return I18n.t('spree.delivery_rates.quoted_after_review') if unpriced?
225
238
 
226
239
  Spree::Money.new(cost, { currency: currency }.merge(options))
227
240
  end
@@ -338,7 +351,7 @@ module Spree
338
351
  #
339
352
  # @return [String, Spree::Money]
340
353
  def display_final_price(**options)
341
- return Spree.t('delivery_rates.quoted_after_review') if unpriced?
354
+ return I18n.t('spree.delivery_rates.quoted_after_review') if unpriced?
342
355
 
343
356
  Spree::Money.new(final_price, { currency: currency }.merge(options))
344
357
  end
@@ -361,9 +374,20 @@ module Spree
361
374
  #
362
375
  # @return [Boolean]
363
376
  def with_free_shipping_promotion?
377
+ return discounts.any?(&:promotion?) if read_association_in_memory?(:discounts)
378
+
364
379
  discounts.promotion.exists?
365
380
  end
366
381
 
382
+ # What promotions took off this shipment's cost, as a negative amount.
383
+ #
384
+ # @return [BigDecimal]
385
+ def promotion_discount_total
386
+ return discounts.select(&:promotion?).sum(BigDecimal('0'), &:amount) if read_association_in_memory?(:discounts)
387
+
388
+ discounts.promotion.sum(:amount)
389
+ end
390
+
367
391
  # Placement no longer moves physical stock — the units are promised in
368
392
  # Spree::Orders::Complete and only leave the shelf when the parcel does.
369
393
  def finalize!
@@ -543,34 +567,13 @@ module Spree
543
567
 
544
568
  # StockEstimator.new assignment below will replace the current delivery_method
545
569
  original_shipping_method_id = delivery_method.try(:id)
546
- # A carrier method quotes several rates (one per service), so the
547
- # method id alone no longer identifies the customer's choice — capture
548
- # the selected rate's carrier service too.
549
570
  original_selection = selected_delivery_rate
550
- original_carrier = original_selection&.carrier
551
- original_service_level = original_selection&.service_level
552
571
 
553
572
  self.delivery_rates = Stock::Estimator.new(owner).
554
573
  delivery_rates(to_package, audience)
555
574
 
556
575
  if delivery_method
557
- # Keep the previously chosen carrier service when it is still quoted,
558
- # then the previously chosen method, then the estimator's own pick
559
- # (the cheapest rate, already flagged selected) rather than leaving
560
- # the fulfillment unselected.
561
- selected_rate =
562
- if original_shipping_method_id && original_carrier
563
- delivery_rates.detect do |rate|
564
- rate.delivery_method_id == original_shipping_method_id &&
565
- rate.carrier == original_carrier &&
566
- rate.service_level == original_service_level
567
- end
568
- end
569
- selected_rate ||=
570
- if original_shipping_method_id
571
- delivery_rates.detect { |rate| rate.delivery_method_id == original_shipping_method_id }
572
- end
573
- selected_rate ||= delivery_rates.detect(&:selected)
576
+ selected_rate = successor_rate(original_shipping_method_id, original_selection)
574
577
 
575
578
  save!
576
579
  self.selected_shipping_rate_id = selected_rate.id if selected_rate
@@ -709,7 +712,7 @@ module Spree
709
712
  #
710
713
  # @return [Spree::Delivery, nil]
711
714
  def primary_delivery
712
- deliveries.first
715
+ read_association_in_memory?(:deliveries) ? deliveries.to_a.first : deliveries.first
713
716
  end
714
717
 
715
718
  # The primary delivery's tracking number. Read-only since 6.0: tracking is
@@ -755,6 +758,25 @@ module Spree
755
758
  end
756
759
  end
757
760
 
761
+ # Carries the customer's choice onto a freshly built, unsaved proposal:
762
+ # takes the first of +candidates+ whose method this proposal is quoted for,
763
+ # selects the rate standing for it and prices the proposal at it, so a
764
+ # rebuild neither resets the choice nor drops its cost. The rate taken is
765
+ # removed from +candidates+, leaving the rest to this proposal's siblings.
766
+ #
767
+ # @param candidates [Array<Spree::DeliveryRate>] the rates selected on the
768
+ # proposals this stock location had before the rebuild
769
+ # @return [void]
770
+ def carry_over_selection(candidates)
771
+ quoted_method_ids = delivery_rates.map(&:delivery_method_id)
772
+ previous_rate = candidates.find { |rate| quoted_method_ids.include?(rate.delivery_method_id) }
773
+ candidates.delete(previous_rate)
774
+
775
+ choice = successor_rate(previous_rate&.delivery_method_id, previous_rate)
776
+ delivery_rates.each { |rate| rate.selected = rate == choice }
777
+ self.cost = choice&.cost || 0
778
+ end
779
+
758
780
  # @deprecated No-op since 6.0; removed in 6.1.
759
781
  #
760
782
  # This re-derived the status from the order's payment state on every order
@@ -792,6 +814,21 @@ module Spree
792
814
 
793
815
  private
794
816
 
817
+ # The previously chosen carrier service when it is still quoted, then the
818
+ # previously chosen method, then the estimator's own pick - a carrier
819
+ # method quotes one rate per service, so the method alone does not
820
+ # identify the choice.
821
+ def successor_rate(delivery_method_id, previous_rate)
822
+ same_method = delivery_method_id ? delivery_rates.select { |rate| rate.delivery_method_id == delivery_method_id } : []
823
+ same_service = if previous_rate&.carrier
824
+ same_method.detect do |rate|
825
+ rate.carrier == previous_rate.carrier && rate.service_level == previous_rate.service_level
826
+ end
827
+ end
828
+
829
+ same_service || same_method.first || delivery_rates.detect(&:selected)
830
+ end
831
+
795
832
  # @return [Boolean] whether this parcel's order shares its payment with
796
833
  # others placed in the same checkout
797
834
  # Draws one parcel's worth from a payment and reports what it took.
@@ -869,8 +906,7 @@ module Spree
869
906
  def capture_at_gateway(payment, amount)
870
907
  return 0 if amount <= 0
871
908
 
872
- units = Spree::Money::Rounding.to_minor_units(amount, payment.currency)
873
- result = Spree.payment_capture_workflow.call(payment: payment, amount: units)
909
+ result = Spree.payment_capture_workflow.call(payment: payment, amount: amount)
874
910
  raise Spree::Core::GatewayError, result.error.value.to_s if result.failure?
875
911
 
876
912
  amount
@@ -957,7 +993,7 @@ module Spree
957
993
  end
958
994
 
959
995
  def exactly_one_owner
960
- errors.add(:base, :exactly_one_of_cart_or_order, message: Spree.t('errors.messages.exactly_one_of_cart_or_order')) unless [order, cart].compact.one?
996
+ errors.add(:base, :exactly_one_of_cart_or_order, message: I18n.t('spree.errors.messages.exactly_one_of_cart_or_order')) unless [order, cart].compact.one?
961
997
  end
962
998
 
963
999
  def update_adjustments
@@ -106,8 +106,8 @@ module Spree
106
106
  end
107
107
 
108
108
  def get_desired_shipment_inventory_unit(state)
109
- desired_shipment.fulfillment_items.find_or_create_by(status: state, variant: variant) do |unit|
110
- current_shipment_unit = current_shipment_units.first
109
+ current_shipment_unit = current_shipment_units.first
110
+ desired_shipment.fulfillment_items.find_or_create_by(status: state, variant: variant, replacement: current_shipment_unit&.replacement || false) do |unit|
111
111
  unit.variant_id = current_shipment_unit.variant_id
112
112
  unit.order_id = current_shipment_unit.order_id
113
113
  unit.line_item_id = current_shipment_unit.line_item_id
@@ -72,12 +72,17 @@ module Spree
72
72
  fulfillments.sum { |fulfillment| fulfillment.discounted_cost.to_d }
73
73
  end
74
74
 
75
+ # @return [String] the currency the parcel was quoted in
76
+ def currency
77
+ primary.currency
78
+ end
79
+
75
80
  # @return [String, Spree::Money] the forwarder's placeholder while a
76
81
  # freight rate is unpriced, money otherwise
77
82
  def display_cost(**options)
78
83
  return primary.display_cost(**options) if unpriced?
79
84
 
80
- Spree::Money.new(cost, { currency: primary.currency }.merge(options))
85
+ Spree::Money.new(cost, { currency: currency }.merge(options))
81
86
  end
82
87
 
83
88
  # @return [Boolean]
@@ -15,11 +15,7 @@ module Spree
15
15
  belongs_to :line_item, class_name: 'Spree::LineItem'
16
16
  end
17
17
 
18
- scope :backordered, -> { where status: 'backordered' }
19
- scope :on_hand, -> { where status: 'on_hand' }
20
18
  scope :on_hand_or_backordered, -> { where status: ['backordered', 'on_hand'] }
21
- scope :shipped, -> { where status: 'shipped' }
22
- scope :returned, -> { where status: 'returned' }
23
19
  scope :backordered_per_variant, ->(stock_level) do
24
20
  includes(:fulfillment, :order).
25
21
  where.not(Spree::Fulfillment.table_name => { status: 'canceled' }).
@@ -6,6 +6,7 @@ module Spree
6
6
  # ShipmentHandler name-constantize mechanism.
7
7
  class Base
8
8
  include Spree::IntegrationBackedProvider
9
+ extend Spree::ApiTyped
9
10
 
10
11
  class << self
11
12
  # Behavior predicates — the class hierarchy IS the vocabulary.
@@ -1,5 +1,7 @@
1
1
  module Spree
2
2
  class Gateway::Bogus < Gateway
3
+ self.accepts_money_amounts = true
4
+
3
5
  TEST_VISA = ['4111111111111111', '4012888888881881', '4222222222222']
4
6
  TEST_MC = ['5500000000000004', '5555555555554444', '5105105105105100', '2223000010309703']
5
7
  TEST_AMEX = ['378282246310005', '371449635398431', '378734493671000', '340000000000009']
@@ -117,6 +119,10 @@ module Spree
117
119
  payment_session.complete
118
120
  end
119
121
 
122
+ def cancel_payment_session(payment_session:)
123
+ payment_session.cancel
124
+ end
125
+
120
126
  def setup_session_supported?
121
127
  true
122
128
  end
@@ -1,5 +1,10 @@
1
1
  module Spree
2
2
  class Gateway::CustomPaymentSourceMethod < Gateway
3
+ # Sources recorded by the merchant, not charged through a provider.
4
+ def self.third_party?
5
+ false
6
+ end
7
+
3
8
  def provider_class
4
9
  self.class
5
10
  end
@@ -1,6 +1,6 @@
1
1
  module Spree
2
2
  class Gateway < PaymentMethod
3
- FROM_DOLLAR_TO_CENT_RATE = 100.0
3
+ FROM_DOLLAR_TO_CENT_RATE = 100
4
4
 
5
5
  delegate :authorize, :purchase, :capture, :void, :credit, to: :provider
6
6
 
@@ -31,6 +31,10 @@ module Spree
31
31
  outer ? outer.underscore : leaf
32
32
  end
33
33
 
34
+ def self.third_party?
35
+ true
36
+ end
37
+
34
38
  def payment_source_class
35
39
  CreditCard
36
40
  end
@@ -49,7 +53,7 @@ module Spree
49
53
  end
50
54
 
51
55
  def options
52
- preferences.each_with_object({}) { |(key, value), memo| memo[key.to_sym] = value; }
56
+ preference_values.symbolize_keys
53
57
  end
54
58
 
55
59
  def method_missing(method, *args)
@@ -5,9 +5,13 @@ module Spree
5
5
  extend DisplayMoney
6
6
  include Spree::SingleStoreResource
7
7
  include Spree::HasCustomFields
8
+ include Spree::CanonicalDecimals
9
+
10
+ canonical_decimals :amount
8
11
  include Spree::Security::GiftCards if defined?(Spree::Security::GiftCards)
9
12
 
10
13
  publishes_lifecycle_events
14
+ publishes_events :canceled, :partially_redeemed, :redeemed
11
15
 
12
16
  #
13
17
  # Status
@@ -35,7 +39,6 @@ module Spree
35
39
  #
36
40
  # Associations
37
41
  #
38
- belongs_to :store, class_name: 'Spree::Store'
39
42
  belongs_to :customer, class_name: Spree.customer_class.to_s, optional: true
40
43
  include Spree::DeprecatedCustomerAlias
41
44
  belongs_to :created_by, class_name: Spree.admin_user_class.to_s, optional: true
@@ -62,10 +65,12 @@ module Spree
62
65
  # Ransack
63
66
  #
64
67
  ransack_alias :state, :status # @deprecated filter alias — removed in 6.1
65
- self.whitelisted_ransackable_attributes = %w[code customer_id status state gift_card_batch_id created_by_id]
68
+ self.whitelisted_ransackable_attributes = %w[code customer_id status state gift_card_batch_id created_by_id currency expires_at]
69
+ search_by :code
66
70
  # `users` is the pre-6.0 name for `customers` — removed in 6.1.
67
71
  self.whitelisted_ransackable_associations = %w[customers users orders batch]
68
72
  self.whitelisted_ransackable_scopes = %w[active expired redeemed partially_redeemed]
73
+ self.ransackable_scope_types = %w[active expired redeemed partially_redeemed].index_with('boolean')
69
74
 
70
75
  normalizes :code, with: ->(value) { value&.to_s&.squish&.presence }
71
76
 
@@ -82,18 +87,23 @@ module Spree
82
87
  #
83
88
  money_methods :amount, :amount_used, :amount_authorized, :amount_remaining
84
89
 
85
- # Sets the amount
86
- # @param amount [String]
87
- def amount=(amount)
88
- self[:amount] = Spree::LocalizedNumber.parse(amount)
89
- end
90
-
91
90
  # Calculates the remaining amount
92
91
  # @return [Decimal]
93
92
  def amount_remaining
94
93
  amount - amount_used - amount_authorized
95
94
  end
96
95
 
96
+ # What the card can still give towards an order: nothing once it has
97
+ # expired, been redeemed or been cancelled, and never less than zero,
98
+ # since an admin can lower the amount below what is already in use.
99
+ #
100
+ # @return [BigDecimal]
101
+ def spendable_amount
102
+ return BigDecimal('0') if expired? || redeemed? || canceled?
103
+
104
+ [amount_remaining, 0].max
105
+ end
106
+
97
107
  delegate :email, to: :customer, prefix: true, allow_nil: true
98
108
 
99
109
  def self.json_api_columns
@@ -4,13 +4,15 @@ module Spree
4
4
 
5
5
  extend DisplayMoney
6
6
  include Spree::SingleStoreResource
7
+ include Spree::CanonicalDecimals
8
+
9
+ canonical_decimals :amount
7
10
 
8
11
  publishes_lifecycle_events
9
12
 
10
13
  #
11
14
  # Associations
12
15
  #
13
- belongs_to :store, class_name: 'Spree::Store'
14
16
  belongs_to :created_by, class_name: Spree.admin_user_class.to_s, optional: true
15
17
  has_many :gift_cards, class_name: 'Spree::GiftCard', inverse_of: :batch, dependent: :destroy
16
18
 
@@ -35,10 +37,6 @@ module Spree
35
37
 
36
38
  money_methods :amount
37
39
 
38
- def amount=(amount)
39
- self[:amount] = Spree::LocalizedNumber.parse(amount)
40
- end
41
-
42
40
  self.whitelisted_ransackable_attributes = %w[prefix]
43
41
 
44
42
  def generate_gift_cards
@@ -17,6 +17,7 @@ module Spree
17
17
  'https://raw.githubusercontent.com/spree/spree/refs/tags/v%<version>s/spree/core/db/sample_data'.freeze
18
18
 
19
19
  publishes_lifecycle_events
20
+ publishes_events :completed, :progress
20
21
 
21
22
  # Set event prefix for all Import subclasses
22
23
  # This ensures Spree::Imports::Products publishes 'import.create' not 'products.create'
@@ -54,6 +55,7 @@ module Spree
54
55
  # Ransack configuration
55
56
  #
56
57
  self.whitelisted_ransackable_attributes = %w[number type status seller_id]
58
+ search_by :number
57
59
  # Lets an operator filter their list by who ran the job — the seller's
58
60
  # name, not an id they would have to look up first.
59
61
  self.whitelisted_ransackable_associations = %w[seller]
@@ -71,6 +73,7 @@ module Spree
71
73
  # Attachments
72
74
  #
73
75
  has_one_attached :attachment, service: Spree.private_storage_service_name
76
+ signed_id_attachments :attachment
74
77
 
75
78
  #
76
79
  # Status
@@ -86,6 +89,7 @@ module Spree
86
89
  # Preferences
87
90
  #
88
91
  preference :delimiter, :string, default: ','
92
+ exposes_preferences :delimiter
89
93
  # Run the pipeline in-process instead of enqueuing it. For rake tasks, seeds
90
94
  # and the console, where the caller needs the data to exist when the call
91
95
  # returns and there may be no worker attached. Never set it from a request:
@@ -191,12 +195,6 @@ module Spree
191
195
  Spree.import_retry_failed_rows_workflow.call(import: self).success?
192
196
  end
193
197
 
194
- # Returns true if the import is in mapping state
195
- # @return [Boolean]
196
- def mapping?
197
- status == 'mapping'
198
- end
199
-
200
198
  # Returns true if the import is processing or completed mapping
201
199
  # @return [Boolean]
202
200
  def processing?
@@ -331,7 +329,7 @@ module Spree
331
329
  # Returns the display name for the import
332
330
  # @return [String]
333
331
  def display_name
334
- "#{Spree.t(type.demodulize.pluralize.downcase)} #{number}"
332
+ "#{self.class.model_name.human(count: 2)} #{number}"
335
333
  end
336
334
 
337
335
  # Returns the headers of the csv file
@@ -5,7 +5,7 @@ module Spree
5
5
  #
6
6
  # Associations
7
7
  #
8
- belongs_to :import, class_name: 'Spree::Import', inverse_of: :mappings, required: true
8
+ belongs_to :import, class_name: 'Spree::Import', inverse_of: :mappings
9
9
 
10
10
  #
11
11
  # Validations
@@ -17,7 +17,6 @@ module Spree
17
17
  #
18
18
  # Scopes
19
19
  #
20
- scope :required, -> { where(schema_field: import.required_fields) }
21
20
  scope :mapped, -> { where.not(file_column: [nil, '']) }
22
21
 
23
22
  # Returns true if the mapping is required