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
@@ -31,6 +31,13 @@ module Spree
31
31
  # Order promotion has to be destroyed before line item removing
32
32
  order.order_promotions.where(promotion_id: promotion.id).destroy_all
33
33
 
34
+ if promotion.multi_codes?
35
+ coupon_code = promotion.coupon_codes.find_by(order: order)
36
+ coupon_code&.remove_from_order
37
+ else
38
+ promotion.touch
39
+ end
40
+
34
41
  remove_promotion_adjustments(promotion)
35
42
  remove_promotion_line_items(promotion)
36
43
  order.update_with_updater!
@@ -47,17 +54,33 @@ module Spree
47
54
  @success = Spree.t(code)
48
55
  end
49
56
 
50
- def set_error_code(code)
57
+ def set_error_code(code, locale_options = {})
51
58
  @status_code = code
52
- @error = Spree.t(code)
59
+ @error = Spree.t(code, locale_options)
53
60
  end
54
61
 
62
+ # Returns the promotion for the order
63
+ #
64
+ # @return [Spree::Promotion]
55
65
  def promotion
56
66
  @promotion ||= store.promotions.active.includes(
57
67
  :promotion_rules, :promotion_actions
58
68
  ).with_coupon_code(order.coupon_code)
59
69
  end
60
70
 
71
+ # Returns the amount of adjustments for the promotion
72
+ #
73
+ # @return [Numeric]
74
+ def adjustments_amount
75
+ @adjustments_amount ||=
76
+ @order.all_adjustments.promotion.eligible.
77
+ where(source: promotion&.actions).
78
+ sum(:amount)
79
+ end
80
+
81
+ # Returns true if the code was applied successfully
82
+ #
83
+ # @return [Boolean]
61
84
  def successful?
62
85
  success.present? && error.blank?
63
86
  end
@@ -82,10 +105,11 @@ module Spree
82
105
  end
83
106
 
84
107
  def handle_present_promotion
85
- return promotion_usage_limit_exceeded if promotion.usage_limit_exceeded?(order)
86
108
  return promotion_applied if promotion_exists_on_order?
109
+ return set_error_code :coupon_code_used if promotion.coupon_codes.used.where(code: order.coupon_code).exists?
110
+ return promotion_usage_limit_exceeded if promotion.usage_limit_exceeded?(order)
87
111
 
88
- unless promotion.eligible?(order)
112
+ unless promotion.eligible?(order, options)
89
113
  self.error = promotion.eligibility_errors.full_messages.first unless promotion.eligibility_errors.blank?
90
114
  return (error || ineligible_for_this_order)
91
115
  end
@@ -116,9 +140,12 @@ module Spree
116
140
  end
117
141
 
118
142
  def determine_promotion_application_result
143
+ coupon_code = order.coupon_code.downcase
144
+
119
145
  # Check for applied adjustments.
120
146
  discount = order.all_adjustments.promotion.eligible.detect do |p|
121
- p.source.promotion.code.try(:downcase) == order.coupon_code.downcase
147
+ p.source.promotion.code.try(:downcase) == coupon_code ||
148
+ p.source.promotion.coupon_codes.unused.where(code: coupon_code).exists?
122
149
  end
123
150
 
124
151
  # Check for applied line items.
@@ -129,18 +156,29 @@ module Spree
129
156
  end
130
157
 
131
158
  if discount || created_line_items
159
+ handle_coupon_code(discount, coupon_code) if discount
160
+
132
161
  order.update_totals
133
162
  order.persist_totals
134
163
  set_success_code :coupon_code_applied
135
164
  elsif order.promotions.with_coupon_code(order.coupon_code)
136
- # if the promotion exists on an order, but wasn't found above,
137
- # we've already selected a better promotion
138
- set_error_code :coupon_code_better_exists
165
+ # since CouponCode is disposable...
166
+ if Spree::CouponCode.used?(order.coupon_code)
167
+ set_error_code :coupon_code_max_usage
168
+ else
169
+ # if the promotion exists on an order, but wasn't found above,
170
+ # we've already selected a better promotion
171
+ set_error_code :coupon_code_better_exists
172
+ end
139
173
  else
140
174
  # if the promotion was created after the order
141
175
  set_error_code :coupon_code_not_found
142
176
  end
143
177
  end
178
+
179
+ def handle_coupon_code(discount, coupon_code)
180
+ discount.source.promotion.coupon_codes.unused.find_by(code: coupon_code)&.apply_order!(order)
181
+ end
144
182
  end
145
183
  end
146
184
  end
@@ -13,7 +13,7 @@ module Spree
13
13
 
14
14
  def activate
15
15
  promotions.each do |promotion|
16
- next if promotion.code.present? && !order_promo_ids.include?(promotion.id)
16
+ next if promotion.coupon_code? && !order_promo_ids.include?(promotion.id)
17
17
 
18
18
  promotion.activate(order: order) if promotion.eligible?(order)
19
19
  end
@@ -2,7 +2,7 @@ module Spree
2
2
  module PromotionHandler
3
3
  class PromotionDuplicator
4
4
  def initialize(promotion, random_string: nil)
5
- @promotion = promotion
5
+ @promotion = promotion.reload
6
6
  @random_string = random_string || generate_random_string(4)
7
7
  end
8
8
 
@@ -19,7 +19,7 @@ module Spree
19
19
  copy_actions
20
20
  end
21
21
 
22
- @new_promotion
22
+ @new_promotion.reload
23
23
  end
24
24
 
25
25
  private
@@ -27,7 +27,8 @@ module Spree
27
27
  def copy_rules
28
28
  @promotion.promotion_rules.each do |rule|
29
29
  new_rule = rule.dup
30
- @new_promotion.promotion_rules << new_rule
30
+ new_rule.promotion = @new_promotion
31
+ new_rule.save!
31
32
 
32
33
  new_rule.users = rule.users if rule.try(:users)
33
34
  new_rule.taxons = rule.taxons if rule.try(:taxons)
@@ -43,9 +44,9 @@ module Spree
43
44
  def copy_actions
44
45
  @promotion.promotion_actions.each do |action|
45
46
  new_action = action.dup
47
+ new_action.promotion = @new_promotion
46
48
  new_action.calculator = action.calculator.dup if action.try(:calculator)
47
-
48
- @new_promotion.promotion_actions << new_action
49
+ new_action.save!
49
50
 
50
51
  next unless action.try(:promotion_action_line_items)
51
52
 
@@ -1,12 +1,13 @@
1
1
  # Base class for all promotion rules
2
2
  module Spree
3
- class PromotionRule < Spree::Base
3
+ class PromotionRule < Spree.base_class
4
4
  belongs_to :promotion, class_name: 'Spree::Promotion', inverse_of: :promotion_rules
5
5
 
6
6
  delegate :stores, to: :promotion
7
7
 
8
8
  scope :of_type, ->(t) { where(type: t) }
9
9
 
10
+ validates :promotion, presence: true
10
11
  validate :unique_per_promotion, on: :create
11
12
 
12
13
  def self.for(promotable)
@@ -31,6 +32,27 @@ module Spree
31
32
  @eligibility_errors ||= ActiveModel::Errors.new(self)
32
33
  end
33
34
 
35
+ # Returns the human name of the promotion rule
36
+ #
37
+ # @return [String] eg. Currency
38
+ def human_name
39
+ Spree.t("promotion_rule_types.#{key}.name")
40
+ end
41
+
42
+ # Returns the human description of the promotion rule
43
+ #
44
+ # @return [String]
45
+ def human_description
46
+ Spree.t("promotion_rule_types.#{key}.description")
47
+ end
48
+
49
+ # Returns the key of the promotion rule
50
+ #
51
+ # @return [String] eg. currency
52
+ def key
53
+ type.demodulize.underscore
54
+ end
55
+
34
56
  private
35
57
 
36
58
  def unique_per_promotion
@@ -1,5 +1,5 @@
1
1
  module Spree
2
- class PromotionRuleTaxon < Spree::Base
2
+ class PromotionRuleTaxon < Spree.base_class
3
3
  belongs_to :promotion_rule, class_name: 'Spree::PromotionRule'
4
4
  belongs_to :taxon, class_name: 'Spree::Taxon'
5
5
 
@@ -1,5 +1,5 @@
1
1
  module Spree
2
- class PromotionRuleUser < Spree::Base
2
+ class PromotionRuleUser < Spree.base_class
3
3
  belongs_to :promotion_rule, class_name: 'Spree::PromotionRule'
4
4
  belongs_to :user, class_name: "::#{Spree.user_class}"
5
5
 
@@ -1,14 +1,16 @@
1
1
  module Spree
2
- class Property < Spree::Base
2
+ class Property < Spree.base_class
3
3
  include Spree::FilterParam
4
4
  include Spree::Metadata
5
+ include Spree::ParameterizableName
6
+ include Spree::DisplayOn
5
7
  include Spree::TranslatableResource
6
8
  if defined?(Spree::Webhooks::HasWebhooks)
7
9
  include Spree::Webhooks::HasWebhooks
8
10
  end
9
11
 
10
12
  if Spree.always_use_translations?
11
- TRANSLATABLE_FIELDS = %i[name presentation filter_param].freeze
13
+ TRANSLATABLE_FIELDS = %i[name presentation].freeze
12
14
  translates(*TRANSLATABLE_FIELDS)
13
15
  else
14
16
  TRANSLATABLE_FIELDS = %i[presentation].freeze
@@ -20,6 +22,7 @@ module Spree
20
22
  end
21
23
 
22
24
  auto_strip_attributes :name, :presentation
25
+ acts_as_list
23
26
 
24
27
  has_many :property_prototypes, class_name: 'Spree::PropertyPrototype'
25
28
  has_many :prototypes, through: :property_prototypes, class_name: 'Spree::Prototype'
@@ -29,10 +32,15 @@ module Spree
29
32
 
30
33
  validates :name, :presentation, presence: true
31
34
 
35
+ default_scope { order(:position) }
32
36
  scope :sorted, -> { order(:name) }
33
37
  scope :filterable, -> { where(filterable: true) }
34
38
 
39
+ KIND_OPTIONS = { short_text: 0, long_text: 1, number: 2, rich_text: 3 }.freeze
40
+ enum :kind, KIND_OPTIONS
41
+
35
42
  after_touch :touch_all_products
43
+ after_update :touch_all_products, if: -> { saved_changes.key?(:presentation) }
36
44
  after_save :ensure_product_properties_have_filter_params
37
45
 
38
46
  self.whitelisted_ransackable_attributes = ['presentation', 'filterable']
@@ -48,7 +56,7 @@ module Spree
48
56
  private
49
57
 
50
58
  def touch_all_products
51
- products.update_all(updated_at: Time.current)
59
+ products.touch_all
52
60
  end
53
61
 
54
62
  def with_uniq_values_cache_key(product_properties_scope, &block)
@@ -1,5 +1,5 @@
1
1
  module Spree
2
- class PropertyPrototype < Spree::Base
2
+ class PropertyPrototype < Spree.base_class
3
3
  belongs_to :prototype, class_name: 'Spree::Prototype'
4
4
  belongs_to :property, class_name: 'Spree::Property'
5
5
 
@@ -1,5 +1,5 @@
1
1
  module Spree
2
- class Prototype < Spree::Base
2
+ class Prototype < Spree.base_class
3
3
  include Spree::Metadata
4
4
  if defined?(Spree::Webhooks::HasWebhooks)
5
5
  include Spree::Webhooks::HasWebhooks
@@ -1,5 +1,5 @@
1
1
  module Spree
2
- class PrototypeTaxon < Spree::Base
2
+ class PrototypeTaxon < Spree.base_class
3
3
  belongs_to :taxon, class_name: 'Spree::Taxon'
4
4
  belongs_to :prototype, class_name: 'Spree::Prototype'
5
5
 
@@ -1,5 +1,5 @@
1
1
  module Spree
2
- class Refund < Spree::Base
2
+ class Refund < Spree.base_class
3
3
  include Spree::Metadata
4
4
  if defined?(Spree::Webhooks::HasWebhooks)
5
5
  include Spree::Webhooks::HasWebhooks
@@ -58,6 +58,13 @@ module Spree
58
58
  reimbursement.customer_return&.return_items || reimbursement.return_items
59
59
  end
60
60
 
61
+ # Returns true if the refund is editable.
62
+ #
63
+ # @return [Boolean]
64
+ def editable?
65
+ !payment.order.canceled?
66
+ end
67
+
61
68
  private
62
69
 
63
70
  # attempts to perform the refund.
@@ -76,13 +83,17 @@ module Spree
76
83
 
77
84
  # return an activemerchant response object if successful or else raise an error
78
85
  def process!(credit_cents)
86
+ refund_total_in_cents = calculate_refund_amount(credit_cents)
87
+
79
88
  response = if payment.payment_method.payment_profiles_supported?
80
- payment.payment_method.credit(credit_cents, payment.source, payment.transaction_id, originator: self)
89
+ payment.payment_method.credit(refund_total_in_cents, payment.source, payment.transaction_id, originator: self)
81
90
  else
82
- payment.payment_method.credit(credit_cents, payment.transaction_id, originator: self)
91
+ payment.payment_method.credit(refund_total_in_cents, payment.transaction_id, originator: self)
83
92
  end
84
93
 
85
- unless response.success?
94
+ if response.success?
95
+ track_order_as_refunded(refund_total_in_cents)
96
+ else
86
97
  Rails.logger.error(Spree.t(:gateway_error) + " #{response.to_yaml}")
87
98
  text = response.params['message'] || response.params['response_reason_text'] || response.message
88
99
  raise Core::GatewayError, text
@@ -94,6 +105,15 @@ module Spree
94
105
  raise Core::GatewayError, Spree.t(:unable_to_connect_to_gateway)
95
106
  end
96
107
 
108
+ def calculate_refund_amount(credit_cents)
109
+ # Overwrite this for more complex calculations
110
+ credit_cents
111
+ end
112
+
113
+ def track_order_as_refunded(credit_cents)
114
+ # You can track refunds here
115
+ end
116
+
97
117
  def create_log_entry
98
118
  log_entries.create!(details: @response.to_yaml)
99
119
  end
@@ -1,13 +1,23 @@
1
1
  module Spree
2
- class RefundReason < Spree::Base
2
+ class RefundReason < Spree.base_class
3
3
  include Spree::NamedType
4
4
 
5
5
  RETURN_PROCESSING_REASON = 'Return processing'
6
+ ORDER_CANCELED_REASON = 'Order Canceled'
7
+ SHIPMENT_CANCELED_REASON = 'Shipment Canceled'
6
8
 
7
9
  has_many :refunds, dependent: :restrict_with_error
8
10
 
9
11
  def self.return_processing_reason
10
- find_by(name: RETURN_PROCESSING_REASON, mutable: false)
12
+ find_or_create_by(name: RETURN_PROCESSING_REASON, mutable: false)
13
+ end
14
+
15
+ def self.order_canceled_reason
16
+ find_or_create_by(name: ORDER_CANCELED_REASON, mutable: false)
17
+ end
18
+
19
+ def self.shipment_canceled_reason
20
+ find_or_create_by(name: SHIPMENT_CANCELED_REASON, mutable: false)
11
21
  end
12
22
  end
13
23
  end
@@ -1,5 +1,5 @@
1
1
  module Spree
2
- class Reimbursement::Credit < Spree::Base
2
+ class Reimbursement::Credit < Spree.base_class
3
3
  class_attribute :default_creditable_class
4
4
  self.default_creditable_class = nil
5
5
 
@@ -1,5 +1,5 @@
1
1
  module Spree
2
- class Reimbursement < Spree::Base
2
+ class Reimbursement < Spree.base_class
3
3
  module Emails
4
4
  def send_reimbursement_email
5
5
  # you can overwrite this method in your application / extension to send out the confirmation email
@@ -1,5 +1,5 @@
1
1
  module Spree
2
- class Reimbursement < Spree::Base
2
+ class Reimbursement < Spree.base_class
3
3
  include Spree::Core::NumberGenerator.new(prefix: 'RI', length: 9)
4
4
  include Spree::NumberIdentifier
5
5
  if defined?(Spree::Webhooks::HasWebhooks)
@@ -20,6 +20,8 @@ module Spree
20
20
  has_many :return_items
21
21
  end
22
22
 
23
+ belongs_to :performed_by, class_name: Spree.admin_user_class.to_s, optional: true
24
+
23
25
  validates :order, presence: true
24
26
  validate :validate_return_items_belong_to_same_order
25
27
 
@@ -104,10 +106,10 @@ module Spree
104
106
  total - paid_amount
105
107
  end
106
108
 
107
- def perform!
109
+ def perform!(performer = nil)
108
110
  reimbursement_tax_calculator.call(self)
109
111
  reload
110
- update!(total: calculated_total)
112
+ update!(total: calculated_total, performed_by: performer)
111
113
 
112
114
  reimbursement_performer.perform(self)
113
115
 
@@ -1,5 +1,5 @@
1
1
  module Spree
2
- class ReimbursementType < Spree::Base
2
+ class ReimbursementType < Spree.base_class
3
3
  include Spree::NamedType
4
4
 
5
5
  KINDS = %w(Spree::ReimbursementType::Credit
@@ -0,0 +1,110 @@
1
+ module Spree
2
+ class Report < Spree.base_class
3
+ include Spree::SingleStoreResource
4
+ include Spree::VendorConcern if defined?(Spree::VendorConcern)
5
+
6
+ #
7
+ # Associations
8
+ #
9
+ belongs_to :store, class_name: 'Spree::Store'
10
+ belongs_to :user, class_name: Spree.admin_user_class.to_s, optional: true
11
+
12
+ #
13
+ # Callbacks
14
+ #
15
+ after_initialize :set_default_values
16
+ after_commit :generate_async, on: :create
17
+
18
+ #
19
+ # Validations
20
+ #
21
+ validates :store, :date_from, :date_to, :currency, presence: true
22
+
23
+ #
24
+ # Attachments
25
+ #
26
+ has_one_attached :attachment, service: Spree.private_storage_service_name
27
+
28
+ # Returns a scope of records to be used for generating report lines
29
+ #
30
+ # @return [ActiveRecord::Relation]
31
+ def line_items_scope
32
+ raise NotImplementedError, "Subclass #{self.class.name} must implement #line_items_scope"
33
+ end
34
+
35
+ # Returns an array of report lines
36
+ #
37
+ # @return [Array<Spree::ReportLineItem>]
38
+ def line_items(options = {})
39
+ scope = line_items_scope
40
+ scope = scope.limit(options[:limit]) if options[:limit].present?
41
+
42
+ scope.map { |record| line_item_class.new(record: record, report: self) }
43
+ end
44
+
45
+ def to_partial_path
46
+ "spree/admin/reports/report"
47
+ end
48
+
49
+ def human_name
50
+ [Spree.t("report_names.#{type.demodulize.underscore}"), store.name, date_from.strftime('%Y-%m-%d'), date_to.strftime('%Y-%m-%d')].join(' - ')
51
+ end
52
+
53
+ def generate_async
54
+ Spree::Reports::GenerateJob.perform_later(id)
55
+ end
56
+
57
+ def generate
58
+ generate_csv
59
+ handle_attachment
60
+ send_report_done_email
61
+ end
62
+
63
+ def generate_csv
64
+ ::CSV.open(report_tmp_file_path, 'wb', encoding: 'UTF-8', col_sep: ',', row_sep: "\r\n") do |csv|
65
+ csv << line_item_class.csv_headers
66
+ line_items_scope.find_in_batches do |batch|
67
+ batch.each do |record|
68
+ csv << line_item_class.new(record: record, report: self).to_csv
69
+ end
70
+ end
71
+ end
72
+ end
73
+
74
+ def handle_attachment
75
+ file = ::File.open(report_tmp_file_path)
76
+ attachment.attach(io: file, filename: attachment_file_name)
77
+ ::File.delete(report_tmp_file_path) if ::File.exist?(report_tmp_file_path)
78
+ end
79
+
80
+ def send_report_done_email
81
+ return unless user.present?
82
+
83
+ Spree::ReportMailer.report_done(self).deliver_later
84
+ end
85
+
86
+ # eg. Spree::ReportLineItems::SalesTotal
87
+ def line_item_class
88
+ "Spree::ReportLineItems::#{type.demodulize}".safe_constantize
89
+ end
90
+
91
+ # eg. "store-sales-total-report-20250201120000.csv"
92
+ def attachment_file_name
93
+ @attachment_file_name ||= "#{store.code}-#{type.demodulize.parameterize}-report-#{created_at.strftime('%Y%m%d%H%M%S')}.csv"
94
+ end
95
+
96
+ private
97
+
98
+ def report_tmp_file_path
99
+ Rails.root.join('tmp', attachment_file_name)
100
+ end
101
+
102
+ def set_default_values
103
+ return if store.blank?
104
+
105
+ self.currency ||= store.default_currency
106
+ self.date_from ||= 1.month.ago.in_time_zone(store.preferred_timezone).beginning_of_day
107
+ self.date_to ||= Time.current.in_time_zone(store.preferred_timezone).end_of_day
108
+ end
109
+ end
110
+ end
@@ -0,0 +1,27 @@
1
+ module Spree
2
+ class ReportLineItem
3
+ include ActiveModel::Model
4
+ include ActiveModel::Attributes
5
+
6
+ attr_accessor :record, :report
7
+
8
+ delegate :store, :currency, to: :report
9
+ delegate :id, to: :record, prefix: true
10
+
11
+ def self.headers
12
+ attribute_types.keys.map do |attribute|
13
+ { name: attribute.to_sym, label: Spree.t(attribute.to_sym) }
14
+ end
15
+ end
16
+
17
+ def self.csv_headers
18
+ attribute_types.keys
19
+ end
20
+
21
+ def to_csv
22
+ self.class.attribute_types.keys.map do |attribute|
23
+ send(attribute)
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,76 @@
1
+ module Spree
2
+ module ReportLineItems
3
+ class ProductsPerformance < Spree::ReportLineItem
4
+ attribute :sku, :string
5
+ attribute :name, :string
6
+ attribute :vendor, :string
7
+ attribute :brand, :string
8
+ attribute :category_lvl0, :string
9
+ attribute :category_lvl1, :string
10
+ attribute :category_lvl2, :string
11
+ attribute :price, :string
12
+ attribute :weeks_online, :integer
13
+ attribute :pre_tax_amount, :string
14
+ attribute :tax_total, :string
15
+ attribute :quantity, :integer
16
+ attribute :promo_total, :string
17
+ attribute :total, :string
18
+
19
+ delegate :sku, :name, to: :record
20
+
21
+ def vendor
22
+ record.try(:vendor_name)
23
+ end
24
+
25
+ def brand
26
+ record.try(:brand_name)
27
+ end
28
+
29
+ def category_lvl0
30
+ mapped_categories&.first
31
+ end
32
+
33
+ def category_lvl1
34
+ mapped_categories&.second
35
+ end
36
+
37
+ def category_lvl2
38
+ mapped_categories&.third
39
+ end
40
+
41
+ def price
42
+ record.price_in(currency).display_amount
43
+ end
44
+
45
+ def pre_tax_amount
46
+ Spree::Money.new(record.pre_tax_amount, currency: currency)
47
+ end
48
+
49
+ def promo_total
50
+ Spree::Money.new(record.promo_total, currency: currency)
51
+ end
52
+
53
+ def tax_total
54
+ Spree::Money.new(record.tax_total, currency: currency)
55
+ end
56
+
57
+ def quantity
58
+ record.quantity
59
+ end
60
+
61
+ def total
62
+ Spree::Money.new(record.total, currency: currency)
63
+ end
64
+
65
+ def weeks_online
66
+ start_date = record.available_on.presence || record.created_at
67
+
68
+ (Time.current - start_date.in_time_zone(store.preferred_timezone)).seconds.in_weeks.to_i
69
+ end
70
+
71
+ def mapped_categories
72
+ @mapped_categories ||= record.main_taxon&.pretty_name&.split('->')&.map(&:strip)
73
+ end
74
+ end
75
+ end
76
+ end