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
@@ -0,0 +1,121 @@
1
+ module Spree
2
+ module Products
3
+ class Duplicator
4
+ prepend ::Spree::ServiceModule::Base
5
+
6
+ def call(product:, include_images: true)
7
+ new_product = duplicate_product(product, include_images)
8
+
9
+ # don't dup the actual variants, just the characterising types
10
+ new_product.option_types = product.option_types if product.has_variants?
11
+
12
+ # allow site to do some customization
13
+ new_product.send(:duplicate_extra, product) if new_product.respond_to?(:duplicate_extra)
14
+ new_product.save
15
+
16
+ new_product.product_properties = duplicate_properties(product.product_properties) if new_product.persisted?
17
+
18
+ new_product.persisted? ? success(new_product) : failure(new_product, duplicate_error_message(new_product))
19
+ end
20
+
21
+ protected
22
+
23
+ def duplicate_product(product, include_images)
24
+ product.dup.tap do |new_product|
25
+ new_product.translations.each do |t|
26
+ t.name = "COPY OF #{t.name}"
27
+ t.slug = nil
28
+ end
29
+
30
+ new_product.status = :draft
31
+ new_product.name = "COPY OF #{product.name}"
32
+ new_product.taxons = product.taxons
33
+ new_product.stores = product.stores
34
+ new_product.created_at = nil
35
+ new_product.deleted_at = nil
36
+ new_product.updated_at = nil
37
+ new_product.tag_list = product.tag_list
38
+ new_product.master = duplicate_master(product.master, include_images)
39
+ new_product.variants = product.variants.map { |variant| duplicate_variant(variant, include_images) }
40
+ end
41
+ end
42
+
43
+ def duplicate_master(master, include_images)
44
+ master.dup.tap do |new_master|
45
+ new_master.sku = sku_generator(master.sku)
46
+ new_master.deleted_at = nil
47
+ new_master.prices = duplicate_prices(master.prices)
48
+ new_master.stock_items = duplicate_stock_items(master.stock_items)
49
+
50
+ master.images.each { |image| duplicate_image(image, new_master) } if include_images
51
+ end
52
+ end
53
+
54
+ def duplicate_variant(variant, include_images)
55
+ new_variant = variant.dup
56
+ new_variant.sku = sku_generator(new_variant.sku)
57
+ new_variant.deleted_at = nil
58
+ new_variant.option_values = variant.option_values.map { |option_value| option_value }
59
+ new_variant.prices = duplicate_prices(variant.prices)
60
+ new_variant.stock_items = duplicate_stock_items(variant.stock_items)
61
+
62
+ variant.images.each { |image| duplicate_image(image, new_variant) } if include_images
63
+
64
+ new_variant
65
+ end
66
+
67
+ def duplicate_prices(prices)
68
+ prices.map do |price|
69
+ new_price = price.dup
70
+ new_price.created_at = nil
71
+ new_price.updated_at = nil
72
+ new_price
73
+ end
74
+ end
75
+
76
+ def duplicate_stock_items(stock_items)
77
+ stock_items.map do |stock_item|
78
+ new_stock_item = stock_item.dup
79
+ new_stock_item.count_on_hand = 0
80
+ new_stock_item
81
+ end
82
+ end
83
+
84
+ def duplicate_image(image, viewable)
85
+ new_image = image.dup
86
+ new_image.viewable = viewable
87
+ new_image.attachment.attach(image.attachment.blob)
88
+ new_image.save
89
+ new_image
90
+ end
91
+
92
+ def duplicate_properties(product_properties)
93
+ product_properties.map do |prop|
94
+ new_prop = prop.dup
95
+ new_prop.product = nil
96
+ new_prop.created_at = nil
97
+ new_prop.updated_at = nil
98
+ new_prop
99
+ end
100
+ end
101
+
102
+ def sku_generator(sku)
103
+ return '' if sku.blank?
104
+
105
+ "COPY OF #{Variant.unscoped.where('sku like ?', "%#{sku}").order(:created_at).last.sku}"
106
+ end
107
+
108
+ def duplicate_error_message(new_product)
109
+ errors = []
110
+ errors << new_product.errors.full_messages
111
+ errors << new_product.master.errors.full_messages
112
+
113
+ new_product.variants.each do |variant|
114
+ errors << variant.errors.full_messages
115
+ end
116
+
117
+ errors.flatten.to_sentence
118
+ end
119
+ end
120
+ end
121
+ end
@@ -0,0 +1,169 @@
1
+ module Spree
2
+ module Products
3
+ class PrepareNestedAttributes
4
+ def initialize(product, store, params, ability)
5
+ @product = product
6
+ @store = store
7
+ @params = params
8
+ @ability = ability
9
+ end
10
+
11
+ def call
12
+ if params[:variants_attributes]
13
+ params[:variants_attributes].each do |key, variant_params|
14
+ existing_variant = variant_params[:id].presence && @product.variants.find_by(id: variant_params[:id])
15
+ variants_to_remove.delete(variant_params[:id]) if variant_params[:id].present?
16
+
17
+ if can_update_prices?
18
+ # If the variant price is nil then mark it for destruction
19
+ variant_params[:prices_attributes]&.each do |price_key, price_params|
20
+ variant_params[:prices_attributes][price_key]['_destroy'] = '1' if price_params[:amount].blank?
21
+ end
22
+ else
23
+ variant_params.delete(:prices_attributes)
24
+ end
25
+
26
+ variant_params[:option_value_variants_attributes] = update_option_value_variants(variant_params.delete(:options), existing_variant)
27
+
28
+ variant_params.delete(:stock_items_attributes) unless can_update_stock_items?
29
+
30
+ params[:variants_attributes].delete(key) if variant_params.blank?
31
+ end
32
+ params[:variants_attributes] = params[:variants_attributes].merge(removed_variants_attributes)
33
+
34
+ params[:product_option_types_attributes] = product_option_types_params.merge(removed_product_option_types_attributes)
35
+ elsif params[:master_attributes]
36
+ params[:master_attributes].delete(:stock_items_attributes) unless can_update_stock_items?
37
+
38
+ if can_update_prices?
39
+ # If the master price is nil then mark it for destruction
40
+ params.dig(:master_attributes, :prices_attributes)&.each do |price_key, price_params|
41
+ params[:master_attributes][:prices_attributes][price_key]['_destroy'] = '1' if price_params[:amount].blank?
42
+ end
43
+ else
44
+ params[:master_attributes].delete(:prices_attributes)
45
+ end
46
+ end
47
+
48
+ params.delete(:variants_attributes) if params[:variants_attributes].blank?
49
+
50
+ # mark resource properties to be removed
51
+ # when value is left blank
52
+ if params[:product_properties_attributes].present?
53
+ params[:product_properties_attributes].each do |key, product_property_params|
54
+ next unless product_property_params[:id].present?
55
+ next if product_property_params[:value].present?
56
+
57
+ # https://api.rubyonrails.org/v7.1.3.4/classes/ActiveRecord/NestedAttributes/ClassMethods.html
58
+ params[:product_properties_attributes][key]['_destroy'] = '1'
59
+ end
60
+ end
61
+
62
+ # ensure there is at least one store
63
+ params[:store_ids] = [store.id] if params[:store_ids].blank?
64
+
65
+ params
66
+ end
67
+
68
+ private
69
+
70
+ attr_reader :product, :store, :params, :ability
71
+
72
+ delegate :can?, :cannot?, to: :ability
73
+
74
+ def product_option_types_params
75
+ @product_option_types_params ||= {}
76
+ end
77
+
78
+ def product_option_types_to_remove
79
+ @product_option_types_to_remove ||= product.product_option_type_ids
80
+ end
81
+
82
+ def variants_to_remove
83
+ @variants_to_remove ||= product.variant_ids.map(&:to_s)
84
+ end
85
+
86
+ def can_update_prices?
87
+ @can_update_prices ||= product.new_record? || can?(:manage, Spree::Price.new(variant_id: product.default_variant.id))
88
+ end
89
+
90
+ def can_manage_option_types?
91
+ @can_manage_option_types ||= product.new_record? || can?(:manage_option_types, product)
92
+ end
93
+
94
+ def can_update_stock_items?
95
+ @can_update_stock_items ||= product.new_record? || can?(:manage, Spree::StockItem.new(variant_id: product.default_variant.id))
96
+ end
97
+
98
+ def can_remove_variants?
99
+ @can_remove_variants ||= product.persisted? && can?(:destroy, product.default_variant)
100
+ end
101
+
102
+ def removed_variants_attributes
103
+ return {} unless can_remove_variants?
104
+
105
+ attributes = {}
106
+ last_index = params[:variants_attributes].keys.map(&:to_i).max
107
+ variants_to_remove.each_with_index do |variant_id, index|
108
+ attributes[(index + last_index + 1).to_s] = { id: variant_id, _destroy: '1' }
109
+ end
110
+
111
+ attributes
112
+ end
113
+
114
+ def removed_product_option_types_attributes
115
+ return {} unless can_manage_option_types?
116
+
117
+ attributes = {}
118
+ last_index = product_option_types_params.keys.map(&:to_i).max
119
+ product_option_types_to_remove.each_with_index do |product_option_type_id, index|
120
+ attributes[(last_index + index + 1).to_s] = { id: product_option_type_id, _destroy: '1' }
121
+ end
122
+
123
+ attributes
124
+ end
125
+
126
+ def update_option_value_variants(option_value_params, existing_variant)
127
+ return {} unless option_value_params.present?
128
+ return {} unless can_manage_option_types?
129
+
130
+ option_value_variant_params = {}
131
+
132
+ option_value_params.each_with_index do |opt, index|
133
+ option_type = Spree::OptionType.where(name: opt[:name].parameterize).first_or_initialize do |o|
134
+ o.name = o.presentation = opt[:name]
135
+ o.save!
136
+ end
137
+ option_value = option_type.option_values.where(name: opt[:value].parameterize).first_or_initialize do |o|
138
+ o.name = o.presentation = opt[:value]
139
+ o.save!
140
+ end
141
+
142
+ existing_option_value_variant = existing_variant&.option_value_variants&.find { |ovv| ovv.option_value_id == option_value.id }
143
+
144
+ option_value_variant_params[index.to_s] = { id: existing_option_value_variant&.id, option_value_id: option_value.id }.compact_blank
145
+
146
+ next if product_option_types_params.find { |_i, v| v[:option_type_id] == option_type.id }
147
+
148
+ existing_product_option_type = @product.product_option_types.find { |pot| pot.option_type_id == option_type.id }
149
+
150
+ if existing_product_option_type
151
+ product_option_types_to_remove.delete(existing_product_option_type.id)
152
+ product_option_types_params[opt[:position]] = {
153
+ id: existing_product_option_type.id,
154
+ position: opt[:position],
155
+ option_type_id: option_type.id
156
+ }
157
+ else
158
+ product_option_types_params[opt[:position]] = {
159
+ option_type_id: option_type.id,
160
+ position: opt[:position]
161
+ }
162
+ end
163
+ end
164
+
165
+ option_value_variant_params
166
+ end
167
+ end
168
+ end
169
+ end
@@ -0,0 +1,22 @@
1
+ module Spree
2
+ module Seeds
3
+ class AdminUser
4
+ prepend Spree::ServiceModule::Base
5
+
6
+ def call
7
+ if Spree.admin_user_class.present? && Spree.admin_user_class.count.zero?
8
+ user = Spree.admin_user_class.create!(
9
+ email: 'spree@example.com',
10
+ password: 'spree123',
11
+ password_confirmation: 'spree123',
12
+ first_name: 'Spree',
13
+ last_name: 'Admin'
14
+ )
15
+
16
+ user.spree_roles << Spree::Role.find_or_create_by(name: :admin)
17
+ user.save!
18
+ end
19
+ end
20
+ end
21
+ end
22
+ end
@@ -21,6 +21,7 @@ module Spree
21
21
  # store & stock location
22
22
  Stores.call
23
23
  StockLocations.call
24
+ AdminUser.call
24
25
 
25
26
  # add store resources
26
27
  PaymentMethods.call
@@ -7,22 +7,17 @@ module Spree
7
7
  default_store = Spree::Store.default
8
8
 
9
9
  if default_store.persisted?
10
- default_store.update!(default_country: Spree::Country.first)
10
+ default_store.update!(default_country: Spree::Country.find_by(iso: 'US') || Spree::Country.first)
11
11
  else
12
12
  Spree::Store.new do |s|
13
- s.name = 'Spree Demo Site'
14
- s.code = 'spree'
15
- s.url = Rails.application.routes.default_url_options[:host] || 'demo.spreecommerce.org'
13
+ s.name = 'Shop'
14
+ s.code = 'shop'
15
+ s.url = Rails.application.routes.default_url_options[:host] || 'localhost:3000'
16
16
  s.mail_from_address = 'no-reply@example.com'
17
17
  s.customer_support_email = 'support@example.com'
18
18
  s.default_currency = 'USD'
19
- s.default_country = Spree::Country.first
19
+ s.default_country = Spree::Country.find_by(iso: 'US') || Spree::Country.first
20
20
  s.default_locale = I18n.locale
21
- s.seo_title = 'Spree Commerce Demo Shop'
22
- s.meta_description = 'This is the new Spree UX DEMO | OVERVIEW: http://bit.ly/new-spree-ux | DOCS: http://bit.ly/spree-ux-customization-docs | CONTACT: https://spreecommerce.org/contact/'
23
- s.facebook = 'spreecommerce'
24
- s.twitter = 'spreecommerce'
25
- s.instagram = 'spreecommerce'
26
21
  end.save!
27
22
  end
28
23
  end
@@ -10,12 +10,14 @@ module Spree
10
10
  Spree::Zone.where(name: 'South America', description: 'South America', kind: 'country').first_or_create!
11
11
  middle_east = Spree::Zone.where(name: 'Middle East', description: 'Middle East', kind: 'country').first_or_create!
12
12
  asia = Spree::Zone.where(name: 'Asia', description: 'Asia', kind: 'country').first_or_create!
13
+ australia_and_oceania = Spree::Zone.where(name: 'Australia and Oceania', description: 'Australia and Oceania', kind: 'country').first_or_create!
13
14
 
14
15
  create_zone_members(eu_vat, %w(PL FI PT RO DE FR SK HU SI IE AT ES IT BE SE LV BG LT CY LU MT DK NL EE HR CZ GR))
15
16
  create_zone_members(north_america, %w(US CA))
16
17
  create_zone_members(middle_east, %w(BH CY EG IR IQ IL JO KW LB OM QA SA SY TR AE YE))
17
18
  create_zone_members(asia, %w(AF AM AZ BH BD BT BN KH CN CX CC GE HK IN ID IR IQ IL JP JO KZ KW KG LA LB MO MY MV MN MM NP
18
19
  KP OM PK PS PH QA SA SG KR LK SY TW TJ TH TR TM AE UZ VN YE))
20
+ create_zone_members(australia_and_oceania, %w(AU NZ))
19
21
  uk_vat.zone_members.where(zoneable: Spree::Country.find_by(iso: 'GB')).first_or_create!
20
22
  end
21
23
 
@@ -0,0 +1,38 @@
1
+ module Spree
2
+ module Tags
3
+ class BulkAdd
4
+ prepend Spree::ServiceModule::Base
5
+
6
+ # Adds the given tags to the given records.
7
+ #
8
+ # @param tag_names [Array<String>] eg. ['tag1', 'tag2']
9
+ # @param records [Array<Spree::Taggable>] eg. Spree::Product, Spree::User
10
+ # @param context [String] default: 'tags'
11
+ # @return [Spree::ServiceModule::Base::Result]
12
+ def call(tag_names: [], records: [], context: 'tags')
13
+ tags = tag_names.map do |tag_name|
14
+ ActsAsTaggableOn::Tag.find_or_create_by(name: tag_name.strip)
15
+ end
16
+
17
+ record_class = records.first.class
18
+
19
+ taggings_to_upsert = records.pluck(:id).map do |record_id|
20
+ tags.map do |tag|
21
+ {
22
+ taggable_id: record_id,
23
+ taggable_type: record_class.to_s,
24
+ context: context,
25
+ tag_id: tag.id
26
+ }
27
+ end
28
+ end.flatten.compact
29
+
30
+ return if taggings_to_upsert.empty?
31
+
32
+ ActsAsTaggableOn::Tagging.insert_all(taggings_to_upsert)
33
+
34
+ record_class.where(id: records.pluck(:id)).touch_all
35
+ end
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,24 @@
1
+ module Spree
2
+ module Tags
3
+ class BulkRemove
4
+ prepend Spree::ServiceModule::Base
5
+
6
+ def call(tag_names: [], records: [], context: 'tags')
7
+ tag_ids = ActsAsTaggableOn::Tag.where(name: tag_names.map(&:strip)).pluck(:id)
8
+
9
+ return if tag_ids.empty?
10
+
11
+ record_class = records.first.class
12
+
13
+ ActsAsTaggableOn::Tagging.where(
14
+ taggable_id: records.pluck(:id),
15
+ taggable_type: record_class.to_s,
16
+ context: context,
17
+ tag_id: tag_ids
18
+ ).delete_all
19
+
20
+ record_class.where(id: records.pluck(:id)).touch_all
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,39 @@
1
+ module Spree
2
+ module Taxons
3
+ class AddProducts
4
+ prepend Spree::ServiceModule::Base
5
+
6
+ # Creates classifications for the given taxons and products in bulk.
7
+ #
8
+ # @param taxons [Array<Spree::Taxon>]
9
+ # @param products [Array<Spree::Product>]
10
+ # @return [Spree::ServiceModule::Base::Result]
11
+ def call(taxons:, products:)
12
+ return if taxons.blank? || products.blank?
13
+
14
+ # build the params for the insert_all
15
+ classifications_params = taxons.pluck(:id).flat_map do |taxon_id|
16
+ position = Spree::Classification.where(taxon_id: taxon_id).count
17
+
18
+ products.pluck(:id).map do |product_id|
19
+ {
20
+ taxon_id: taxon_id,
21
+ product_id: product_id,
22
+ position: (position += 1),
23
+ created_at: Time.current,
24
+ updated_at: Time.current
25
+ }
26
+ end
27
+ end
28
+ # doing a quick insert_all here to avoid the overhead of instantiating
29
+ Spree::Classification.insert_all(classifications_params)
30
+
31
+ # clearing cache
32
+ Spree::Product.where(id: products.pluck(:id)).touch_all
33
+ Spree::Taxon.where(id: taxons.pluck(:id)).touch_all
34
+
35
+ success(true)
36
+ end
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,53 @@
1
+ module Spree
2
+ module Taxons
3
+ class RegenerateProducts
4
+ prepend ::Spree::ServiceModule::Base
5
+
6
+ def call(taxon:)
7
+ return if taxon.nil?
8
+ return if taxon.manual?
9
+
10
+ previous_products_ids = taxon.classifications.order(position: :asc).pluck(:product_id)
11
+
12
+ # https://api.rubyonrails.org/classes/ActiveRecord/Associations/CollectionProxy.html#method-i-delete_all
13
+ # we don't want to run destroy_all here to avoid callbacks
14
+ # default dependent value is nullify and that won't work for us
15
+ taxon.classifications.delete_all(:delete_all)
16
+
17
+ products_matching_rules = taxon.products_matching_rules
18
+ product_ids_to_insert = products_matching_rules.ids
19
+
20
+ previous_filtered_products_ids = previous_products_ids & product_ids_to_insert
21
+ max_products_position = previous_filtered_products_ids.size || 0
22
+
23
+ if product_ids_to_insert.any?
24
+ records_to_insert = product_ids_to_insert.map do |product_id|
25
+ position = previous_filtered_products_ids.index(product_id)
26
+ position = position.present? ? position + 1 : max_products_position += 1
27
+
28
+ classification_attributes(product_id, taxon, position)
29
+ end
30
+
31
+ Spree::Classification.insert_all(records_to_insert)
32
+
33
+ # expire product cache
34
+ Spree::Product.where(id: (previous_products_ids + product_ids_to_insert).uniq).touch_all
35
+ end
36
+
37
+ success(taxon)
38
+ end
39
+
40
+ private
41
+
42
+ def classification_attributes(product_id, taxon, position)
43
+ {
44
+ product_id: product_id,
45
+ taxon_id: taxon.id,
46
+ position: position,
47
+ created_at: Time.current,
48
+ updated_at: Time.current
49
+ }
50
+ end
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,53 @@
1
+ module Spree
2
+ module Taxons
3
+ class RemoveProducts
4
+ prepend Spree::ServiceModule::Base
5
+
6
+ # Removes the given products from the given taxons.
7
+ #
8
+ # @param taxons [Array<Spree::Taxon>]
9
+ # @param products [Array<Spree::Product>]
10
+ # @return [Spree::ServiceModule::Base::Result]
11
+ def call(taxons:, products:)
12
+ return if taxons.blank? || products.blank?
13
+
14
+ ApplicationRecord.transaction do
15
+ taxons.pluck(:id).each do |taxon_id|
16
+ Spree::Classification.where(taxon_id: taxon_id, product_id: products.pluck(:id)).delete_all
17
+ end
18
+
19
+ classifications_params = taxons.pluck(:id).flat_map do |taxon_id|
20
+ position = 0
21
+ existing_product_ids = Spree::Classification.where(taxon_id: taxon_id).pluck(:product_id)
22
+
23
+ existing_product_ids.map do |product_id|
24
+ {
25
+ taxon_id: taxon_id,
26
+ product_id: product_id,
27
+ position: (position += 1),
28
+ created_at: Time.current,
29
+ updated_at: Time.current
30
+ }
31
+ end
32
+ end
33
+
34
+ if classifications_params.any?
35
+ opts = {}
36
+ opts[:unique_by] = :index_spree_products_taxons_on_product_id_and_taxon_id unless ActiveRecord::Base.connection.adapter_name == 'Mysql2'
37
+
38
+ Spree::Classification.upsert_all(
39
+ classifications_params,
40
+ **opts
41
+ )
42
+ end
43
+ end
44
+
45
+ # clear cache
46
+ Spree::Product.where(id: products.pluck(:id)).touch_all
47
+ Spree::Taxon.where(id: taxons.pluck(:id)).touch_all
48
+
49
+ success(true)
50
+ end
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,21 @@
1
+ module Spree
2
+ module Taxons
3
+ class TouchFeaturedSections
4
+ prepend Spree::ServiceModule::Base
5
+
6
+ def call(taxon_ids:)
7
+ return if taxon_ids.empty?
8
+
9
+ featured_taxons = Spree::PageSections::FeaturedTaxon.published.by_taxon_id(taxon_ids)
10
+
11
+ return if featured_taxons.empty?
12
+
13
+ featured_taxons.touch_all
14
+ pages = Spree::Page.where(id: featured_taxons.where(pageable_type: 'Spree::Page').pluck(:pageable_id))
15
+ pages.touch_all
16
+ themes = Spree::Theme.where(id: pages.where(pageable_type: 'Spree::Theme').pluck(:pageable_id).uniq)
17
+ themes.touch_all
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,40 @@
1
+ module Spree
2
+ module Themes
3
+ class Duplicator
4
+ def initialize(theme)
5
+ @theme = theme
6
+ end
7
+
8
+ def duplicate
9
+ duplicated_theme = @theme.dup
10
+ duplicated_theme.duplicating = true
11
+ duplicated_theme.default = false
12
+ duplicated_theme.ready = false
13
+ duplicated_theme.name = generate_new_name(@theme.name)
14
+ duplicated_theme.save!
15
+
16
+ # We need to duplicate files, pages and sections in the background
17
+ Spree::Themes::DuplicateComponentsJob.set(wait: 5.seconds).perform_later(@theme.id, duplicated_theme.id)
18
+
19
+ duplicated_theme
20
+ end
21
+
22
+ protected
23
+
24
+ def generate_new_name(name)
25
+ depth_of_name = name.count('#')
26
+ highest_number_for_each_depth = Spree::Theme.where('spree_themes.name LIKE ?', "%#{name}").
27
+ pluck(:name).
28
+ group_by { |theme_name| theme_name.count('#') }.
29
+ transform_values do |names|
30
+ # ['Copy #3 of Theme', 'Copy #2 of Theme', Copy #1 of Theme'] => '3'
31
+ names.map{ |n| n.split.select { |s| s.starts_with?('#') }.first&.delete('#') }.max
32
+ end
33
+
34
+ next_usable_number = highest_number_for_each_depth[depth_of_name + 1].to_i + 1
35
+
36
+ "Copy ##{next_usable_number} of #{name}"
37
+ end
38
+ end
39
+ end
40
+ end
@@ -4,7 +4,7 @@ module Spree
4
4
  prepend Spree::ServiceModule::Base
5
5
 
6
6
  def call(variant:)
7
- variant.line_items.joins(:order).where.not(spree_orders: { state: 'complete' }).find_each do |line_item|
7
+ variant.line_items.joins(:order).where(spree_orders: { state: Spree::Order::LINE_ITEM_REMOVABLE_STATES }).find_each do |line_item|
8
8
  Spree::Variants::RemoveLineItemJob.perform_later(line_item: line_item)
9
9
  end
10
10