spree_core 4.10.1 → 5.0.0.rc1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (476) hide show
  1. checksums.yaml +4 -4
  2. data/app/assets/config/spree_core_manifest.js +4 -0
  3. data/app/assets/images/google_on_white_hdpi.png +0 -0
  4. data/app/controllers/spree/base_controller.rb +1 -4
  5. data/app/finders/spree/orders/find_complete.rb +14 -2
  6. data/app/finders/spree/products/find.rb +74 -23
  7. data/app/finders/spree/stores/find_current.rb +6 -2
  8. data/app/helpers/spree/addresses_helper.rb +106 -0
  9. data/app/helpers/spree/base_helper.rb +83 -55
  10. data/app/helpers/spree/currency_helper.rb +12 -0
  11. data/app/helpers/spree/images_helper.rb +18 -0
  12. data/app/helpers/spree/mail_helper.rb +24 -0
  13. data/app/helpers/spree/products_helper.rb +1 -20
  14. data/app/javascript/spree/core/controllers/address_autocomplete_controller.js +233 -0
  15. data/app/javascript/spree/core/controllers/address_form_controller.js +98 -0
  16. data/app/javascript/spree/core/controllers/enable_button_controller.js +29 -0
  17. data/app/javascript/spree/core/helpers/address_autocomplete/google_places_suggestions_provider.js +179 -0
  18. data/app/javascript/spree/core/helpers/debounce.js +9 -0
  19. data/app/javascript/spree/core/helpers/scroll_lock.js +25 -0
  20. data/app/jobs/spree/addresses/geocode_address_job.rb +31 -0
  21. data/app/jobs/spree/base_job.rb +1 -0
  22. data/app/jobs/spree/coupon_codes/bulk_generate_job.rb +17 -0
  23. data/app/jobs/spree/exports/generate_job.rb +12 -0
  24. data/app/jobs/spree/products/auto_match_taxons_job.rb +14 -0
  25. data/app/jobs/spree/products/touch_taxons_job.rb +12 -0
  26. data/app/jobs/spree/reports/generate_job.rb +12 -0
  27. data/app/jobs/spree/stock_locations/stock_items/create_job.rb +1 -1
  28. data/app/jobs/spree/themes/duplicate_components_job.rb +59 -0
  29. data/app/jobs/spree/themes/screenshot_job.rb +81 -0
  30. data/app/jobs/spree/variants/remove_from_incomplete_orders_job.rb +2 -0
  31. data/app/jobs/spree/variants/remove_line_item_job.rb +2 -0
  32. data/app/mailers/spree/base_mailer.rb +51 -0
  33. data/app/mailers/spree/export_mailer.rb +19 -0
  34. data/app/mailers/spree/report_mailer.rb +20 -0
  35. data/app/mailers/spree/test_mailer.rb +8 -0
  36. data/app/models/action_text/rich_text_decorator.rb +11 -0
  37. data/app/models/action_text/video_embed.rb +13 -0
  38. data/app/models/acts_as_taggable_on/tag_decorator.rb +10 -0
  39. data/app/models/concerns/spree/calculated_adjustments.rb +2 -0
  40. data/app/models/concerns/spree/display_on.rb +13 -0
  41. data/app/models/concerns/spree/has_one_link.rb +42 -0
  42. data/app/models/concerns/spree/has_page_links.rb +53 -0
  43. data/app/models/concerns/spree/metadata.rb +3 -7
  44. data/app/models/concerns/spree/multi_searchable.rb +21 -0
  45. data/app/models/concerns/spree/multi_store_resource.rb +9 -0
  46. data/app/models/concerns/spree/number_as_param.rb +4 -2
  47. data/app/models/concerns/spree/parameterizable_name.rb +21 -0
  48. data/app/models/concerns/spree/previewable.rb +17 -0
  49. data/app/models/concerns/spree/product_scopes.rb +7 -7
  50. data/app/models/concerns/spree/stores/setup.rb +52 -0
  51. data/app/models/concerns/spree/stores/socials.rb +68 -0
  52. data/app/models/concerns/spree/user_address.rb +4 -0
  53. data/app/models/concerns/spree/user_methods.rb +61 -11
  54. data/app/models/concerns/spree/user_reporting.rb +14 -6
  55. data/app/models/concerns/spree/user_roles.rb +1 -1
  56. data/app/models/spree/ability.rb +4 -2
  57. data/app/models/spree/address.rb +122 -28
  58. data/app/models/spree/adjustment.rb +4 -1
  59. data/app/models/spree/asset/support/active_storage.rb +1 -1
  60. data/app/models/spree/asset.rb +18 -6
  61. data/app/models/spree/base.rb +38 -7
  62. data/app/models/spree/base_analytics_event_handler.rb +49 -0
  63. data/app/models/spree/calculator.rb +1 -1
  64. data/app/models/spree/classification.rb +10 -1
  65. data/app/models/spree/country.rb +1 -1
  66. data/app/models/spree/coupon_code.rb +38 -0
  67. data/app/models/spree/credit_card.rb +4 -3
  68. data/app/models/spree/current.rb +9 -0
  69. data/app/models/spree/custom_domain.rb +53 -0
  70. data/app/models/spree/customer_return.rb +13 -1
  71. data/app/models/spree/data_feed.rb +1 -1
  72. data/app/models/spree/digital.rb +5 -6
  73. data/app/models/spree/digital_link.rb +11 -6
  74. data/app/models/spree/export.rb +172 -0
  75. data/app/models/spree/exports/orders.rb +23 -0
  76. data/app/models/spree/exports/products.rb +35 -0
  77. data/app/models/spree/gateway/bogus.rb +5 -0
  78. data/app/models/spree/gateway.rb +14 -4
  79. data/app/models/spree/gateway_customer.rb +13 -0
  80. data/app/models/spree/image/configuration/active_storage.rb +1 -1
  81. data/app/models/spree/inventory_unit.rb +1 -1
  82. data/app/models/spree/legacy_user.rb +3 -1
  83. data/app/models/spree/line_item.rb +37 -2
  84. data/app/models/spree/log_entry.rb +1 -1
  85. data/app/models/spree/option_type.rb +40 -6
  86. data/app/models/spree/option_type_prototype.rb +1 -1
  87. data/app/models/spree/option_value.rb +35 -5
  88. data/app/models/spree/option_value_variant.rb +2 -2
  89. data/app/models/spree/order/address_book.rb +34 -15
  90. data/app/models/spree/order/checkout.rb +12 -2
  91. data/app/models/spree/order/currency_updater.rb +1 -1
  92. data/app/models/spree/order/digital.rb +14 -3
  93. data/app/models/spree/order/emails.rb +1 -1
  94. data/app/models/spree/order/payments.rb +1 -13
  95. data/app/models/spree/order/store_credit.rb +17 -5
  96. data/app/models/spree/order/webhooks.rb +1 -1
  97. data/app/models/spree/order.rb +179 -49
  98. data/app/models/spree/order_promotion.rb +1 -1
  99. data/app/models/spree/order_updater.rb +9 -3
  100. data/app/models/spree/page.rb +184 -0
  101. data/app/models/spree/page_block.rb +69 -0
  102. data/app/models/spree/page_blocks/buttons.rb +29 -0
  103. data/app/models/spree/page_blocks/heading.rb +18 -0
  104. data/app/models/spree/page_blocks/image.rb +17 -0
  105. data/app/models/spree/page_blocks/link.rb +21 -0
  106. data/app/models/spree/page_blocks/mega_nav.rb +33 -0
  107. data/app/models/spree/page_blocks/mega_nav_with_subcategories.rb +32 -0
  108. data/app/models/spree/page_blocks/nav.rb +15 -0
  109. data/app/models/spree/page_blocks/newsletter_form.rb +18 -0
  110. data/app/models/spree/page_blocks/products/buy_buttons.rb +12 -0
  111. data/app/models/spree/page_blocks/products/price.rb +18 -0
  112. data/app/models/spree/page_blocks/products/quantity_selector.rb +12 -0
  113. data/app/models/spree/page_blocks/products/share.rb +8 -0
  114. data/app/models/spree/page_blocks/products/title.rb +15 -0
  115. data/app/models/spree/page_blocks/products/variant_picker.rb +9 -0
  116. data/app/models/spree/page_blocks/subheading.rb +17 -0
  117. data/app/models/spree/page_blocks/text.rb +16 -0
  118. data/app/models/spree/page_link.rb +59 -0
  119. data/app/models/spree/page_section.rb +215 -0
  120. data/app/models/spree/page_sections/announcement_bar.rb +30 -0
  121. data/app/models/spree/page_sections/collection_banner.rb +18 -0
  122. data/app/models/spree/page_sections/custom_code.rb +11 -0
  123. data/app/models/spree/page_sections/featured_posts.rb +43 -0
  124. data/app/models/spree/page_sections/featured_product.rb +46 -0
  125. data/app/models/spree/page_sections/featured_taxon.rb +88 -0
  126. data/app/models/spree/page_sections/featured_taxons.rb +45 -0
  127. data/app/models/spree/page_sections/footer.rb +94 -0
  128. data/app/models/spree/page_sections/header.rb +62 -0
  129. data/app/models/spree/page_sections/image_banner.rb +38 -0
  130. data/app/models/spree/page_sections/image_with_text.rb +48 -0
  131. data/app/models/spree/page_sections/main_password_footer.rb +18 -0
  132. data/app/models/spree/page_sections/main_password_header.rb +20 -0
  133. data/app/models/spree/page_sections/newsletter.rb +47 -0
  134. data/app/models/spree/page_sections/page_title.rb +19 -0
  135. data/app/models/spree/page_sections/post_details.rb +19 -0
  136. data/app/models/spree/page_sections/post_grid.rb +19 -0
  137. data/app/models/spree/page_sections/product_details.rb +16 -0
  138. data/app/models/spree/page_sections/product_grid.rb +13 -0
  139. data/app/models/spree/page_sections/related_products.rb +60 -0
  140. data/app/models/spree/page_sections/rich_text.rb +20 -0
  141. data/app/models/spree/page_sections/taxon_banner.rb +18 -0
  142. data/app/models/spree/page_sections/taxon_grid.rb +17 -0
  143. data/app/models/spree/page_sections/video.rb +99 -0
  144. data/app/models/spree/pages/account.rb +15 -0
  145. data/app/models/spree/pages/cart.rb +9 -0
  146. data/app/models/spree/pages/checkout.rb +9 -0
  147. data/app/models/spree/pages/custom.rb +34 -0
  148. data/app/models/spree/pages/homepage.rb +68 -0
  149. data/app/models/spree/pages/login.rb +15 -0
  150. data/app/models/spree/pages/password.rb +57 -0
  151. data/app/models/spree/pages/post.rb +27 -0
  152. data/app/models/spree/pages/post_list.rb +32 -0
  153. data/app/models/spree/pages/product_details.rb +29 -0
  154. data/app/models/spree/pages/search_results.rb +43 -0
  155. data/app/models/spree/pages/shop_all.rb +36 -0
  156. data/app/models/spree/pages/taxon.rb +29 -0
  157. data/app/models/spree/pages/taxon_list.rb +41 -0
  158. data/app/models/spree/pages/wishlist.rb +11 -0
  159. data/app/models/spree/payment/gateway_options.rb +13 -15
  160. data/app/models/spree/payment/processing.rb +1 -1
  161. data/app/models/spree/payment/webhooks.rb +1 -1
  162. data/app/models/spree/payment.rb +61 -12
  163. data/app/models/spree/payment_capture_event.rb +1 -1
  164. data/app/models/spree/payment_method/store_credit.rb +9 -1
  165. data/app/models/spree/payment_method.rb +26 -8
  166. data/app/models/spree/payment_source.rb +2 -2
  167. data/app/models/spree/post.rb +108 -0
  168. data/app/models/spree/post_category.rb +33 -0
  169. data/app/models/spree/preference.rb +1 -1
  170. data/app/models/spree/price.rb +10 -3
  171. data/app/models/spree/product/webhooks.rb +1 -1
  172. data/app/models/spree/product.rb +325 -54
  173. data/app/models/spree/product_option_type.rb +1 -1
  174. data/app/models/spree/product_promotion_rule.rb +1 -1
  175. data/app/models/spree/product_property.rb +4 -1
  176. data/app/models/spree/promotion/actions/create_line_items.rb +37 -2
  177. data/app/models/spree/promotion/rules/country.rb +22 -4
  178. data/app/models/spree/promotion/rules/currency.rb +21 -0
  179. data/app/models/spree/promotion/rules/item_total.rb +12 -3
  180. data/app/models/spree/promotion/rules/option_value.rb +5 -20
  181. data/app/models/spree/promotion/rules/product.rb +33 -1
  182. data/app/models/spree/promotion/rules/taxon.rb +31 -0
  183. data/app/models/spree/promotion/rules/user.rb +33 -4
  184. data/app/models/spree/promotion.rb +136 -15
  185. data/app/models/spree/promotion_action.rb +31 -1
  186. data/app/models/spree/promotion_action_line_item.rb +1 -1
  187. data/app/models/spree/promotion_category.rb +1 -1
  188. data/app/models/spree/promotion_handler/cart.rb +1 -1
  189. data/app/models/spree/promotion_handler/coupon.rb +46 -8
  190. data/app/models/spree/promotion_handler/free_shipping.rb +1 -1
  191. data/app/models/spree/promotion_handler/promotion_duplicator.rb +6 -5
  192. data/app/models/spree/promotion_rule.rb +23 -1
  193. data/app/models/spree/promotion_rule_taxon.rb +1 -1
  194. data/app/models/spree/promotion_rule_user.rb +1 -1
  195. data/app/models/spree/property.rb +11 -3
  196. data/app/models/spree/property_prototype.rb +1 -1
  197. data/app/models/spree/prototype.rb +1 -1
  198. data/app/models/spree/prototype_taxon.rb +1 -1
  199. data/app/models/spree/refund.rb +24 -4
  200. data/app/models/spree/refund_reason.rb +12 -2
  201. data/app/models/spree/reimbursement/credit.rb +1 -1
  202. data/app/models/spree/reimbursement/emails.rb +1 -1
  203. data/app/models/spree/reimbursement.rb +5 -3
  204. data/app/models/spree/reimbursement_type.rb +1 -1
  205. data/app/models/spree/report.rb +110 -0
  206. data/app/models/spree/report_line_item.rb +27 -0
  207. data/app/models/spree/report_line_items/products_performance.rb +76 -0
  208. data/app/models/spree/report_line_items/sales_total.rb +49 -0
  209. data/app/models/spree/reports/products_performance.rb +44 -0
  210. data/app/models/spree/reports/sales_total.rb +14 -0
  211. data/app/models/spree/return_authorization.rb +4 -1
  212. data/app/models/spree/return_authorization_reason.rb +1 -1
  213. data/app/models/spree/return_item.rb +2 -1
  214. data/app/models/spree/role.rb +5 -2
  215. data/app/models/spree/role_user.rb +2 -2
  216. data/app/models/spree/shipment/emails.rb +1 -1
  217. data/app/models/spree/shipment/webhooks.rb +1 -1
  218. data/app/models/spree/shipment.rb +14 -4
  219. data/app/models/spree/shipping_category.rb +1 -1
  220. data/app/models/spree/shipping_method.rb +48 -8
  221. data/app/models/spree/shipping_method_category.rb +1 -1
  222. data/app/models/spree/shipping_method_zone.rb +1 -1
  223. data/app/models/spree/shipping_rate.rb +21 -3
  224. data/app/models/spree/state.rb +1 -1
  225. data/app/models/spree/state_change.rb +1 -1
  226. data/app/models/spree/stock_item/webhooks.rb +1 -1
  227. data/app/models/spree/stock_item.rb +9 -6
  228. data/app/models/spree/stock_location.rb +35 -8
  229. data/app/models/spree/stock_movement/webhooks.rb +1 -1
  230. data/app/models/spree/stock_movement.rb +9 -3
  231. data/app/models/spree/stock_transfer.rb +41 -3
  232. data/app/models/spree/store.rb +345 -72
  233. data/app/models/spree/store_credit.rb +12 -4
  234. data/app/models/spree/store_credit_category.rb +1 -1
  235. data/app/models/spree/store_credit_event.rb +21 -1
  236. data/app/models/spree/store_credit_type.rb +1 -1
  237. data/app/models/spree/store_payment_method.rb +1 -1
  238. data/app/models/spree/store_product.rb +1 -1
  239. data/app/models/spree/store_promotion.rb +1 -1
  240. data/app/models/spree/tax_category.rb +15 -1
  241. data/app/models/spree/tax_rate.rb +1 -1
  242. data/app/models/spree/taxon.rb +210 -27
  243. data/app/models/spree/taxon_image/configuration/active_storage.rb +1 -1
  244. data/app/models/spree/taxon_rule.rb +25 -0
  245. data/app/models/spree/taxon_rules/available_on.rb +20 -0
  246. data/app/models/spree/taxon_rules/sale.rb +16 -0
  247. data/app/models/spree/taxon_rules/tag.rb +19 -0
  248. data/app/models/spree/taxonomy.rb +3 -2
  249. data/app/models/spree/theme.rb +200 -0
  250. data/app/models/spree/themes/default.rb +94 -0
  251. data/app/models/spree/variant/webhooks.rb +1 -1
  252. data/app/models/spree/variant.rb +211 -62
  253. data/app/models/spree/wished_item.rb +1 -1
  254. data/app/models/spree/wishlist.rb +1 -1
  255. data/app/models/spree/zone.rb +1 -1
  256. data/app/models/spree/zone_member.rb +1 -1
  257. data/app/presenters/spree/csv/order_line_item_presenter.rb +145 -0
  258. data/app/presenters/spree/csv/product_presenter.rb +63 -0
  259. data/app/presenters/spree/csv/product_variant_presenter.rb +130 -0
  260. data/app/services/spree/addresses/create.rb +31 -6
  261. data/app/services/spree/addresses/helper.rb +7 -4
  262. data/app/services/spree/addresses/update.rb +79 -12
  263. data/app/services/spree/cart/remove_out_of_stock_items.rb +56 -0
  264. data/app/services/spree/checkout/add_store_credit.rb +6 -1
  265. data/app/services/spree/checkout/advance.rb +44 -4
  266. data/app/services/spree/checkout/select_shipping_method.rb +2 -0
  267. data/app/services/spree/coupon_codes/bulk_generate.rb +37 -0
  268. data/app/services/spree/coupon_codes/coupon_codes_handler.rb +34 -0
  269. data/app/services/spree/orders/create_user_account.rb +70 -0
  270. data/app/services/spree/orders/update_contact_information.rb +19 -0
  271. data/app/services/spree/products/auto_match_taxons.rb +64 -0
  272. data/app/services/spree/products/duplicator.rb +121 -0
  273. data/app/services/spree/products/prepare_nested_attributes.rb +169 -0
  274. data/app/services/spree/seeds/admin_user.rb +22 -0
  275. data/app/services/spree/seeds/all.rb +1 -0
  276. data/app/services/spree/seeds/stores.rb +5 -10
  277. data/app/services/spree/seeds/zones.rb +2 -0
  278. data/app/services/spree/tags/bulk_add.rb +38 -0
  279. data/app/services/spree/tags/bulk_remove.rb +24 -0
  280. data/app/services/spree/taxons/add_products.rb +39 -0
  281. data/app/services/spree/taxons/regenerate_products.rb +53 -0
  282. data/app/services/spree/taxons/remove_products.rb +53 -0
  283. data/app/services/spree/taxons/touch_featured_sections.rb +21 -0
  284. data/app/services/spree/themes/duplicator.rb +40 -0
  285. data/app/services/spree/variants/remove_line_items.rb +1 -1
  286. data/app/sorters/spree/classifications/sort.rb +68 -0
  287. data/app/sorters/spree/products/sort.rb +2 -3
  288. data/app/views/action_text/video_embeds/_thumbnail.html.erb +1 -0
  289. data/app/views/action_text/video_embeds/_video_embed.html.erb +3 -0
  290. data/app/views/active_storage/blobs/_blob.html.erb +20 -0
  291. data/app/views/layouts/action_text/contents/_content.html.erb +3 -0
  292. data/app/views/layouts/spree/base_mailer.html.erb +46 -0
  293. data/app/views/spree/addresses/_form.html.erb +58 -0
  294. data/app/views/spree/addresses/_suggestions_box.erb +14 -0
  295. data/app/views/spree/export_mailer/export_done.html.erb +15 -0
  296. data/app/views/spree/report_mailer/report_done.html.erb +15 -0
  297. data/app/views/spree/shared/_base_mailer_footer.html.erb +12 -0
  298. data/app/views/spree/shared/_base_mailer_header.html.erb +6 -0
  299. data/app/views/spree/shared/_base_mailer_stylesheets.html.erb +456 -0
  300. data/app/views/spree/shared/_google_places_api_loader.html.erb +7 -0
  301. data/app/views/spree/shared/_mailer_line_item.html.erb +12 -0
  302. data/app/views/spree/shared/_mailer_logo.html.erb +41 -0
  303. data/app/views/spree/shared/_payment.html.erb +41 -0
  304. data/app/views/spree/test_mailer/test_email.html.erb +40 -0
  305. data/app/views/spree/test_mailer/test_email.text.erb +4 -0
  306. data/config/i18n-tasks.yml +178 -0
  307. data/config/importmap.rb +14 -0
  308. data/config/initializers/acts_as_taggable_on.rb +2 -0
  309. data/config/initializers/inflections.rb +2 -0
  310. data/config/initializers/mobility.rb +4 -0
  311. data/config/initializers/oembed.rb +1 -0
  312. data/config/locales/en.yml +823 -413
  313. data/config/routes.rb +6 -5
  314. data/db/migrate/20210914000000_spree_four_three.rb +0 -79
  315. data/db/migrate/20240913143518_change_default_value_of_spree_option_types_filterable.rb +5 -0
  316. data/db/migrate/20240914153106_add_display_on_to_spree_properties.rb +5 -0
  317. data/db/migrate/20240915144935_add_position_to_spree_properties.rb +6 -0
  318. data/db/migrate/20241004181911_create_spree_coupon_codes.rb +15 -0
  319. data/db/migrate/20241005093437_add_multi_code_to_spree_promotions.rb +14 -0
  320. data/db/migrate/20241014140140_translate_rich_texts.rb +17 -0
  321. data/db/migrate/20241030134309_create_spree_exports.rb +23 -0
  322. data/db/migrate/20241104083457_migrate_spree_promotion_rules_option_value_eligible_values.rb +16 -0
  323. data/db/migrate/20241123110646_add_estimated_transit_fields_to_spree_shipping_methods.rb +6 -0
  324. data/db/migrate/20241127193411_create_spree_taxon_rules.rb +13 -0
  325. data/db/migrate/20241127223627_add_rules_match_policy_and_sort_order_to_spree_taxons.rb +6 -0
  326. data/db/migrate/20241128103947_add_automatic_to_spree_taxons.rb +5 -0
  327. data/db/migrate/20241218183905_add_gateway_customer_profile_id_to_spree_payment_sources.rb +5 -0
  328. data/db/migrate/20250107220432_add_accept_marketing_and_signup_for_an_account_to_spree_orders.rb +6 -0
  329. data/db/migrate/20250110171203_add_quick_checkout_to_spree_addresses.rb +6 -0
  330. data/db/migrate/20250113180019_add_preferences_to_spree_stores.rb +5 -0
  331. data/db/migrate/20250114193857_add_metadata_to_spree_stores.rb +13 -0
  332. data/db/migrate/20250119165904_create_spree_custom_domains.rb +22 -0
  333. data/db/migrate/20250120094216_create_page_builder_models.rb +78 -0
  334. data/db/migrate/20250120152208_add_default_locale_to_action_text_rich_texts.rb +7 -0
  335. data/db/migrate/20250121160028_create_spree_posts_and_spree_post_categories.rb +33 -0
  336. data/db/migrate/20250122113708_add_first_and_last_name_to_spree_admin_class.rb +9 -0
  337. data/db/migrate/20250123135358_add_custom_code_fields_to_spree_store.rb +7 -0
  338. data/db/migrate/20250127083740_add_kind_to_spree_properties.rb +5 -0
  339. data/db/migrate/20250127112758_add_latitude_and_longitude_to_spree_addresses.rb +6 -0
  340. data/db/migrate/20250127151258_add_phone_to_spree_users.rb +5 -0
  341. data/db/migrate/20250204162453_create_spree_reports.rb +13 -0
  342. data/db/migrate/20250207084000_add_accepts_email_marketing_field_to_spree_users_table.rb +6 -0
  343. data/db/migrate/20250217171018_create_action_text_video_embeds.rb +11 -0
  344. data/db/migrate/20250304115943_add_performed_by_to_spree_reimbursements.rb +5 -0
  345. data/db/migrate/20250305121352_remove_page_builder_indices.rb +11 -0
  346. data/db/migrate/20250305121657_remove_spree_posts_indices.rb +7 -0
  347. data/db/migrate/20250311105934_create_spree_gateway_customers.rb +18 -0
  348. data/db/migrate/20250312202705_add_unique_index_on_promotion_rules_associations.rb +75 -0
  349. data/db/migrate/20250313104226_add_user_type_to_spree_role_users.rb +18 -0
  350. data/db/migrate/20250313175830_add_unique_index_on_promotion_action_line_items.rb +29 -0
  351. data/db/migrate/20250314144210_create_spree_taggings_and_spree_tags.rb +38 -0
  352. data/lib/generators/spree/authentication/custom/custom_generator.rb +49 -0
  353. data/lib/generators/spree/authentication/custom/templates/authentication_helpers.rb.tt +50 -0
  354. data/lib/generators/spree/authentication/devise/devise_generator.rb +57 -0
  355. data/lib/generators/spree/authentication/devise/templates/authentication_helpers.rb.tt +50 -0
  356. data/lib/generators/spree/dummy/dummy_generator.rb +1 -1
  357. data/lib/generators/spree/dummy/templates/rails/database.yml +1 -0
  358. data/lib/generators/spree/dummy/templates/rails/test.rb +1 -1
  359. data/lib/generators/spree/install/install_generator.rb +21 -36
  360. data/lib/generators/spree/install/templates/config/initializers/spree.rb +40 -6
  361. data/lib/normalize_string.rb +18 -0
  362. data/lib/spree/analytics.rb +11 -0
  363. data/lib/spree/core/components.rb +1 -1
  364. data/lib/spree/core/configuration.rb +14 -11
  365. data/lib/spree/core/controller_helpers/auth.rb +0 -26
  366. data/lib/spree/core/controller_helpers/order.rb +77 -12
  367. data/lib/spree/core/controller_helpers/search.rb +2 -0
  368. data/lib/spree/core/controller_helpers/store.rb +0 -5
  369. data/lib/spree/core/controller_helpers/strong_parameters.rb +4 -0
  370. data/lib/spree/core/controller_helpers/turbo.rb +19 -0
  371. data/lib/spree/core/dependencies.rb +3 -5
  372. data/lib/spree/core/engine.rb +158 -7
  373. data/lib/spree/core/preferences/configuration.rb +2 -44
  374. data/lib/spree/core/preferences/preferable.rb +9 -0
  375. data/lib/spree/core/preferences/preferable_class_methods.rb +11 -1
  376. data/lib/spree/core/preferences/runtime_configuration.rb +13 -2
  377. data/lib/spree/core/search/base.rb +2 -0
  378. data/lib/spree/core/version.rb +1 -1
  379. data/lib/spree/core.rb +61 -11
  380. data/lib/spree/i18n.rb +5 -8
  381. data/lib/spree/migrations.rb +1 -1
  382. data/lib/spree/permitted_attributes.rb +24 -15
  383. data/lib/spree/testing_support/authorization_helpers.rb +9 -6
  384. data/lib/spree/testing_support/capybara_config.rb +11 -0
  385. data/lib/spree/testing_support/common_rake.rb +20 -12
  386. data/lib/spree/testing_support/factories/calculator_factory.rb +16 -0
  387. data/lib/spree/testing_support/factories/country_factory.rb +8 -0
  388. data/lib/spree/testing_support/factories/coupon_code_factory.rb +7 -0
  389. data/lib/spree/testing_support/factories/custom_domain_factory.rb +7 -0
  390. data/lib/spree/testing_support/factories/export_factory.rb +32 -0
  391. data/lib/spree/testing_support/factories/gateway_customer_factory.rb +7 -0
  392. data/lib/spree/testing_support/factories/order_factory.rb +14 -3
  393. data/lib/spree/testing_support/factories/page_block_factory.rb +21 -0
  394. data/lib/spree/testing_support/factories/page_factory.rb +33 -0
  395. data/lib/spree/testing_support/factories/page_link_factory.rb +7 -0
  396. data/lib/spree/testing_support/factories/page_section_factory.rb +24 -0
  397. data/lib/spree/testing_support/factories/payment_factory.rb +2 -2
  398. data/lib/spree/testing_support/factories/post_category_factory.rb +7 -0
  399. data/lib/spree/testing_support/factories/post_factory.rb +10 -0
  400. data/lib/spree/testing_support/factories/product_factory.rb +3 -8
  401. data/lib/spree/testing_support/factories/product_property_factory.rb +1 -1
  402. data/lib/spree/testing_support/factories/promotion_action_factory.rb +5 -0
  403. data/lib/spree/testing_support/factories/promotion_factory.rb +7 -9
  404. data/lib/spree/testing_support/factories/promotion_rule_factory.rb +12 -0
  405. data/lib/spree/testing_support/factories/property_factory.rb +1 -1
  406. data/lib/spree/testing_support/factories/report_factory.rb +33 -0
  407. data/lib/spree/testing_support/factories/role_user_factory.rb +7 -0
  408. data/lib/spree/testing_support/factories/shipping_method_factory.rb +9 -0
  409. data/lib/spree/testing_support/factories/stock_transfer_factory.rb +5 -2
  410. data/lib/spree/testing_support/factories/store_factory.rb +6 -9
  411. data/lib/spree/testing_support/factories/taxon_factory.rb +13 -0
  412. data/lib/spree/testing_support/factories/taxon_rule_factory.rb +38 -0
  413. data/lib/spree/testing_support/factories/theme_factory.rb +14 -0
  414. data/lib/spree/testing_support/factories/user_factory.rb +13 -11
  415. data/lib/spree/testing_support/factories/variant_factory.rb +23 -5
  416. data/lib/spree/testing_support/jobs.rb +1 -7
  417. data/lib/spree/testing_support/metadata.rb +13 -1
  418. data/lib/spree/testing_support/order_walkthrough.rb +1 -7
  419. data/lib/spree/testing_support/preferences.rb +1 -4
  420. data/lib/spree/testing_support/store.rb +26 -0
  421. data/lib/spree_core.rb +1 -0
  422. data/vendor/javascript/@rails--request.js.js +4 -0
  423. data/vendor/javascript/@stimulus-components--auto-submit.js +4 -0
  424. data/vendor/javascript/stimulus-reveal-controller.js +4 -0
  425. data/vendor/javascript/tailwindcss-stimulus-components.js +4 -0
  426. metadata +340 -197
  427. data/Gemfile +0 -3
  428. data/app/finders/spree/cms_pages/find.rb +0 -41
  429. data/app/finders/spree/menus/find.rb +0 -11
  430. data/app/models/concerns/spree/display_link.rb +0 -30
  431. data/app/models/spree/cms/pages/feature_page.rb +0 -7
  432. data/app/models/spree/cms/pages/homepage.rb +0 -20
  433. data/app/models/spree/cms/pages/standard_page.rb +0 -4
  434. data/app/models/spree/cms/sections/featured_article.rb +0 -22
  435. data/app/models/spree/cms/sections/hero_image.rb +0 -38
  436. data/app/models/spree/cms/sections/image_gallery.rb +0 -97
  437. data/app/models/spree/cms/sections/product_carousel.rb +0 -14
  438. data/app/models/spree/cms/sections/rich_text_content.rb +0 -13
  439. data/app/models/spree/cms/sections/side_by_side_images.rb +0 -68
  440. data/app/models/spree/cms_page.rb +0 -70
  441. data/app/models/spree/cms_section.rb +0 -69
  442. data/app/models/spree/cms_section_image.rb +0 -9
  443. data/app/models/spree/cms_section_image_one.rb +0 -4
  444. data/app/models/spree/cms_section_image_three.rb +0 -4
  445. data/app/models/spree/cms_section_image_two.rb +0 -4
  446. data/app/models/spree/icon.rb +0 -7
  447. data/app/models/spree/linkable/homepage.rb +0 -3
  448. data/app/models/spree/linkable/uri.rb +0 -3
  449. data/app/models/spree/menu.rb +0 -66
  450. data/app/models/spree/menu_item.rb +0 -72
  451. data/brakeman.ignore +0 -328
  452. data/db/migrate/20211201202851_update_linkable_resource_types.rb +0 -10
  453. data/db/migrate/20220329113557_fix_cms_pages_unique_indexes.rb +0 -8
  454. data/lib/generators/spree/custom_authentication/custom_authentication_generator.rb +0 -23
  455. data/lib/generators/spree/custom_authentication/templates/authentication_helpers.rb.tt +0 -23
  456. data/lib/generators/spree/custom_user/custom_user_generator.rb +0 -46
  457. data/lib/generators/spree/custom_user/templates/current_user_helpers.rb.tt +0 -15
  458. data/lib/generators/spree/custom_user/templates/initializer.rb.tt +0 -1
  459. data/lib/generators/spree/custom_user/templates/migration.rb.tt +0 -7
  460. data/lib/generators/spree/dummy/templates/package.json +0 -12
  461. data/lib/spree/core/product_duplicator.rb +0 -85
  462. data/lib/spree/core/runtime_configuration.rb +0 -10
  463. data/lib/spree/testing_support/factories/cms_page_factory.rb +0 -20
  464. data/lib/spree/testing_support/factories/cms_section_factory.rb +0 -31
  465. data/lib/spree/testing_support/factories/icon_factory.rb +0 -9
  466. data/lib/spree/testing_support/factories/menu_factory.rb +0 -16
  467. data/lib/spree/testing_support/factories/menu_item_factory.rb +0 -10
  468. data/script/rails +0 -9
  469. data/spec/fixtures/favicon.ico +0 -0
  470. data/spec/fixtures/files/icon_256x256.gif +0 -0
  471. data/spec/fixtures/files/icon_256x256.png +0 -0
  472. data/spec/fixtures/files/icon_512x512.png +0 -0
  473. data/spec/fixtures/files/img_256x128.png +0 -0
  474. data/spec/fixtures/text-file.txt +0 -1
  475. data/spec/fixtures/thinking-cat.jpg +0 -0
  476. data/spree_core.gemspec +0 -62
@@ -1,5 +1,5 @@
1
1
  module Spree
2
- class Variant < Spree::Base
2
+ class Variant < Spree.base_class
3
3
  acts_as_paranoid
4
4
  acts_as_list scope: :product
5
5
 
@@ -7,7 +7,10 @@ module Spree
7
7
  include Spree::Metadata
8
8
  include Spree::Variant::Webhooks
9
9
 
10
- MEMOIZED_METHODS = %w(purchasable in_stock backorderable tax_category options_text compare_at_price)
10
+ MEMOIZED_METHODS = %w(purchasable in_stock on_sale backorderable tax_category options_text compare_at_price)
11
+
12
+ DIMENSION_UNITS = %w[mm cm in ft]
13
+ WEIGHT_UNITS = %w[g kg lb oz]
11
14
 
12
15
  belongs_to :product, -> { with_deleted }, touch: true, class_name: 'Spree::Product', inverse_of: :variants
13
16
  belongs_to :tax_category, class_name: 'Spree::TaxCategory', optional: true
@@ -55,7 +58,7 @@ module Spree
55
58
 
56
59
  validate :check_price
57
60
 
58
- validates :option_values, presence: true, unless: :is_master?
61
+ validates :option_value_variants, presence: true, unless: :is_master?
59
62
 
60
63
  with_options numericality: { greater_than_or_equal_to: 0, allow_nil: true } do
61
64
  validates :cost_price
@@ -64,25 +67,30 @@ module Spree
64
67
  validates :sku, uniqueness: { conditions: -> { where(deleted_at: nil) }, case_sensitive: false, scope: spree_base_uniqueness_scope },
65
68
  allow_blank: true, unless: :disable_sku_validation?
66
69
 
70
+ validates :dimensions_unit, inclusion: { in: DIMENSION_UNITS }, allow_blank: true
71
+ validates :weight_unit, inclusion: { in: WEIGHT_UNITS }, allow_blank: true
72
+
67
73
  after_create :create_stock_items
68
74
  after_create :set_master_out_of_stock, unless: :is_master?
75
+ after_commit :clear_line_items_cache, on: :update
76
+ after_update_commit :handle_track_inventory_change
77
+
78
+ after_commit :remove_prices_from_master_variant, on: [:create, :update], unless: :is_master?
79
+ after_commit :remove_stock_items_from_master_variant, on: :create, unless: :is_master?
69
80
 
70
81
  after_touch :clear_in_stock_cache
71
82
 
72
- scope :in_stock, -> { joins(:stock_items).where("#{Spree::StockItem.table_name}.count_on_hand > ? OR #{Spree::Variant.table_name}.track_inventory = ?", 0, false) }
73
- scope :backorderable, -> { joins(:stock_items).where(spree_stock_items: { backorderable: true }) }
83
+ scope :in_stock, -> { left_joins(:stock_items).where("#{Spree::StockItem.table_name}.count_on_hand > ? OR #{Spree::Variant.table_name}.track_inventory = ?", 0, false) }
84
+ scope :backorderable, -> { left_joins(:stock_items).where(spree_stock_items: { backorderable: true }) }
74
85
  scope :in_stock_or_backorderable, -> { in_stock.or(backorderable) }
75
86
 
76
87
  scope :eligible, -> {
77
88
  where(is_master: false).or(
78
89
  where(
79
- <<-SQL
80
- #{Variant.quoted_table_name}.id IN (
81
- SELECT MIN(#{Variant.quoted_table_name}.id) FROM #{Variant.quoted_table_name}
82
- GROUP BY #{Variant.quoted_table_name}.product_id
83
- HAVING COUNT(*) = 1
84
- )
85
- SQL
90
+ product_id: Spree::Variant.
91
+ select(:product_id).
92
+ group(:product_id).
93
+ having("COUNT(#{Spree::Variant.table_name}.id) = 1")
86
94
  )
87
95
  )
88
96
  }
@@ -99,7 +107,7 @@ module Spree
99
107
 
100
108
  scope :for_currency_and_available_price_amount, ->(currency = nil) do
101
109
  currency ||= Spree::Store.default.default_currency
102
- joins(:prices).where('spree_prices.currency = ?', currency).where('spree_prices.amount IS NOT NULL').distinct
110
+ joins(:prices).where("#{Spree::Price.table_name}.currency = ?", currency).where("#{Spree::Price.table_name}.amount IS NOT NULL").distinct
103
111
  end
104
112
 
105
113
  scope :active, ->(currency = nil) do
@@ -114,6 +122,36 @@ module Spree
114
122
  joins(:option_values).where(Spree::OptionValue.table_name => { name: option_value, option_type_id: option_type_ids })
115
123
  }
116
124
 
125
+ scope :with_digital_assets, -> { joins(:digitals) }
126
+
127
+ if defined?(PgSearch)
128
+ include PgSearch::Model
129
+
130
+ pg_search_scope :search_by_sku, against: :sku, using: { tsearch: { prefix: true } }
131
+
132
+ pg_search_scope :search_by_sku_or_options,
133
+ against: :sku,
134
+ using: { tsearch: { prefix: true } },
135
+ associated_against: { option_values: %i[presentation] }
136
+
137
+ pg_search_scope :search_by_name_sku_or_options, against: :sku, associated_against: {
138
+ product: %i[name],
139
+ option_values: %i[presentation]
140
+ }, using: { tsearch: { prefix: true } }
141
+
142
+ scope :multi_search, lambda { |query|
143
+ return none if query.blank? || query.length < 3
144
+
145
+ search_by_name_sku_or_options(query)
146
+ }
147
+ else
148
+ scope :multi_search, lambda { |query|
149
+ return none if query.blank? || query.length < 3
150
+
151
+ product_name_or_sku_cont(query)
152
+ }
153
+ end
154
+
117
155
  # FIXME: cost price should be represented with DisplayMoney class
118
156
  LOCALIZED_NUMBERS = %w(cost_price weight depth width height)
119
157
 
@@ -123,6 +161,24 @@ module Spree
123
161
  end
124
162
  end
125
163
 
164
+ accepts_nested_attributes_for(
165
+ :stock_items,
166
+ reject_if: ->(attributes) { attributes['stock_location_id'].blank? || attributes['count_on_hand'].blank? },
167
+ allow_destroy: false
168
+ )
169
+
170
+ accepts_nested_attributes_for(
171
+ :prices,
172
+ reject_if: ->(attributes) { attributes['currency'].blank? || attributes['amount'].blank? },
173
+ allow_destroy: true
174
+ )
175
+
176
+ accepts_nested_attributes_for(
177
+ :option_value_variants,
178
+ reject_if: ->(attributes) { attributes['option_value_id'].blank? },
179
+ allow_destroy: false
180
+ )
181
+
126
182
  self.whitelisted_ransackable_associations = %w[option_values product tax_category prices default_price]
127
183
  self.whitelisted_ransackable_attributes = %w[weight depth width height sku discontinue_on is_master cost_price cost_currency track_inventory deleted_at]
128
184
  self.whitelisted_ransackable_scopes = %i(product_name_or_sku_cont search_by_product_name_or_sku)
@@ -133,7 +189,9 @@ module Spree
133
189
  sku_condition = arel_table[:sku].lower.matches(query_pattern)
134
190
 
135
191
  if Spree.use_translations?
136
- product_name_condition = Product.translation_table[:name].lower.matches(query_pattern)
192
+ translation_arel_table = Product::Translation.arel_table.alias(Product.translation_table_alias)[:name]
193
+ product_name_condition = translation_arel_table.lower.matches(query_pattern)
194
+
137
195
  joins(:product).
138
196
  join_translation_table(Product).
139
197
  where(product_name_condition.or(sku_condition))
@@ -147,6 +205,14 @@ module Spree
147
205
  product_name_or_sku_cont(query)
148
206
  end
149
207
 
208
+ def human_name
209
+ @human_name ||= option_values.
210
+ joins(option_type: :product_option_types).
211
+ merge(product.product_option_types).
212
+ reorder('spree_product_option_types.position').
213
+ pluck(:presentation).join('/')
214
+ end
215
+
150
216
  def available?
151
217
  !discontinued? && product.available?
152
218
  end
@@ -183,31 +249,61 @@ module Spree
183
249
  !!deleted_at
184
250
  end
185
251
 
252
+ # Returns default Image for Variant
253
+ # @return [Spree::Image]
254
+ def default_image
255
+ @default_image ||= if images.size.positive?
256
+ images.first
257
+ else
258
+ product.default_image
259
+ end
260
+ end
261
+
262
+ # Returns secondary Image for Variant
263
+ # @return [Spree::Image]
264
+ def secondary_image
265
+ @secondary_image ||= if images.size > 1
266
+ images.second
267
+ else
268
+ product.secondary_image
269
+ end
270
+ end
271
+
272
+ # Returns additional Images for Variant
273
+ # @return [Array<Spree::Image>]
274
+ def additional_images
275
+ @additional_images ||= (images + product.images).uniq.find_all { |image| image.id != default_image&.id }
276
+ end
277
+
186
278
  # Returns an array of hashes with the option type name, value and presentation
187
279
  # @return [Array<Hash>]
188
280
  def options
189
- option_values.includes(:option_type).map do |option_value|
190
- {
191
- name: option_value.option_type.name,
192
- value: option_value.name,
193
- presentation: option_value.presentation
194
- }
195
- end
281
+ @options ||= option_values.
282
+ includes(option_type: :product_option_types).
283
+ merge(product.product_option_types).
284
+ reorder('spree_product_option_types.position').
285
+ map do |option_value|
286
+ {
287
+ name: option_value.option_type.name,
288
+ value: option_value.name,
289
+ presentation: option_value.presentation
290
+ }
291
+ end
196
292
  end
197
293
 
198
294
  def options=(options = {})
199
295
  options.each do |option|
200
296
  next if option[:name].blank? || option[:value].blank?
201
297
 
202
- set_option_value(option[:name], option[:value])
298
+ set_option_value(option[:name], option[:value], option[:position])
203
299
  end
204
300
  end
205
301
 
206
- def set_option_value(opt_name, opt_value)
302
+ def set_option_value(opt_name, opt_value, opt_type_position = nil)
207
303
  # no option values on master
208
304
  return if is_master
209
305
 
210
- option_type = Spree::OptionType.where(['LOWER(name) = ?', opt_name.downcase.strip]).first_or_initialize do |o|
306
+ option_type = Spree::OptionType.where(name: opt_name.parameterize).first_or_initialize do |o|
211
307
  o.name = o.presentation = opt_name
212
308
  o.save!
213
309
  end
@@ -216,16 +312,21 @@ module Spree
216
312
 
217
313
  if current_value.nil?
218
314
  # then we have to check to make sure that the product has the option type
219
- unless product.option_types.include? option_type
220
- product.option_types << option_type
221
- end
315
+ product_option_type = if (existing_prod_ot = product.product_option_types.find { |ot| ot.option_type_id == option_type.id })
316
+ existing_prod_ot
317
+ else
318
+ product_option_type = product.product_option_types.new
319
+ product_option_type.option_type = option_type
320
+ end
321
+ product_option_type.position = opt_type_position if opt_type_position
322
+ product_option_type.save! if product_option_type.new_record? || product_option_type.changed?
222
323
  else
223
- return if current_value.name.downcase.strip == opt_value.downcase.strip
324
+ return if current_value.name.parameterize == opt_value.parameterize
224
325
 
225
326
  option_values.delete(current_value)
226
327
  end
227
328
 
228
- option_value = option_type.option_values.where(['LOWER(name) = ?', opt_value.downcase.strip]).first_or_initialize do |o|
329
+ option_value = option_type.option_values.where(name: opt_value.parameterize).first_or_initialize do |o|
229
330
  o.name = o.presentation = opt_value
230
331
  o.save!
231
332
  end
@@ -235,28 +336,39 @@ module Spree
235
336
  end
236
337
 
237
338
  def find_option_value(opt_name)
238
- option_values.detect { |o| o.option_type.name.downcase.strip == opt_name.downcase.strip }
339
+ option_values.includes(:option_type).detect { |o| o.option_type.name.parameterize == opt_name.parameterize }
239
340
  end
240
341
 
241
- def option_value(opt_name)
242
- find_option_value(opt_name).try(:presentation)
342
+ def option_value(option_type)
343
+ if option_type.is_a?(Spree::OptionType)
344
+ option_values.detect { |o| o.option_type_id == option_type.id }.try(:presentation)
345
+ else
346
+ find_option_value(option_type).try(:presentation)
347
+ end
243
348
  end
244
349
 
245
350
  def price_in(currency)
246
351
  currency = currency&.upcase
247
- find_or_build_price = lambda do
248
- if prices.loaded?
249
- prices.detect { |price| price.currency == currency } || prices.build(currency: currency)
250
- else
251
- prices.find_or_initialize_by(currency: currency)
252
- end
253
- end
254
352
 
255
- Rails.cache.fetch("spree/prices/#{cache_key_with_version}/price_in/#{currency}") do
256
- find_or_build_price.call
353
+ price = if prices.loaded? && prices.any?
354
+ prices.detect { |p| p.currency == currency }
355
+ else
356
+ prices.find_by(currency: currency)
357
+ end
358
+
359
+ if price.nil?
360
+ return Spree::Price.new(
361
+ currency: currency,
362
+ variant_id: id
363
+ )
257
364
  end
365
+
366
+ price
258
367
  rescue TypeError
259
- find_or_build_price.call
368
+ Spree::Price.new(
369
+ currency: currency,
370
+ variant_id: id
371
+ )
260
372
  end
261
373
 
262
374
  def amount_in(currency)
@@ -306,13 +418,13 @@ module Spree
306
418
  end
307
419
 
308
420
  def in_stock?
309
- # Issue 10280
310
- # Check if model responds to cache version and fall back to updated_at for older rails versions
311
- # This makes sure a version is supplied when recyclable cache keys are disabled.
312
- version = respond_to?(:cache_version) ? cache_version : updated_at.to_i
313
- @in_stock ||= Rails.cache.fetch(in_stock_cache_key, version: version) do
314
- total_on_hand > 0
315
- end
421
+ @in_stock ||= if association(:stock_items).loaded? && association(:stock_locations).loaded?
422
+ total_on_hand.positive?
423
+ else
424
+ Rails.cache.fetch(in_stock_cache_key, version: cache_version) do
425
+ total_on_hand.positive?
426
+ end
427
+ end
316
428
  end
317
429
 
318
430
  def backorderable?
@@ -321,6 +433,10 @@ module Spree
321
433
  end
322
434
  end
323
435
 
436
+ def on_sale?(currency)
437
+ @on_sale ||= price_in(currency)&.discounted?
438
+ end
439
+
324
440
  delegate :total_on_hand, :can_supply?, to: :quantifier
325
441
 
326
442
  alias is_backorderable? backorderable?
@@ -343,6 +459,12 @@ module Spree
343
459
  (width || 0) + (height || 0) + (depth || 0)
344
460
  end
345
461
 
462
+ # Returns the weight unit for the variant
463
+ # @return [String]
464
+ def weight_unit
465
+ attributes['weight_unit'] || Spree::Store.default.preferred_weight_unit
466
+ end
467
+
346
468
  def discontinue!
347
469
  update_attribute(:discontinue_on, Time.current)
348
470
  end
@@ -355,9 +477,17 @@ module Spree
355
477
  @backordered ||= !in_stock? && stock_items.exists?(backorderable: true)
356
478
  end
357
479
 
358
- # Is this variant to be downloaded by the customer?
480
+ # Is this variant purely digital? (no physical product)
359
481
  def digital?
360
- digitals.present?
482
+ product.digital?
483
+ end
484
+
485
+ def with_digital_assets?
486
+ digitals.any?
487
+ end
488
+
489
+ def clear_in_stock_cache
490
+ Rails.cache.delete(in_stock_cache_key)
361
491
  end
362
492
 
363
493
  private
@@ -386,14 +516,18 @@ module Spree
386
516
 
387
517
  # Ensures a new variant takes the product master price when price is not supplied
388
518
  def check_price
389
- if price.nil? && Spree::Config[:require_master_price]
390
- return errors.add(:base, :no_master_variant_found_to_infer_price) unless product&.master
391
- return errors.add(:base, :must_supply_price_for_variant_or_master) if self == product.master
519
+ return if (has_default_price? && default_price.valid?) || prices.any?
392
520
 
393
- self.price = product.master.price
394
- end
395
- if price.present? && currency.nil?
396
- self.currency = Spree::Store.default.default_currency
521
+ infer_price_from_default_variant_if_needed
522
+ self.currency = Spree::Store.default.default_currency if price.present? && currency.nil?
523
+ end
524
+
525
+ def infer_price_from_default_variant_if_needed
526
+ if price.nil?
527
+ return errors.add(:base, :no_master_variant_found_to_infer_price) unless product&.master
528
+
529
+ # At this point, master can have or have no price, so let's use price from the default variant
530
+ self.price = product.default_variant.price
397
531
  end
398
532
  end
399
533
 
@@ -411,12 +545,27 @@ module Spree
411
545
  "variant-#{id}-in_stock"
412
546
  end
413
547
 
414
- def clear_in_stock_cache
415
- Rails.cache.delete(in_stock_cache_key)
416
- end
417
-
418
548
  def disable_sku_validation?
419
549
  Spree::Config[:disable_sku_validation]
420
550
  end
551
+
552
+ def clear_line_items_cache
553
+ line_items.update_all(updated_at: Time.current)
554
+ end
555
+
556
+ def handle_track_inventory_change
557
+ return unless track_inventory_previously_changed?
558
+ return if track_inventory
559
+
560
+ stock_items.update_all(backorderable: true, count_on_hand: 0, updated_at: Time.current)
561
+ end
562
+
563
+ def remove_prices_from_master_variant
564
+ product.master.prices.delete_all if prices.exists?
565
+ end
566
+
567
+ def remove_stock_items_from_master_variant
568
+ product.master.stock_items.delete_all
569
+ end
421
570
  end
422
571
  end
@@ -1,5 +1,5 @@
1
1
  module Spree
2
- class WishedItem < Spree::Base
2
+ class WishedItem < Spree.base_class
3
3
  if defined?(Spree::Webhooks::HasWebhooks)
4
4
  include Spree::Webhooks::HasWebhooks
5
5
  end
@@ -1,5 +1,5 @@
1
1
  module Spree
2
- class Wishlist < Spree::Base
2
+ class Wishlist < Spree.base_class
3
3
  include Spree::SingleStoreResource
4
4
  if defined?(Spree::Webhooks::HasWebhooks)
5
5
  include Spree::Webhooks::HasWebhooks
@@ -1,5 +1,5 @@
1
1
  module Spree
2
- class Zone < Spree::Base
2
+ class Zone < Spree.base_class
3
3
  include Spree::UniqueName
4
4
  if defined?(Spree::Webhooks::HasWebhooks)
5
5
  include Spree::Webhooks::HasWebhooks
@@ -1,5 +1,5 @@
1
1
  module Spree
2
- class ZoneMember < Spree::Base
2
+ class ZoneMember < Spree.base_class
3
3
  belongs_to :zone, class_name: 'Spree::Zone', counter_cache: true, inverse_of: :zone_members
4
4
  belongs_to :zoneable, polymorphic: true
5
5
 
@@ -0,0 +1,145 @@
1
+ module Spree
2
+ module CSV
3
+ class OrderLineItemPresenter
4
+ HEADERS = [
5
+ 'Number',
6
+ 'Email',
7
+ 'Status',
8
+ 'Currency',
9
+ 'Subtotal',
10
+ 'Shipping',
11
+ 'Taxes',
12
+ 'Taxes included',
13
+ 'Discount Used',
14
+ 'Free Shipping',
15
+ 'Discount',
16
+ 'Discount Code',
17
+ 'Store Credit amount',
18
+ 'Total',
19
+ 'Shipping method',
20
+ 'Total weight',
21
+ 'Payment Type Used',
22
+ 'Product ID',
23
+ 'Item Quantity',
24
+ 'Item SKU',
25
+ 'Item Name',
26
+ 'Item Price',
27
+ 'Item Total Discount',
28
+ 'Item Total Price',
29
+ 'Item requires shipping',
30
+ 'Item taxbale',
31
+ 'Item Vendor',
32
+ 'Category lvl0',
33
+ 'Category lvl1',
34
+ 'Category lvl2',
35
+ 'Category lvl3',
36
+ 'Category lvl4',
37
+ 'Billing Name',
38
+ 'Billing Address 1',
39
+ 'Billing Address 2',
40
+ 'Billing Company',
41
+ 'Billing City',
42
+ 'Billing Zip',
43
+ 'Billing State',
44
+ 'Billing Country',
45
+ 'Billing Phone',
46
+ 'Shipping Name',
47
+ 'Shipping Address 1',
48
+ 'Shipping Address 2',
49
+ 'Shipping Company',
50
+ 'Shipping City',
51
+ 'Shipping Zip',
52
+ 'Shipping State',
53
+ 'Shipping Country',
54
+ 'Shipping Phone',
55
+ 'Placed at',
56
+ 'Shipped at',
57
+ 'Cancelled at',
58
+ 'Cancelled by',
59
+ 'Notes'
60
+ ].freeze
61
+
62
+ def initialize(order, line_item, index)
63
+ @order = order
64
+ @line_item = line_item
65
+ @index = index
66
+ end
67
+
68
+ attr_accessor :order, :line_item, :index
69
+
70
+ def call
71
+ [
72
+ order.number,
73
+ index.zero? ? order.email : nil,
74
+ index.zero? ? order.state : nil,
75
+ index.zero? ? order.currency : nil,
76
+ index.zero? ? order.item_total.to_f : nil,
77
+ index.zero? ? order.shipment_total.to_f : nil,
78
+ index.zero? ? order.tax_total.to_f : nil,
79
+ index.zero? ? order.included_tax_total.positive? : nil,
80
+ index.zero? ? (order.promo_total.negative? || line_item.promo_total.negative?) : nil,
81
+ index.zero? ? order.has_free_shipping? : nil,
82
+ index.zero? ? order.promo_total.abs : nil,
83
+ index.zero? ? order.promo_code : nil,
84
+ index.zero? ? order.payments.store_credits.sum(:amount).abs : nil,
85
+ index.zero? ? order.total.to_f : nil,
86
+ index.zero? ? order.shipping_method&.name : nil,
87
+ index.zero? ? order.total_weight.to_f : nil,
88
+ index.zero? && order.payments.valid.any? ? order.payments.valid.first.display_source_name : nil,
89
+ line_item.product_id,
90
+ line_item.quantity,
91
+ line_item.sku,
92
+ line_item.name,
93
+ line_item.price,
94
+ line_item.promo_total.abs,
95
+ line_item.total,
96
+ !line_item.product.digital?,
97
+ line_item.product.tax_category.present?,
98
+ line_item.product.try(:vendor_name),
99
+ taxon_dict(line_item.product.main_taxon)[0],
100
+ taxon_dict(line_item.product.main_taxon)[1],
101
+ taxon_dict(line_item.product.main_taxon)[2],
102
+ taxon_dict(line_item.product.main_taxon)[3],
103
+ taxon_dict(line_item.product.main_taxon)[4],
104
+ index.zero? ? order.bill_address&.full_name : nil,
105
+ index.zero? ? order.bill_address&.address1 : nil,
106
+ index.zero? ? order.bill_address&.address2 : nil,
107
+ index.zero? ? order.bill_address&.company : nil,
108
+ index.zero? ? order.bill_address&.city : nil,
109
+ index.zero? ? order.bill_address&.zipcode : nil,
110
+ index.zero? ? order.bill_address&.state_name : nil,
111
+ index.zero? ? order.bill_address&.country&.name : nil,
112
+ index.zero? ? order.bill_address&.phone : nil,
113
+ index.zero? ? order.ship_address&.full_name : nil,
114
+ index.zero? ? order.ship_address&.address1 : nil,
115
+ index.zero? ? order.ship_address&.address2 : nil,
116
+ index.zero? ? order.ship_address&.company : nil,
117
+ index.zero? ? order.ship_address&.city : nil,
118
+ index.zero? ? order.ship_address&.zipcode : nil,
119
+ index.zero? ? order.ship_address&.state_name : nil,
120
+ index.zero? ? order.ship_address&.country&.name : nil,
121
+ index.zero? ? order.ship_address&.phone : nil,
122
+ index.zero? ? format_date(order.completed_at) : nil,
123
+ format_date(line_item.order.shipments.first&.shipped_at),
124
+ index.zero? ? format_date(order.canceled_at) : nil,
125
+ index.zero? ? order.canceler&.email : nil,
126
+ index.zero? ? order.special_instructions : nil
127
+ ]
128
+ end
129
+
130
+ private
131
+
132
+ def taxon_dict(taxon)
133
+ return [] if taxon.blank?
134
+
135
+ taxon.pretty_name.to_s.split('->').map(&:strip)
136
+ end
137
+
138
+ def format_date(date)
139
+ return nil if date.blank?
140
+
141
+ date.in_time_zone(order.store.preferred_timezone).strftime('%Y-%m-%d %H:%M:%S')
142
+ end
143
+ end
144
+ end
145
+ end
@@ -0,0 +1,63 @@
1
+ require 'csv'
2
+
3
+ module Spree
4
+ module CSV
5
+ class ProductPresenter
6
+ def initialize(product, properties = [], store = nil)
7
+ @product = product
8
+ @properties = properties
9
+ @store = store || product.stores.first
10
+ @currency = @store.default_currency
11
+ end
12
+
13
+ attr_accessor :product, :properties, :store, :currency
14
+
15
+ def call
16
+ [
17
+ product.id,
18
+ product.try(:vendor_name),
19
+ product.try(:brand_name),
20
+ product.name,
21
+ product.description&.html_safe,
22
+ product.amount_in(currency).to_f,
23
+ product.meta_title,
24
+ product.meta_description,
25
+ product.meta_keywords,
26
+ product.tag_list.to_s,
27
+ product.label_list.to_s,
28
+ product.width,
29
+ product.height,
30
+ product.depth,
31
+ product.weight,
32
+ product.available_on&.strftime('%Y-%m-%d %H:%M:%S'),
33
+ product.discontinue_on&.strftime('%Y-%m-%d %H:%M:%S'),
34
+ product.status,
35
+ *map_categories(product),
36
+ product.total_on_hand == BigDecimal::INFINITY ? '∞' : product.total_on_hand,
37
+ *present_properties
38
+ ]
39
+ end
40
+
41
+ private
42
+
43
+ def map_categories(product)
44
+ categories = [nil, nil, nil]
45
+
46
+ product.taxons.reorder(depth: :desc).first(3).pluck(:pretty_name).each_with_index do |category_name, index|
47
+ categories[index] = category_name
48
+ end
49
+
50
+ categories
51
+ end
52
+
53
+ def present_properties
54
+ properties.flat_map do |property|
55
+ [
56
+ property.name,
57
+ product.property(property.name)
58
+ ]
59
+ end
60
+ end
61
+ end
62
+ end
63
+ end