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
@@ -97,7 +97,7 @@ module Spree
97
97
  index.zero? ? order.status : nil,
98
98
  index.zero? ? order.currency : nil,
99
99
  index.zero? ? order.item_total.to_f : nil,
100
- index.zero? ? order.shipment_total.to_f : nil,
100
+ index.zero? ? order.delivery_total.to_f : nil,
101
101
  index.zero? ? order.tax_total.to_f : nil,
102
102
  index.zero? ? order.included_tax_total.positive? : nil,
103
103
  index.zero? ? (order.discount_total.negative? || line_item.discount_total.negative?) : nil,
@@ -30,6 +30,7 @@ module Spree
30
30
  'track_inventory',
31
31
  'inventory_count',
32
32
  'inventory_backorderable',
33
+ 'stock_location',
33
34
  'tax_category',
34
35
  'product_type',
35
36
  'image1_src',
@@ -46,7 +47,13 @@ module Spree
46
47
  'category3',
47
48
  ].freeze
48
49
 
49
- def initialize(product, variant, index = 0, properties = [], categories = [], store = nil, custom_fields = [], currency = nil)
50
+ # @param default_stock_location [Spree::StockLocation, nil] where the full row's
51
+ # inventory columns read from, blank when the variant has no stock
52
+ # there; the store's default when nil
53
+ # @param stock_level [Spree::StockLevel, nil] turns the row into a
54
+ # stock-only row for that level's location
55
+ def initialize(product, variant, index = 0, properties = [], categories = [], store = nil, custom_fields = [], currency = nil,
56
+ default_stock_location: nil, stock_level: nil)
50
57
  @product = product
51
58
  @variant = variant
52
59
  @index = index
@@ -56,9 +63,12 @@ module Spree
56
63
  @currency = currency || @store.default_currency
57
64
  @price_only = @currency != @store.default_currency
58
65
  @custom_fields = custom_fields
66
+ @default_stock_location = default_stock_location
67
+ @stock_level = stock_level
59
68
  end
60
69
 
61
- attr_accessor :product, :variant, :index, :properties, :categories, :store, :currency, :price_only, :custom_fields
70
+ attr_accessor :product, :variant, :index, :properties, :categories, :store, :currency, :price_only, :custom_fields,
71
+ :default_stock_location, :stock_level
62
72
 
63
73
  ##
64
74
  # Generates an array representing a CSV row of product variant data.
@@ -73,8 +83,7 @@ module Spree
73
83
  # @return [Array] An array containing the combined product and variant CSV data.
74
84
  def call
75
85
  return price_only_row if price_only
76
-
77
- total_on_hand = variant.total_on_hand
86
+ return stock_only_row if stock_level
78
87
 
79
88
  csv = [
80
89
  product.id,
@@ -104,19 +113,23 @@ module Spree
104
113
  publication_available_on&.strftime('%Y-%m-%d %H:%M:%S'),
105
114
  (variant.discontinue_on || publication_discontinue_on)&.strftime('%Y-%m-%d %H:%M:%S'),
106
115
  variant.track_inventory?,
107
- total_on_hand == BigDecimal::INFINITY ? '∞' : total_on_hand,
108
- variant.backorderable?,
116
+ # The shelf count at the one location the import writes this row
117
+ # back to, not what is left to sell: that would drop every unit held
118
+ # by a cart or promised to an order on each round trip.
119
+ variant.should_track_inventory? ? default_stock_level&.count_on_hand : '∞',
120
+ default_stock_level&.backorderable,
121
+ nil,
109
122
  variant.tax_category&.name,
110
123
  product.product_type&.name,
111
124
  spree_image_url(variant.images[0], image_url_options),
112
125
  spree_image_url(variant.images[1], image_url_options),
113
126
  spree_image_url(variant.images[2], image_url_options),
114
- index.positive? ? option_type(0)&.label : nil,
115
- index.positive? ? option_value(option_type(0)) : nil,
116
- index.positive? ? option_type(1)&.label : nil,
117
- index.positive? ? option_value(option_type(1)) : nil,
118
- index.positive? ? option_type(2)&.label : nil,
119
- index.positive? ? option_value(option_type(2)) : nil
127
+ option_type(0)&.label,
128
+ option_value(option_type(0)),
129
+ option_type(1)&.label,
130
+ option_value(option_type(1)),
131
+ option_type(2)&.label,
132
+ option_value(option_type(2))
120
133
  ]
121
134
 
122
135
  if index.zero?
@@ -168,6 +181,23 @@ module Spree
168
181
  csv
169
182
  end
170
183
 
184
+ def stock_only_row
185
+ csv = Array.new(CSV_HEADERS.size)
186
+ csv[CSV_HEADERS.index('sku')] = variant.sku
187
+ csv[CSV_HEADERS.index('slug')] = product.slug
188
+ csv[CSV_HEADERS.index('inventory_count')] = stock_level.count_on_hand
189
+ csv[CSV_HEADERS.index('inventory_backorderable')] = stock_level.backorderable
190
+ csv[CSV_HEADERS.index('stock_location')] = stock_level.stock_location.name
191
+ csv
192
+ end
193
+
194
+ def default_stock_level
195
+ return @default_stock_level if defined?(@default_stock_level)
196
+
197
+ location = default_stock_location || store.default_stock_location
198
+ @default_stock_level = variant.stock_levels.find { |level| level.stock_location_id == location.id }
199
+ end
200
+
171
201
  def image_url_options
172
202
  { variant: :xlarge }
173
203
  end
@@ -0,0 +1,14 @@
1
+ module Spree
2
+ module Emails
3
+ # Base for the serializers that feed email templates. Emails read plain
4
+ # JSON, the same data a renderer outside Ruby would receive, never models.
5
+ # Never expose a token here: URLs carrying secrets are built by the mailer.
6
+ class BaseSerializer
7
+ include Alba::Resource
8
+
9
+ attribute :id do |object|
10
+ object.prefixed_id if object.respond_to?(:prefixed_id)
11
+ end
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,13 @@
1
+ module Spree
2
+ module Emails
3
+ class ExportSerializer < BaseSerializer
4
+ attributes :number, :results_url
5
+
6
+ attribute :filename do |export|
7
+ export.attachment.filename.to_s if export.attachment.attached?
8
+ end
9
+
10
+ one :user, resource: Spree::Emails::UserSerializer
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,19 @@
1
+ module Spree
2
+ module Emails
3
+ class ImportSerializer < BaseSerializer
4
+ attributes :number
5
+
6
+ attribute :results_url, &:results_page_url
7
+
8
+ attribute :completed_count do |import|
9
+ import.rows_status_counts['completed'].to_i
10
+ end
11
+
12
+ attribute :failed_count do |import|
13
+ import.rows_status_counts['failed'].to_i
14
+ end
15
+
16
+ one :user, resource: Spree::Emails::UserSerializer
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,28 @@
1
+ module Spree
2
+ module Emails
3
+ class InvitationSerializer < BaseSerializer
4
+ attributes :email
5
+
6
+ attribute :resource_name do |invitation|
7
+ invitation.resource.try(:name)
8
+ end
9
+
10
+ attribute :inviter_name do |invitation|
11
+ invitation.inviter.try(:full_name).presence || invitation.inviter.try(:name)
12
+ end
13
+
14
+ attribute :inviter_first_name do |invitation|
15
+ invitation.inviter.try(:first_name)
16
+ end
17
+
18
+ # Nil until the invitation is accepted by someone with an account.
19
+ attribute :invitee_name do |invitation|
20
+ invitation.invitee.try(:full_name).presence || invitation.invitee.try(:name)
21
+ end
22
+
23
+ attribute :invitee_first_name do |invitation|
24
+ invitation.invitee.try(:first_name)
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,31 @@
1
+ module Spree
2
+ module Emails
3
+ class StoreSerializer < BaseSerializer
4
+ include Spree::ImagesHelper
5
+
6
+ LOGO_HEIGHT = 32
7
+
8
+ attributes :name, :address, :mail_from_address, :default_currency, :default_locale
9
+
10
+ attribute :url, &:storefront_url
11
+
12
+ attribute :support_email, &:support_email_address
13
+
14
+ attribute :logo_url do |store|
15
+ spree_image_url(store.email_logo, height: LOGO_HEIGHT)
16
+ end
17
+
18
+ # The width the logo shows at, from its proportions. Nil until the file
19
+ # has been analyzed, when the layout lets the width follow the height.
20
+ attribute :logo_width do |store|
21
+ width, height = store.email_logo&.metadata.to_h.values_at('width', 'height').map(&:to_f)
22
+ (LOGO_HEIGHT * width / height).round if height.to_f.positive?
23
+ end
24
+
25
+ # Colors and font (see Spree::Emails::Branding).
26
+ attribute :branding do |store|
27
+ store.email_branding(params[:branding] || {}).to_h
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,12 @@
1
+ module Spree
2
+ module Emails
3
+ # Staff and seller-panel users, as back-office emails address them.
4
+ class UserSerializer < BaseSerializer
5
+ attributes :email, :first_name, :last_name
6
+
7
+ attribute :name do |user|
8
+ user.try(:full_name).presence || user.try(:name).presence
9
+ end
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,16 @@
1
+ module Spree
2
+ module Emails
3
+ # Never add the signing secret: an email must not carry it.
4
+ class WebhookEndpointSerializer < BaseSerializer
5
+ attributes :name, :url, :disabled_reason
6
+
7
+ attribute :display_name do |endpoint|
8
+ endpoint.name.presence || endpoint.url
9
+ end
10
+
11
+ attribute :disabled_at do |endpoint|
12
+ endpoint.disabled_at&.iso8601
13
+ end
14
+ end
15
+ end
16
+ end
@@ -9,8 +9,14 @@ module Spree
9
9
  store = @params.delete(:store)
10
10
  return failure(:store_is_required) if store.nil?
11
11
 
12
+ customer = @params.delete(:customer)
13
+ if @params.key?(:user)
14
+ Spree::Deprecation.warn('Passing user: to Spree::Carts::Create is deprecated and will be removed in Spree 6.1. Pass customer: instead.')
15
+ customer ||= @params.delete(:user)
16
+ end
17
+
12
18
  cart = store.carts.create!(
13
- user: @params.delete(:user),
19
+ customer: customer,
14
20
  market: @params.delete(:market) || Spree::Current.market,
15
21
  channel: @params.delete(:channel) || Spree::Current.channel,
16
22
  currency: @params.delete(:currency) || store.default_currency,
@@ -19,7 +25,7 @@ module Spree
19
25
 
20
26
  # Delegate all attribute/address/item processing to Carts::Update
21
27
  if @params.present?
22
- result = Spree::Carts::Update.call(cart: cart, params: @params)
28
+ result = Spree.carts_update_service.call(cart: cart, params: @params)
23
29
  return result if result.failure?
24
30
  end
25
31
 
@@ -20,7 +20,7 @@ module Spree
20
20
  private
21
21
 
22
22
  def check_if_can_be_destroyed(cart:)
23
- return failure(false, Spree.t(:cannot_be_destroyed)) unless cart&.can_be_deleted?
23
+ return failure(false, I18n.t('spree.cannot_be_destroyed')) unless cart&.can_be_deleted?
24
24
 
25
25
  success(cart: cart)
26
26
  end
@@ -62,7 +62,7 @@ module Spree
62
62
  def destroy_order(cart:)
63
63
  destroyed_result = cart.destroy
64
64
 
65
- return failure(false, Spree.t(:cannot_be_destroyed)) unless destroyed_result.present?
65
+ return failure(false, I18n.t('spree.cannot_be_destroyed')) unless destroyed_result.present?
66
66
 
67
67
  success(cart)
68
68
  end
@@ -72,7 +72,7 @@ module Spree
72
72
  def valid_status?(line_item)
73
73
  product = line_item.product
74
74
  if !product.active? || product.deleted? || product.discontinued? || line_item.variant.discontinued?
75
- message = Spree.t('cart_line_item.discontinued', li_name: line_item.name)
75
+ message = I18n.t('spree.cart_line_item.discontinued', li_name: line_item.name)
76
76
  @messages << message
77
77
  @warnings << {
78
78
  code: 'line_item_removed',
@@ -87,7 +87,7 @@ module Spree
87
87
 
88
88
  def stock_available?(line_item)
89
89
  if line_item.insufficient_stock?
90
- message = Spree.t('cart_line_item.out_of_stock', li_name: line_item.name)
90
+ message = I18n.t('spree.cart_line_item.out_of_stock', li_name: line_item.name)
91
91
  @messages << message
92
92
  @warnings << {
93
93
  code: 'line_item_removed',
@@ -242,7 +242,7 @@ module Spree
242
242
  end
243
243
 
244
244
  def clone_fulfillment(fulfillment, sibling)
245
- attributes = fulfillment.attributes.except('id', 'cart_id', 'number', 'created_at', 'updated_at')
245
+ attributes = fulfillment.attributes.except('id', 'cart_id', 'number', 'tracking', 'created_at', 'updated_at')
246
246
  replacement = sibling.fulfillments.create!(
247
247
  attributes.merge('order_id' => sibling.id, 'address_id' => sibling.ship_address_id)
248
248
  )
@@ -16,6 +16,9 @@ module Spree
16
16
  assign_default_addresses
17
17
 
18
18
  cart.save!
19
+ # Read before the items: processing them reloads the cart, which forgets what this save changed.
20
+ @destination_changed = destination_changed?
21
+ @new_delivery_destination = new_delivery_destination?
19
22
 
20
23
  process_items
21
24
  try_advance
@@ -45,7 +48,7 @@ module Spree
45
48
  end
46
49
  cart.warnings |= warnings if warnings.present?
47
50
 
48
- failure(cart, Spree.t('cart_line_item.pricing_unavailable'))
51
+ failure(cart, I18n.t('spree.cart_line_item.pricing_unavailable'))
49
52
  rescue ActiveRecord::RecordInvalid => e
50
53
  failure(cart, e.record.errors.full_messages.to_sentence)
51
54
  rescue StandardError => e
@@ -65,6 +68,15 @@ module Spree
65
68
  cart.saved_change_to_preferred_stock_location_id?
66
69
  end
67
70
 
71
+ # Whether the chosen delivery belongs to a place the cart no longer
72
+ # ships to. A market change or a corrected phone number keeps the
73
+ # choice, quoted again.
74
+ def new_delivery_destination?
75
+ cart.saved_change_to_ship_address_id? ||
76
+ cart.saved_change_to_preferred_stock_location_id? ||
77
+ cart.ship_address&.saved_change_to_destination? || false
78
+ end
79
+
68
80
  def assign_cart_attributes
69
81
  cart.email = params[:email] if params[:email].present?
70
82
  cart.customer_note = params[:customer_note] if params.key?(:customer_note)
@@ -98,7 +110,7 @@ module Spree
98
110
  cart.po_document.attach(value)
99
111
  end
100
112
  rescue ActiveStorage::FileNotFoundError
101
- cart.errors.add(:po_document, :po_document_upload_incomplete, message: Spree.t(:po_document_upload_incomplete))
113
+ cart.errors.add(:po_document, :po_document_upload_incomplete, message: I18n.t('spree.po_document_upload_incomplete'))
102
114
  raise ActiveRecord::RecordInvalid, cart
103
115
  end
104
116
 
@@ -175,7 +187,9 @@ module Spree
175
187
  def process_items
176
188
  return unless params[:items].is_a?(Array)
177
189
 
178
- result = Spree.cart_upsert_items_workflow.call(cart: cart, items: params[:items])
190
+ # Resolved after the save, so a company named in the same request
191
+ # decides which catalogs the items are checked against.
192
+ result = Spree.cart_upsert_items_workflow.call(cart: cart, items: params[:items], orderable_variants: cart.orderable_variants)
179
193
 
180
194
  raise StandardError, result.error.to_s if result.failure?
181
195
  end
@@ -237,8 +251,8 @@ module Spree
237
251
  def try_advance
238
252
  return if cart.complete?
239
253
 
240
- if @address_invalidated || destination_changed?
241
- cart.recalculate_for_address_change!
254
+ if @address_invalidated || @destination_changed
255
+ cart.recalculate_for_address_change!(keep_selection: !@new_delivery_destination)
242
256
  else
243
257
  cart.recalculate_totals!
244
258
  end
@@ -21,7 +21,7 @@ module Spree
21
21
  return success(catalog) if terms.empty?
22
22
 
23
23
  foreign = terms.keys.reject { |product| product.store_id == catalog.store_id }
24
- return failure(catalog, Spree.t('catalogs.product_not_in_store')) if foreign.any?
24
+ return failure(catalog, I18n.t('spree.catalogs.product_not_in_store')) if foreign.any?
25
25
 
26
26
  invalid = nil
27
27
 
@@ -2,8 +2,9 @@ module Spree
2
2
  module Categories
3
3
  class AddProducts
4
4
  prepend Spree::ServiceModule::Base
5
+ include Spree::ProductMemberships
5
6
 
6
- # Creates product-category links for the given categories and products in bulk.
7
+ # Adds the given products to the given categories, in bulk.
7
8
  #
8
9
  # @param categories [Array<Spree::Category>]
9
10
  # @param products [Array<Spree::Product>]
@@ -11,40 +12,21 @@ module Spree
11
12
  def call(categories:, products:)
12
13
  return if categories.blank? || products.blank?
13
14
 
14
- # build the params for the insert_all
15
- product_categories_params = categories.pluck(:id).flat_map do |category_id|
16
- position = Spree::ProductCategory.where(category_id: category_id).count
17
-
18
- products.pluck(:id).map do |product_id|
19
- {
20
- category_id: category_id,
21
- product_id: product_id,
22
- position: (position += 1),
23
- created_at: Time.current,
24
- updated_at: Time.current
25
- }
26
- end
27
- end
28
- # doing a quick insert_all here to avoid the overhead of instantiating
29
- Spree::ProductCategory.insert_all(product_categories_params)
15
+ add_memberships(categories, products)
16
+ success(true)
17
+ end
30
18
 
31
- # update counter caches
32
- category_ids = categories.pluck(:id)
33
- product_ids = products.pluck(:id)
34
- product_ids.each { |id| Spree::Product.reset_counters(id, :product_categories) }
35
- # Recompute the descendant-inclusive products_count for the categories and
36
- # their ancestors (bulk insert skips ProductCategory callbacks).
37
- Spree::Category.recalculate_products_count(category_ids)
19
+ private
38
20
 
39
- # clear cache & index products
40
- Spree::Product.where(id: product_ids).touch_all
41
- products.each(&:enqueue_search_index)
21
+ def membership_class = Spree::ProductCategory
22
+ def group_class = Spree::Category
42
23
 
43
- Spree::Category.where(id: category_ids).touch_all
44
- # Optional external hook (e.g. storefront featured sections); namespace owned by that gem.
24
+ # Recomputes the descendant-inclusive products_count for the categories
25
+ # and their ancestors, and lets an optional storefront extension refresh
26
+ # its featured sections.
27
+ def refresh_group_counters(category_ids)
28
+ Spree::Category.recalculate_products_count(category_ids)
45
29
  Spree::Taxons::TouchFeaturedSections.call(taxon_ids: category_ids) if defined?(Spree::Taxons::TouchFeaturedSections)
46
-
47
- success(true)
48
30
  end
49
31
  end
50
32
  end
@@ -2,8 +2,9 @@ module Spree
2
2
  module Categories
3
3
  class RemoveProducts
4
4
  prepend Spree::ServiceModule::Base
5
+ include Spree::ProductMemberships
5
6
 
6
- # Removes the given products from the given categories.
7
+ # Removes the given products from the given categories and re-packs positions.
7
8
  #
8
9
  # @param categories [Array<Spree::Category>]
9
10
  # @param products [Array<Spree::Product>]
@@ -11,61 +12,21 @@ module Spree
11
12
  def call(categories:, products:)
12
13
  return if categories.blank? || products.blank?
13
14
 
14
- category_ids = categories.pluck(:id)
15
- product_ids = products.pluck(:id)
16
-
17
- ApplicationRecord.transaction do
18
- category_ids.each do |category_id|
19
- Spree::ProductCategory.where(category_id: category_id, product_id: product_ids).delete_all
20
- end
21
-
22
- product_categories_params = category_ids.flat_map do |category_id|
23
- position = 0
24
- existing_product_ids = Spree::ProductCategory.where(category_id: category_id).pluck(:product_id)
25
-
26
- existing_product_ids.map do |product_id|
27
- {
28
- category_id: category_id,
29
- product_id: product_id,
30
- position: (position += 1),
31
- created_at: Time.current,
32
- updated_at: Time.current
33
- }
34
- end
35
- end
36
-
37
- if product_categories_params.any?
38
- opts = {}
39
- opts[:unique_by] = %i[product_id category_id] unless mysql_adapter?
40
-
41
- Spree::ProductCategory.upsert_all(
42
- product_categories_params,
43
- **opts
44
- )
45
- end
46
- end
47
-
48
- # update counter caches
49
- product_ids.each { |id| Spree::Product.reset_counters(id, :product_categories) }
50
- # Recompute the descendant-inclusive products_count for the categories and
51
- # their ancestors (delete_all skips ProductCategory callbacks).
52
- Spree::Category.recalculate_products_count(category_ids)
53
-
54
- # clear cache & index products
55
- Spree::Product.where(id: product_ids).touch_all
56
- products.each(&:enqueue_search_index)
57
-
58
- Spree::Category.where(id: category_ids).touch_all
59
- # Optional external hook (e.g. storefront featured sections); namespace owned by that gem.
60
- Spree::Taxons::TouchFeaturedSections.call(taxon_ids: category_ids) if defined?(Spree::Taxons::TouchFeaturedSections)
61
-
15
+ remove_memberships(categories, products)
62
16
  success(true)
63
17
  end
64
18
 
65
19
  private
66
20
 
67
- def mysql_adapter?
68
- Spree.mysql?
21
+ def membership_class = Spree::ProductCategory
22
+ def group_class = Spree::Category
23
+
24
+ # Recomputes the descendant-inclusive products_count for the categories
25
+ # and their ancestors, and lets an optional storefront extension refresh
26
+ # its featured sections.
27
+ def refresh_group_counters(category_ids)
28
+ Spree::Category.recalculate_products_count(category_ids)
29
+ Spree::Taxons::TouchFeaturedSections.call(taxon_ids: category_ids) if defined?(Spree::Taxons::TouchFeaturedSections)
69
30
  end
70
31
  end
71
32
  end
@@ -11,10 +11,7 @@ module Spree
11
11
  def call(order:, state: nil, shipping_method_id: nil)
12
12
  cart = order
13
13
 
14
- # A destination is either a shipping address or a pickup intent — a
15
- # pure-pickup cart never gets an address, but still needs proposals.
16
- has_destination = cart.ship_address.present? || cart.preferred_stock_location_id.present?
17
- if cart.fulfillments.empty? && cart.delivery_step_required? && has_destination && cart.respond_to?(:rebuild_fulfillments!)
14
+ if cart.fulfillments.empty? && cart.delivery_step_required? && cart.can_propose_deliveries? && cart.respond_to?(:rebuild_fulfillments!)
18
15
  cart.rebuild_fulfillments!
19
16
  end
20
17
 
@@ -2,8 +2,9 @@ module Spree
2
2
  module Collections
3
3
  class AddProducts
4
4
  prepend Spree::ServiceModule::Base
5
+ include Spree::ProductMemberships
5
6
 
6
- # Adds the given products to the given collections (manual curation), in bulk.
7
+ # Adds the given products to the given collections, in bulk.
7
8
  #
8
9
  # @param collections [Array<Spree::Collection>]
9
10
  # @param products [Array<Spree::Product>]
@@ -11,31 +12,17 @@ module Spree
11
12
  def call(collections:, products:)
12
13
  return if collections.blank? || products.blank?
13
14
 
14
- product_collections_params = collections.pluck(:id).flat_map do |collection_id|
15
- position = Spree::ProductCollection.where(collection_id: collection_id).count
16
-
17
- products.pluck(:id).map do |product_id|
18
- {
19
- collection_id: collection_id,
20
- product_id: product_id,
21
- position: (position += 1),
22
- created_at: Time.current,
23
- updated_at: Time.current
24
- }
25
- end
26
- end
27
- Spree::ProductCollection.insert_all(product_collections_params)
15
+ add_memberships(collections, products)
16
+ success(true)
17
+ end
28
18
 
29
- collection_ids = collections.pluck(:id)
30
- product_ids = products.pluck(:id)
31
- collection_ids.each { |id| Spree::Collection.reset_counters(id, :product_collections) }
32
- product_ids.each { |id| Spree::Product.reset_counters(id, :product_collections) }
19
+ private
33
20
 
34
- Spree::Product.where(id: product_ids).touch_all
35
- products.each(&:enqueue_search_index)
36
- Spree::Collection.where(id: collection_ids).touch_all
21
+ def membership_class = Spree::ProductCollection
22
+ def group_class = Spree::Collection
37
23
 
38
- success(true)
24
+ def refresh_group_counters(collection_ids)
25
+ collection_ids.each { |id| Spree::Collection.reset_counters(id, :product_collections) }
39
26
  end
40
27
  end
41
28
  end