spree_core 5.6.0 → 6.0.0.beta1

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 (1588) hide show
  1. checksums.yaml +4 -4
  2. data/LICENSE +30 -0
  3. data/README.md +2 -3
  4. data/app/finders/spree/line_items/find_by_variant.rb +5 -3
  5. data/app/finders/spree/sellers/panel_url.rb +47 -0
  6. data/app/finders/spree/stores/dashboard_url.rb +76 -0
  7. data/app/helpers/spree/images_helper.rb +1 -1
  8. data/app/jobs/spree/carts/reap_expired_job.rb +43 -0
  9. data/app/jobs/spree/collections/regenerate_time_based_job.rb +18 -0
  10. data/app/jobs/spree/data_requests/process_job.rb +42 -0
  11. data/app/jobs/spree/events/subscriber_job.rb +1 -1
  12. data/app/jobs/spree/images/migrate_product_media_job.rb +80 -0
  13. data/app/jobs/spree/images/save_from_url_job.rb +70 -13
  14. data/app/jobs/spree/imports/create_categories_job.rb +23 -14
  15. data/app/jobs/spree/imports/create_rows_job.rb +3 -62
  16. data/app/jobs/spree/imports/process_group_job.rb +46 -27
  17. data/app/jobs/spree/imports/process_job.rb +164 -0
  18. data/app/jobs/spree/imports/process_rows_job.rb +4 -63
  19. data/app/jobs/spree/orders/finalize_stale_drafts_job.rb +29 -0
  20. data/app/jobs/spree/payments/handle_webhook_job.rb +1 -1
  21. data/app/jobs/spree/price_lists/sync_currencies_job.rb +42 -0
  22. data/app/jobs/spree/product_types/apply_to_products_job.rb +74 -0
  23. data/app/jobs/spree/products/auto_match_collections_job.rb +14 -0
  24. data/app/jobs/spree/products/auto_match_taxons_job.rb +6 -7
  25. data/app/jobs/spree/products/refresh_metrics_job.rb +1 -1
  26. data/app/jobs/spree/products/touch_categories_job.rb +15 -0
  27. data/app/jobs/spree/products/touch_taxons_job.rb +7 -6
  28. data/app/jobs/spree/seller_payouts/sweep_due_job.rb +57 -0
  29. data/app/jobs/spree/seller_payouts/sweep_seller_job.rb +70 -0
  30. data/app/jobs/spree/seller_transfers/execute_pending_due_job.rb +56 -0
  31. data/app/jobs/spree/seller_transfers/execute_pending_job.rb +52 -0
  32. data/app/jobs/spree/shipping_labels/store_file_job.rb +17 -0
  33. data/app/jobs/spree/stock_locations/stock_items/create_job.rb +7 -5
  34. data/app/jobs/spree/stock_locations/stock_levels/create_job.rb +13 -0
  35. data/app/jobs/spree/stock_reservations/expire_job.rb +1 -1
  36. data/app/jobs/spree/tax_identifiers/validate_job.rb +14 -0
  37. data/app/jobs/spree/variants/remove_line_item_job.rb +29 -1
  38. data/app/mailers/spree/admin_user_mailer.rb +6 -35
  39. data/app/mailers/spree/base_mailer.rb +1 -20
  40. data/app/mailers/spree/invitation_mailer.rb +1 -1
  41. data/app/mailers/spree/seller_user_mailer.rb +47 -0
  42. data/app/models/concerns/spree/account_lockout.rb +38 -0
  43. data/app/models/concerns/spree/admin_user_methods.rb +14 -34
  44. data/app/models/concerns/spree/calculated_adjustments.rb +8 -14
  45. data/app/models/concerns/spree/calculator/currency_amounts.rb +43 -0
  46. data/app/models/concerns/spree/capture_method.rb +33 -0
  47. data/app/models/concerns/spree/custom_field_filterable.rb +163 -0
  48. data/app/models/concerns/spree/customer_methods.rb +421 -0
  49. data/app/models/concerns/spree/deprecated_customer_alias.rb +22 -0
  50. data/app/models/concerns/spree/derived_number.rb +66 -0
  51. data/app/models/concerns/spree/has_address_book.rb +89 -0
  52. data/app/models/concerns/spree/has_custom_fields.rb +360 -0
  53. data/app/models/concerns/spree/has_external_references.rb +95 -0
  54. data/app/models/concerns/spree/has_iso_geography.rb +71 -0
  55. data/app/models/concerns/spree/has_library_media.rb +94 -0
  56. data/app/models/concerns/spree/has_list_position.rb +15 -0
  57. data/app/models/concerns/spree/has_number.rb +139 -0
  58. data/app/models/concerns/spree/has_status.rb +76 -0
  59. data/app/models/concerns/spree/has_tracking_carrier.rb +18 -0
  60. data/app/models/concerns/spree/instruments_gateway_calls.rb +20 -0
  61. data/app/models/concerns/spree/integration_backed_provider.rb +89 -0
  62. data/app/models/concerns/spree/integrations_concern.rb +15 -2
  63. data/app/models/concerns/spree/label_translatable.rb +37 -0
  64. data/app/models/concerns/spree/marketplace/order_group.rb +35 -0
  65. data/app/models/concerns/spree/metadata.rb +18 -22
  66. data/app/models/concerns/spree/named_type.rb +11 -1
  67. data/app/models/concerns/spree/parameterizable_name.rb +5 -5
  68. data/app/models/concerns/spree/password_policy.rb +37 -0
  69. data/app/models/concerns/spree/payment_session_transitions.rb +93 -0
  70. data/app/models/concerns/spree/payment_source_concern.rb +1 -1
  71. data/app/models/concerns/spree/personal_data_matching.rb +120 -0
  72. data/app/models/concerns/spree/preference_schema.rb +120 -19
  73. data/app/models/concerns/spree/prefixed_id.rb +25 -3
  74. data/app/models/concerns/spree/presentation_translatable.rb +4 -37
  75. data/app/models/concerns/spree/product_scopes.rb +51 -191
  76. data/app/models/concerns/spree/provider_failure_policy.rb +44 -0
  77. data/app/models/concerns/spree/publishable.rb +7 -7
  78. data/app/models/concerns/spree/purchase/addresses.rb +258 -0
  79. data/app/models/concerns/spree/purchase/channel.rb +19 -0
  80. data/app/models/concerns/spree/purchase/checkout_steps.rb +68 -0
  81. data/app/models/concerns/spree/purchase/company.rb +96 -0
  82. data/app/models/concerns/spree/purchase/currency.rb +29 -0
  83. data/app/models/concerns/spree/purchase/digital_items.rb +60 -0
  84. data/app/models/concerns/spree/purchase/freight.rb +52 -0
  85. data/app/models/concerns/spree/purchase/gift_cards.rb +70 -0
  86. data/app/models/concerns/spree/purchase/lifecycle.rb +53 -0
  87. data/app/models/concerns/spree/purchase/line_item_currencies.rb +38 -0
  88. data/app/models/concerns/spree/purchase/line_item_lookup.rb +25 -0
  89. data/app/models/concerns/spree/purchase/locale.rb +30 -0
  90. data/app/models/concerns/spree/purchase/market.rb +29 -0
  91. data/app/models/concerns/spree/purchase/payment_processing.rb +93 -0
  92. data/app/models/concerns/spree/purchase/purchase_order.rb +94 -0
  93. data/app/models/concerns/spree/purchase/quantity_rules.rb +136 -0
  94. data/app/models/concerns/spree/purchase/store_credits.rb +100 -0
  95. data/app/models/concerns/spree/purchase/taxation.rb +146 -0
  96. data/app/models/concerns/spree/purchase/totals.rb +116 -0
  97. data/app/models/concerns/spree/purchase/validations.rb +45 -0
  98. data/app/models/concerns/spree/receivable.rb +103 -0
  99. data/app/models/concerns/spree/receivable_item.rb +78 -0
  100. data/app/models/concerns/spree/refund_methods.rb +19 -0
  101. data/app/models/concerns/spree/sanitizable_rich_text.rb +109 -0
  102. data/app/models/concerns/spree/search_indexable.rb +5 -2
  103. data/app/models/concerns/spree/single_store_resource.rb +3 -1
  104. data/app/models/concerns/spree/store_data_sources.rb +75 -0
  105. data/app/models/concerns/spree/store_preferences.rb +49 -0
  106. data/app/models/concerns/spree/stores/markets.rb +75 -28
  107. data/app/models/concerns/spree/stores/setup.rb +16 -1
  108. data/app/models/concerns/spree/translatable_resource.rb +25 -6
  109. data/app/models/concerns/spree/typed_adjustment_line.rb +55 -0
  110. data/app/models/concerns/spree/user_address.rb +13 -3
  111. data/app/models/concerns/spree/user_management.rb +18 -6
  112. data/app/models/concerns/spree/user_methods.rb +5 -330
  113. data/app/models/concerns/spree/user_payment_source.rb +1 -1
  114. data/app/models/concerns/spree/user_roles.rb +18 -11
  115. data/app/models/concerns/spree/validates_store_uniqueness.rb +53 -0
  116. data/app/models/concerns/spree/vat_price_calculation.rb +58 -6
  117. data/app/models/concerns/spree/webhook_payload_redaction.rb +1 -1
  118. data/app/models/spree/ability.rb +147 -43
  119. data/app/models/spree/address.rb +387 -118
  120. data/app/models/spree/adjusters/base.rb +26 -0
  121. data/app/models/spree/adjusters/largest_remainder.rb +24 -0
  122. data/app/models/spree/adjusters/promotion.rb +253 -0
  123. data/app/models/spree/admin_user.rb +26 -0
  124. data/app/models/spree/allowed_origin.rb +1 -1
  125. data/app/models/spree/api_key.rb +29 -28
  126. data/app/models/spree/asset.rb +14 -211
  127. data/app/models/spree/authentication/strategies/base_strategy.rb +54 -3
  128. data/app/models/spree/authentication/strategies/email_password_strategy.rb +11 -3
  129. data/app/models/spree/authentication/strategies/oidc_strategy.rb +330 -0
  130. data/app/models/spree/authentication/strategy_registry.rb +83 -0
  131. data/app/models/spree/base.rb +119 -13
  132. data/app/models/spree/base_analytics_event_handler.rb +1 -1
  133. data/app/models/spree/calculator/shipping/digital_delivery.rb +5 -2
  134. data/app/models/spree/calculator/shipping/flat_rate.rb +10 -5
  135. data/app/models/spree/calculator/shipping/flexi_rate.rb +6 -0
  136. data/app/models/spree/calculator/shipping/per_item.rb +8 -3
  137. data/app/models/spree/calculator/shipping/price_sack.rb +6 -0
  138. data/app/models/spree/calculator/tiered_flat_rate.rb +5 -1
  139. data/app/models/spree/calculator.rb +10 -0
  140. data/app/models/spree/cart.rb +285 -0
  141. data/app/models/spree/carts/item_warning.rb +28 -0
  142. data/app/models/spree/carts/resolved_item.rb +37 -0
  143. data/app/models/spree/catalog.rb +466 -0
  144. data/app/models/spree/catalog_assignment.rb +41 -0
  145. data/app/models/spree/catalog_order_minimum.rb +26 -0
  146. data/app/models/spree/catalog_price.rb +100 -0
  147. data/app/models/spree/catalog_price_tier.rb +36 -0
  148. data/app/models/spree/catalog_product.rb +23 -0
  149. data/app/models/spree/catalog_quantity_rule.rb +46 -0
  150. data/app/models/spree/catalogs/product_term.rb +43 -0
  151. data/app/models/spree/category.rb +430 -12
  152. data/app/models/spree/channel/gating.rb +5 -6
  153. data/app/models/spree/channel.rb +38 -4
  154. data/app/models/spree/channel_stock_location.rb +8 -0
  155. data/app/models/spree/checkout/default_requirements.rb +105 -22
  156. data/app/models/spree/checkout/registry.rb +64 -12
  157. data/app/models/spree/checkout/requirement.rb +9 -9
  158. data/app/models/spree/checkout/requirements.rb +25 -20
  159. data/app/models/spree/checkout/step.rb +12 -12
  160. data/app/models/spree/claim.rb +68 -0
  161. data/app/models/spree/claim_line_item.rb +46 -0
  162. data/app/models/spree/claim_reason.rb +24 -0
  163. data/app/models/spree/classification.rb +7 -49
  164. data/app/models/spree/collection.rb +269 -0
  165. data/app/models/spree/collection_rule.rb +58 -0
  166. data/app/models/spree/collection_rules/available_on.rb +21 -0
  167. data/app/models/spree/collection_rules/sale.rb +18 -0
  168. data/app/models/spree/collection_rules/tag.rb +21 -0
  169. data/app/models/spree/commission_line.rb +85 -0
  170. data/app/models/spree/commission_rate.rb +370 -0
  171. data/app/models/spree/commission_rate_value.rb +47 -0
  172. data/app/models/spree/commission_rule.rb +77 -0
  173. data/app/models/spree/commission_rule_product.rb +19 -0
  174. data/app/models/spree/commission_rules/category_rule.rb +33 -0
  175. data/app/models/spree/commission_rules/item_total_rule.rb +31 -0
  176. data/app/models/spree/commission_rules/product_rule.rb +55 -0
  177. data/app/models/spree/commission_rules/seller_rule.rb +33 -0
  178. data/app/models/spree/commission_tax.rb +51 -0
  179. data/app/models/spree/commissions/context.rb +71 -0
  180. data/app/models/spree/company.rb +347 -0
  181. data/app/models/spree/company_invitation.rb +81 -0
  182. data/app/models/spree/company_membership.rb +25 -0
  183. data/app/models/spree/consent_record.rb +125 -0
  184. data/app/models/spree/country.rb +128 -52
  185. data/app/models/spree/coupon_code.rb +18 -5
  186. data/app/models/spree/credit_card.rb +10 -9
  187. data/app/models/spree/current.rb +124 -18
  188. data/app/models/spree/custom_field.rb +160 -7
  189. data/app/models/spree/custom_field_definition/search_capabilities.rb +98 -0
  190. data/app/models/spree/custom_field_definition.rb +219 -5
  191. data/app/models/spree/custom_fields/boolean.rb +15 -0
  192. data/app/models/spree/custom_fields/json.rb +38 -0
  193. data/app/models/spree/custom_fields/long_text.rb +11 -0
  194. data/app/models/spree/custom_fields/number.rb +23 -0
  195. data/app/models/spree/custom_fields/rich_text.rb +23 -0
  196. data/app/models/spree/custom_fields/short_text.rb +15 -0
  197. data/app/models/spree/customer.rb +34 -0
  198. data/app/models/spree/customer_group.rb +12 -12
  199. data/app/models/spree/customer_group_user.rb +6 -4
  200. data/app/models/spree/data_feed.rb +1 -1
  201. data/app/models/spree/data_request.rb +98 -0
  202. data/app/models/spree/delivery.rb +169 -0
  203. data/app/models/spree/delivery_method.rb +566 -0
  204. data/app/models/spree/delivery_method_rule.rb +55 -0
  205. data/app/models/spree/delivery_method_rule_product.rb +8 -0
  206. data/app/models/spree/delivery_method_rules/channel_rule.rb +41 -0
  207. data/app/models/spree/delivery_method_rules/company_rule.rb +24 -0
  208. data/app/models/spree/delivery_method_rules/excluded_products_rule.rb +48 -0
  209. data/app/models/spree/delivery_method_rules/item_total_rule.rb +19 -0
  210. data/app/models/spree/delivery_method_rules/volume_rule.rb +35 -0
  211. data/app/models/spree/delivery_method_rules/weight_rule.rb +19 -0
  212. data/app/models/spree/delivery_method_service.rb +24 -0
  213. data/app/models/spree/delivery_method_stock_location.rb +11 -0
  214. data/app/models/spree/delivery_origin_group.rb +73 -0
  215. data/app/models/spree/delivery_origin_group_location.rb +9 -0
  216. data/app/models/spree/delivery_profile.rb +141 -0
  217. data/app/models/spree/delivery_profiles/digital.rb +20 -0
  218. data/app/models/spree/delivery_profiles/shipping.rb +12 -0
  219. data/app/models/spree/delivery_rate.rb +164 -0
  220. data/app/models/spree/delivery_rate_provider/base.rb +120 -0
  221. data/app/models/spree/delivery_rate_provider/estimate.rb +44 -0
  222. data/app/models/spree/delivery_rate_provider/freight.rb +41 -0
  223. data/app/models/spree/delivery_rate_provider/internal.rb +28 -0
  224. data/app/models/spree/delivery_rate_provider/service_catalog.rb +48 -0
  225. data/app/models/spree/delivery_zone.rb +64 -0
  226. data/app/models/spree/delivery_zone_member.rb +98 -0
  227. data/app/models/spree/digital.rb +12 -16
  228. data/app/models/spree/digital_asset.rb +144 -0
  229. data/app/models/spree/digital_asset_provider/base.rb +95 -0
  230. data/app/models/spree/digital_asset_provider/file.rb +20 -0
  231. data/app/models/spree/digital_delivery.rb +28 -0
  232. data/app/models/spree/digital_link.rb +31 -11
  233. data/app/models/spree/discount.rb +51 -0
  234. data/app/models/spree/event.rb +6 -6
  235. data/app/models/spree/exchange.rb +49 -44
  236. data/app/models/spree/exchange_line_item.rb +52 -0
  237. data/app/models/spree/export.rb +138 -23
  238. data/app/models/spree/exports/customers.rb +2 -2
  239. data/app/models/spree/exports/gift_cards.rb +2 -2
  240. data/app/models/spree/exports/newsletter_subscribers.rb +2 -2
  241. data/app/models/spree/exports/orders.rb +35 -5
  242. data/app/models/spree/exports/price_list_prices.rb +107 -0
  243. data/app/models/spree/exports/products.rb +4 -4
  244. data/app/models/spree/exports/purchase_orders.rb +39 -0
  245. data/app/models/spree/exports/report.rb +95 -0
  246. data/app/models/spree/external_reference.rb +77 -0
  247. data/app/models/spree/external_video.rb +117 -0
  248. data/app/models/spree/fee.rb +40 -0
  249. data/app/models/spree/freight_summary/line.rb +257 -0
  250. data/app/models/spree/freight_summary.rb +180 -0
  251. data/app/models/spree/fulfillment/custom_events.rb +96 -0
  252. data/app/models/spree/fulfillment.rb +973 -0
  253. data/app/models/spree/fulfillment_changer.rb +170 -0
  254. data/app/models/spree/fulfillment_item.rb +148 -0
  255. data/app/models/spree/fulfillment_provider/base.rb +130 -0
  256. data/app/models/spree/fulfillment_provider/digital.rb +44 -0
  257. data/app/models/spree/fulfillment_provider/manual.rb +15 -0
  258. data/app/models/spree/fulfillment_provider/pickup.rb +38 -0
  259. data/app/models/spree/fulfillment_provider/pickup_point.rb +24 -0
  260. data/app/models/spree/fulfilment_changer.rb +3 -164
  261. data/app/models/spree/gateway/bogus.rb +5 -4
  262. data/app/models/spree/gateway/custom_payment_source_method.rb +5 -5
  263. data/app/models/spree/gateway.rb +2 -2
  264. data/app/models/spree/gateway_customer.rb +12 -4
  265. data/app/models/spree/gift_card.rb +140 -48
  266. data/app/models/spree/gift_card_batch.rb +2 -2
  267. data/app/models/spree/import.rb +222 -92
  268. data/app/models/spree/import_mapping.rb +1 -1
  269. data/app/models/spree/import_row.rb +35 -31
  270. data/app/models/spree/import_schemas/price_list_prices.rb +13 -0
  271. data/app/models/spree/import_schemas/products.rb +8 -1
  272. data/app/models/spree/import_schemas/purchase_orders.rb +19 -0
  273. data/app/models/spree/imports/price_list_prices.rb +72 -0
  274. data/app/models/spree/imports/purchase_orders.rb +28 -0
  275. data/app/models/spree/integration.rb +128 -5
  276. data/app/models/spree/inventory_provider/base.rb +30 -0
  277. data/app/models/spree/inventory_provider/internal.rb +22 -0
  278. data/app/models/spree/inventory_unit.rb +3 -139
  279. data/app/models/spree/invitation.rb +53 -47
  280. data/app/models/spree/label_purchase.rb +29 -0
  281. data/app/models/spree/line_item.rb +239 -105
  282. data/app/models/spree/market.rb +106 -15
  283. data/app/models/spree/market_country.rb +10 -5
  284. data/app/models/spree/media.rb +497 -0
  285. data/app/models/spree/media_usage_reference.rb +27 -0
  286. data/app/models/spree/metafield.rb +14 -91
  287. data/app/models/spree/metafield_definition.rb +11 -127
  288. data/app/models/spree/metafields.rb +32 -0
  289. data/app/models/spree/newsletter_subscriber.rb +10 -9
  290. data/app/models/spree/number_generators/base.rb +56 -0
  291. data/app/models/spree/number_generators/random.rb +23 -0
  292. data/app/models/spree/number_generators/sequential.rb +88 -0
  293. data/app/models/spree/number_sequence.rb +86 -0
  294. data/app/models/spree/option_type.rb +18 -32
  295. data/app/models/spree/option_type_product_type.rb +8 -0
  296. data/app/models/spree/option_value.rb +19 -13
  297. data/app/models/spree/option_value_variant.rb +0 -1
  298. data/app/models/spree/order.rb +628 -571
  299. data/app/models/spree/order_cancellation_reason.rb +25 -0
  300. data/app/models/spree/order_group.rb +244 -0
  301. data/app/models/spree/order_inventory.rb +42 -33
  302. data/app/models/spree/order_merger.rb +11 -6
  303. data/app/models/spree/order_promotion.rb +32 -6
  304. data/app/models/spree/order_routing/has_strategy_preference.rb +1 -2
  305. data/app/models/spree/order_routing/rules/minimize_splits.rb +6 -6
  306. data/app/models/spree/order_routing/strategy/rules.rb +12 -8
  307. data/app/models/spree/order_routing_rule.rb +5 -15
  308. data/app/models/spree/order_updater.rb +131 -163
  309. data/app/models/spree/package_type.rb +191 -0
  310. data/app/models/spree/payment/custom_events.rb +52 -5
  311. data/app/models/spree/payment/gateway_options.rb +20 -5
  312. data/app/models/spree/payment/processing.rb +95 -116
  313. data/app/models/spree/payment.rb +299 -123
  314. data/app/models/spree/payment_method/check.rb +4 -3
  315. data/app/models/spree/payment_method/session_instrumentation.rb +34 -0
  316. data/app/models/spree/payment_method/store_credit.rb +4 -2
  317. data/app/models/spree/payment_method.rb +106 -9
  318. data/app/models/spree/payment_session.rb +150 -53
  319. data/app/models/spree/payment_setup_session.rb +4 -37
  320. data/app/models/spree/payment_source.rb +5 -4
  321. data/app/models/spree/payment_split.rb +90 -0
  322. data/app/models/spree/payout_provider/base.rb +218 -0
  323. data/app/models/spree/payout_provider/system.rb +36 -0
  324. data/app/models/spree/permission_sets.rb +17 -0
  325. data/app/models/spree/pickup_point_option.rb +28 -0
  326. data/app/models/spree/pickup_point_provider/base.rb +24 -0
  327. data/app/models/spree/policy.rb +12 -11
  328. data/app/models/spree/price.rb +91 -5
  329. data/app/models/spree/price_adjustment_tier.rb +63 -0
  330. data/app/models/spree/price_history.rb +1 -1
  331. data/app/models/spree/price_list.rb +266 -142
  332. data/app/models/spree/price_rule.rb +51 -7
  333. data/app/models/spree/price_rules/channel_rule.rb +3 -0
  334. data/app/models/spree/price_rules/customer_group_rule.rb +10 -1
  335. data/app/models/spree/price_rules/market_rule.rb +6 -0
  336. data/app/models/spree/price_rules/user_rule.rb +12 -3
  337. data/app/models/spree/price_rules/volume_rule.rb +6 -0
  338. data/app/models/spree/price_rules/zone_rule.rb +5 -13
  339. data/app/models/spree/pricing_provider/base.rb +51 -0
  340. data/app/models/spree/pricing_provider/internal.rb +255 -0
  341. data/app/models/spree/pricing_provider.rb +28 -0
  342. data/app/models/spree/product.rb +539 -537
  343. data/app/models/spree/product_category.rb +65 -0
  344. data/app/models/spree/product_collection.rb +20 -0
  345. data/app/models/spree/product_option_type.rb +0 -1
  346. data/app/models/spree/product_promotion_rule.rb +0 -1
  347. data/app/models/spree/product_publication.rb +0 -1
  348. data/app/models/spree/product_submission.rb +72 -0
  349. data/app/models/spree/product_type.rb +116 -0
  350. data/app/models/spree/product_type_category.rb +8 -0
  351. data/app/models/spree/product_type_custom_field_definition.rb +39 -0
  352. data/app/models/spree/promotion/actions/create_adjustment.rb +9 -7
  353. data/app/models/spree/promotion/actions/create_item_adjustments.rb +11 -16
  354. data/app/models/spree/promotion/actions/create_line_items.rb +19 -8
  355. data/app/models/spree/promotion/actions/free_shipping.rb +12 -18
  356. data/app/models/spree/promotion/rules/category.rb +181 -0
  357. data/app/models/spree/promotion/rules/channel.rb +3 -3
  358. data/app/models/spree/promotion/rules/country.rb +43 -17
  359. data/app/models/spree/promotion/rules/currency.rb +2 -2
  360. data/app/models/spree/promotion/rules/customer_group.rb +13 -5
  361. data/app/models/spree/promotion/rules/first_order.rb +3 -3
  362. data/app/models/spree/promotion/rules/item_total.rb +2 -2
  363. data/app/models/spree/promotion/rules/market.rb +3 -3
  364. data/app/models/spree/promotion/rules/one_use_per_user.rb +4 -4
  365. data/app/models/spree/promotion/rules/option_value.rb +34 -11
  366. data/app/models/spree/promotion/rules/product.rb +3 -5
  367. data/app/models/spree/promotion/rules/taxon.rb +10 -131
  368. data/app/models/spree/promotion/rules/user.rb +14 -22
  369. data/app/models/spree/promotion/rules/user_logged_in.rb +3 -3
  370. data/app/models/spree/promotion.rb +59 -21
  371. data/app/models/spree/promotion_action.rb +52 -11
  372. data/app/models/spree/promotion_action_line_item.rb +1 -1
  373. data/app/models/spree/promotion_handler/coupon.rb +44 -16
  374. data/app/models/spree/promotion_handler/free_shipping.rb +1 -1
  375. data/app/models/spree/promotion_handler/promotion_duplicator.rb +1 -1
  376. data/app/models/spree/promotion_rule.rb +2 -9
  377. data/app/models/spree/promotion_rule_category.rb +22 -0
  378. data/app/models/spree/promotion_rule_taxon.rb +7 -6
  379. data/app/models/spree/promotion_rule_user.rb +2 -2
  380. data/app/models/spree/prototype.rb +3 -13
  381. data/app/models/spree/purchase_order.rb +95 -0
  382. data/app/models/spree/purchase_order_item.rb +40 -0
  383. data/app/models/spree/quantity_rule.rb +114 -0
  384. data/app/models/spree/refresh_token.rb +20 -2
  385. data/app/models/spree/refund.rb +94 -41
  386. data/app/models/spree/refund_reason.rb +32 -11
  387. data/app/models/spree/return.rb +150 -0
  388. data/app/models/spree/return_authorization_reason.rb +11 -6
  389. data/app/models/spree/return_line_item.rb +42 -0
  390. data/app/models/spree/return_reason.rb +26 -0
  391. data/app/models/spree/rich_text_sanitizer.rb +99 -0
  392. data/app/models/spree/role.rb +179 -4
  393. data/app/models/spree/role_user.rb +5 -21
  394. data/app/models/spree/saved_report.rb +56 -0
  395. data/app/models/spree/search_provider/base.rb +11 -0
  396. data/app/models/spree/search_provider/database.rb +106 -9
  397. data/app/models/spree/search_provider.rb +29 -0
  398. data/app/models/spree/seller.rb +475 -0
  399. data/app/models/spree/seller_balance.rb +86 -0
  400. data/app/models/spree/seller_payout.rb +116 -0
  401. data/app/models/spree/seller_requirement.rb +274 -0
  402. data/app/models/spree/seller_requirement_custom_field.rb +38 -0
  403. data/app/models/spree/seller_requirement_status.rb +144 -0
  404. data/app/models/spree/seller_requirement_submission.rb +91 -0
  405. data/app/models/spree/seller_requirements/accept_terms.rb +104 -0
  406. data/app/models/spree/seller_requirements/attestation.rb +22 -0
  407. data/app/models/spree/seller_requirements/billing_address.rb +13 -0
  408. data/app/models/spree/seller_requirements/complete_profile.rb +26 -0
  409. data/app/models/spree/seller_requirements/delivery_method.rb +20 -0
  410. data/app/models/spree/seller_requirements/document.rb +62 -0
  411. data/app/models/spree/seller_requirements/minimum_products.rb +20 -0
  412. data/app/models/spree/seller_requirements/operator_review.rb +23 -0
  413. data/app/models/spree/seller_requirements/package_type.rb +40 -0
  414. data/app/models/spree/seller_requirements/payout_account.rb +55 -0
  415. data/app/models/spree/seller_requirements/policy.rb +43 -0
  416. data/app/models/spree/seller_requirements/required_custom_fields.rb +56 -0
  417. data/app/models/spree/seller_requirements/returns_address.rb +19 -0
  418. data/app/models/spree/seller_transfer.rb +166 -0
  419. data/app/models/spree/setup_tasks.rb +3 -6
  420. data/app/models/spree/shipment.rb +3 -560
  421. data/app/models/spree/shipping_calculator.rb +7 -0
  422. data/app/models/spree/shipping_document.rb +32 -0
  423. data/app/models/spree/shipping_label.rb +146 -0
  424. data/app/models/spree/shipping_method.rb +3 -146
  425. data/app/models/spree/shipping_rate.rb +3 -97
  426. data/app/models/spree/state.rb +57 -23
  427. data/app/models/spree/stock/availability_validator.rb +1 -1
  428. data/app/models/spree/stock/content_item.rb +1 -0
  429. data/app/models/spree/stock/coordinator.rb +69 -9
  430. data/app/models/spree/stock/estimator.rb +198 -33
  431. data/app/models/spree/stock/inventory_unit_builder.rb +6 -1
  432. data/app/models/spree/stock/package.rb +145 -14
  433. data/app/models/spree/stock/packer.rb +20 -3
  434. data/app/models/spree/stock/prioritizer.rb +27 -4
  435. data/app/models/spree/stock/quantifier.rb +63 -38
  436. data/app/models/spree/stock/splitter/base.rb +3 -1
  437. data/app/models/spree/stock/splitter/delivery_profile.rb +41 -0
  438. data/app/models/spree/stock_item.rb +3 -147
  439. data/app/models/spree/stock_level/custom_events.rb +48 -0
  440. data/app/models/spree/stock_level.rb +305 -0
  441. data/app/models/spree/stock_location.rb +165 -87
  442. data/app/models/spree/stock_movement/custom_events.rb +1 -1
  443. data/app/models/spree/stock_movement.rb +166 -22
  444. data/app/models/spree/stock_receipt.rb +56 -0
  445. data/app/models/spree/stock_receipt_item.rb +35 -0
  446. data/app/models/spree/stock_reservation.rb +114 -11
  447. data/app/models/spree/stock_transfer.rb +84 -59
  448. data/app/models/spree/stock_transfer_item.rb +25 -0
  449. data/app/models/spree/store.rb +427 -107
  450. data/app/models/spree/store_credit.rb +88 -21
  451. data/app/models/spree/store_credit_category.rb +21 -28
  452. data/app/models/spree/store_credit_event.rb +1 -1
  453. data/app/models/spree/store_credit_type.rb +10 -2
  454. data/app/models/spree/store_payment_method.rb +0 -1
  455. data/app/models/spree/store_product.rb +2 -2
  456. data/app/models/spree/store_promotion.rb +0 -1
  457. data/app/models/spree/storefront/access_denied.rb +22 -0
  458. data/app/models/spree/storefront/access_policy.rb +180 -0
  459. data/app/models/spree/subscriber.rb +6 -6
  460. data/app/models/spree/supplier.rb +76 -0
  461. data/app/models/spree/tax_category.rb +15 -5
  462. data/app/models/spree/tax_exemption/item_override.rb +22 -0
  463. data/app/models/spree/tax_exemption.rb +81 -0
  464. data/app/models/spree/tax_exemption_certificate.rb +95 -0
  465. data/app/models/spree/tax_identifier.rb +209 -0
  466. data/app/models/spree/tax_identifiers/validation_result.rb +41 -0
  467. data/app/models/spree/tax_identifiers/validator/base.rb +53 -0
  468. data/app/models/spree/tax_identifiers/validator/eu_vat.rb +76 -0
  469. data/app/models/spree/tax_line.rb +79 -0
  470. data/app/models/spree/tax_provider/base.rb +150 -0
  471. data/app/models/spree/tax_provider/internal.rb +227 -0
  472. data/app/models/spree/tax_rate.rb +63 -100
  473. data/app/models/spree/taxon.rb +13 -514
  474. data/app/models/spree/taxon_rule.rb +8 -9
  475. data/app/models/spree/taxonomy.rb +8 -42
  476. data/app/models/spree/user_identity.rb +2 -2
  477. data/app/models/spree/variant.rb +629 -150
  478. data/app/models/spree/variant_media.rb +6 -7
  479. data/app/models/spree/webhook_endpoint.rb +2 -1
  480. data/app/models/spree/wished_item.rb +3 -31
  481. data/app/models/spree/wishlist.rb +17 -10
  482. data/app/models/spree/wishlist_item/custom_events.rb +42 -0
  483. data/app/models/spree/wishlist_item.rb +37 -0
  484. data/app/models/spree/zone.rb +24 -182
  485. data/app/models/spree/zone_member.rb +5 -8
  486. data/app/presenters/spree/csv/custom_fields_helper.rb +25 -0
  487. data/app/presenters/spree/csv/customer_presenter.rb +7 -6
  488. data/app/presenters/spree/csv/gift_card_presenter.rb +9 -8
  489. data/app/presenters/spree/csv/newsletter_subscriber_presenter.rb +7 -6
  490. data/app/presenters/spree/csv/order_line_item_presenter.rb +53 -21
  491. data/app/presenters/spree/csv/price_list_price_presenter.rb +41 -0
  492. data/app/presenters/spree/csv/product_variant_presenter.rb +22 -21
  493. data/app/presenters/spree/csv/purchase_order_item_presenter.rb +50 -0
  494. data/app/presenters/spree/data_feeds/google_presenter.rb +24 -10
  495. data/app/presenters/spree/filters/options_presenter.rb +2 -2
  496. data/app/services/spree/addresses/create.rb +70 -27
  497. data/app/services/spree/addresses/helper.rb +17 -54
  498. data/app/services/spree/addresses/update.rb +53 -31
  499. data/app/services/spree/carts/associate.rb +46 -0
  500. data/app/services/spree/carts/check_availability.rb +72 -0
  501. data/app/services/spree/carts/create.rb +7 -1
  502. data/app/services/spree/carts/destroy.rb +71 -0
  503. data/app/services/spree/carts/empty.rb +42 -0
  504. data/app/services/spree/carts/partition_by_seller.rb +71 -0
  505. data/app/services/spree/carts/price_items.rb +119 -0
  506. data/app/services/spree/carts/remove_item.rb +42 -0
  507. data/app/services/spree/carts/remove_line_item.rb +42 -0
  508. data/app/services/spree/carts/remove_out_of_stock_items.rb +104 -0
  509. data/app/services/spree/carts/set_quantity.rb +39 -0
  510. data/app/services/spree/carts/split_by_seller.rb +475 -0
  511. data/app/services/spree/carts/update.rb +157 -51
  512. data/app/services/spree/catalogs/resolve_prices.rb +192 -0
  513. data/app/services/spree/catalogs/resolve_quantity_rules.rb +115 -0
  514. data/app/services/spree/catalogs/set_assignments.rb +59 -0
  515. data/app/services/spree/catalogs/set_order_minimums.rb +44 -0
  516. data/app/services/spree/catalogs/set_product_terms.rb +83 -0
  517. data/app/services/spree/categories/add_products.rb +51 -0
  518. data/app/services/spree/categories/remove_products.rb +72 -0
  519. data/app/services/spree/checkout/add_store_credit.rb +11 -79
  520. data/app/services/spree/checkout/advance.rb +38 -38
  521. data/app/services/spree/checkout/remove_store_credit.rb +7 -12
  522. data/app/services/spree/collections/add_products.rb +42 -0
  523. data/app/services/spree/collections/regenerate_products.rb +67 -0
  524. data/app/services/spree/collections/remove_products.rb +65 -0
  525. data/app/services/spree/commissions/calculate_line.rb +132 -0
  526. data/app/services/spree/commissions/commission_order.rb +185 -0
  527. data/app/services/spree/commissions/resolve_rate.rb +114 -0
  528. data/app/services/spree/commissions/resolve_tax_rate.rb +78 -0
  529. data/app/services/spree/companies/add_member.rb +38 -0
  530. data/app/services/spree/company_invitations/accept.rb +60 -0
  531. data/app/services/spree/coupon_codes/coupon_codes_handler.rb +14 -2
  532. data/app/services/spree/customers/data_export.rb +372 -0
  533. data/app/services/spree/data_requests/create.rb +77 -0
  534. data/app/services/spree/deliveries/create.rb +56 -0
  535. data/app/services/spree/deliveries/destroy.rb +39 -0
  536. data/app/services/spree/deliveries/upsert_primary.rb +43 -0
  537. data/app/services/spree/fulfillments/recalculate_delivery.rb +62 -0
  538. data/app/services/spree/fulfillments/stand_down_provider.rb +47 -0
  539. data/app/services/spree/imports/row_processors/customer.rb +8 -8
  540. data/app/services/spree/imports/row_processors/price_list_price.rb +164 -0
  541. data/app/services/spree/imports/row_processors/product_variant.rb +199 -58
  542. data/app/services/spree/imports/row_processors/purchase_order.rb +157 -0
  543. data/app/services/spree/media/destroy.rb +88 -0
  544. data/app/services/spree/media/usage.rb +175 -0
  545. data/app/services/spree/newsletter/link_user.rb +4 -3
  546. data/app/services/spree/newsletter/subscribe.rb +3 -3
  547. data/app/services/spree/newsletter/verify.rb +6 -2
  548. data/app/services/spree/order_groups/allocate_payments.rb +101 -0
  549. data/app/services/spree/orders/approve.rb +9 -23
  550. data/app/services/spree/orders/build_fulfillments.rb +29 -0
  551. data/app/services/spree/orders/create.rb +38 -17
  552. data/app/services/spree/orders/create_from_cart.rb +88 -0
  553. data/app/services/spree/orders/create_user_account.rb +14 -67
  554. data/app/services/spree/orders/discounts/create.rb +84 -0
  555. data/app/services/spree/orders/discounts/destroy.rb +25 -0
  556. data/app/services/spree/orders/discounts/update.rb +29 -0
  557. data/app/services/spree/orders/fees/create.rb +28 -0
  558. data/app/services/spree/orders/fees/destroy.rb +22 -0
  559. data/app/services/spree/orders/fees/update.rb +25 -0
  560. data/app/services/spree/orders/remove_item.rb +13 -0
  561. data/app/services/spree/orders/remove_line_item.rb +13 -0
  562. data/app/services/spree/orders/update.rb +56 -11
  563. data/app/services/spree/orders/update_item.rb +105 -0
  564. data/app/services/spree/orders/update_statuses.rb +109 -0
  565. data/app/services/spree/payments/create.rb +31 -5
  566. data/app/services/spree/prices/bulk_upsert.rb +156 -30
  567. data/app/services/spree/product_submissions/close.rb +46 -0
  568. data/app/services/spree/product_types/apply_to_products.rb +167 -0
  569. data/app/services/spree/products/auto_match_collections.rb +75 -0
  570. data/app/services/spree/products/auto_match_taxons.rb +7 -54
  571. data/app/services/spree/products/duplicator.rb +16 -22
  572. data/app/services/spree/products/for_context.rb +54 -0
  573. data/app/services/spree/products/select_buy_box.rb +98 -0
  574. data/app/services/spree/purchases/amount_due_at_checkout.rb +33 -0
  575. data/app/services/spree/returns/eligibility_validator.rb +53 -0
  576. data/app/services/spree/sample_data/helper.rb +55 -0
  577. data/app/services/spree/sample_data/import_builder.rb +36 -0
  578. data/app/services/spree/sample_data/import_runner.rb +25 -41
  579. data/app/services/spree/sample_data/loader.rb +21 -63
  580. data/app/services/spree/search_provider/custom_field_schema.rb +153 -0
  581. data/app/services/spree/seeds/admin_user.rb +72 -12
  582. data/app/services/spree/seeds/all.rb +24 -13
  583. data/app/services/spree/seeds/allowed_origins.rb +3 -4
  584. data/app/services/spree/seeds/commission_rates.rb +53 -0
  585. data/app/services/spree/seeds/digital_delivery.rb +18 -8
  586. data/app/services/spree/seeds/product_types.rb +23 -0
  587. data/app/services/spree/seeds/returns_environment.rb +44 -16
  588. data/app/services/spree/seeds/roles.rb +6 -1
  589. data/app/services/spree/seeds/saved_reports.rb +107 -0
  590. data/app/services/spree/seeds/seller_requirements.rb +19 -0
  591. data/app/services/spree/seeds/stores.rb +1 -1
  592. data/app/services/spree/seeds/tax_categories.rb +9 -2
  593. data/app/services/spree/sellers/requirements.rb +129 -0
  594. data/app/services/spree/shipments/update.rb +4 -33
  595. data/app/services/spree/shipping_labels/confirm_refund.rb +47 -0
  596. data/app/services/spree/shipping_labels/store_file.rb +68 -0
  597. data/app/services/spree/stock_levels/bulk_upsert.rb +67 -0
  598. data/app/services/spree/stock_levels/correct.rb +69 -0
  599. data/app/services/spree/stock_levels/recount.rb +103 -0
  600. data/app/services/spree/stock_locations/stock_items/create.rb +8 -13
  601. data/app/services/spree/stock_locations/stock_levels/create.rb +25 -0
  602. data/app/services/spree/stock_reservations/extend.rb +9 -5
  603. data/app/services/spree/stock_reservations/release.rb +4 -3
  604. data/app/services/spree/stock_reservations/reserve.rb +43 -32
  605. data/app/services/spree/store_credits/apply.rb +93 -0
  606. data/app/services/spree/store_credits/remove.rb +17 -0
  607. data/app/services/spree/stores/provision_defaults.rb +269 -0
  608. data/app/services/spree/tax/resolve_exemptions.rb +44 -0
  609. data/app/services/spree/tax/unusable_exemption_error.rb +8 -0
  610. data/app/services/spree/tax_identifiers/validate.rb +77 -0
  611. data/app/services/spree/taxons/add_products.rb +6 -39
  612. data/app/services/spree/taxons/regenerate_products.rb +8 -53
  613. data/app/services/spree/taxons/remove_products.rb +6 -60
  614. data/app/services/spree/variants/remove_line_items.rb +6 -1
  615. data/app/subscribers/spree/event_log_subscriber.rb +1 -1
  616. data/app/subscribers/spree/order_commission_subscriber.rb +23 -0
  617. data/app/subscribers/spree/order_placed_subscriber.rb +58 -0
  618. data/app/subscribers/spree/order_status_subscriber.rb +82 -0
  619. data/app/subscribers/spree/payment_split_subscriber.rb +71 -0
  620. data/app/subscribers/spree/product_metrics_subscriber.rb +2 -2
  621. data/app/subscribers/spree/seller_onboarding_subscriber.rb +32 -0
  622. data/app/subscribers/spree/seller_transfer_reversal_subscriber.rb +26 -0
  623. data/app/subscribers/spree/seller_transfer_subscriber.rb +26 -0
  624. data/app/subscribers/spree/seller_user_email_subscriber.rb +27 -0
  625. data/app/subscribers/spree/tax_identifier_validation_subscriber.rb +26 -0
  626. data/app/validators/spree/addresses/phone_validator.rb +2 -2
  627. data/app/validators/spree/attachment_content_type_validator.rb +183 -0
  628. data/app/validators/spree/password_length_validator.rb +23 -0
  629. data/app/views/spree/seller_user_mailer/password_reset_email.html.erb +8 -0
  630. data/app/views/spree/shared/_purchased_items_styles.html.erb +7 -0
  631. data/app/workflows/spree/carts/add_item.rb +234 -0
  632. data/app/workflows/spree/carts/complete.rb +516 -0
  633. data/app/workflows/spree/carts/merge.rb +90 -0
  634. data/app/workflows/spree/carts/recalculate.rb +54 -0
  635. data/app/workflows/spree/carts/recalculate_totals.rb +233 -0
  636. data/app/workflows/spree/carts/upsert_items.rb +368 -0
  637. data/app/workflows/spree/catalogs/activate.rb +51 -0
  638. data/app/workflows/spree/catalogs/create.rb +83 -0
  639. data/app/workflows/spree/catalogs/deactivate.rb +35 -0
  640. data/app/workflows/spree/catalogs/set_price_list.rb +130 -0
  641. data/app/workflows/spree/catalogs/update.rb +85 -0
  642. data/app/workflows/spree/claims/approve.rb +38 -0
  643. data/app/workflows/spree/claims/cancel.rb +37 -0
  644. data/app/workflows/spree/claims/create.rb +82 -0
  645. data/app/workflows/spree/claims/deny.rb +35 -0
  646. data/app/workflows/spree/claims/resolve.rb +166 -0
  647. data/app/workflows/spree/customers/anonymize.rb +502 -0
  648. data/app/workflows/spree/customers/create.rb +247 -0
  649. data/app/workflows/spree/data_requests/fulfill.rb +165 -0
  650. data/app/workflows/spree/deliveries/update_tracking.rb +96 -0
  651. data/app/workflows/spree/exchanges/approve.rb +38 -0
  652. data/app/workflows/spree/exchanges/cancel.rb +40 -0
  653. data/app/workflows/spree/exchanges/create.rb +90 -0
  654. data/app/workflows/spree/exchanges/fulfill.rb +146 -0
  655. data/app/workflows/spree/exchanges/receive.rb +86 -0
  656. data/app/workflows/spree/fulfillments/cancel.rb +87 -0
  657. data/app/workflows/spree/fulfillments/create.rb +346 -0
  658. data/app/workflows/spree/fulfillments/fulfill.rb +354 -0
  659. data/app/workflows/spree/fulfillments/mark_delivered.rb +67 -0
  660. data/app/workflows/spree/fulfillments/purchase_label.rb +40 -0
  661. data/app/workflows/spree/fulfillments/update.rb +123 -0
  662. data/app/workflows/spree/gift_cards/apply.rb +235 -0
  663. data/app/workflows/spree/gift_cards/cancel.rb +43 -0
  664. data/app/workflows/spree/gift_cards/redeem.rb +59 -0
  665. data/app/workflows/spree/gift_cards/remove.rb +78 -0
  666. data/app/workflows/spree/imports/complete.rb +45 -0
  667. data/app/workflows/spree/imports/complete_mapping.rb +50 -0
  668. data/app/workflows/spree/imports/retry_failed_rows.rb +46 -0
  669. data/app/workflows/spree/imports/start_mapping.rb +48 -0
  670. data/app/workflows/spree/imports/start_processing.rb +36 -0
  671. data/app/workflows/spree/invitations/accept.rb +50 -0
  672. data/app/workflows/spree/orders/add_item.rb +12 -0
  673. data/app/workflows/spree/orders/cancel.rb +260 -0
  674. data/app/workflows/spree/orders/complete.rb +169 -0
  675. data/app/workflows/spree/orders/recalculate.rb +11 -0
  676. data/app/workflows/spree/orders/recalculate_totals.rb +12 -0
  677. data/app/workflows/spree/orders/upsert_items.rb +28 -0
  678. data/app/workflows/spree/payment_sessions/complete.rb +44 -0
  679. data/app/workflows/spree/payments/capture.rb +109 -0
  680. data/app/workflows/spree/payments/handle_webhook.rb +100 -0
  681. data/app/workflows/spree/payments/process.rb +73 -0
  682. data/app/workflows/spree/payments/void.rb +72 -0
  683. data/app/workflows/spree/price_lists/activate.rb +49 -0
  684. data/app/workflows/spree/price_lists/create.rb +57 -0
  685. data/app/workflows/spree/price_lists/deactivate.rb +30 -0
  686. data/app/workflows/spree/price_lists/update.rb +117 -0
  687. data/app/workflows/spree/products/activate.rb +33 -0
  688. data/app/workflows/spree/products/approve.rb +52 -0
  689. data/app/workflows/spree/products/archive.rb +44 -0
  690. data/app/workflows/spree/products/create.rb +82 -0
  691. data/app/workflows/spree/products/destroy.rb +35 -0
  692. data/app/workflows/spree/products/draft.rb +49 -0
  693. data/app/workflows/spree/products/nested_attributes.rb +242 -0
  694. data/app/workflows/spree/products/propose.rb +62 -0
  695. data/app/workflows/spree/products/reject.rb +54 -0
  696. data/app/workflows/spree/products/update.rb +83 -0
  697. data/app/workflows/spree/purchase_orders/cancel.rb +57 -0
  698. data/app/workflows/spree/purchase_orders/close.rb +57 -0
  699. data/app/workflows/spree/purchase_orders/create.rb +68 -0
  700. data/app/workflows/spree/purchase_orders/mark_draft.rb +49 -0
  701. data/app/workflows/spree/purchase_orders/mark_ordered.rb +50 -0
  702. data/app/workflows/spree/purchase_orders/receive.rb +65 -0
  703. data/app/workflows/spree/purchase_orders/update.rb +56 -0
  704. data/app/workflows/spree/receivables/incoming_counter.rb +50 -0
  705. data/app/workflows/spree/refunds/create.rb +136 -0
  706. data/app/workflows/spree/refunds/order_payments.rb +129 -0
  707. data/app/workflows/spree/returns/approve.rb +47 -0
  708. data/app/workflows/spree/returns/cancel.rb +52 -0
  709. data/app/workflows/spree/returns/create.rb +135 -0
  710. data/app/workflows/spree/returns/purchase_label.rb +32 -0
  711. data/app/workflows/spree/returns/receive.rb +94 -0
  712. data/app/workflows/spree/returns/refund.rb +134 -0
  713. data/app/workflows/spree/seller_payouts/complete.rb +80 -0
  714. data/app/workflows/spree/seller_payouts/sweep.rb +192 -0
  715. data/app/workflows/spree/seller_requirement_submissions/accept.rb +40 -0
  716. data/app/workflows/spree/seller_requirement_submissions/create.rb +104 -0
  717. data/app/workflows/spree/seller_requirement_submissions/reject.rb +45 -0
  718. data/app/workflows/spree/seller_requirement_submissions/reviewable.rb +19 -0
  719. data/app/workflows/spree/seller_requirement_submissions/waive.rb +64 -0
  720. data/app/workflows/spree/seller_transfers/create.rb +131 -0
  721. data/app/workflows/spree/seller_transfers/reverse.rb +241 -0
  722. data/app/workflows/spree/sellers/approve.rb +88 -0
  723. data/app/workflows/spree/sellers/create.rb +72 -0
  724. data/app/workflows/spree/sellers/invite.rb +61 -0
  725. data/app/workflows/spree/sellers/reject.rb +52 -0
  726. data/app/workflows/spree/sellers/reopen_onboarding.rb +55 -0
  727. data/app/workflows/spree/sellers/start_onboarding.rb +46 -0
  728. data/app/workflows/spree/sellers/submit_for_review.rb +76 -0
  729. data/app/workflows/spree/sellers/suspend.rb +55 -0
  730. data/app/workflows/spree/shipping_labels/purchase.rb +154 -0
  731. data/app/workflows/spree/shipping_labels/record.rb +102 -0
  732. data/app/workflows/spree/shipping_labels/refund.rb +77 -0
  733. data/app/workflows/spree/stock_receipts/recording.rb +165 -0
  734. data/app/workflows/spree/stock_transfers/cancel.rb +103 -0
  735. data/app/workflows/spree/stock_transfers/close.rb +56 -0
  736. data/app/workflows/spree/stock_transfers/create.rb +63 -0
  737. data/app/workflows/spree/stock_transfers/mark_draft.rb +41 -0
  738. data/app/workflows/spree/stock_transfers/mark_in_transit.rb +91 -0
  739. data/app/workflows/spree/stock_transfers/mark_ready.rb +36 -0
  740. data/app/workflows/spree/stock_transfers/receive.rb +62 -0
  741. data/app/workflows/spree/stock_transfers/update.rb +55 -0
  742. data/app/workflows/spree/tax_exemption_certificates/verify.rb +48 -0
  743. data/app/workflows/spree/variants/create.rb +66 -0
  744. data/app/workflows/spree/variants/nested_attributes.rb +90 -0
  745. data/app/workflows/spree/variants/update.rb +55 -0
  746. data/config/brakeman.ignore +79 -79
  747. data/config/i18n-tasks.yml +4 -3
  748. data/config/initializers/mobility.rb +0 -4
  749. data/config/initializers/spree_store_setup_tasks.rb +4 -0
  750. data/config/locales/en.yml +955 -187
  751. data/config/routes.rb +11 -4
  752. data/db/migrate/20220201103922_add_first_name_and_last_name_to_spree_users.rb +7 -5
  753. data/db/migrate/20221215151408_add_selected_locale_to_spree_users.rb +6 -4
  754. data/db/migrate/20250127151258_add_phone_to_spree_users.rb +3 -1
  755. data/db/migrate/20250207084000_add_accepts_email_marketing_field_to_spree_users_table.rb +4 -2
  756. data/db/migrate/20250410061306_create_spree_invitations.rb +1 -1
  757. data/db/migrate/20250605131334_add_missing_fields_to_users.rb +24 -20
  758. data/db/migrate/20260315100000_add_product_media_support.rb +4 -1
  759. data/db/migrate/20260613000001_add_store_id_to_spree_role_users.rb +6 -3
  760. data/db/migrate/20260630000001_add_default_variant_id_to_spree_products.rb +9 -0
  761. data/db/migrate/20260708160054_create_spree_collections.rb +69 -0
  762. data/db/migrate/20260710000001_rename_taxons_to_categories.rb +28 -0
  763. data/db/migrate/20260722000001_add_searchable_sortable_to_spree_metafield_definitions.rb +6 -0
  764. data/db/migrate/20260727000001_add_unique_country_abbr_index_to_spree_states.rb +53 -0
  765. data/db/migrate/20260728000000_create_spree_customers_and_admin_users.rb +59 -0
  766. data/db/migrate/20260728000001_rename_prototypes_to_product_types.rb +59 -0
  767. data/db/migrate/20260728000002_create_spree_delivery_zones.rb +29 -0
  768. data/db/migrate/20260728000003_create_spree_carts.rb +58 -0
  769. data/db/migrate/20260728000003_rename_user_id_to_customer_id.rb +30 -0
  770. data/db/migrate/20260728000004_rename_shipping_to_fulfillment_and_delivery.rb +83 -0
  771. data/db/migrate/20260728000005_add_fulfillment_type_columns.rb +33 -0
  772. data/db/migrate/20260729000001_create_typed_adjustment_tables.rb +87 -0
  773. data/db/migrate/20260729100001_add_checkout_columns_to_spree_carts.rb +15 -0
  774. data/db/migrate/20260729100002_relax_owner_null_constraints.rb +7 -0
  775. data/db/migrate/20260729120001_rename_order_totals_to_standard_names.rb +7 -0
  776. data/db/migrate/20260729130001_create_spree_delivery_method_rules.rb +19 -0
  777. data/db/migrate/20260729140001_add_coupon_code_to_spree_orders.rb +6 -0
  778. data/db/migrate/20260729150001_add_store_to_delivery_methods_and_stock_locations.rb +6 -0
  779. data/db/migrate/20260802190001_create_spree_returns.rb +49 -0
  780. data/db/migrate/20260802210001_create_spree_exchanges_and_claims.rb +80 -0
  781. data/db/migrate/20260805100001_rename_return_authorization_reasons_to_return_reasons.rb +109 -0
  782. data/db/migrate/20260806000001_add_store_id_to_spree_tax_rates_and_categories.rb +16 -0
  783. data/db/migrate/20260806000001_create_spree_delivery_method_rule_products.rb +22 -0
  784. data/db/migrate/20260806000002_add_treatment_columns_to_spree_tax_lines.rb +19 -0
  785. data/db/migrate/20260806000003_create_spree_tax_identifiers.rb +41 -0
  786. data/db/migrate/20260806000004_add_tax_provider_to_spree_markets.rb +9 -0
  787. data/db/migrate/20260806000005_add_country_and_state_to_spree_tax_rates.rb +20 -0
  788. data/db/migrate/20260806100001_create_spree_product_type_custom_field_definitions.rb +17 -0
  789. data/db/migrate/20260806120001_add_rate_provider_to_delivery_methods.rb +7 -0
  790. data/db/migrate/20260806120002_add_carrier_columns_to_delivery_rates.rb +13 -0
  791. data/db/migrate/20260806130001_add_unique_store_type_index_to_spree_integrations.rb +9 -0
  792. data/db/migrate/20260807000001_add_permissions_to_spree_roles.rb +14 -0
  793. data/db/migrate/20260807000001_create_spree_companies.rb +66 -0
  794. data/db/migrate/20260807000003_create_spree_tax_exemption_certificates.rb +31 -0
  795. data/db/migrate/20260807120001_remove_claim_type_from_spree_claims.rb +5 -0
  796. data/db/migrate/20260807130001_add_country_state_codes_to_consumers.rb +28 -0
  797. data/db/migrate/20260807150001_reindex_spree_categories_by_store.rb +42 -0
  798. data/db/migrate/20260809000001_rename_metafields_to_custom_fields.rb +126 -0
  799. data/db/migrate/20260809100001_support_multi_rate_delivery_methods.rb +35 -0
  800. data/db/migrate/20260809120001_consolidate_metadata_columns.rb +120 -0
  801. data/db/migrate/20260809150001_promote_shipping_categories_to_delivery_profiles.rb +153 -0
  802. data/db/migrate/20260809170001_create_spree_channel_stock_locations.rb +17 -0
  803. data/db/migrate/20260809200001_add_rich_text_columns_for_action_text_removal.rb +11 -0
  804. data/db/migrate/20260810000001_rename_digitals_to_digital_assets.rb +38 -0
  805. data/db/migrate/20260811000001_add_delivery_tracking_to_fulfillments.rb +17 -0
  806. data/db/migrate/20260812000001_add_provider_type_to_spree_digital_assets.rb +7 -0
  807. data/db/migrate/20260812000002_add_metadata_to_spree_digital_assets.rb +11 -0
  808. data/db/migrate/20260813000001_create_spree_number_sequences.rb +13 -0
  809. data/db/migrate/20260813120001_add_setup_token_to_spree_stores.rb +11 -0
  810. data/db/migrate/20260813130001_add_capture_method_to_payment_methods.rb +17 -0
  811. data/db/migrate/20260813130001_add_unique_indexes_to_spree_data_feeds.rb +10 -0
  812. data/db/migrate/20260814000001_add_customs_fields_to_spree_variants.rb +7 -0
  813. data/db/migrate/20260814000001_rename_stock_items_to_stock_levels.rb +72 -0
  814. data/db/migrate/20260814000002_add_typed_stock_movements.rb +25 -0
  815. data/db/migrate/20260815000001_add_audience_to_spree_refresh_tokens.rb +14 -0
  816. data/db/migrate/20260815000002_add_resource_to_spree_roles.rb +190 -0
  817. data/db/migrate/20260816000001_create_spree_sellers.rb +59 -0
  818. data/db/migrate/20260816000002_add_seller_to_spree_products.rb +10 -0
  819. data/db/migrate/20260816210001_create_spree_commission_rates.rb +229 -0
  820. data/db/migrate/20260816210002_create_spree_commission_lines.rb +72 -0
  821. data/db/migrate/20260817000001_add_seller_to_spree_line_items.rb +15 -0
  822. data/db/migrate/20260817000002_add_seller_and_delivery_profile_to_spree_variants.rb +17 -0
  823. data/db/migrate/20260818000001_create_spree_external_references.rb +33 -0
  824. data/db/migrate/20260818000001_create_spree_seller_requirements.rb +60 -0
  825. data/db/migrate/20260818000001_rename_spree_payments_state_to_status.rb +5 -0
  826. data/db/migrate/20260818000002_add_price_source_to_spree_line_items.rb +9 -0
  827. data/db/migrate/20260818090001_rename_assets_to_media.rb +10 -0
  828. data/db/migrate/20260819000001_create_spree_seller_requirement_custom_fields.rb +15 -0
  829. data/db/migrate/20260819000001_replace_payment_method_display_on_with_storefront_visible.rb +25 -0
  830. data/db/migrate/20260819000002_rename_spree_gift_cards_state_to_status.rb +5 -0
  831. data/db/migrate/20260820000001_add_seller_business_identity_and_stock_locations.rb +17 -0
  832. data/db/migrate/20260820000001_create_spree_order_groups.rb +33 -0
  833. data/db/migrate/20260820000002_add_order_group_to_spree_orders_and_payments.rb +14 -0
  834. data/db/migrate/20260820000003_create_spree_payment_splits.rb +21 -0
  835. data/db/migrate/20260820000004_add_order_to_spree_refunds.rb +32 -0
  836. data/db/migrate/20260821000001_add_claimed_amount_to_spree_payment_splits.rb +14 -0
  837. data/db/migrate/20260822000001_create_spree_seller_transfers.rb +94 -0
  838. data/db/migrate/20260822000002_create_spree_seller_payouts.rb +48 -0
  839. data/db/migrate/20260822000003_add_payout_account_to_spree_sellers.rb +11 -0
  840. data/db/migrate/20260824000001_add_store_to_spree_media.rb +16 -0
  841. data/db/migrate/20260825000001_create_spree_catalogs.rb +40 -0
  842. data/db/migrate/20260826000001_add_owner_to_spree_addresses.rb +30 -0
  843. data/db/migrate/20260826000002_create_spree_product_submissions.rb +28 -0
  844. data/db/migrate/20260827000001_add_seller_to_spree_exports.rb +5 -0
  845. data/db/migrate/20260828100001_unify_import_and_export_tenancy.rb +79 -0
  846. data/db/migrate/20260830000001_add_catalog_to_spree_price_lists.rb +47 -0
  847. data/db/migrate/20260830000002_add_po_number_to_purchases.rb +16 -0
  848. data/db/migrate/20260830000002_add_price_adjustment_to_spree_price_lists.rb +14 -0
  849. data/db/migrate/20260831000001_add_description_to_spree_catalogs.rb +7 -0
  850. data/db/migrate/20260831000002_add_quantity_rules_to_spree_variants.rb +18 -0
  851. data/db/migrate/20260831000003_create_spree_catalog_terms.rb +35 -0
  852. data/db/migrate/20260831100001_create_spree_package_types.rb +38 -0
  853. data/db/migrate/20260831100002_add_carton_details_to_spree_variants.rb +10 -0
  854. data/db/migrate/20260831100003_add_unpriced_to_spree_delivery_rates.rb +8 -0
  855. data/db/migrate/20260901000001_add_seller_to_spree_delivery_methods.rb +11 -0
  856. data/db/migrate/20260902000001_add_store_and_filter_key_to_custom_field_definitions.rb +143 -0
  857. data/db/migrate/20260902000002_add_eu_legal_compliance.rb +68 -0
  858. data/db/migrate/20260902000002_replace_order_cancellation_tables_with_cancel_columns.rb +88 -0
  859. data/db/migrate/20260902100001_rename_wished_items_to_wishlist_items.rb +8 -0
  860. data/db/migrate/20260902100002_rename_promo_total_to_discount_total.rb +9 -0
  861. data/db/migrate/20260902100003_rename_address_name_and_postal_code_columns.rb +24 -0
  862. data/db/migrate/20260902100004_rename_option_presentation_to_label.rb +13 -0
  863. data/db/migrate/20260903000001_create_spree_shipping_labels_and_deliveries.rb +73 -0
  864. data/db/migrate/20260903120000_create_spree_saved_reports.rb +57 -0
  865. data/db/migrate/20260904000001_add_returns_enabled_to_stock_locations.rb +14 -0
  866. data/db/migrate/20260906000001_add_commission_total_to_orders.rb +22 -0
  867. data/db/migrate/20260907000001_add_inventory_operations.rb +113 -0
  868. data/db/migrate/20260907000001_add_min_quantity_to_spree_prices.rb +28 -0
  869. data/db/migrate/20260907000002_create_spree_price_adjustment_tiers.rb +19 -0
  870. data/db/migrate/20260908000001_add_seller_to_spree_package_types.rb +78 -0
  871. data/db/migrate/20260910000001_add_stock_receipts.rb +44 -0
  872. data/db/migrate/20260910000002_add_counters_to_spree_stock_levels.rb +6 -0
  873. data/db/migrate/20260910120000_index_reporting_time_columns.rb +12 -0
  874. data/db/migrate/20260912120000_index_reporting_lifetime_and_promotion_columns.rb +19 -0
  875. data/db/migrate/20260914000001_add_settlement_to_spree_seller_transfers.rb +18 -0
  876. data/db/migrate/20260914230000_add_store_to_spree_option_types.rb +46 -0
  877. data/db/sample_data/collections.rb +58 -0
  878. data/db/sample_data/custom_field_definitions.rb +9 -0
  879. data/db/sample_data/digital_products.rb +26 -0
  880. data/db/sample_data/fulfillment.rb +88 -0
  881. data/db/sample_data/options.rb +25 -1
  882. data/db/sample_data/orders.rb +53 -30
  883. data/db/sample_data/payment_methods.rb +2 -2
  884. data/db/sample_data/price_list_prices.csv +25 -0
  885. data/db/sample_data/product_type_categories.rb +27 -0
  886. data/db/sample_data/product_types.rb +83 -0
  887. data/db/sample_data/products.csv +39 -38
  888. data/db/sample_data/wholesale.rb +42 -3
  889. data/lib/generators/spree/api_resource/api_resource_generator.rb +1 -1
  890. data/lib/generators/spree/authentication/dummy/templates/create_spree_admin_users.rb.tt +1 -0
  891. data/lib/generators/spree/controller_decorator/controller_decorator_generator.rb +4 -4
  892. data/lib/generators/spree/dummy/dummy_generator.rb +5 -12
  893. data/lib/generators/spree/dummy/templates/rails/test.rb +5 -0
  894. data/lib/generators/spree/model/model_generator.rb +6 -6
  895. data/lib/generators/spree/model/templates/model.rb.tt +7 -3
  896. data/lib/spree/category_permalink_deduplicator.rb +328 -0
  897. data/lib/spree/core/configuration.rb +96 -47
  898. data/lib/spree/core/controller_helpers/store.rb +8 -9
  899. data/lib/spree/core/dependencies.rb +324 -71
  900. data/lib/spree/core/dependencies_helper.rb +4 -0
  901. data/lib/spree/core/engine.rb +333 -75
  902. data/lib/spree/core/number_generator.rb +37 -33
  903. data/lib/spree/core/permission_configuration.rb +183 -91
  904. data/lib/spree/core/permissions/default_catalog.rb +236 -0
  905. data/lib/spree/core/permissions/entry.rb +15 -0
  906. data/lib/spree/core/permissions/scope.rb +76 -0
  907. data/lib/spree/core/preferences/masking.rb +24 -2
  908. data/lib/spree/core/preferences/preferable.rb +59 -3
  909. data/lib/spree/core/preferences/preferable_class_methods.rb +41 -2
  910. data/lib/spree/core/preferences/runtime_configuration.rb +68 -9
  911. data/lib/spree/core/previews/admin_user_preview.rb +0 -8
  912. data/lib/spree/core/previews/invitation_preview.rb +1 -1
  913. data/lib/spree/core/previews/preview_data.rb +2 -2
  914. data/lib/spree/core/previews/seller_user_preview.rb +14 -0
  915. data/lib/spree/core/pricing/context.rb +28 -8
  916. data/lib/spree/core/pricing/price_resolution.rb +150 -0
  917. data/lib/spree/core/pricing/resolver.rb +17 -107
  918. data/lib/spree/core/ransack_configuration.rb +3 -3
  919. data/lib/spree/core/routes.rb +4 -6
  920. data/lib/spree/core/version.rb +1 -1
  921. data/lib/spree/core.rb +422 -62
  922. data/lib/spree/events/adapters/base.rb +42 -15
  923. data/lib/spree/hooks.rb +146 -0
  924. data/lib/spree/iso_data.rb +188 -0
  925. data/lib/spree/measurement.rb +113 -0
  926. data/lib/spree/money.rb +70 -0
  927. data/lib/spree/number_generators/registry.rb +80 -0
  928. data/lib/spree/reporting/adapters/base.rb +71 -0
  929. data/lib/spree/reporting/adapters/live.rb +677 -0
  930. data/lib/spree/reporting/counter_result.rb +19 -0
  931. data/lib/spree/reporting/counters.rb +33 -0
  932. data/lib/spree/reporting/default_vocabulary.rb +705 -0
  933. data/lib/spree/reporting/hydration.rb +64 -0
  934. data/lib/spree/reporting/query.rb +487 -0
  935. data/lib/spree/reporting/registry.rb +294 -0
  936. data/lib/spree/reporting/result.rb +10 -0
  937. data/lib/spree/reporting/schema.rb +124 -0
  938. data/lib/spree/reporting.rb +49 -0
  939. data/lib/spree/service_module.rb +6 -0
  940. data/lib/spree/store_scope_guard.rb +180 -0
  941. data/lib/spree/testing_support/action_text.rb +33 -0
  942. data/lib/spree/testing_support/common_rake.rb +4 -66
  943. data/lib/spree/testing_support/country_pool.rb +50 -0
  944. data/lib/spree/testing_support/extension_rake.rb +2 -6
  945. data/lib/spree/testing_support/factories/address_factory.rb +45 -15
  946. data/lib/spree/testing_support/factories/api_key_factory.rb +1 -1
  947. data/lib/spree/testing_support/factories/calculator_factory.rb +0 -3
  948. data/lib/spree/testing_support/factories/cart_factory.rb +60 -0
  949. data/lib/spree/testing_support/factories/catalog_factory.rb +37 -0
  950. data/lib/spree/testing_support/factories/category_factory.rb +21 -0
  951. data/lib/spree/testing_support/factories/claim_factory.rb +33 -0
  952. data/lib/spree/testing_support/factories/classification_factory.rb +3 -2
  953. data/lib/spree/testing_support/factories/collection_factory.rb +25 -0
  954. data/lib/spree/testing_support/factories/collection_rule_factory.rb +47 -0
  955. data/lib/spree/testing_support/factories/commission_rate_factory.rb +75 -0
  956. data/lib/spree/testing_support/factories/company_factory.rb +31 -0
  957. data/lib/spree/testing_support/factories/consent_record_factory.rb +10 -0
  958. data/lib/spree/testing_support/factories/country_factory.rb +28 -9
  959. data/lib/spree/testing_support/factories/custom_field_definition_factory.rb +79 -0
  960. data/lib/spree/testing_support/factories/custom_field_factory.rb +64 -0
  961. data/lib/spree/testing_support/factories/customer_group_user_factory.rb +1 -1
  962. data/lib/spree/testing_support/factories/data_request_factory.rb +17 -0
  963. data/lib/spree/testing_support/factories/delivery_factory.rb +13 -0
  964. data/lib/spree/testing_support/factories/delivery_method_factory.rb +47 -0
  965. data/lib/spree/testing_support/factories/delivery_origin_group_factory.rb +5 -0
  966. data/lib/spree/testing_support/factories/delivery_profile_factory.rb +9 -0
  967. data/lib/spree/testing_support/factories/delivery_rate_factory.rb +7 -0
  968. data/lib/spree/testing_support/factories/delivery_zone_factory.rb +34 -0
  969. data/lib/spree/testing_support/factories/digital_asset_factory.rb +13 -0
  970. data/lib/spree/testing_support/factories/digital_link_factory.rb +1 -1
  971. data/lib/spree/testing_support/factories/discount_factory.rb +9 -0
  972. data/lib/spree/testing_support/factories/exchange_factory.rb +50 -0
  973. data/lib/spree/testing_support/factories/external_reference_factory.rb +8 -0
  974. data/lib/spree/testing_support/factories/fee_factory.rb +8 -0
  975. data/lib/spree/testing_support/factories/fulfillment_factory.rb +32 -0
  976. data/lib/spree/testing_support/factories/fulfillment_item_factory.rb +17 -0
  977. data/lib/spree/testing_support/factories/gateway_customer_factory.rb +1 -1
  978. data/lib/spree/testing_support/factories/gift_card_factory.rb +2 -2
  979. data/lib/spree/testing_support/factories/import_factory.rb +33 -1
  980. data/lib/spree/testing_support/factories/line_item_factory.rb +10 -5
  981. data/lib/spree/testing_support/factories/market_factory.rb +5 -12
  982. data/lib/spree/testing_support/factories/media_factory.rb +36 -0
  983. data/lib/spree/testing_support/factories/newsletter_subscriber_factory.rb +1 -1
  984. data/lib/spree/testing_support/factories/number_sequence_factory.rb +7 -0
  985. data/lib/spree/testing_support/factories/options_factory.rb +5 -5
  986. data/lib/spree/testing_support/factories/order_factory.rb +39 -23
  987. data/lib/spree/testing_support/factories/order_group_factory.rb +29 -0
  988. data/lib/spree/testing_support/factories/package_type_factory.rb +36 -0
  989. data/lib/spree/testing_support/factories/payment_factory.rb +7 -7
  990. data/lib/spree/testing_support/factories/payment_method_factory.rb +5 -5
  991. data/lib/spree/testing_support/factories/payment_session_factory.rb +10 -6
  992. data/lib/spree/testing_support/factories/payment_setup_session_factory.rb +2 -2
  993. data/lib/spree/testing_support/factories/payment_split_factory.rb +10 -0
  994. data/lib/spree/testing_support/factories/price_adjustment_tier_factory.rb +7 -0
  995. data/lib/spree/testing_support/factories/price_factory.rb +6 -0
  996. data/lib/spree/testing_support/factories/price_rule_factory.rb +0 -10
  997. data/lib/spree/testing_support/factories/product_collection_factory.rb +8 -0
  998. data/lib/spree/testing_support/factories/product_factory.rb +35 -7
  999. data/lib/spree/testing_support/factories/product_type_custom_field_definition_factory.rb +10 -0
  1000. data/lib/spree/testing_support/factories/product_type_factory.rb +12 -0
  1001. data/lib/spree/testing_support/factories/promotion_factory.rb +4 -0
  1002. data/lib/spree/testing_support/factories/promotion_rule_factory.rb +1 -1
  1003. data/lib/spree/testing_support/factories/purchase_order_factory.rb +49 -0
  1004. data/lib/spree/testing_support/factories/refresh_token_factory.rb +2 -0
  1005. data/lib/spree/testing_support/factories/refund_factory.rb +1 -2
  1006. data/lib/spree/testing_support/factories/return_factory.rb +52 -0
  1007. data/lib/spree/testing_support/factories/return_reason_factory.rb +19 -0
  1008. data/lib/spree/testing_support/factories/role_factory.rb +1 -0
  1009. data/lib/spree/testing_support/factories/saved_report_factory.rb +7 -0
  1010. data/lib/spree/testing_support/factories/seller_factory.rb +34 -0
  1011. data/lib/spree/testing_support/factories/seller_payout_factory.rb +14 -0
  1012. data/lib/spree/testing_support/factories/seller_requirement_factory.rb +61 -0
  1013. data/lib/spree/testing_support/factories/seller_requirement_submission_factory.rb +32 -0
  1014. data/lib/spree/testing_support/factories/seller_transfer_factory.rb +21 -0
  1015. data/lib/spree/testing_support/factories/shipping_label_factory.rb +44 -0
  1016. data/lib/spree/testing_support/factories/state_factory.rb +18 -5
  1017. data/lib/spree/testing_support/factories/stock_level_factory.rb +24 -0
  1018. data/lib/spree/testing_support/factories/stock_location_factory.rb +12 -6
  1019. data/lib/spree/testing_support/factories/stock_movement_factory.rb +20 -3
  1020. data/lib/spree/testing_support/factories/stock_receipt_factory.rb +37 -0
  1021. data/lib/spree/testing_support/factories/stock_reservation_factory.rb +11 -8
  1022. data/lib/spree/testing_support/factories/stock_transfer_factory.rb +53 -6
  1023. data/lib/spree/testing_support/factories/store_credit_factory.rb +1 -3
  1024. data/lib/spree/testing_support/factories/supplier_factory.rb +17 -0
  1025. data/lib/spree/testing_support/factories/tax_exemption_certificate_factory.rb +18 -0
  1026. data/lib/spree/testing_support/factories/tax_identifier_factory.rb +33 -0
  1027. data/lib/spree/testing_support/factories/tax_line_factory.rb +19 -0
  1028. data/lib/spree/testing_support/factories/tax_rate_factory.rb +5 -2
  1029. data/lib/spree/testing_support/factories/taxon_factory.rb +6 -23
  1030. data/lib/spree/testing_support/factories/taxonomy_factory.rb +8 -0
  1031. data/lib/spree/testing_support/factories/user_factory.rb +8 -12
  1032. data/lib/spree/testing_support/factories/variant_factory.rb +1 -1
  1033. data/lib/spree/testing_support/factories/wishlist_factory.rb +1 -1
  1034. data/lib/spree/testing_support/factories/wishlist_item_factory.rb +10 -0
  1035. data/lib/spree/testing_support/factories/zone_factory.rb +2 -25
  1036. data/lib/spree/testing_support/factories/zone_member_factory.rb +2 -1
  1037. data/lib/spree/testing_support/factories.rb +1 -6
  1038. data/lib/spree/testing_support/imports.rb +16 -0
  1039. data/lib/spree/testing_support/label_provider.rb +49 -0
  1040. data/lib/spree/testing_support/lifecycle_events.rb +6 -1
  1041. data/lib/spree/testing_support/metadata.rb +38 -36
  1042. data/lib/spree/testing_support/order_walkthrough.rb +46 -36
  1043. data/lib/spree/testing_support/preferences.rb +39 -0
  1044. data/lib/spree/testing_support/purchase_order.rb +111 -0
  1045. data/lib/spree/testing_support/store.rb +6 -2
  1046. data/lib/spree/testing_support/tax_identifier_validators.rb +68 -0
  1047. data/lib/spree/translations.rb +139 -5
  1048. data/lib/spree/upgrades/5_4_to_5_5/manifest.yml +1 -1
  1049. data/lib/spree/upgrades/5_5_to_5_6/manifest.yml +17 -9
  1050. data/lib/spree/upgrades/5_6_to_6_0/manifest.yml +683 -0
  1051. data/lib/spree/validators.rb +74 -0
  1052. data/lib/spree/workflow.rb +335 -0
  1053. data/lib/tasks/backfill_custom_field_definition_stores.rake +82 -0
  1054. data/lib/tasks/backfill_import_export_tenancy.rake +93 -0
  1055. data/lib/tasks/backfill_library_media_placements.rake +29 -0
  1056. data/lib/tasks/backfill_media_store_ids.rake +70 -0
  1057. data/lib/tasks/backfill_reason_store_ids.rake +28 -0
  1058. data/lib/tasks/capture_methods_migration.rake +30 -0
  1059. data/lib/tasks/carts_migration.rake +113 -0
  1060. data/lib/tasks/categories_and_collections.rake +188 -0
  1061. data/lib/tasks/cli.rake +22 -3
  1062. data/lib/tasks/consolidate_metadata.rake +73 -0
  1063. data/lib/tasks/core.rake +1 -11
  1064. data/lib/tasks/deliveries_migration.rake +52 -0
  1065. data/lib/tasks/delivery_method_rules_migration.rake +51 -0
  1066. data/lib/tasks/delivery_migration.rake +213 -0
  1067. data/lib/tasks/delivery_profiles_migration.rake +109 -0
  1068. data/lib/tasks/fold_store_credit_categories.rake +40 -0
  1069. data/lib/tasks/fulfillment_statuses_migration.rake +59 -0
  1070. data/lib/tasks/inventory_operations_migration.rake +223 -0
  1071. data/lib/tasks/markets.rake +7 -2
  1072. data/lib/tasks/media.rake +2 -2
  1073. data/lib/tasks/media_class_rename.rake +56 -0
  1074. data/lib/tasks/migrate_country_state_codes.rake +228 -0
  1075. data/lib/tasks/migrate_promotion_option_value_rules.rake +124 -0
  1076. data/lib/tasks/migrate_returns.rake +322 -0
  1077. data/lib/tasks/migrate_rich_text_to_columns.rake +128 -0
  1078. data/lib/tasks/migrate_users_to_customers.rake +161 -0
  1079. data/lib/tasks/order_coupon_code_backfill.rake +32 -0
  1080. data/lib/tasks/order_market_backfill.rake +27 -0
  1081. data/lib/tasks/package_types.rake +62 -0
  1082. data/lib/tasks/price_history.rake +49 -6
  1083. data/lib/tasks/product_tag_tenants.rake +1 -3
  1084. data/lib/tasks/products.rake +21 -2
  1085. data/lib/tasks/remove_master_variant.rake +58 -0
  1086. data/lib/tasks/rich_text.rake +44 -0
  1087. data/lib/tasks/seller_sample_data.rake +142 -0
  1088. data/lib/tasks/setup.rake +18 -0
  1089. data/lib/tasks/single_store_associations.rake +11 -0
  1090. data/lib/tasks/stock.rake +15 -0
  1091. data/lib/tasks/store_binding_migration.rake +33 -0
  1092. data/lib/tasks/store_settings.rake +104 -0
  1093. data/lib/tasks/tax_zones_migration.rake +147 -0
  1094. data/lib/tasks/taxons.rake +9 -10
  1095. data/lib/tasks/typed_adjustments_migration.rake +189 -0
  1096. data/lib/tasks/typed_stock_movements_migration.rake +250 -0
  1097. data/lib/tasks/upgrade.rake +13 -8
  1098. data/spec/fixtures/files/eu_vat_numbers.txt +56 -0
  1099. data/spec/fixtures/files/price_list_prices_import.csv +4 -0
  1100. data/spec/fixtures/files/products_import.csv +1 -1
  1101. data/spec/fixtures/tiny-video.mp4 +0 -0
  1102. metadata +792 -538
  1103. data/app/assets/config/spree_core_manifest.js +0 -4
  1104. data/app/assets/images/google_on_white_hdpi.png +0 -0
  1105. data/app/assets/images/logo.png +0 -0
  1106. data/app/assets/images/payment_icons/ach.svg +0 -1
  1107. data/app/assets/images/payment_icons/achdirectdebit.svg +0 -1
  1108. data/app/assets/images/payment_icons/ada.svg +0 -1
  1109. data/app/assets/images/payment_icons/adyen.svg +0 -1
  1110. data/app/assets/images/payment_icons/affinbank.svg +0 -1
  1111. data/app/assets/images/payment_icons/affirm.svg +0 -1
  1112. data/app/assets/images/payment_icons/afterpay.svg +0 -1
  1113. data/app/assets/images/payment_icons/airtel_money.svg +0 -1
  1114. data/app/assets/images/payment_icons/airteltigo_mobile_money.svg +0 -1
  1115. data/app/assets/images/payment_icons/alandsbanken.svg +0 -1
  1116. data/app/assets/images/payment_icons/alipay.svg +0 -1
  1117. data/app/assets/images/payment_icons/alipay_hk.svg +0 -1
  1118. data/app/assets/images/payment_icons/alliancebank.svg +0 -1
  1119. data/app/assets/images/payment_icons/alma.svg +0 -1
  1120. data/app/assets/images/payment_icons/amazon.svg +0 -1
  1121. data/app/assets/images/payment_icons/ambank.svg +0 -1
  1122. data/app/assets/images/payment_icons/american_express.svg +0 -1
  1123. data/app/assets/images/payment_icons/apecoin.svg +0 -1
  1124. data/app/assets/images/payment_icons/apple_pay.svg +0 -1
  1125. data/app/assets/images/payment_icons/arbitrum.svg +0 -1
  1126. data/app/assets/images/payment_icons/atome.svg +0 -1
  1127. data/app/assets/images/payment_icons/authorizenet.svg +0 -1
  1128. data/app/assets/images/payment_icons/avalanche.svg +0 -1
  1129. data/app/assets/images/payment_icons/bacs.svg +0 -1
  1130. data/app/assets/images/payment_icons/bancontact.svg +0 -1
  1131. data/app/assets/images/payment_icons/bangkokbank.svg +0 -1
  1132. data/app/assets/images/payment_icons/bankislam.svg +0 -1
  1133. data/app/assets/images/payment_icons/bankmuamalat.svg +0 -1
  1134. data/app/assets/images/payment_icons/bankofamerica.svg +0 -1
  1135. data/app/assets/images/payment_icons/bankrakyat.svg +0 -1
  1136. data/app/assets/images/payment_icons/banktransfer.svg +0 -1
  1137. data/app/assets/images/payment_icons/base.svg +0 -1
  1138. data/app/assets/images/payment_icons/becs.svg +0 -1
  1139. data/app/assets/images/payment_icons/benefit.svg +0 -1
  1140. data/app/assets/images/payment_icons/billie.svg +0 -1
  1141. data/app/assets/images/payment_icons/bitcoin.svg +0 -1
  1142. data/app/assets/images/payment_icons/bitcoin_cash.svg +0 -1
  1143. data/app/assets/images/payment_icons/bizum.svg +0 -1
  1144. data/app/assets/images/payment_icons/blik.svg +0 -1
  1145. data/app/assets/images/payment_icons/bnbchain.svg +0 -1
  1146. data/app/assets/images/payment_icons/bogus.svg +0 -1
  1147. data/app/assets/images/payment_icons/bogus_app_coin.svg +0 -1
  1148. data/app/assets/images/payment_icons/boleto.svg +0 -1
  1149. data/app/assets/images/payment_icons/boost.svg +0 -1
  1150. data/app/assets/images/payment_icons/braintree.svg +0 -1
  1151. data/app/assets/images/payment_icons/buckaroopaybybank.svg +0 -1
  1152. data/app/assets/images/payment_icons/busd.svg +0 -1
  1153. data/app/assets/images/payment_icons/cartes_bancaires.svg +0 -1
  1154. data/app/assets/images/payment_icons/cash.svg +0 -1
  1155. data/app/assets/images/payment_icons/cashappafterpay.svg +0 -1
  1156. data/app/assets/images/payment_icons/cashapppay.svg +0 -1
  1157. data/app/assets/images/payment_icons/checkout_finance.svg +0 -1
  1158. data/app/assets/images/payment_icons/chinabank.svg +0 -1
  1159. data/app/assets/images/payment_icons/clearpay.svg +0 -1
  1160. data/app/assets/images/payment_icons/collector_bank.svg +0 -1
  1161. data/app/assets/images/payment_icons/dai.svg +0 -1
  1162. data/app/assets/images/payment_icons/dana.svg +0 -1
  1163. data/app/assets/images/payment_icons/dankort.svg +0 -1
  1164. data/app/assets/images/payment_icons/danske_bank.svg +0 -1
  1165. data/app/assets/images/payment_icons/deutschebank.svg +0 -1
  1166. data/app/assets/images/payment_icons/diners_club.svg +0 -1
  1167. data/app/assets/images/payment_icons/directbanktransferlatinamerica.svg +0 -1
  1168. data/app/assets/images/payment_icons/discover.svg +0 -1
  1169. data/app/assets/images/payment_icons/dogecoin.svg +0 -1
  1170. data/app/assets/images/payment_icons/duitnow.svg +0 -1
  1171. data/app/assets/images/payment_icons/eft_secure.svg +0 -1
  1172. data/app/assets/images/payment_icons/eftpos_au.svg +0 -1
  1173. data/app/assets/images/payment_icons/eftposau.svg +0 -1
  1174. data/app/assets/images/payment_icons/elo.svg +0 -1
  1175. data/app/assets/images/payment_icons/eos.svg +0 -1
  1176. data/app/assets/images/payment_icons/eps.svg +0 -1
  1177. data/app/assets/images/payment_icons/ethereum.svg +0 -1
  1178. data/app/assets/images/payment_icons/eurobonuscheckout.svg +0 -1
  1179. data/app/assets/images/payment_icons/ezcash.svg +0 -1
  1180. data/app/assets/images/payment_icons/fantom.svg +0 -1
  1181. data/app/assets/images/payment_icons/fpx.svg +0 -1
  1182. data/app/assets/images/payment_icons/gcash.svg +0 -1
  1183. data/app/assets/images/payment_icons/generic.svg +0 -1
  1184. data/app/assets/images/payment_icons/genericbank.svg +0 -1
  1185. data/app/assets/images/payment_icons/gift-card.svg +0 -1
  1186. data/app/assets/images/payment_icons/giropay.svg +0 -1
  1187. data/app/assets/images/payment_icons/glbepaypal.svg +0 -1
  1188. data/app/assets/images/payment_icons/gmobanktransfer.svg +0 -1
  1189. data/app/assets/images/payment_icons/gnosis.svg +0 -1
  1190. data/app/assets/images/payment_icons/google_pay.svg +0 -1
  1191. data/app/assets/images/payment_icons/google_wallet.svg +0 -1
  1192. data/app/assets/images/payment_icons/gopay.svg +0 -1
  1193. data/app/assets/images/payment_icons/grabpay.svg +0 -1
  1194. data/app/assets/images/payment_icons/gusd.svg +0 -1
  1195. data/app/assets/images/payment_icons/handelsbanken.svg +0 -1
  1196. data/app/assets/images/payment_icons/hongleongbank.svg +0 -1
  1197. data/app/assets/images/payment_icons/hypercard.svg +0 -1
  1198. data/app/assets/images/payment_icons/hypercash.svg +0 -1
  1199. data/app/assets/images/payment_icons/ideal.svg +0 -1
  1200. data/app/assets/images/payment_icons/in3viaideal.svg +0 -1
  1201. data/app/assets/images/payment_icons/inbank.svg +0 -1
  1202. data/app/assets/images/payment_icons/jcb.svg +0 -1
  1203. data/app/assets/images/payment_icons/kakao_pay.svg +0 -1
  1204. data/app/assets/images/payment_icons/kasikornbank.svg +0 -1
  1205. data/app/assets/images/payment_icons/klarna-pay-later.svg +0 -1
  1206. data/app/assets/images/payment_icons/klarna-pay-now.svg +0 -1
  1207. data/app/assets/images/payment_icons/klarna-slice-it.svg +0 -1
  1208. data/app/assets/images/payment_icons/klarna.svg +0 -1
  1209. data/app/assets/images/payment_icons/knet.svg +0 -1
  1210. data/app/assets/images/payment_icons/krungthaibank.svg +0 -1
  1211. data/app/assets/images/payment_icons/landbank.svg +0 -1
  1212. data/app/assets/images/payment_icons/laybuybyklarna.svg +0 -1
  1213. data/app/assets/images/payment_icons/litecoin.svg +0 -1
  1214. data/app/assets/images/payment_icons/lunchcheck.svg +0 -1
  1215. data/app/assets/images/payment_icons/mach.svg +0 -1
  1216. data/app/assets/images/payment_icons/mada.svg +0 -1
  1217. data/app/assets/images/payment_icons/maestro.svg +0 -1
  1218. data/app/assets/images/payment_icons/master.svg +0 -1
  1219. data/app/assets/images/payment_icons/mayabank.svg +0 -1
  1220. data/app/assets/images/payment_icons/maybank.svg +0 -1
  1221. data/app/assets/images/payment_icons/maybankm2u.svg +0 -1
  1222. data/app/assets/images/payment_icons/maybankqrpay.svg +0 -1
  1223. data/app/assets/images/payment_icons/mbway.svg +0 -1
  1224. data/app/assets/images/payment_icons/mcash.svg +0 -1
  1225. data/app/assets/images/payment_icons/medicinosbankas.svg +0 -1
  1226. data/app/assets/images/payment_icons/mercadopago.svg +0 -1
  1227. data/app/assets/images/payment_icons/metrobank.svg +0 -1
  1228. data/app/assets/images/payment_icons/mobilepay.svg +0 -1
  1229. data/app/assets/images/payment_icons/momopay.svg +0 -1
  1230. data/app/assets/images/payment_icons/monero.svg +0 -1
  1231. data/app/assets/images/payment_icons/mtn_mobile_money.svg +0 -1
  1232. data/app/assets/images/payment_icons/mybank.svg +0 -1
  1233. data/app/assets/images/payment_icons/naver_pay.svg +0 -1
  1234. data/app/assets/images/payment_icons/nelo.svg +0 -1
  1235. data/app/assets/images/payment_icons/netbanking.svg +0 -1
  1236. data/app/assets/images/payment_icons/novalnetdirectdebitach.svg +0 -1
  1237. data/app/assets/images/payment_icons/nubank.svg +0 -1
  1238. data/app/assets/images/payment_icons/ocbcbank.svg +0 -1
  1239. data/app/assets/images/payment_icons/offlinebanktransferlatinamerica.svg +0 -1
  1240. data/app/assets/images/payment_icons/ola_money.svg +0 -1
  1241. data/app/assets/images/payment_icons/oney.svg +0 -1
  1242. data/app/assets/images/payment_icons/onlinebanking.svg +0 -1
  1243. data/app/assets/images/payment_icons/onlinebanktransfer.svg +0 -1
  1244. data/app/assets/images/payment_icons/orangemobilemoney.svg +0 -1
  1245. data/app/assets/images/payment_icons/otpbank.svg +0 -1
  1246. data/app/assets/images/payment_icons/oxxo.svg +0 -1
  1247. data/app/assets/images/payment_icons/paybybank.svg +0 -1
  1248. data/app/assets/images/payment_icons/paybybankmollie.svg +0 -1
  1249. data/app/assets/images/payment_icons/paybybankus.svg +0 -1
  1250. data/app/assets/images/payment_icons/paycash.svg +0 -1
  1251. data/app/assets/images/payment_icons/payco.svg +0 -1
  1252. data/app/assets/images/payment_icons/payeverpaybybank.svg +0 -1
  1253. data/app/assets/images/payment_icons/payme.svg +0 -1
  1254. data/app/assets/images/payment_icons/paynow.svg +0 -1
  1255. data/app/assets/images/payment_icons/paynowmbank.svg +0 -1
  1256. data/app/assets/images/payment_icons/paypal.svg +0 -1
  1257. data/app/assets/images/payment_icons/paypo.svg +0 -1
  1258. data/app/assets/images/payment_icons/payrexxbanktransfer.svg +0 -1
  1259. data/app/assets/images/payment_icons/payrexxpaybybank.svg +0 -1
  1260. data/app/assets/images/payment_icons/paysafecash.svg +0 -1
  1261. data/app/assets/images/payment_icons/pix.svg +0 -1
  1262. data/app/assets/images/payment_icons/polygon.svg +0 -1
  1263. data/app/assets/images/payment_icons/przelewy24.svg +0 -1
  1264. data/app/assets/images/payment_icons/publicbank.svg +0 -1
  1265. data/app/assets/images/payment_icons/publicbank_pbe.svg +0 -1
  1266. data/app/assets/images/payment_icons/qr_promptpay.svg +0 -1
  1267. data/app/assets/images/payment_icons/rabobank.svg +0 -1
  1268. data/app/assets/images/payment_icons/revolut.svg +0 -1
  1269. data/app/assets/images/payment_icons/rhbbank.svg +0 -1
  1270. data/app/assets/images/payment_icons/riverty.svg +0 -1
  1271. data/app/assets/images/payment_icons/safetypaybanktransfer.svg +0 -1
  1272. data/app/assets/images/payment_icons/samsung_pay.svg +0 -1
  1273. data/app/assets/images/payment_icons/satispay.svg +0 -1
  1274. data/app/assets/images/payment_icons/scalapay.svg +0 -1
  1275. data/app/assets/images/payment_icons/scotiabank.svg +0 -1
  1276. data/app/assets/images/payment_icons/seabankid.svg +0 -1
  1277. data/app/assets/images/payment_icons/seabankph.svg +0 -1
  1278. data/app/assets/images/payment_icons/sepa_bank_transfer.svg +0 -1
  1279. data/app/assets/images/payment_icons/sepadebit.svg +0 -22
  1280. data/app/assets/images/payment_icons/sepadirectdebit.svg +0 -1
  1281. data/app/assets/images/payment_icons/seveneleven.svg +0 -1
  1282. data/app/assets/images/payment_icons/shib.svg +0 -1
  1283. data/app/assets/images/payment_icons/shopcash.svg +0 -1
  1284. data/app/assets/images/payment_icons/siauliubankas.svg +0 -1
  1285. data/app/assets/images/payment_icons/skeps.svg +0 -1
  1286. data/app/assets/images/payment_icons/snap_checkout.svg +0 -1
  1287. data/app/assets/images/payment_icons/sofort.svg +0 -1
  1288. data/app/assets/images/payment_icons/softbank.svg +0 -1
  1289. data/app/assets/images/payment_icons/spei.svg +0 -1
  1290. data/app/assets/images/payment_icons/storecredit.svg +0 -1
  1291. data/app/assets/images/payment_icons/stripe.svg +0 -1
  1292. data/app/assets/images/payment_icons/sveacheckout.svg +0 -1
  1293. data/app/assets/images/payment_icons/swedbank.svg +0 -1
  1294. data/app/assets/images/payment_icons/swish.svg +0 -1
  1295. data/app/assets/images/payment_icons/tbibank.svg +0 -1
  1296. data/app/assets/images/payment_icons/thanachartbank.svg +0 -1
  1297. data/app/assets/images/payment_icons/tnmmoney.svg +0 -1
  1298. data/app/assets/images/payment_icons/touchngo.svg +0 -1
  1299. data/app/assets/images/payment_icons/trustly.svg +0 -1
  1300. data/app/assets/images/payment_icons/twint.svg +0 -1
  1301. data/app/assets/images/payment_icons/uaevisa.svg +0 -1
  1302. data/app/assets/images/payment_icons/unionpay.svg +0 -1
  1303. data/app/assets/images/payment_icons/unzerbanktransfer.svg +0 -1
  1304. data/app/assets/images/payment_icons/upi.svg +0 -1
  1305. data/app/assets/images/payment_icons/usbank.svg +0 -1
  1306. data/app/assets/images/payment_icons/usdc.svg +0 -1
  1307. data/app/assets/images/payment_icons/usdt.svg +0 -1
  1308. data/app/assets/images/payment_icons/venmo.svg +0 -1
  1309. data/app/assets/images/payment_icons/vipps.svg +0 -1
  1310. data/app/assets/images/payment_icons/visa.svg +0 -1
  1311. data/app/assets/images/payment_icons/volksbank.svg +0 -1
  1312. data/app/assets/images/payment_icons/waavepaybybank.svg +0 -1
  1313. data/app/assets/images/payment_icons/walley.svg +0 -1
  1314. data/app/assets/images/payment_icons/wbtc.svg +0 -1
  1315. data/app/assets/images/payment_icons/wechatpay.svg +0 -1
  1316. data/app/assets/images/payment_icons/whishcheckout.svg +0 -1
  1317. data/app/assets/images/payment_icons/wingbank.svg +0 -1
  1318. data/app/assets/images/payment_icons/xrp.svg +0 -1
  1319. data/app/assets/images/payment_icons/zip.svg +0 -1
  1320. data/app/controllers/spree/base_controller.rb +0 -11
  1321. data/app/finders/concerns/spree/product_filterable.rb +0 -9
  1322. data/app/finders/spree/addresses/find.rb +0 -27
  1323. data/app/finders/spree/base_finder.rb +0 -14
  1324. data/app/finders/spree/countries/find.rb +0 -31
  1325. data/app/finders/spree/credit_cards/find.rb +0 -12
  1326. data/app/finders/spree/option_values/find_available.rb +0 -28
  1327. data/app/finders/spree/orders/find_complete.rb +0 -114
  1328. data/app/finders/spree/orders/find_current.rb +0 -24
  1329. data/app/finders/spree/orders/finder_helper.rb +0 -17
  1330. data/app/finders/spree/products/find.rb +0 -328
  1331. data/app/finders/spree/taxons/find.rb +0 -99
  1332. data/app/finders/spree/variants/find.rb +0 -30
  1333. data/app/finders/spree/variants/option_types_finder.rb +0 -21
  1334. data/app/helpers/spree/addresses_helper.rb +0 -97
  1335. data/app/helpers/spree/base_helper.rb +0 -132
  1336. data/app/helpers/spree/currency_helper.rb +0 -46
  1337. data/app/helpers/spree/integrations_helper.rb +0 -15
  1338. data/app/helpers/spree/locale_helper.rb +0 -44
  1339. data/app/helpers/spree/localized_names_helper.rb +0 -25
  1340. data/app/helpers/spree/products_helper.rb +0 -147
  1341. data/app/helpers/spree/shipment_helper.rb +0 -12
  1342. data/app/javascript/spree/core/controllers/address_autocomplete_controller.js +0 -230
  1343. data/app/javascript/spree/core/controllers/address_form_controller.js +0 -98
  1344. data/app/javascript/spree/core/controllers/disable_submit_button_controller.js +0 -19
  1345. data/app/javascript/spree/core/controllers/enable_button_controller.js +0 -29
  1346. data/app/javascript/spree/core/helpers/address_autocomplete/google_places_suggestions_provider.js +0 -161
  1347. data/app/javascript/spree/core/helpers/debounce.js +0 -9
  1348. data/app/javascript/spree/core/helpers/scroll_lock.js +0 -25
  1349. data/app/jobs/spree/media/migrate_product_assets_job.rb +0 -83
  1350. data/app/jobs/spree/reports/generate_job.rb +0 -23
  1351. data/app/mailers/spree/report_mailer.rb +0 -20
  1352. data/app/models/concerns/spree/adjustment_source.rb +0 -58
  1353. data/app/models/concerns/spree/default_price.rb +0 -138
  1354. data/app/models/concerns/spree/display_on.rb +0 -50
  1355. data/app/models/concerns/spree/image_methods.rb +0 -40
  1356. data/app/models/concerns/spree/legacy_multi_store_support.rb +0 -48
  1357. data/app/models/concerns/spree/metafields.rb +0 -256
  1358. data/app/models/concerns/spree/number_as_param.rb +0 -13
  1359. data/app/models/concerns/spree/store_scoped_resource.rb +0 -37
  1360. data/app/models/concerns/spree/unique_name.rb +0 -12
  1361. data/app/models/spree/adjustable/adjuster/base.rb +0 -25
  1362. data/app/models/spree/adjustable/adjuster/promotion.rb +0 -41
  1363. data/app/models/spree/adjustable/adjuster/tax.rb +0 -26
  1364. data/app/models/spree/adjustable/adjustments_updater.rb +0 -74
  1365. data/app/models/spree/adjustable/promotion_accumulator.rb +0 -78
  1366. data/app/models/spree/adjustment.rb +0 -130
  1367. data/app/models/spree/asset/support/active_storage.rb +0 -26
  1368. data/app/models/spree/calculator/default_tax.rb +0 -87
  1369. data/app/models/spree/calculator/returns/default_refund_amount.rb +0 -40
  1370. data/app/models/spree/customer_return.rb +0 -94
  1371. data/app/models/spree/image/configuration/active_storage.rb +0 -11
  1372. data/app/models/spree/image.rb +0 -6
  1373. data/app/models/spree/legacy_admin_user.rb +0 -31
  1374. data/app/models/spree/legacy_user.rb +0 -33
  1375. data/app/models/spree/log_entry.rb +0 -30
  1376. data/app/models/spree/metafields/boolean.rb +0 -15
  1377. data/app/models/spree/metafields/json.rb +0 -37
  1378. data/app/models/spree/metafields/long_text.rb +0 -7
  1379. data/app/models/spree/metafields/number.rb +0 -15
  1380. data/app/models/spree/metafields/rich_text.rb +0 -18
  1381. data/app/models/spree/metafields/short_text.rb +0 -7
  1382. data/app/models/spree/option_type_prototype.rb +0 -9
  1383. data/app/models/spree/order/address_book.rb +0 -100
  1384. data/app/models/spree/order/checkout.rb +0 -350
  1385. data/app/models/spree/order/currency_updater.rb +0 -37
  1386. data/app/models/spree/order/digital.rb +0 -60
  1387. data/app/models/spree/order/gift_card.rb +0 -71
  1388. data/app/models/spree/order/payments.rb +0 -64
  1389. data/app/models/spree/order/store_credit.rb +0 -110
  1390. data/app/models/spree/order/webhooks.rb +0 -19
  1391. data/app/models/spree/order_approval.rb +0 -19
  1392. data/app/models/spree/order_cancellation.rb +0 -19
  1393. data/app/models/spree/order_contents.rb +0 -31
  1394. data/app/models/spree/order_routing/strategy/legacy.rb +0 -33
  1395. data/app/models/spree/payment/webhooks.rb +0 -15
  1396. data/app/models/spree/permission_sets/base.rb +0 -81
  1397. data/app/models/spree/permission_sets/configuration_management.rb +0 -64
  1398. data/app/models/spree/permission_sets/dashboard_display.rb +0 -17
  1399. data/app/models/spree/permission_sets/default_customer.rb +0 -83
  1400. data/app/models/spree/permission_sets/order_display.rb +0 -27
  1401. data/app/models/spree/permission_sets/order_management.rb +0 -33
  1402. data/app/models/spree/permission_sets/product_display.rb +0 -27
  1403. data/app/models/spree/permission_sets/product_management.rb +0 -28
  1404. data/app/models/spree/permission_sets/promotion_management.rb +0 -22
  1405. data/app/models/spree/permission_sets/role_management.rb +0 -21
  1406. data/app/models/spree/permission_sets/stock_display.rb +0 -19
  1407. data/app/models/spree/permission_sets/stock_management.rb +0 -19
  1408. data/app/models/spree/permission_sets/super_user.rb +0 -28
  1409. data/app/models/spree/permission_sets/user_display.rb +0 -19
  1410. data/app/models/spree/permission_sets/user_management.rb +0 -20
  1411. data/app/models/spree/product/webhooks.rb +0 -17
  1412. data/app/models/spree/prototype_taxon.rb +0 -9
  1413. data/app/models/spree/reimbursement/credit.rb +0 -27
  1414. data/app/models/spree/reimbursement/reimbursement_type_engine.rb +0 -44
  1415. data/app/models/spree/reimbursement/reimbursement_type_validator.rb +0 -15
  1416. data/app/models/spree/reimbursement.rb +0 -167
  1417. data/app/models/spree/reimbursement_performer.rb +0 -34
  1418. data/app/models/spree/reimbursement_tax_calculator.rb +0 -37
  1419. data/app/models/spree/reimbursement_type/credit.rb +0 -13
  1420. data/app/models/spree/reimbursement_type/exchange.rb +0 -9
  1421. data/app/models/spree/reimbursement_type/original_payment.rb +0 -13
  1422. data/app/models/spree/reimbursement_type/reimbursement_helpers.rb +0 -64
  1423. data/app/models/spree/reimbursement_type/store_credit.rb +0 -20
  1424. data/app/models/spree/reimbursement_type.rb +0 -22
  1425. data/app/models/spree/report.rb +0 -130
  1426. data/app/models/spree/report_line_item.rb +0 -27
  1427. data/app/models/spree/report_line_items/products_performance.rb +0 -76
  1428. data/app/models/spree/report_line_items/sales_total.rb +0 -49
  1429. data/app/models/spree/reports/products_performance.rb +0 -44
  1430. data/app/models/spree/reports/sales_total.rb +0 -18
  1431. data/app/models/spree/return_authorization.rb +0 -109
  1432. data/app/models/spree/return_item/eligibility_validator/base_validator.rb +0 -24
  1433. data/app/models/spree/return_item/eligibility_validator/default.rb +0 -30
  1434. data/app/models/spree/return_item/eligibility_validator/inventory_shipped.rb +0 -16
  1435. data/app/models/spree/return_item/eligibility_validator/no_reimbursements.rb +0 -16
  1436. data/app/models/spree/return_item/eligibility_validator/order_completed.rb +0 -16
  1437. data/app/models/spree/return_item/eligibility_validator/rma_required.rb +0 -16
  1438. data/app/models/spree/return_item/eligibility_validator/time_since_purchase.rb +0 -16
  1439. data/app/models/spree/return_item/exchange_variant_eligibility/same_option_value.rb +0 -34
  1440. data/app/models/spree/return_item/exchange_variant_eligibility/same_product.rb +0 -9
  1441. data/app/models/spree/return_item.rb +0 -287
  1442. data/app/models/spree/returns_calculator.rb +0 -7
  1443. data/app/models/spree/search_provider/meilisearch.rb +0 -496
  1444. data/app/models/spree/shipment/custom_events.rb +0 -47
  1445. data/app/models/spree/shipment/webhooks.rb +0 -11
  1446. data/app/models/spree/shipment_handler.rb +0 -38
  1447. data/app/models/spree/shipping_category.rb +0 -25
  1448. data/app/models/spree/shipping_method_category.rb +0 -8
  1449. data/app/models/spree/shipping_method_zone.rb +0 -8
  1450. data/app/models/spree/state_change.rb +0 -12
  1451. data/app/models/spree/stock/splitter/digital.rb +0 -35
  1452. data/app/models/spree/stock/splitter/shipping_category.rb +0 -31
  1453. data/app/models/spree/stock_item/webhooks.rb +0 -6
  1454. data/app/models/spree/stock_movement/webhooks.rb +0 -6
  1455. data/app/models/spree/variant/webhooks.rb +0 -6
  1456. data/app/presenters/spree/csv/metafields_helper.rb +0 -17
  1457. data/app/presenters/spree/search_provider/product_presenter.rb +0 -138
  1458. data/app/presenters/spree/variant_presenter.rb +0 -81
  1459. data/app/presenters/spree/variants/option_types_presenter.rb +0 -87
  1460. data/app/services/spree/account/addresses/create.rb +0 -8
  1461. data/app/services/spree/account/addresses/update.rb +0 -8
  1462. data/app/services/spree/account/create.rb +0 -17
  1463. data/app/services/spree/account/update.rb +0 -15
  1464. data/app/services/spree/cart/add_item.rb +0 -63
  1465. data/app/services/spree/cart/associate.rb +0 -29
  1466. data/app/services/spree/cart/change_currency.rb +0 -27
  1467. data/app/services/spree/cart/create.rb +0 -26
  1468. data/app/services/spree/cart/destroy.rb +0 -50
  1469. data/app/services/spree/cart/empty.rb +0 -38
  1470. data/app/services/spree/cart/estimate_shipping_rates.rb +0 -39
  1471. data/app/services/spree/cart/recalculate.rb +0 -35
  1472. data/app/services/spree/cart/remove_item.rb +0 -39
  1473. data/app/services/spree/cart/remove_line_item.rb +0 -28
  1474. data/app/services/spree/cart/remove_out_of_stock_items.rb +0 -65
  1475. data/app/services/spree/cart/set_quantity.rb +0 -32
  1476. data/app/services/spree/cart/update.rb +0 -37
  1477. data/app/services/spree/carts/complete.rb +0 -50
  1478. data/app/services/spree/carts/upsert_items.rb +0 -76
  1479. data/app/services/spree/checkout/complete.rb +0 -23
  1480. data/app/services/spree/checkout/get_shipping_rates.rb +0 -52
  1481. data/app/services/spree/checkout/next.rb +0 -13
  1482. data/app/services/spree/checkout/select_shipping_method.rb +0 -63
  1483. data/app/services/spree/checkout/update.rb +0 -72
  1484. data/app/services/spree/credit_cards/destroy.rb +0 -25
  1485. data/app/services/spree/fulfillments/create.rb +0 -259
  1486. data/app/services/spree/gift_cards/apply.rb +0 -78
  1487. data/app/services/spree/gift_cards/redeem.rb +0 -17
  1488. data/app/services/spree/gift_cards/remove.rb +0 -39
  1489. data/app/services/spree/line_items/create.rb +0 -20
  1490. data/app/services/spree/line_items/destroy.rb +0 -18
  1491. data/app/services/spree/line_items/helper.rb +0 -11
  1492. data/app/services/spree/line_items/update.rb +0 -17
  1493. data/app/services/spree/orders/build_shipments.rb +0 -29
  1494. data/app/services/spree/orders/cancel.rb +0 -54
  1495. data/app/services/spree/orders/complete.rb +0 -53
  1496. data/app/services/spree/orders/update_contact_information.rb +0 -19
  1497. data/app/services/spree/orders/upsert_items.rb +0 -70
  1498. data/app/services/spree/payments/handle_webhook.rb +0 -57
  1499. data/app/services/spree/seeds/countries.rb +0 -35
  1500. data/app/services/spree/seeds/default_reimbursement_types.rb +0 -12
  1501. data/app/services/spree/seeds/shipping_categories.rb +0 -12
  1502. data/app/services/spree/seeds/states.rb +0 -63
  1503. data/app/services/spree/seeds/stock_locations.rb +0 -18
  1504. data/app/services/spree/seeds/store_credit_categories.rb +0 -14
  1505. data/app/services/spree/seeds/zones.rb +0 -52
  1506. data/app/services/spree/shipments/add_item.rb +0 -32
  1507. data/app/services/spree/shipments/change_state.rb +0 -14
  1508. data/app/services/spree/shipments/create.rb +0 -48
  1509. data/app/services/spree/shipments/helper.rb +0 -23
  1510. data/app/services/spree/shipments/remove_item.rb +0 -55
  1511. data/app/services/spree/stores/settings_defaults_by_country.rb +0 -38
  1512. data/app/services/spree/themes/duplicator.rb +0 -40
  1513. data/app/sorters/spree/base_sorter.rb +0 -47
  1514. data/app/sorters/spree/classifications/sort.rb +0 -68
  1515. data/app/sorters/spree/products/sort.rb +0 -73
  1516. data/app/subscribers/spree/report_subscriber.rb +0 -26
  1517. data/app/views/spree/addresses/_form.html.erb +0 -59
  1518. data/app/views/spree/addresses/_suggestions_box.erb +0 -14
  1519. data/app/views/spree/admin_user_mailer/confirmation_email.html.erb +0 -7
  1520. data/app/views/spree/report_mailer/report_done.html.erb +0 -7
  1521. data/app/views/spree/shared/_google_places_api_loader.html.erb +0 -7
  1522. data/app/views/spree/shared/_mailer_line_item.html.erb +0 -12
  1523. data/app/views/spree/shared/_payment.html.erb +0 -52
  1524. data/config/importmap.rb +0 -15
  1525. data/config/initializers/carmen.rb +0 -23
  1526. data/config/initializers/rails61_fixes.rb +0 -1
  1527. data/db/migrate/20250120152208_add_default_locale_to_action_text_rich_texts.rb +0 -7
  1528. data/db/migrate/20250122113708_add_first_and_last_name_to_spree_admin_class.rb +0 -9
  1529. data/db/migrate/20250204162453_create_spree_reports.rb +0 -13
  1530. data/db/migrate/20250217171018_create_action_text_video_embeds.rb +0 -11
  1531. data/db/migrate/20250508060800_add_selected_locale_to_spree_admin_users.rb +0 -8
  1532. data/db/migrate/20260429000001_create_spree_order_cancellations.rb +0 -25
  1533. data/db/migrate/20260429000002_create_spree_order_approvals.rb +0 -22
  1534. data/db/sample_data/markets.rb +0 -64
  1535. data/db/sample_data/metafield_definitions.rb +0 -7
  1536. data/db/sample_data/shipping_methods.rb +0 -61
  1537. data/lib/generators/spree/authentication/custom/custom_generator.rb +0 -49
  1538. data/lib/generators/spree/authentication/custom/templates/authentication_helpers.rb.tt +0 -50
  1539. data/lib/generators/spree/authentication/devise/devise_generator.rb +0 -84
  1540. data/lib/generators/spree/authentication/devise/templates/authentication_helpers.rb.tt +0 -50
  1541. data/lib/generators/spree/dummy/templates/app/assets/config/manifest.js +0 -0
  1542. data/lib/spree/core/components.rb +0 -31
  1543. data/lib/spree/core/controller_helpers/auth.rb +0 -52
  1544. data/lib/spree/core/controller_helpers/currency.rb +0 -59
  1545. data/lib/spree/core/controller_helpers/locale.rb +0 -99
  1546. data/lib/spree/core/controller_helpers/order.rb +0 -172
  1547. data/lib/spree/core/controller_helpers/strong_parameters.rb +0 -48
  1548. data/lib/spree/core/controller_helpers/turbo.rb +0 -19
  1549. data/lib/spree/core/partials.rb +0 -42
  1550. data/lib/spree/core/previews/report_preview.rb +0 -40
  1551. data/lib/spree/permitted_attributes.rb +0 -333
  1552. data/lib/spree/testing_support/authorization_helpers.rb +0 -72
  1553. data/lib/spree/testing_support/caching.rb +0 -47
  1554. data/lib/spree/testing_support/capybara_config.rb +0 -45
  1555. data/lib/spree/testing_support/capybara_ext.rb +0 -83
  1556. data/lib/spree/testing_support/controller_requests.rb +0 -11
  1557. data/lib/spree/testing_support/factories/adjustment_factory.rb +0 -28
  1558. data/lib/spree/testing_support/factories/asset_factory.rb +0 -12
  1559. data/lib/spree/testing_support/factories/customer_return_factory.rb +0 -36
  1560. data/lib/spree/testing_support/factories/digital_factory.rb +0 -10
  1561. data/lib/spree/testing_support/factories/image_factory.rb +0 -11
  1562. data/lib/spree/testing_support/factories/inventory_unit_factory.rb +0 -18
  1563. data/lib/spree/testing_support/factories/log_entry_factory.rb +0 -6
  1564. data/lib/spree/testing_support/factories/metafield_definition_factory.rb +0 -72
  1565. data/lib/spree/testing_support/factories/metafield_factory.rb +0 -68
  1566. data/lib/spree/testing_support/factories/prototype_factory.rb +0 -9
  1567. data/lib/spree/testing_support/factories/reimbursement_credit_factory.rb +0 -7
  1568. data/lib/spree/testing_support/factories/reimbursement_factory.rb +0 -16
  1569. data/lib/spree/testing_support/factories/reimbursement_type_factory.rb +0 -7
  1570. data/lib/spree/testing_support/factories/report_factory.rb +0 -33
  1571. data/lib/spree/testing_support/factories/return_authorization_factory.rb +0 -21
  1572. data/lib/spree/testing_support/factories/return_item_factory.rb +0 -14
  1573. data/lib/spree/testing_support/factories/shipment_factory.rb +0 -21
  1574. data/lib/spree/testing_support/factories/shipping_category_factory.rb +0 -9
  1575. data/lib/spree/testing_support/factories/shipping_method_factory.rb +0 -31
  1576. data/lib/spree/testing_support/factories/shipping_rate_factory.rb +0 -7
  1577. data/lib/spree/testing_support/factories/state_change_factory.rb +0 -8
  1578. data/lib/spree/testing_support/factories/stock_item_factory.rb +0 -22
  1579. data/lib/spree/testing_support/factories/store_credit_category_factory.rb +0 -9
  1580. data/lib/spree/testing_support/factories/store_credit_type_factory.rb +0 -16
  1581. data/lib/spree/testing_support/factories/wished_variant_factory.rb +0 -6
  1582. data/lib/spree/webhooks.rb +0 -22
  1583. data/lib/tasks/role_users.rake +0 -21
  1584. data/vendor/javascript/@rails--request.js.js +0 -4
  1585. data/vendor/javascript/@stimulus-components--auto-submit.js +0 -4
  1586. data/vendor/javascript/stimulus-reveal-controller.js +0 -4
  1587. data/vendor/javascript/stimulus-textarea-autogrow.js +0 -4
  1588. data/vendor/javascript/tailwindcss-stimulus-components.js +0 -4
@@ -4,22 +4,18 @@
4
4
  # Products properties include description, permalink, availability,
5
5
  # shipping category, etc. that do not change by variant.
6
6
  #
7
- # MASTER VARIANT
8
- # Every product has one master variant, which stores master price and sku, size and weight, etc.
9
- # The master variant does not have option values associated with it.
10
- # Price, SKU, size, weight, etc. are all delegated to the master variant.
11
- # Contains on_hand inventory levels only when there are no variants for the product.
12
- #
13
7
  # VARIANTS
14
- # All variants can access the product properties directly (via reverse delegation).
15
- # Inventory units are tied to Variant.
16
- # The master variant can have inventory units, but not option values.
17
- # All other variants have option values and may have inventory units.
18
- # Sum of on_hand each variant's inventory level determine "on_hand" level for the product.
8
+ # Every product has at least one variant. Simple products have a single variant
9
+ # with no option values; products with options have one variant per combination.
10
+ # `default_variant` is the "face" of the product — price, SKU, size, weight, etc.
11
+ # are delegated to it. Inventory units are tied to Variant; the sum of each
12
+ # variant's on_hand determines the product's "on_hand" level.
19
13
  #
20
14
 
21
15
  module Spree
22
16
  class Product < Spree.base_class
17
+ include Spree::CSV::CustomFieldsHelper
18
+
23
19
  has_prefix_id :prod # Stripe: prod_
24
20
 
25
21
  acts_as_paranoid
@@ -30,38 +26,52 @@ module Spree
30
26
  include Spree::ProductScopes
31
27
  include Spree::TranslatableResource
32
28
  include Spree::MemoizedData
33
- include Spree::Metafields
29
+ include Spree::SanitizableRichText
30
+ include Spree::HasCustomFields
31
+ include Spree::CustomFieldFilterable
34
32
  include Spree::Metadata
35
- include Spree::Product::Webhooks
33
+ include Spree::HasExternalReferences
34
+ include Spree::Searchable
36
35
  include Spree::Product::Slugs
37
36
  include Spree::Product::Channels
38
- include Spree::LegacyMultiStoreSupport unless defined?(SpreeMultiStore)
39
37
  include Spree::SearchIndexable
40
- if defined?(Spree::VendorConcern)
41
- include Spree::VendorConcern
42
- end
38
+ include Spree::StorePreferences
43
39
 
44
40
  publishes_lifecycle_events
45
41
 
46
- MEMOIZED_METHODS = %w[total_on_hand taxonomy_ids taxon_and_ancestors
42
+ MEMOIZED_METHODS = %w[total_on_hand category_and_ancestors
47
43
  default_variant_id tax_category default_variant variant_for_images
48
- brand_taxon main_taxon
44
+ primary_category buy_box_variants resolved_delivery_profile
49
45
  purchasable? in_stock? backorderable? digital?]
50
46
 
47
+ # Statuses an operator sets directly. `bulk_status_update` validates
48
+ # against this, which is why the review statuses are not in it: reaching
49
+ # `proposed` is a seller submitting and reaching `rejected` is an operator
50
+ # deciding, and neither is a value to assign in bulk.
51
51
  STATUSES = %w[draft active archived].freeze
52
52
 
53
- STATUS_TO_WEBHOOK_EVENT = {
54
- 'active' => 'activated',
55
- 'draft' => 'drafted',
56
- 'archived' => 'archived'
57
- }.freeze
53
+ # Where a product sits in the marketplace review lifecycle. Only the
54
+ # workflows (Products::Propose, ::Approve, ::Reject) move a product in or
55
+ # out of these; see docs/plans/6.0-seller-product-submission.md.
56
+ REVIEW_STATUSES = %w[proposed rejected].freeze
57
+
58
+ ALL_STATUSES = (STATUSES + REVIEW_STATUSES).freeze
58
59
 
59
60
  TRANSLATABLE_FIELDS = %i[name description slug meta_description meta_title].freeze
60
61
  RICH_TEXT_TRANSLATABLE_FIELDS = %i[description].freeze
61
62
  translates(*TRANSLATABLE_FIELDS, column_fallback: Spree.mobility_column_fallback)
62
63
 
64
+ has_spree_rich_text :description
65
+
66
+ # Translated descriptions live on the Mobility translation table, so writes
67
+ # through locale accessors or +upsert_translations+ never touch the base
68
+ # record's +description+ attribute — the translation model needs its own
69
+ # sanitization pass.
63
70
  self::Translation.class_eval do
71
+ include Spree::SanitizableRichText
72
+
64
73
  normalizes :name, :meta_title, with: ->(value) { value&.to_s&.squish&.presence }
74
+ sanitizes_rich_text :description
65
75
  end
66
76
 
67
77
  # we need to have this callback before any dependent: :destroy associations
@@ -70,10 +80,76 @@ module Spree
70
80
 
71
81
  has_many :product_option_types, -> { order(:position) }, dependent: :destroy, inverse_of: :product
72
82
  has_many :option_types, through: :product_option_types
73
- has_many :classifications, -> { order(created_at: :asc) }, dependent: :delete_all, inverse_of: :product
74
- has_many :taxons, through: :classifications, before_remove: :remove_taxon
75
- has_many :categories, through: :classifications, class_name: 'Spree::Category', source: :taxon
76
- has_many :taxonomies, through: :taxons
83
+ has_many :product_categories, -> { order(created_at: :asc) }, class_name: 'Spree::ProductCategory', dependent: :delete_all, inverse_of: :product
84
+ has_many :categories, through: :product_categories, class_name: 'Spree::Category', source: :category, before_remove: :remove_category
85
+ has_many :taxonomies, through: :categories, deprecated: true
86
+
87
+ # @deprecated The classification_count column was renamed to categories_count in 6.0.
88
+ alias_attribute :classification_count, :categories_count
89
+
90
+ # @deprecated Use #product_categories; removed in 6.1.
91
+ # The live row in the review trail, or nil for a product nobody submitted.
92
+ #
93
+ # @return [Spree::ProductSubmission, nil]
94
+ def latest_submission
95
+ submissions.latest_first.first
96
+ end
97
+
98
+ # Why the marketplace turned this listing down, for the seller to read.
99
+ # Answers nil once the product moves on, so a stale note never shows
100
+ # against a product that is no longer rejected.
101
+ #
102
+ # @return [String, nil]
103
+ def rejection_reason
104
+ return unless rejected?
105
+
106
+ latest_submission&.review_note
107
+ end
108
+
109
+ # Whether this product is waiting on, or has had, a marketplace decision.
110
+ #
111
+ # @return [Boolean]
112
+ def in_review?
113
+ REVIEW_STATUSES.include?(status)
114
+ end
115
+
116
+ def classifications
117
+ Spree::Deprecation.warn('Spree::Product#classifications is deprecated and will be removed in Spree 6.1. Use #product_categories instead.')
118
+ product_categories
119
+ end
120
+
121
+ # @deprecated Use #categories / #category_ids; removed in 6.1.
122
+ def taxons
123
+ Spree::Deprecation.warn('Spree::Product#taxons is deprecated and will be removed in Spree 6.1. Use #categories instead.')
124
+ categories
125
+ end
126
+
127
+ def taxons=(value)
128
+ Spree::Deprecation.warn('Spree::Product#taxons= is deprecated and will be removed in Spree 6.1. Use #categories= instead.')
129
+ self.categories = value
130
+ end
131
+
132
+ def taxon_ids
133
+ Spree::Deprecation.warn('Spree::Product#taxon_ids is deprecated and will be removed in Spree 6.1. Use #category_ids instead.')
134
+ category_ids
135
+ end
136
+
137
+ def taxon_ids=(ids)
138
+ Spree::Deprecation.warn('Spree::Product#taxon_ids= is deprecated and will be removed in Spree 6.1. Use #category_ids= instead.')
139
+ self.category_ids = ids
140
+ end
141
+
142
+ has_many :product_collections, class_name: 'Spree::ProductCollection', dependent: :destroy_async, inverse_of: :product
143
+ has_many :collections, through: :product_collections
144
+ has_many :catalog_products, class_name: 'Spree::CatalogProduct', dependent: :destroy_async, inverse_of: :product
145
+ has_many :catalogs, through: :catalog_products
146
+
147
+ # Dropped with the product so a deleted product stops excluding itself from
148
+ # delivery methods. Destroyed inline rather than async — a product sits in
149
+ # at most a handful of exclusion rules, and deferring it would leave the
150
+ # rule reporting an id the API can no longer resolve.
151
+ has_many :delivery_method_rule_products, class_name: 'Spree::DeliveryMethodRuleProduct',
152
+ dependent: :destroy, inverse_of: :product
77
153
 
78
154
  has_many :product_promotion_rules, class_name: 'Spree::ProductPromotionRule'
79
155
  has_many :promotion_rules, through: :product_promotion_rules, class_name: 'Spree::PromotionRule'
@@ -84,65 +160,92 @@ module Spree
84
160
  class_name: 'Spree::Promotion',
85
161
  source: :promotion
86
162
 
87
- belongs_to :tax_category, class_name: 'Spree::TaxCategory'
88
- belongs_to :shipping_category, class_name: 'Spree::ShippingCategory', inverse_of: :products
89
- has_many :shipping_methods, through: :shipping_category, class_name: 'Spree::ShippingMethod'
163
+ belongs_to :tax_category, class_name: 'Spree::TaxCategory', optional: true
164
+ belongs_to :product_type, class_name: 'Spree::ProductType', optional: true, counter_cache: :products_count
165
+ # The seller this product belongs to on a marketplace. Nil is the
166
+ # operator's own catalog — which is everything, on a store selling only
167
+ # its own goods.
168
+ belongs_to :seller, class_name: 'Spree::Seller', optional: true, inverse_of: :products
169
+
170
+ # How this product ships: origins, zones and methods all hang off the
171
+ # profile. Required — a product without one could not be fulfilled at
172
+ # all — and auto-assigned on create (type template, else the store's
173
+ # default profile), so callers never have to think about it. The FK is
174
+ # the renamed 5.x shipping_category_id, so migrated catalogs arrive
175
+ # already assigned.
176
+ belongs_to :delivery_profile, class_name: 'Spree::DeliveryProfile', inverse_of: :products
177
+
178
+ # Every one of the product's variants now resolves its profile through
179
+ # +resolved_delivery_profile+, which memoizes — so a reassignment has to
180
+ # drop the memo, or the whole fleet keeps shipping on the old profile
181
+ # until the product is saved.
182
+ %i[delivery_profile delivery_profile_id].each do |name|
183
+ define_method(:"#{name}=") do |value|
184
+ @resolved_delivery_profile = nil
185
+ @digital = nil
186
+ super(value)
187
+ end
188
+ end
90
189
 
91
- has_one :master,
92
- -> { where is_master: true },
93
- inverse_of: :product,
94
- class_name: 'Spree::Variant'
190
+ # Guards every write path (including raw prefixed-id assignment) against
191
+ # linking another store's profile.
192
+ validate :delivery_profile_must_belong_to_store, if: :delivery_profile_id_changed?
193
+ # Either side moving breaks the pair, and a product's store is not frozen
194
+ # after create, so watching only the seller would let a move carry the old
195
+ # store's seller along.
196
+ validate :seller_must_belong_to_store, if: -> { seller_id_changed? || store_id_changed? }
95
197
 
198
+ # Every product has at least one variant. `default_variant` is the "face" of
199
+ # the product (price display, default add-to-cart, property delegation).
96
200
  has_many :variants,
97
- -> { where(is_master: false).order(:position) },
98
- inverse_of: :product,
99
- class_name: 'Spree::Variant'
100
-
101
- has_many :variants_including_master,
102
201
  -> { order(:position) },
103
202
  inverse_of: :product,
104
203
  class_name: 'Spree::Variant',
105
204
  dependent: :destroy
106
205
 
107
- has_many :prices, -> { order('spree_variants.position, spree_variants.id, currency') }, through: :variants
206
+ belongs_to :default_variant, class_name: 'Spree::Variant', optional: true, autosave: true
207
+
208
+ has_many :prices, through: :variants
108
209
 
109
- has_many :stock_items, through: :variants_including_master
210
+ has_many :stock_levels, through: :variants
110
211
 
111
- has_many :line_items, through: :variants_including_master
212
+ has_many :line_items, through: :variants
112
213
  has_many :orders, through: :line_items
113
214
  has_many :completed_orders, -> { reorder(nil).distinct.complete }, through: :line_items, source: :order
114
215
 
115
- has_many :media, -> { order(:position) }, as: :viewable, dependent: :destroy, class_name: 'Spree::Asset'
216
+ has_many :media, -> { order(:position) }, as: :viewable, dependent: :destroy, class_name: 'Spree::Media'
116
217
 
117
- has_many :variant_images, -> { order(:position) }, source: :images, through: :variants_including_master
118
- has_many :variant_images_without_master, -> { order(:position) }, source: :images, through: :variants
218
+ # The review trail for a seller's listing. Append-only: the latest row is
219
+ # the live one (docs/plans/6.0-seller-product-submission.md).
220
+ has_many :submissions, class_name: 'Spree::ProductSubmission', dependent: :destroy, inverse_of: :product
119
221
 
120
- belongs_to :primary_media, class_name: 'Spree::Asset', optional: true, foreign_key: :primary_media_id
222
+ has_many :variant_images, -> { order(:position) }, source: :images, through: :variants
223
+
224
+ belongs_to :primary_media, class_name: 'Spree::Media', optional: true, foreign_key: :primary_media_id
121
225
 
122
226
  has_many :option_value_variants, class_name: 'Spree::OptionValueVariant', through: :variants
123
227
  has_many :option_values, class_name: 'Spree::OptionValue', through: :variants
124
228
 
125
- has_many :prices_including_master, -> { non_zero }, through: :variants_including_master, source: :prices
126
-
127
- has_many :digitals, through: :variants_including_master
229
+ has_many :digital_assets, through: :variants, class_name: 'Spree::DigitalAsset'
230
+ has_many :digitals, through: :variants, class_name: 'Spree::DigitalAsset', source: :digital_assets, deprecated: true
128
231
 
129
- after_initialize :ensure_master
130
232
  after_initialize :assign_default_tax_category
233
+ # The type's profile is a creation-time template: stamped here, never
234
+ # synced afterwards — reassigning the type later leaves the product's
235
+ # profile alone. Falls back to the store's default profile, keeping the
236
+ # required association satisfiable without the caller naming one.
237
+ before_validation :stamp_template_delivery_profile, on: :create
131
238
 
132
- before_validation :validate_master
133
- before_validation :ensure_default_shipping_category
239
+ after_create :sync_associations_from_product_type
240
+ after_update :sync_associations_from_product_type, if: :saved_change_to_product_type_id?
241
+ after_create :ensure_default_variant
242
+ after_create :set_default_variant
134
243
 
135
- after_create :add_associations_from_prototype
136
- after_create :build_variants_from_option_values_hash, if: :option_values_hash
137
- after_create :apply_pending_variants, if: :pending_variants?
138
- after_save :apply_pending_media, if: :pending_media?
244
+ after_save :auto_promote_default_variant
245
+ after_save :run_touch_callbacks, if: :saved_changes?
246
+ after_touch :touch_categories
139
247
 
140
- after_save :save_master
141
- after_save :run_touch_callbacks, if: :anything_changed?
142
- after_save :reset_nested_changes
143
- after_touch :touch_taxons
144
-
145
- after_commit :auto_match_taxons, if: :eligible_for_taxon_matching?
248
+ after_commit :auto_match_collections, if: :eligible_for_collection_matching?
146
249
 
147
250
  with_options length: { maximum: 255 }, allow_blank: true do
148
251
  validates :meta_keywords
@@ -150,28 +253,53 @@ module Spree
150
253
  end
151
254
  with_options presence: true do
152
255
  validates :name
153
- validates :shipping_category, if: :requires_shipping_category?
154
- validates :price, if: :requires_price?
155
256
  end
156
257
 
258
+ # Every persisted product must keep a default variant. It's assigned on
259
+ # create by +set_default_variant+, so only enforce it on update.
260
+ validates :default_variant, presence: true, on: :update
261
+
157
262
  validate :discontinue_on_must_be_later_than_make_active_at, if: -> { make_active_at && discontinue_on }
158
263
 
159
264
  scope :for_store, ->(store) { where(store_id: store.id) }
160
- scope :draft, -> { where(status: 'draft') }
161
- scope :archived, -> { where(status: 'archived') }
265
+ # Products belonging to the given seller. Passing nil selects the
266
+ # operator's own catalog, which on a store with no sellers is everything.
267
+ # The same answer `Seller#products` gives, as a scope a bulk read can chain.
268
+ scope :for_seller, ->(seller) { where(seller_id: seller.respond_to?(:id) ? seller.id : seller) }
162
269
  scope :not_archived, -> { where.not(status: 'archived') }
163
270
  scope :on_sale, lambda { |currency = nil|
164
271
  currency ||= Spree::Store.default.default_currency
165
- joins(:prices_including_master).with_currency(currency).
272
+ with_currency(currency).
273
+ where.not(spree_prices: { amount: 0 }).
166
274
  where.not(spree_prices: { compare_at_amount: [nil, 0] }).
167
275
  where("#{Spree::Price.table_name}.compare_at_amount > #{Spree::Price.table_name}.amount")
168
276
  }
169
277
 
170
- scope :search, ->(query) {
278
+ # @param store [Spree::Store] whose custom-field definitions take part in
279
+ # the search. Definitions are store-owned, so the caller says which store
280
+ # is searching; `Spree::Current.store` is the request's answer and a
281
+ # background caller (an export, say) passes its own.
282
+ scope :search, ->(query, store = Spree::Current.store) {
171
283
  next none if query.blank?
172
284
 
173
285
  product_ids = Spree::Variant.search_by_product_name_or_sku(query).pluck(:product_id)
174
- where(id: product_ids.uniq.compact)
286
+
287
+ searchable_definitions = store.custom_field_definitions.
288
+ for_resource_type('Spree::Product').
289
+ searchable
290
+
291
+ # Only pay for the custom_field scan when some definition opts into search;
292
+ # the leading-wildcard LIKE over spree_custom_fields can't use an index.
293
+ custom_field_ids = if searchable_definitions.exists?
294
+ joins(custom_fields: :custom_field_definition).
295
+ merge(searchable_definitions).
296
+ where(search_condition(Spree::CustomField, :value, query)).
297
+ unscope(:order).distinct.pluck(:id)
298
+ else
299
+ []
300
+ end
301
+
302
+ where(id: (product_ids + custom_field_ids).uniq.compact)
175
303
  }
176
304
 
177
305
  # Backward compatibility alias — remove in Spree 6.0
@@ -179,19 +307,15 @@ module Spree
179
307
 
180
308
  scope :archivable, -> { where(status: %w[active draft]) }
181
309
  scope :by_source, ->(source) { send(source) }
182
- scope :paused, -> { where(status: 'paused') }
183
310
  scope :published, -> { where(status: 'active') }
184
311
 
185
- attr_accessor :option_values_hash
186
-
187
312
  accepts_nested_attributes_for(
188
313
  :variants,
189
314
  allow_destroy: true,
190
315
  reject_if: lambda do |v|
191
- v[:option_value_variants_attributes].blank? && v[:stock_items_attributes].blank? && v[:prices_attributes].blank?
316
+ v[:option_value_variants_attributes].blank? && v[:stock_levels_attributes].blank? && v[:prices_attributes].blank?
192
317
  end
193
318
  )
194
- accepts_nested_attributes_for :master, reject_if: :all_blank
195
319
  accepts_nested_attributes_for(
196
320
  :product_option_types,
197
321
  allow_destroy: true,
@@ -206,179 +330,203 @@ module Spree
206
330
  self.tag_list = tags
207
331
  end
208
332
 
209
- # Sets prices on the master variant.
333
+ # Sets prices on the default variant.
210
334
  # Accepts array of { currency:, amount:, compare_at_amount: } hashes.
211
335
  def prices=(prices_params)
212
- find_or_build_master.prices = prices_params
336
+ find_or_build_default_variant.prices = prices_params
213
337
  end
214
338
 
215
- # Maps 6.0 API name (category_ids) to model column (taxon_ids).
216
- # Accepts both prefixed IDs and raw integer IDs. Only taxons belonging to
217
- # the product's own store are assigned ids from another store's
218
- # taxonomies are dropped, preventing cross-store category attachment.
339
+ # Accepts both prefixed IDs and raw integer IDs. Only categories belonging to
340
+ # the product's own store are assigned ids from another store are dropped,
341
+ # preventing cross-store category attachment. Decodes prefixed IDs, then calls
342
+ # the generated setter via super.
219
343
  def category_ids=(ids)
220
344
  decoded_ids = Array(ids).filter_map do |id|
221
- id.to_s.include?('_') ? Spree::Taxon.decode_prefixed_id(id) : id
345
+ id.to_s.include?('_') ? Spree::Category.decode_prefixed_id(id) : id
222
346
  end
223
- self.taxon_ids = Spree::Taxon.for_store(assignable_store).where(id: decoded_ids).ids
347
+ super(Spree::Category.for_store(assignable_store).where(id: decoded_ids).ids)
224
348
  end
225
349
 
226
- # Sync media inline. Entries with `id` patch the existing asset
227
- # (alt/position/variant_ids); entries with `signed_id` create + attach a
228
- # fresh upload; missing items are left alone (delete still goes through
229
- # the dedicated DELETE /media endpoint to avoid accidental data loss when
230
- # a form ships stale state).
350
+ # Accepts both prefixed IDs and raw integer IDs. Only collections belonging
351
+ # to the product's own store are assigned; this overrides the generated
352
+ # setter and calls super.
231
353
  #
232
- # Deferred: ActiveStorage attaches require a persisted record, so on new
233
- # records we stash the params and replay them in `after_create`.
234
- # @param media_params [Array<Hash>]
235
- # @return [void]
236
- def media=(media_params)
237
- # Blank input is a no-op never call `super` with an empty array,
238
- # because the ActiveRecord collection setter would replace media with
239
- # `[]` and trigger `dependent: :destroy` on every persisted asset.
240
- # Explicit deletes go through the dedicated DELETE /media endpoint.
241
- return if media_params.blank?
242
- return super if media_params.first.is_a?(Spree::Asset)
243
-
244
- if new_record?
245
- @pending_media_params = media_params
246
- return
247
- end
248
-
249
- apply_media(media_params)
250
- end
251
-
252
- # Syncs variants from an array of hashes.
253
- # Creates new variants, updates existing ones (matched by :id), and removes unlisted ones.
254
- # Must be called on a persisted product (use after_save or call explicitly after create).
255
- # @param variants_params [Array<Hash>] array of variant attribute hashes
256
- # @return [void]
257
- def variants=(variants_params)
258
- return super if variants_params.blank? || variants_params.first.is_a?(Spree::Variant)
259
-
260
- # Store for deferred processing if product is not yet persisted
261
- if new_record?
262
- @pending_variants_params = variants_params
263
- return
354
+ # This is a replace setter, so it governs manual membership only. Automatic
355
+ # collections are materialized from their rules, which means they must
356
+ # survive a write that doesn't mention them (otherwise saving a product
357
+ # would wipe rule-derived membership until the next regeneration) and must
358
+ # not be assignable by hand (a hand-set member would vanish at that
359
+ # regeneration). So incoming automatic ids are ignored, and the product's
360
+ # existing automatic memberships are carried over untouched.
361
+ def collection_ids=(ids)
362
+ decoded_ids = Array(ids).filter_map do |id|
363
+ id.to_s.include?('_') ? Spree::Collection.decode_prefixed_id(id) : id
264
364
  end
265
-
266
- apply_variants(variants_params)
365
+ manual_ids = Spree::Collection.for_store(assignable_store).manual.where(id: decoded_ids).ids
366
+ super(manual_ids | collections.automatic.ids)
267
367
  end
268
368
 
269
- self.whitelisted_ransackable_attributes = %w[description name slug discontinue_on status available_on created_at updated_at]
270
- self.whitelisted_ransackable_associations = %w[taxons categories store channels variants_including_master master variants tags labels
271
- shipping_category classifications option_types]
272
- self.whitelisted_ransackable_scopes = %w[not_discontinued search_by_name in_taxon in_category in_categories price_between
369
+ self.whitelisted_ransackable_attributes = %w[description name slug discontinue_on status available_on created_at updated_at seller_id]
370
+ self.whitelisted_ransackable_associations = %w[categories collections store channels variants default_variant tags labels
371
+ product_type product_categories option_types seller]
372
+ self.whitelisted_ransackable_scopes = %w[not_discontinued search_by_name in_taxon in_category in_categories in_collection price_between
273
373
  price_lte price_gte
274
374
  search multi_search in_stock out_of_stock with_option_value_ids
275
375
 
276
376
  ascend_by_price descend_by_price]
277
377
 
378
+ # All product-level convenience attributes delegate to the default variant —
379
+ # one target, no branching. Reads resolve the existing default variant
380
+ # (nil-safe) and never build one as a side effect; only setters build on
381
+ # demand, so `product.sku = ...` still works before the product is persisted.
278
382
  [
279
- :sku, :barcode, :weight, :height, :width, :depth, :is_master, :dimensions_unit, :weight_unit
383
+ :sku, :barcode, :weight, :height, :width, :depth, :dimensions_unit, :weight_unit,
384
+ :price_in, :amount_in, :compare_at_amount_in,
385
+ :cost_currency, :cost_price, :track_inventory
280
386
  ].each do |method_name|
281
- delegate method_name, :"#{method_name}=", to: :find_or_build_master
387
+ delegate method_name, to: :default_variant, allow_nil: true
388
+ delegate :"#{method_name}=", to: :find_or_build_default_variant
282
389
  end
283
390
 
284
- [
285
- :price, :price_in, :amount_in, :compare_at_price, :compare_at_amount_in,
286
- :currency, :cost_currency, :cost_price, :track_inventory
287
- ].each do |method_name|
288
- delegate method_name, :"#{method_name}=", to: :default_variant
391
+ # Read-only delegations: these have no product-level writer. Prices are
392
+ # written per currency through the default variant's +set_price+.
393
+ delegate :compare_at_price, :track_inventory?, :images, to: :default_variant, allow_nil: true
394
+
395
+ # No state machine — status moves through the Spree::Products workflows
396
+ # (docs/plans/6.0-service-workflows.md). has_status generates the `draft`
397
+ # and `archived` scopes and every predicate, but never redefines what the
398
+ # model already owns: `Product.active` stays the currency-aware scope from
399
+ # Spree::ProductScopes rather than a plain status lookup.
400
+ include Spree::HasStatus
401
+ has_status(*ALL_STATUSES, default: :draft)
402
+
403
+ # @deprecated Call Spree.product_activate_workflow — removed in 6.1.
404
+ def activate!
405
+ Spree::Deprecation.warn('Spree::Product#activate! is deprecated and will be removed in Spree 6.1. Call Spree.product_activate_workflow instead.')
406
+ run_status_workflow(Spree.product_activate_workflow)
289
407
  end
290
408
 
291
- delegate :display_amount, :display_price, :has_default_price?, :track_inventory?,
292
- :display_compare_at_price, :images, to: :default_variant
409
+ # @deprecated Call Spree.product_archive_workflow — removed in 6.1.
410
+ def archive!
411
+ Spree::Deprecation.warn('Spree::Product#archive! is deprecated and will be removed in Spree 6.1. Call Spree.product_archive_workflow instead.')
412
+ run_status_workflow(Spree.product_archive_workflow)
413
+ end
293
414
 
294
- # Rails doesn't provide _id methods for has_one associations by default
295
- delegate :id, to: :master, prefix: true, allow_nil: true
415
+ # @deprecated Call Spree.product_draft_workflow removed in 6.1.
416
+ def draft!
417
+ Spree::Deprecation.warn('Spree::Product#draft! is deprecated and will be removed in Spree 6.1. Call Spree.product_draft_workflow instead.')
418
+ run_status_workflow(Spree.product_draft_workflow)
419
+ end
296
420
 
297
- alias master_images images
421
+ def self.bulk_auto_match_collections(store, product_ids)
422
+ return if store.collections.automatic.none?
298
423
 
299
- state_machine :status, initial: :draft do
300
- event :activate do
301
- transition to: :active
302
- end
303
- after_transition to: :active, do: [:after_activate, :send_product_activated_webhook, :publish_product_activated_event]
424
+ products_to_auto_match_ids = store.products.not_deleted.not_archived.where(id: product_ids).ids
304
425
 
305
- event :archive do
306
- transition to: :archived
426
+ auto_match_collections_jobs = products_to_auto_match_ids.map do |product_id|
427
+ Spree::Products::AutoMatchCollectionsJob.new(product_id).tap { |job| job.scheduled_at = 30.seconds.from_now }
307
428
  end
308
- after_transition to: :archived, do: [:after_archive, :send_product_archived_webhook, :publish_product_archived_event]
309
429
 
310
- event :draft do
311
- transition to: :draft
312
- end
313
- after_transition to: :draft, do: [:after_draft, :send_product_drafted_webhook]
430
+ ActiveJob.perform_all_later(auto_match_collections_jobs)
314
431
  end
315
432
 
316
- def self.bulk_auto_match_taxons(store, product_ids)
317
- return if store.taxons.automatic.none?
318
-
319
- products_to_auto_match_ids = store.products.not_deleted.not_archived.where(id: product_ids).ids
433
+ # The variant this product leads with: its price, its stock, and what the
434
+ # add-to-cart button buys. On a product only the operator sells this is
435
+ # simply the cheapest available variant; once sellers share the listing it
436
+ # is whichever of their offers wins the buy box.
437
+ #
438
+ # Computed per read and never stored — it turns on price, stock and who is
439
+ # currently selling, none of which hold still
440
+ # (docs/plans/6.0-multi-vendor-marketplace.md, Decision 11).
441
+ #
442
+ # @param currency [String, nil]
443
+ # @param option_value_ids [Array<String, Integer>, nil] narrows to one option
444
+ # combination, so a `Condition` option gives a new buy box and a used one
445
+ # @return [Spree::Variant, nil]
446
+ def buy_box_variant(currency: nil, option_value_ids: nil)
447
+ currency ||= Spree::Current.currency
448
+ key = [currency, option_value_ids]
449
+ @buy_box_variants ||= {}
450
+ return @buy_box_variants[key] if @buy_box_variants.key?(key)
320
451
 
321
- auto_match_taxons_jobs = products_to_auto_match_ids.map do |product_id|
322
- Spree::Products::AutoMatchTaxonsJob.new(product_id).tap { |job| job.scheduled_at = 30.seconds.from_now }
323
- end
452
+ @buy_box_variants[key] = Spree::Dependencies.product_buy_box_service.constantize.call(
453
+ product: self, currency: currency, option_value_ids: option_value_ids
454
+ ).value
455
+ end
324
456
 
325
- ActiveJob.perform_all_later(auto_match_taxons_jobs)
457
+ # The variant this product leads with in a currency: the buy-box winner,
458
+ # else the default. One definition, because a caller that prices a page and
459
+ # a caller that preloads for it must land on the same variant — otherwise
460
+ # the preload misses and every row falls back to a query of its own.
461
+ #
462
+ # @param currency [String, nil]
463
+ # @return [Spree::Variant, nil]
464
+ def featured_variant(currency: nil)
465
+ buy_box_variant(currency: currency) || default_variant
326
466
  end
327
467
 
328
- # Can't use short form block syntax due to https://github.com/Netflix/fast_jsonapi/issues/259
468
+ # Availability asks whether *anyone who is selling today* has this on the
469
+ # shelf. It deliberately does not ask the buy box: the winner is picked
470
+ # by price in one currency, and stock is a fact about the shelf, not about
471
+ # pricing — a variant stocked but priced only in another currency is still
472
+ # in stock. What it does share with the buy box is the seller test: a
473
+ # variant from a seller who is suspended, still onboarding or away is not
474
+ # for sale, so it cannot make the product available.
329
475
  def purchasable?
330
- @purchasable ||= default_variant.purchasable? || variants.any?(&:purchasable?)
476
+ return @purchasable unless @purchasable.nil?
477
+
478
+ @purchasable = sellable_variants.any?(&:purchasable?)
331
479
  end
332
480
 
333
- # Can't use short form block syntax due to https://github.com/Netflix/fast_jsonapi/issues/259
334
481
  def in_stock?
335
- @in_stock ||= default_variant.in_stock? || variants.any?(&:in_stock?)
336
- end
482
+ return @in_stock unless @in_stock.nil?
337
483
 
338
- # Can't use short form block syntax due to https://github.com/Netflix/fast_jsonapi/issues/259
339
- def backorderable?
340
- default_variant.backorderable? || variants.any?(&:backorderable?)
484
+ @in_stock = sellable_variants.any?(&:in_stock?)
341
485
  end
342
486
 
343
- def on_sale?(currency)
344
- prices_including_master.find_all { |p| p.currency == currency }.any?(&:discounted?)
487
+ def backorderable?
488
+ sellable_variants.any?(&:backorderable?)
345
489
  end
346
490
 
347
- def find_or_build_master
348
- master || build_master
491
+ # The variants a shopper could actually buy from: every one whose seller
492
+ # is selling today, first-party included. Reads the loaded association so
493
+ # a serialized list resolves it from variants it already holds.
494
+ #
495
+ # @return [Array<Spree::Variant>]
496
+ def sellable_variants
497
+ variants.select do |variant|
498
+ seller = variant.resolved_seller
499
+ seller.nil? || seller.sellable?
500
+ end
349
501
  end
350
502
 
351
- # Checks if product has variants (non-master variants)
352
- # Uses variant_count counter cache for performance
353
- # @return [Boolean]
354
- def has_variants?
355
- return variants.size.positive? if variants.loaded?
356
-
357
- variant_count.positive?
503
+ def on_sale?(currency)
504
+ prices.find_all { |p| p.currency == currency && p.non_zero? }.any?(&:discounted?)
358
505
  end
359
506
 
360
- # Returns default Variant for Product
361
- # If `track_inventory_levels` is enabled it will try to find the first Variant
362
- # in stock or backorderable, if there's none it will return first Variant sorted
363
- # by `position` attribute
364
- # If `track_inventory_levels` is disabled it will return first Variant sorted
365
- # by `position` attribute
366
- #
507
+ # The default variant is a persisted FK, but before the product is saved (or
508
+ # if it somehow has no default set) fall back to the first variant —
509
+ # including one built in memory via the delegated setters.
367
510
  # @return [Spree::Variant]
368
511
  def default_variant
369
- @default_variant ||= if Spree::Config[:track_inventory_levels] && has_variants? && available_variant = variants.detect(&:purchasable?)
370
- available_variant
371
- else
372
- has_variants? ? variants.first : find_or_build_master
373
- end
512
+ super || variants.first
374
513
  end
375
514
 
376
- # Returns default Variant ID for Product
377
- # @return [Integer]
378
- def default_variant_id
379
- @default_variant_id ||= default_variant.id
515
+ def find_or_build_default_variant
516
+ default_variant || variants.build
380
517
  end
381
518
 
519
+ # "Does this product have more than one variant (i.e. options to choose from)?"
520
+ # Uses the variant_count counter cache for performance.
521
+ # @return [Boolean]
522
+ def has_multiple_variants?
523
+ return variants.size > 1 if variants.loaded?
524
+
525
+ variant_count > 1
526
+ end
527
+ # @deprecated Use #has_multiple_variants? — will be removed in Spree 6.1.
528
+ alias has_variants? has_multiple_variants?
529
+
382
530
  # Returns the product's media gallery.
383
531
  # Uses product-level media if present, otherwise falls back to variant images.
384
532
  # @return [ActiveRecord::Relation]
@@ -401,34 +549,19 @@ module Spree
401
549
  alias has_variant_images? has_media?
402
550
 
403
551
  # Returns the variant that should be used for displaying images.
404
- # Priority: master > default_variant > first variant with images
552
+ # Priority: default_variant > first variant with images
405
553
  # @return [Spree::Variant, nil]
406
554
  def variant_for_images
407
555
  @variant_for_images ||= find_variant_for_images
408
556
  end
409
557
 
410
- # @deprecated Use #primary_media instead.
411
- def default_image
412
- Spree::Deprecation.warn('Spree::Product#default_image is deprecated and will be removed in Spree 6.0. Please use Spree::Product#primary_media instead.')
413
- primary_media
414
- end
415
-
416
- # @deprecated Use #primary_media instead.
417
- def featured_image
418
- Spree::Deprecation.warn('Spree::Product#featured_image is deprecated and will be removed in Spree 6.0. Please use Spree::Product#primary_media instead.')
419
- primary_media
420
- end
421
-
422
- # @deprecated Use #primary_media instead.
423
- def primary_image
424
- Spree::Deprecation.warn('Spree::Product#primary_image is deprecated and will be removed in Spree 6.0. Please use Spree::Product#primary_media instead.')
425
- primary_media
426
- end
427
-
428
- # Returns secondary media for Product (for hover effects).
429
- # @return [Spree::Asset, nil]
558
+ # @deprecated Read #gallery_media directly; removed in 6.1. Nothing in
559
+ # Spree calls this — a hover image is a storefront presentation choice,
560
+ # not something core should name.
561
+ # @return [Spree::Media, nil]
430
562
  def secondary_image
431
- variant_for_images&.secondary_image
563
+ Spree::Deprecation.warn('Spree::Product#secondary_image is deprecated and will be removed in Spree 6.1. Use #gallery_media instead.')
564
+ gallery_media.second
432
565
  end
433
566
 
434
567
  # @deprecated Use media_count instead
@@ -440,7 +573,14 @@ module Spree
440
573
  # Checks product-level media first, then falls back to variant images.
441
574
  # Called when media is added, removed, or reordered.
442
575
  def update_thumbnail!
443
- first_media = media.order(:position).first || variant_images.order(:position).first
576
+ # Fresh scopes, never the cached associations — this runs from a media
577
+ # row's after_commit, where a loaded association is missing its siblings.
578
+ candidates = media.reload.order(:position).presence || variant_images.reload.order(:position)
579
+ # Only media that can draw as an image: `thumbnail_url` is rendered in an
580
+ # <img> everywhere it's used, so a video with no still leaves no
581
+ # thumbnail at all rather than a broken one.
582
+ first_media = candidates.find(&:renderable_as_image?)
583
+
444
584
  update_column(:primary_media_id, first_media&.id)
445
585
  end
446
586
 
@@ -461,12 +601,9 @@ module Spree
461
601
  # Returns tax category for Product
462
602
  # @return [Spree::TaxCategory, nil]
463
603
  def tax_category
464
- @tax_category ||= super || TaxCategory.default
604
+ @tax_category ||= super || TaxCategory.default(assignable_store)
465
605
  end
466
606
 
467
- # Adding properties and option types on creation based on a chosen prototype
468
- attr_accessor :prototype_id
469
-
470
607
  def first_or_default_variant(currency)
471
608
  if !has_variants?
472
609
  default_variant
@@ -477,38 +614,50 @@ module Spree
477
614
  end
478
615
  end
479
616
 
617
+ # The first variant a shopper can actually buy, in this currency.
618
+ #
619
+ # Position order, deliberately — this is "the first one that works", not
620
+ # the buy box's ranked winner. What it does borrow from the buy box is the
621
+ # seller check: a variant from a seller who is suspended, still onboarding
622
+ # or away is not for sale, so it cannot be what the product displays.
480
623
  def first_available_variant(currency)
481
- variants.find { |v| v.purchasable? && v.price_in(currency).amount.present? }
624
+ sellable_variants.find do |variant|
625
+ variant.purchasable? && variant.price_in(currency).amount.present?
626
+ end
482
627
  end
483
628
 
629
+ # Whether the shop price differs between this product's variants. Reads
630
+ # base prices only: a price list's rows are what one audience pays under an
631
+ # agreement, and a ladder holds several rows for one variant, so counting
632
+ # them would report a product as varying when it does not
633
+ # (docs/plans/6.0-volume-pricing.md).
484
634
  def price_varies?(currency)
485
- prices_including_master.find_all { |p| p.currency == currency && p.amount.present? }.map(&:amount).uniq.count > 1
635
+ shop_prices(currency).map(&:amount).uniq.count > 1
486
636
  end
487
637
 
488
638
  def any_variant_available?(currency)
489
- if has_variants?
490
- first_available_variant(currency).present?
491
- else
492
- master.purchasable? && master.price_in(currency).amount.present?
493
- end
639
+ first_available_variant(currency).present?
494
640
  end
495
641
 
496
642
  # returns the lowest price for the product in the given currency
497
- # prices_including_master are usually already loaded, so this should not trigger an extra query
643
+ # prices are usually already loaded, so this should not trigger an extra query
498
644
  def lowest_price(currency)
499
- prices_including_master.find_all { |p| p.currency == currency }.min_by(&:amount)
645
+ shop_prices(currency).min_by(&:amount)
500
646
  end
501
647
 
502
- # Ensures option_types and product_option_types exist for keys in option_values_hash
503
- def ensure_option_types_exist_for_values_hash
504
- return if option_values_hash.nil?
505
-
506
- # we need to convert the keys to string to make it work with UUIDs
507
- required_option_type_ids = option_values_hash.keys.map(&:to_s)
508
- missing_option_type_ids = required_option_type_ids - option_type_ids.map(&:to_s)
509
- missing_option_type_ids.each do |id|
510
- product_option_types.create(option_type_id: id)
511
- end
648
+ # The prices this product is offered at publicly — one per variant. A price
649
+ # list's rows are excluded: they are what one audience pays under an
650
+ # agreement, so surfacing the cheapest of them as the product's "from"
651
+ # price would quote every shopper a contract they are not party to.
652
+ #
653
+ # Filtered in memory rather than through a scope because callers have
654
+ # already loaded the association, and a scope would re-query per product
655
+ # in a listing.
656
+ #
657
+ # @param currency [String]
658
+ # @return [Array<Spree::Price>]
659
+ def shop_prices(currency)
660
+ prices.find_all { |p| p.currency == currency && p.price_list_id.nil? && p.non_zero? }
512
661
  end
513
662
 
514
663
  # for adding products which are closely related to existing ones
@@ -554,21 +703,14 @@ module Spree
554
703
  !!discontinue_on && discontinue_on <= Time.current
555
704
  end
556
705
 
557
- # determine if any variant (including master) can be supplied
706
+ # determine if any variant can be supplied
558
707
  def can_supply?
559
- variants_including_master.any?(&:can_supply?)
708
+ variants.any?(&:can_supply?)
560
709
  end
561
710
 
562
- # determine if any variant (including master) is out of stock and backorderable
711
+ # determine if any variant is out of stock and backorderable
563
712
  def backordered?
564
- variants_including_master.any?(&:backordered?)
565
- end
566
-
567
- def self.like_any(fields, values)
568
- conditions = fields.product(values).map do |(field, value)|
569
- arel_table[field].matches("%#{value}%")
570
- end
571
- where conditions.inject(:or)
713
+ variants.any?(&:backordered?)
572
714
  end
573
715
 
574
716
  # Suitable for displaying only variants that has at least one option value.
@@ -584,78 +726,91 @@ module Spree
584
726
  @total_on_hand ||= if any_variants_not_track_inventory?
585
727
  BigDecimal::INFINITY
586
728
  else
587
- if variants_including_master.loaded?
588
- variants_including_master.sum(&:total_on_hand)
729
+ if variants.loaded?
730
+ variants.sum(&:total_on_hand)
589
731
  else
590
- stock_items.loaded? ? stock_items.sum(&:count_on_hand) : stock_items.sum(:count_on_hand)
732
+ stock_levels.loaded? ? stock_levels.sum(&:available_count) : stock_levels.sum('count_on_hand - allocated_count')
591
733
  end
592
734
  end
593
735
  end
594
736
 
595
- # Master variant may be deleted (i.e. when the product is deleted)
596
- # which would make AR's default finder return nil.
597
- # This is a stopgap for that little problem.
737
+ # @deprecated Use {#stock_levels}; removed in 6.1.
738
+ def stock_items
739
+ Spree::Deprecation.warn('Spree::Product#stock_items is deprecated and will be removed in Spree 6.1. Use #stock_levels instead.')
740
+ stock_levels
741
+ end
742
+
743
+ # @deprecated Use #default_variant — the master variant is being removed in Spree 6.1.
598
744
  def master
599
- super || variants_including_master.with_deleted.find_by(is_master: true)
600
- end
601
-
602
- # Returns the brand taxon for the product
603
- # @return [Spree::Taxon]
604
- def brand_taxon
605
- @brand_taxon ||= if classification_count.zero?
606
- nil
607
- elsif Spree.use_translations?
608
- taxons.joins(:taxonomy).
609
- join_translation_table(Taxonomy).
610
- find_by(Taxonomy.translation_table_alias => { name: Spree.t(:taxonomy_brands_name) })
611
- elsif taxons.loaded?
612
- taxons.find { |taxon| taxon.taxonomy.name == Spree.t(:taxonomy_brands_name) }
613
- else
614
- taxons.joins(:taxonomy).find_by(Taxonomy.table_name => { name: Spree.t(:taxonomy_brands_name) })
615
- end
616
- end
617
-
618
- # Returns the brand name for the product
619
- # @return [String]
620
- def brand_name
621
- brand_taxon&.name
745
+ Spree::Deprecation.warn('Spree::Product#master is deprecated and will be removed in Spree 6.1. Use #default_variant instead.')
746
+ default_variant
622
747
  end
623
748
 
624
- def main_taxon
625
- return if classification_count.zero?
749
+ # @deprecated Use #variants — will be removed in Spree 6.1.
750
+ def variants_including_master
751
+ Spree::Deprecation.warn('Spree::Product#variants_including_master is deprecated and will be removed in Spree 6.1. Use #variants instead.')
752
+ variants
753
+ end
626
754
 
627
- @main_taxon ||= taxons.first
755
+ # @deprecated Use #default_variant_id — will be removed in Spree 6.1.
756
+ def master_id
757
+ default_variant_id
628
758
  end
629
759
 
630
- def taxons_for_store(store)
631
- return if classification_count.zero?
760
+ # The product's primary category — the first manually-assigned category.
761
+ # Renamed from #main_taxon in 6.0.
762
+ def primary_category
763
+ return if categories_count.zero?
632
764
 
633
- taxons.loaded? ? taxons.find_all { |taxon| taxon.taxonomy.store_id == store.id } : taxons.for_store(store)
765
+ @primary_category ||= categories.first
766
+ end
767
+
768
+ # @deprecated Use {#primary_category}.
769
+ def main_taxon
770
+ Spree::Deprecation.warn('Spree::Product#main_taxon is deprecated and will be removed in Spree 6.1. Use #primary_category instead.')
771
+ primary_category
634
772
  end
635
773
 
636
774
  def any_variant_in_stock_or_backorderable?
637
775
  if has_variants?
638
- variants_including_master.in_stock_or_backorderable.exists?
776
+ variants.in_stock_or_backorderable.not_discontinued.exists?
639
777
  else
640
- master.in_stock_or_backorderable?
778
+ default_variant.in_stock_or_backorderable?
641
779
  end
642
780
  end
643
781
 
644
782
  # Check if the product is digital by checking if any of its shipping methods are digital delivery
645
783
  # This is used to determine if the product is digital and should have a digital delivery price
646
- # instead of a physical shipping price
784
+ # instead of a physical shipping price. The profile kind declares it —
785
+ # capabilities are never stated twice.
647
786
  #
648
787
  # @return [Boolean]
649
788
  def digital?
650
- @digital ||= shipping_category&.includes_digital_shipping_method?
789
+ # Memoized via nil-check: MemoizedData resets caches by assigning nil.
790
+ return @digital unless @digital.nil?
791
+
792
+ @digital = !!resolved_delivery_profile&.digital?
793
+ end
794
+
795
+ # The profile that governs this product. The association is required for
796
+ # new records; the store-default fallback covers rows predating the
797
+ # backfill (never expected in practice, cheap to be safe about).
798
+ #
799
+ # @return [Spree::DeliveryProfile, nil]
800
+ def resolved_delivery_profile
801
+ # Memoized via nil-check, like +digital?+ above: the store-default branch
802
+ # is an uncached lookup, and every variant on the product asks.
803
+ return @resolved_delivery_profile unless @resolved_delivery_profile.nil?
804
+
805
+ @resolved_delivery_profile = delivery_profile || (store && Spree::DeliveryProfile.default_for(store))
651
806
  end
652
807
 
653
- def auto_match_taxons
808
+ def auto_match_collections
654
809
  return if deleted?
655
810
  return if archived?
656
- return if store.nil? || store.taxons.automatic.none?
811
+ return if store.nil? || store.collections.automatic.none?
657
812
 
658
- Spree::Products::AutoMatchTaxonsJob.set(wait: 30.seconds).perform_later(id)
813
+ Spree::Products::AutoMatchCollectionsJob.set(wait: 30.seconds).perform_later(id)
659
814
  end
660
815
 
661
816
  def to_csv(store = nil)
@@ -670,21 +825,22 @@ module Spree
670
825
  else
671
826
  []
672
827
  end
673
- metafields_for_csv ||= Spree::MetafieldDefinition.for_resource_type('Spree::Product').order(:namespace, :key).map do |mf_def|
674
- metafields.find { |mf| mf.metafield_definition_id == mf_def.id }&.csv_value
675
- end
676
- taxons_for_csv ||= taxons.manual.reorder(depth: :desc).first(3).pluck(:pretty_name)
677
- taxons_for_csv.fill(nil, taxons_for_csv.size...3)
828
+ # Through the shared helper so the value row is built from exactly the
829
+ # definitions, in exactly the order, that Export#custom_fields_headers
830
+ # used for the header row — a mismatch shifts every value column.
831
+ custom_field_values = custom_fields_for_csv(self, store)
832
+ categories_for_csv ||= categories.reorder(depth: :desc).first(3).pluck(:pretty_name)
833
+ categories_for_csv.fill(nil, categories_for_csv.size...3)
678
834
 
679
835
  csv_lines = []
680
- all_variants = has_variants? ? variants_including_master.to_a : [master]
836
+ all_variants = variants.to_a
681
837
  default_currency = store.default_currency
682
838
  additional_currencies = store.supported_currencies_list.map(&:iso_code) - [default_currency]
683
839
 
684
840
  # Primary rows in the store's default currency
685
841
  all_variants.each_with_index do |variant, index|
686
- csv_lines << Spree::CSV::ProductVariantPresenter.new(self, variant, index, properties_for_csv, taxons_for_csv, store,
687
- metafields_for_csv).call
842
+ csv_lines << Spree::CSV::ProductVariantPresenter.new(self, variant, index, properties_for_csv, categories_for_csv, store,
843
+ custom_field_values).call
688
844
  end
689
845
 
690
846
  # Price-only rows for each additional currency
@@ -714,257 +870,118 @@ module Spree
714
870
  private
715
871
 
716
872
  # Determines which variant should be used for displaying media.
717
- # Priority: master > default_variant > first variant with media
873
+ # Priority: default_variant > first variant with media
718
874
  def find_variant_for_images
719
- return master if master.has_media?
720
- return default_variant if has_variants? && default_variant.has_media?
875
+ return default_variant if default_variant&.has_media?
721
876
  return find_variant_with_images if has_media?
722
877
 
723
878
  nil
724
879
  end
725
880
 
726
- def pending_variants?
727
- @pending_variants_params.present?
728
- end
729
-
730
- def apply_pending_variants
731
- return unless @pending_variants_params
732
-
733
- apply_variants(@pending_variants_params)
734
- @pending_variants_params = nil
735
- end
736
-
737
- def pending_media?
738
- @pending_media_params.present?
739
- end
740
-
741
- def apply_pending_media
742
- return unless @pending_media_params
743
-
744
- apply_media(@pending_media_params)
745
- @pending_media_params = nil
746
- end
747
-
748
- def apply_media(media_params)
749
- # Eager-load Asset descendants once so the type allowlist is stable across
750
- # the loop (and across requests once subclasses are referenced). Computed
751
- # per-call rather than at class-load to avoid forcing autoload of every
752
- # Asset subclass during boot.
753
- allowed_types = [Spree::Asset, *Spree::Asset.descendants].map(&:name).to_set
754
- media_params.each do |raw|
755
- attrs = raw.respond_to?(:to_h) ? raw.to_h : raw
756
- attrs = attrs.with_indifferent_access
757
-
758
- # Upsert path: entries with an `id` patch an existing asset (alt,
759
- # position, variant_ids). Entries with a `signed_id` create+attach.
760
- # Omitting an entry leaves it alone — explicit DELETE on the dedicated
761
- # media endpoint is still the only way to remove an asset.
762
- asset_id = attrs.delete(:id)
763
- if asset_id.present?
764
- asset = media.find_by_param(asset_id) || next
765
- asset.update!(attrs.except(:signed_id, :type))
766
- next
767
- end
768
-
769
- signed_id = attrs.delete(:signed_id)
770
- next if signed_id.blank?
881
+ # Additive, unlike the legacy prototype callback which replaced both sets —
882
+ # the type seeds the product when attached (creation, later assignment,
883
+ # reassignment) and never removes anything; detaching seeds nothing.
884
+ # The already-linked sets come from the join tables in one query each: the
885
+ # cached associations can be stale here (pending variants insert option-type
886
+ # joins through product_option_types within the same save).
887
+ def sync_associations_from_product_type
888
+ return unless product_type
771
889
 
772
- media_type = attrs.delete(:type) || 'Spree::Image'
773
- next unless allowed_types.include?(media_type)
890
+ missing_option_type_ids = product_type.option_type_ids - product_option_types.pluck(:option_type_id)
891
+ Spree::OptionType.where(id: missing_option_type_ids).each { |option_type| option_types << option_type }
774
892
 
775
- asset = media.build(attrs.except(:id))
776
- asset.type = media_type
777
- asset.attachment.attach(signed_id)
778
- asset.save!
779
- end
780
- end
781
-
782
- def apply_variants(variants_params)
783
- variant_ids_in_payload = []
784
- master_touched = false
785
- mutated = false
786
-
787
- variants_params.each do |variant_data|
788
- variant_data = variant_data.to_h.with_indifferent_access
789
- variant_id = variant_data.delete(:id)
790
- options = variant_data[:options]
791
-
792
- if variant_id.present?
793
- variant = variants_including_master.find_by_param!(variant_id)
794
- variant.update!(variant_data)
795
- variant_ids_in_payload << variant.id
796
- mutated = true
797
- elsif options.blank? || (options.is_a?(Array) && options.empty?)
798
- # An entry with no options addresses the master variant. Building a
799
- # non-master here would create a phantom duplicate (the auto-built
800
- # master already exists, and `variants` excludes it). Upsert onto
801
- # the master instead — the merchant-visible "default variant" on a
802
- # simple product IS the master.
803
- variant_data = variant_data.except(:options)
804
- target = find_or_build_master
805
- target.assign_attributes(variant_data)
806
- target.save!
807
- master_touched = true
808
- mutated = true
809
- else
810
- variant = variants.build
811
- variant.assign_attributes(variant_data)
812
- variant.save!
813
- variant_ids_in_payload << variant.id
814
- mutated = true
815
- end
816
- end
817
-
818
- # Remove variants not in the payload (only non-master). If only the
819
- # master was touched (simple product), leave existing non-master
820
- # variants alone — the payload is partial, not a full replacement.
821
- if variant_ids_in_payload.any? && !master_touched
822
- removed = variants.where.not(id: variant_ids_in_payload).destroy_all
823
- mutated ||= removed.any?
824
- end
825
-
826
- sync_variant_state! if mutated
827
- end
828
-
829
- # Re-syncs the in-memory derived variant state after `apply_variants`
830
- # mutates variants out-of-band (the variant counter is bumped via
831
- # `Spree::Product.increment_counter` in a Variant callback, and
832
- # `default_variant` is memoized). Without this, a freshly built/updated
833
- # product serialized in the same request returns a stale `variant_count`
834
- # and a stale `price` (delegated to the memoized `default_variant`).
835
- #
836
- # The `default_variant` memo reset here changes shape once master is
837
- # removed (it becomes a `belongs_to` FK). See
838
- # docs/plans/6.0-remove-master-variant.md (Phase 3).
839
- # @return [void]
840
- def sync_variant_state!
841
- # `variant_count` is maintained by a direct counter update in a Variant
842
- # callback, so the in-memory attribute is stale here — re-read it from the
843
- # row without reloading the whole record.
844
- self[:variant_count] = self.class.where(id: id).pick(:variant_count)
845
- variants.reset
846
- @default_variant = nil
847
- @default_variant_id = nil
848
- end
849
-
850
- def add_associations_from_prototype
851
- if prototype_id && prototype = Spree::Prototype.find_by(id: prototype_id)
852
- self.option_types = prototype.option_types
853
- self.taxons = prototype.taxons
854
- end
893
+ # Scoped to the product's own store, matching `category_ids=`: a type
894
+ # pointing at another store's category must not drag it onto the product.
895
+ missing_category_ids = product_type.category_ids - product_categories.pluck(:category_id)
896
+ Spree::Category.for_store(assignable_store).where(id: missing_category_ids).
897
+ each { |category| categories << category }
855
898
  end
856
899
 
857
900
  def any_variants_not_track_inventory?
858
- return true unless Spree::Config.track_inventory_levels
901
+ return true unless store_preference(:track_inventory_levels)
859
902
 
860
- if variants_including_master.loaded?
861
- variants_including_master.any? { |v| !v.track_inventory? }
903
+ if variants.loaded?
904
+ variants.any? { |v| !v.track_inventory? }
862
905
  else
863
- variants_including_master.where(track_inventory: false).exists?
906
+ variants.where(track_inventory: false).exists?
864
907
  end
865
908
  end
866
909
 
867
- # Builds variants from a hash of option types & values
868
- def build_variants_from_option_values_hash
869
- Spree::Deprecation.warn('Spree::Product#build_variants_from_option_values_hash is deprecated and will be removed in Spree 6.0.')
870
- ensure_option_types_exist_for_values_hash
871
- values = option_values_hash.values
872
- values = values.inject(values.shift) { |memo, value| memo.product(value).map(&:flatten) }
873
-
874
- default_currency = store&.default_currency || Spree::Store.default.default_currency
875
- master_price = master.price_in(default_currency).amount
876
-
877
- values.each do |ids|
878
- variant = variants.create!(option_value_ids: ids)
879
- variant.set_price(default_currency, master_price) if master_price.present?
880
- end
881
- save
910
+ # Every product must have at least one variant. If none was built via the
911
+ # delegated setters or supplied as nested attributes, create an empty one.
912
+ def ensure_default_variant
913
+ variants.create! if variants.empty?
882
914
  end
883
915
 
884
- def ensure_master
885
- return unless new_record?
916
+ def set_default_variant
917
+ return if default_variant_id.present?
886
918
 
887
- self.master ||= build_master
919
+ first_variant = preferred_default_variant
920
+ update_column(:default_variant_id, first_variant.id) if first_variant
888
921
  end
889
922
 
890
- def assign_default_tax_category
891
- self.tax_category = Spree::TaxCategory.default if new_record? && self[:tax_category_id].blank?
892
- end
923
+ # Re-point the default variant when the current one has been removed. Sticky
924
+ # otherwise an in-stock change never re-points (see the plan's open question).
925
+ def auto_promote_default_variant
926
+ current = association(:default_variant).reader
927
+ return if current && current.deleted_at.nil?
893
928
 
894
- def anything_changed?
895
- saved_changes? || @nested_changes
929
+ next_variant = preferred_default_variant(reload: true)
930
+ update_column(:default_variant_id, next_variant&.id) if next_variant && next_variant.id != default_variant_id
896
931
  end
897
932
 
898
- def reset_nested_changes
899
- @nested_changes = false
933
+ # The product's public identity — its headline price, its SKU, and the
934
+ # Omnibus `prior_price`, which is a compliance claim about this shop's own
935
+ # pricing history. Where the operator sells the item themselves that is the
936
+ # listing that should speak for the product, not whichever seller happens
937
+ # to sort first.
938
+ def preferred_default_variant(reload: false)
939
+ candidates = reload ? variants.reload : variants
940
+ candidates.detect { |variant| variant.resolved_seller_id.nil? } || candidates.first
900
941
  end
901
942
 
902
- def master_updated?
903
- master && (
904
- master.new_record? ||
905
- master.changed? ||
906
- (
907
- Spree::Config.enable_legacy_default_price &&
908
- master.default_price &&
909
- (
910
- master.default_price.new_record? ||
911
- master.default_price.changed?
912
- )
913
- )
914
- )
943
+ def assign_default_tax_category
944
+ self.tax_category = Spree::TaxCategory.default(assignable_store) if new_record? && self[:tax_category_id].blank?
915
945
  end
916
946
 
917
- # there's a weird quirk with the delegate stuff that does not automatically save the delegate object
918
- # when saving so we force a save using a hook
919
- # Fix for issue #5306
920
- def save_master
921
- if master_updated?
922
- master.save!
923
- @nested_changes = true
924
- end
925
- end
947
+ def stamp_template_delivery_profile
948
+ return if delivery_profile_id.present?
926
949
 
927
- # If the master cannot be saved, the Product object will get its errors
928
- # and will be destroyed
929
- def validate_master
930
- if Spree::Config.enable_legacy_default_price
931
- # We call master.default_price here to ensure price is initialized.
932
- # Required to avoid Variant#check_price validation failing on create.
933
- master.default_price
934
- end
950
+ self.delivery_profile_id = product_type&.delivery_profile_id ||
951
+ (store && Spree::DeliveryProfile.default_for(store)&.id)
952
+ end
935
953
 
936
- unless master.valid?
937
- master.errors.map { |error| { field: error.attribute, message: error&.message } }.each do |err|
938
- next if err[:field].blank? || err[:message].blank?
954
+ def delivery_profile_must_belong_to_store
955
+ return if delivery_profile.nil? || store.nil?
956
+ return if delivery_profile.store_id == store_id
939
957
 
940
- errors.add(err[:field], err[:message])
941
- end
942
- end
958
+ errors.add(:delivery_profile, :invalid)
943
959
  end
944
960
 
945
- def ensure_default_shipping_category
946
- return if shipping_category.present?
961
+ # A seller from another store would put one store's catalog inside another
962
+ # store's seller — and every seller-scoped read is rooted in exactly that
963
+ # association.
964
+ def seller_must_belong_to_store
965
+ return if seller.nil? || store.nil?
966
+ return if seller.store_id == store_id
947
967
 
948
- if new_record?
949
- name = I18n.t('spree.seed.shipping.categories.default')
950
- self.shipping_category = Spree::ShippingCategory.find_or_create_by!(name: name)
951
- end
968
+ errors.add(:seller, :invalid)
952
969
  end
953
970
 
954
971
  def run_touch_callbacks
955
972
  run_callbacks(:touch)
956
973
  end
957
974
 
958
- def taxon_and_ancestors
959
- @taxon_and_ancestors ||= taxons.map(&:self_and_ancestors).flatten.uniq
975
+ def category_and_ancestors
976
+ @category_and_ancestors ||= categories.map(&:self_and_ancestors).flatten.uniq
960
977
  end
961
978
 
962
- # Iterate through this products taxons and taxonomies and touch their timestamps in a batch
963
- def touch_taxons
964
- if taxons.any?
965
- Spree::Products::TouchTaxonsJob.
979
+ # Iterate through this product's categories and touch their timestamps in a batch
980
+ def touch_categories
981
+ if categories.any?
982
+ Spree::Products::TouchCategoriesJob.
966
983
  set(wait: 5.seconds).
967
- perform_later(taxon_and_ancestors.map(&:id), taxonomy_ids.uniq)
984
+ perform_later(category_and_ancestors.map(&:id))
968
985
  end
969
986
  end
970
987
 
@@ -975,48 +992,33 @@ module Spree
975
992
  end
976
993
  end
977
994
 
978
- def remove_taxon(taxon)
979
- removed_classifications = classifications.where(taxon: taxon)
980
- removed_classifications.each(&:remove_from_list)
995
+ def remove_category(category)
996
+ removed_product_categories = product_categories.where(category: category)
997
+ removed_product_categories.each(&:remove_from_list)
981
998
  end
982
999
 
983
1000
  def discontinue_on_must_be_later_than_make_active_at
984
- Spree::Deprecation.warn('Spree::Product#discontinue_on_must_be_later_than_make_active_at is deprecated and will be removed in Spree 6.0.')
985
1001
  if discontinue_on < make_active_at
986
1002
  errors.add(:discontinue_on, :invalid_date_range)
987
1003
  end
988
1004
  end
989
1005
 
990
- def requires_price?
991
- Spree::Config[:require_master_price]
992
- end
1006
+ # Mirrors the machine's bang events: an illegal move raised.
1007
+ def run_status_workflow(workflow)
1008
+ result = workflow.call(product: self)
1009
+
1010
+ if result.failure?
1011
+ # ResultError#to_s unwraps an ActiveModel::Errors into its full
1012
+ # messages; its `value` would inspect the object into the message.
1013
+ errors.add(:base, result.error.to_s)
1014
+ raise ActiveRecord::RecordInvalid, self
1015
+ end
993
1016
 
994
- def requires_shipping_category?
995
1017
  true
996
1018
  end
997
1019
 
998
- def eligible_for_taxon_matching?
1020
+ def eligible_for_collection_matching?
999
1021
  previously_new_record? || tag_list_previously_changed? || available_on_previously_changed?
1000
1022
  end
1001
-
1002
- def after_activate
1003
- # Implement your logic here
1004
- end
1005
-
1006
- def after_archive
1007
- # Implement your logic here
1008
- end
1009
-
1010
- def after_draft
1011
- # Implement your logic here
1012
- end
1013
-
1014
- def publish_product_activated_event
1015
- publish_event('product.activated')
1016
- end
1017
-
1018
- def publish_product_archived_event
1019
- publish_event('product.archived')
1020
- end
1021
1023
  end
1022
1024
  end