spree_core 6.0.0.beta4 → 6.0.0.rc1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (495) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +2 -2
  3. data/app/helpers/spree/images_helper.rb +0 -57
  4. data/app/jobs/spree/base_job.rb +21 -0
  5. data/app/jobs/spree/carts/reap_expired_job.rb +2 -2
  6. data/app/jobs/spree/collections/regenerate_time_based_job.rb +6 -3
  7. data/app/jobs/spree/orders/finalize_stale_drafts_job.rb +8 -5
  8. data/app/jobs/spree/payments/cancel_unused_sessions_job.rb +61 -0
  9. data/app/jobs/spree/seller_payouts/sweep_seller_job.rb +2 -0
  10. data/app/mailers/spree/admin_user_mailer.rb +4 -19
  11. data/app/mailers/spree/base_mailer.rb +96 -13
  12. data/app/mailers/spree/email_template_mailer.rb +27 -0
  13. data/app/mailers/spree/export_mailer.rb +3 -4
  14. data/app/mailers/spree/import_mailer.rb +3 -4
  15. data/app/mailers/spree/invitation_mailer.rb +24 -7
  16. data/app/mailers/spree/seller_user_mailer.rb +4 -16
  17. data/app/mailers/spree/webhook_mailer.rb +2 -2
  18. data/app/models/concerns/spree/acted_by.rb +1 -1
  19. data/app/models/concerns/spree/admin_user_methods.rb +13 -1
  20. data/app/models/concerns/spree/api_typed.rb +91 -0
  21. data/app/models/concerns/spree/calculator/tiers.rb +76 -0
  22. data/app/models/concerns/spree/customer_methods.rb +15 -14
  23. data/app/models/concerns/spree/email_template_identity.rb +52 -0
  24. data/app/models/concerns/spree/parameterizable_name.rb +2 -0
  25. data/app/models/concerns/spree/payment_session_transitions.rb +8 -2
  26. data/app/models/concerns/spree/post_sale/taxation.rb +54 -0
  27. data/app/models/concerns/spree/post_sale/taxed_line.rb +51 -0
  28. data/app/models/concerns/spree/preference_schema.rb +4 -12
  29. data/app/models/concerns/spree/product_scopes.rb +11 -1
  30. data/app/models/concerns/spree/provider_listing.rb +41 -0
  31. data/app/models/concerns/spree/publishable.rb +82 -14
  32. data/app/models/concerns/spree/purchase/addresses.rb +23 -10
  33. data/app/models/concerns/spree/purchase/currency.rb +1 -1
  34. data/app/models/concerns/spree/purchase/deprecated_aliases.rb +87 -0
  35. data/app/models/concerns/spree/purchase/gift_cards.rb +10 -4
  36. data/app/models/concerns/spree/purchase/locale.rb +1 -1
  37. data/app/models/concerns/spree/purchase/payment_processing.rb +7 -3
  38. data/app/models/concerns/spree/purchase/purchase_order.rb +2 -2
  39. data/app/models/concerns/spree/purchase/totals.rb +49 -1
  40. data/app/models/concerns/spree/receivable.rb +1 -1
  41. data/app/models/concerns/spree/searchable.rb +0 -4
  42. data/app/models/concerns/spree/secret_preferences.rb +59 -0
  43. data/app/models/concerns/spree/single_store_resource.rb +1 -1
  44. data/app/models/concerns/spree/stores/email_branding.rb +31 -0
  45. data/app/models/concerns/spree/type_labels.rb +38 -0
  46. data/app/models/concerns/spree/typed_adjustment_line.rb +1 -1
  47. data/app/models/concerns/spree/typed_associations.rb +6 -0
  48. data/app/models/spree/address.rb +11 -1
  49. data/app/models/spree/adjusters/promotion.rb +1 -15
  50. data/app/models/spree/allowed_origin.rb +0 -1
  51. data/app/models/spree/api_key.rb +5 -6
  52. data/app/models/spree/authentication/strategies/oidc_strategy.rb +5 -5
  53. data/app/models/spree/base.rb +70 -22
  54. data/app/models/spree/calculator/flat_percent_item_total.rb +1 -3
  55. data/app/models/spree/calculator/flat_rate.rb +1 -3
  56. data/app/models/spree/calculator/flexi_rate.rb +1 -3
  57. data/app/models/spree/calculator/percent_on_line_item.rb +1 -1
  58. data/app/models/spree/calculator/price_sack.rb +1 -3
  59. data/app/models/spree/calculator/shipping/digital_delivery.rb +1 -3
  60. data/app/models/spree/calculator/shipping/flat_percent_item_total.rb +1 -3
  61. data/app/models/spree/calculator/shipping/flat_rate.rb +1 -3
  62. data/app/models/spree/calculator/shipping/flexi_rate.rb +1 -3
  63. data/app/models/spree/calculator/shipping/per_item.rb +1 -3
  64. data/app/models/spree/calculator/shipping/price_sack.rb +1 -3
  65. data/app/models/spree/calculator/tiered_flat_rate.rb +6 -23
  66. data/app/models/spree/calculator/tiered_percent.rb +7 -25
  67. data/app/models/spree/cart.rb +78 -18
  68. data/app/models/spree/channel/gating.rb +1 -1
  69. data/app/models/spree/channel.rb +4 -4
  70. data/app/models/spree/claim.rb +21 -1
  71. data/app/models/spree/claim_line_item.rb +41 -6
  72. data/app/models/spree/collection.rb +0 -1
  73. data/app/models/spree/collection_rule.rb +5 -21
  74. data/app/models/spree/commission_line.rb +1 -1
  75. data/app/models/spree/commission_rate.rb +2 -3
  76. data/app/models/spree/commission_rate_value.rb +1 -1
  77. data/app/models/spree/commission_rule.rb +4 -11
  78. data/app/models/spree/commission_rules/product_rule.rb +1 -1
  79. data/app/models/spree/company_invitation.rb +1 -0
  80. data/app/models/spree/consent_record.rb +0 -1
  81. data/app/models/spree/credit_card.rb +1 -1
  82. data/app/models/spree/current.rb +23 -0
  83. data/app/models/spree/custom_field.rb +2 -2
  84. data/app/models/spree/custom_field_definition.rb +9 -23
  85. data/app/models/spree/custom_fields/boolean.rb +1 -1
  86. data/app/models/spree/custom_fields/json.rb +1 -1
  87. data/app/models/spree/data_feed/google.rb +0 -2
  88. data/app/models/spree/data_request.rb +1 -1
  89. data/app/models/spree/delivery_method.rb +30 -13
  90. data/app/models/spree/delivery_method_rule.rb +4 -11
  91. data/app/models/spree/delivery_rate.rb +4 -5
  92. data/app/models/spree/delivery_rate_provider/base.rb +1 -0
  93. data/app/models/spree/delivery_zone.rb +0 -1
  94. data/app/models/spree/delivery_zone_member.rb +4 -4
  95. data/app/models/spree/digital_asset.rb +7 -0
  96. data/app/models/spree/digital_asset_provider/base.rb +2 -0
  97. data/app/models/spree/digital_link.rb +2 -5
  98. data/app/models/spree/discount.rb +1 -1
  99. data/app/models/spree/email_template.rb +25 -0
  100. data/app/models/spree/email_template_draft.rb +13 -0
  101. data/app/models/spree/email_template_revision.rb +17 -0
  102. data/app/models/spree/email_templates/entry.rb +118 -0
  103. data/app/models/spree/email_templates/sample_record.rb +16 -0
  104. data/app/models/spree/emails/branding.rb +80 -0
  105. data/app/models/spree/exchange.rb +15 -1
  106. data/app/models/spree/exchange_line_item.rb +121 -5
  107. data/app/models/spree/export.rb +0 -1
  108. data/app/models/spree/exports/products.rb +10 -1
  109. data/app/models/spree/exports/report.rb +1 -1
  110. data/app/models/spree/fee.rb +4 -2
  111. data/app/models/spree/fulfillment.rb +53 -27
  112. data/app/models/spree/fulfillment_changer.rb +2 -2
  113. data/app/models/spree/fulfillment_item.rb +0 -4
  114. data/app/models/spree/fulfillment_provider/base.rb +1 -0
  115. data/app/models/spree/gateway/bogus.rb +4 -0
  116. data/app/models/spree/gateway/custom_payment_source_method.rb +5 -0
  117. data/app/models/spree/gateway.rb +5 -1
  118. data/app/models/spree/gift_card.rb +12 -1
  119. data/app/models/spree/gift_card_batch.rb +0 -1
  120. data/app/models/spree/import.rb +2 -7
  121. data/app/models/spree/import_mapping.rb +1 -2
  122. data/app/models/spree/import_row.rb +2 -2
  123. data/app/models/spree/import_schemas/products.rb +1 -0
  124. data/app/models/spree/integration.rb +8 -18
  125. data/app/models/spree/invitation.rb +5 -5
  126. data/app/models/spree/line_item.rb +13 -2
  127. data/app/models/spree/locale.rb +11 -8
  128. data/app/models/spree/market.rb +1 -0
  129. data/app/models/spree/newsletter_subscriber.rb +3 -0
  130. data/app/models/spree/order.rb +56 -138
  131. data/app/models/spree/order_group.rb +11 -2
  132. data/app/models/spree/order_inventory.rb +10 -5
  133. data/app/models/spree/order_promotion.rb +1 -1
  134. data/app/models/spree/order_routing/has_strategy_preference.rb +1 -1
  135. data/app/models/spree/order_routing/strategy/base.rb +4 -2
  136. data/app/models/spree/order_routing/strategy/rules.rb +1 -1
  137. data/app/models/spree/order_routing_rule.rb +6 -22
  138. data/app/models/spree/package_type.rb +1 -2
  139. data/app/models/spree/payment/gateway_options.rb +14 -52
  140. data/app/models/spree/payment/processing.rb +2 -4
  141. data/app/models/spree/payment.rb +20 -5
  142. data/app/models/spree/payment_method/session_instrumentation.rb +1 -0
  143. data/app/models/spree/payment_method/store_credit.rb +4 -4
  144. data/app/models/spree/payment_method.rb +36 -4
  145. data/app/models/spree/payment_session.rb +5 -1
  146. data/app/models/spree/payment_setup_session.rb +1 -0
  147. data/app/models/spree/payout_provider/base.rb +3 -1
  148. data/app/models/spree/pickup_point_provider/base.rb +2 -0
  149. data/app/models/spree/price_list.rb +1 -2
  150. data/app/models/spree/price_rules/channel_rule.rb +1 -1
  151. data/app/models/spree/price_rules/customer_group_rule.rb +1 -1
  152. data/app/models/spree/product.rb +31 -7
  153. data/app/models/spree/product_submission.rb +0 -12
  154. data/app/models/spree/product_type.rb +0 -1
  155. data/app/models/spree/promotion/actions/create_line_items.rb +27 -17
  156. data/app/models/spree/promotion/rules/category.rb +0 -4
  157. data/app/models/spree/promotion/rules/channel.rb +0 -4
  158. data/app/models/spree/promotion/rules/country.rb +0 -4
  159. data/app/models/spree/promotion/rules/currency.rb +0 -4
  160. data/app/models/spree/promotion/rules/customer_group.rb +0 -4
  161. data/app/models/spree/promotion/rules/first_order.rb +0 -4
  162. data/app/models/spree/promotion/rules/item_total.rb +4 -8
  163. data/app/models/spree/promotion/rules/market.rb +0 -4
  164. data/app/models/spree/promotion/rules/one_use_per_user.rb +0 -4
  165. data/app/models/spree/promotion/rules/option_value.rb +0 -3
  166. data/app/models/spree/promotion/rules/product.rb +0 -4
  167. data/app/models/spree/promotion/rules/user.rb +0 -4
  168. data/app/models/spree/promotion/rules/user_logged_in.rb +0 -4
  169. data/app/models/spree/promotion.rb +35 -6
  170. data/app/models/spree/promotion_action.rb +7 -14
  171. data/app/models/spree/promotion_action_line_item.rb +1 -1
  172. data/app/models/spree/promotion_handler/coupon.rb +59 -27
  173. data/app/models/spree/promotion_rule.rb +7 -14
  174. data/app/models/spree/purchase_order.rb +1 -0
  175. data/app/models/spree/quantity_rule.rb +1 -1
  176. data/app/models/spree/refund.rb +28 -3
  177. data/app/models/spree/return.rb +30 -2
  178. data/app/models/spree/return_line_item.rb +65 -14
  179. data/app/models/spree/role.rb +7 -7
  180. data/app/models/spree/saved_report.rb +1 -1
  181. data/app/models/spree/seller.rb +2 -1
  182. data/app/models/spree/seller_payout.rb +7 -0
  183. data/app/models/spree/seller_requirement.rb +4 -12
  184. data/app/models/spree/seller_requirement_custom_field.rb +1 -2
  185. data/app/models/spree/seller_requirement_submission.rb +2 -2
  186. data/app/models/spree/seller_requirements/accept_terms.rb +2 -3
  187. data/app/models/spree/seller_transfer.rb +5 -0
  188. data/app/models/spree/shipping_label.rb +1 -0
  189. data/app/models/spree/stock/availability_validator.rb +2 -2
  190. data/app/models/spree/stock/inventory_unit_builder.rb +1 -1
  191. data/app/models/spree/stock/packer.rb +2 -2
  192. data/app/models/spree/stock/splitter/weight.rb +1 -1
  193. data/app/models/spree/stock_level/custom_events.rb +6 -0
  194. data/app/models/spree/stock_level.rb +1 -1
  195. data/app/models/spree/stock_movement.rb +1 -1
  196. data/app/models/spree/stock_receipt_item.rb +1 -1
  197. data/app/models/spree/stock_reservation.rb +2 -2
  198. data/app/models/spree/stock_transfer.rb +4 -2
  199. data/app/models/spree/store.rb +33 -7
  200. data/app/models/spree/store_credit.rb +8 -9
  201. data/app/models/spree/store_credit_event.rb +5 -5
  202. data/app/models/spree/tax_exemption_certificate.rb +2 -0
  203. data/app/models/spree/tax_identifier.rb +3 -1
  204. data/app/models/spree/tax_line.rb +53 -3
  205. data/app/models/spree/tax_provider/base.rb +71 -14
  206. data/app/models/spree/tax_provider/internal.rb +59 -33
  207. data/app/models/spree/tax_provider/recorded_share.rb +114 -0
  208. data/app/models/spree/tax_rate.rb +1 -2
  209. data/app/models/spree/taxonomy.rb +0 -1
  210. data/app/models/spree/variant.rb +4 -3
  211. data/app/models/spree/variant_media.rb +1 -1
  212. data/app/models/spree/webhook_endpoint.rb +10 -13
  213. data/app/models/spree/wishlist.rb +1 -6
  214. data/app/models/spree/wishlist_item/custom_events.rb +6 -0
  215. data/app/presenters/spree/csv/customer_presenter.rb +1 -1
  216. data/app/presenters/spree/csv/newsletter_subscriber_presenter.rb +1 -1
  217. data/app/presenters/spree/csv/order_line_item_presenter.rb +1 -1
  218. data/app/presenters/spree/csv/product_variant_presenter.rb +42 -12
  219. data/app/serializers/spree/emails/base_serializer.rb +14 -0
  220. data/app/serializers/spree/emails/export_serializer.rb +13 -0
  221. data/app/serializers/spree/emails/import_serializer.rb +19 -0
  222. data/app/serializers/spree/emails/invitation_serializer.rb +28 -0
  223. data/app/serializers/spree/emails/store_serializer.rb +31 -0
  224. data/app/serializers/spree/emails/user_serializer.rb +12 -0
  225. data/app/serializers/spree/emails/webhook_endpoint_serializer.rb +16 -0
  226. data/app/services/spree/carts/create.rb +8 -2
  227. data/app/services/spree/carts/destroy.rb +2 -2
  228. data/app/services/spree/carts/remove_out_of_stock_items.rb +2 -2
  229. data/app/services/spree/carts/split_by_seller.rb +1 -1
  230. data/app/services/spree/carts/update.rb +19 -5
  231. data/app/services/spree/catalogs/set_product_terms.rb +1 -1
  232. data/app/services/spree/categories/add_products.rb +13 -31
  233. data/app/services/spree/categories/remove_products.rb +12 -51
  234. data/app/services/spree/checkout/advance.rb +1 -4
  235. data/app/services/spree/collections/add_products.rb +10 -23
  236. data/app/services/spree/collections/remove_products.rb +7 -43
  237. data/app/services/spree/coupon_codes/bulk_generate.rb +8 -14
  238. data/app/services/spree/data_requests/create.rb +1 -1
  239. data/app/services/spree/deliveries/destroy.rb +2 -2
  240. data/app/services/spree/email_templates/check.rb +58 -0
  241. data/app/services/spree/email_templates/no_sample_record.rb +7 -0
  242. data/app/services/spree/email_templates/preview.rb +99 -0
  243. data/app/services/spree/emails/file_system.rb +23 -0
  244. data/app/services/spree/emails/filters.rb +84 -0
  245. data/app/services/spree/emails/liquid_context.rb +48 -0
  246. data/app/services/spree/emails/rendered_email.rb +14 -0
  247. data/app/services/spree/emails/renderer.rb +125 -0
  248. data/app/services/spree/emails/template.rb +51 -0
  249. data/app/services/spree/emails/template_data.rb +23 -0
  250. data/app/services/spree/emails/template_resolver.rb +109 -0
  251. data/app/services/spree/emails/text_converter.rb +72 -0
  252. data/app/services/spree/emails/translation_inliner.rb +134 -0
  253. data/app/services/spree/imports/row_processors/price_list_price.rb +10 -10
  254. data/app/services/spree/imports/row_processors/product_variant.rb +65 -34
  255. data/app/services/spree/imports/row_processors/purchase_order.rb +13 -13
  256. data/app/services/spree/locales.rb +4 -5
  257. data/app/services/spree/newsletter/subscribe.rb +8 -12
  258. data/app/services/spree/orders/build_fulfillments.rb +3 -2
  259. data/app/services/spree/orders/create.rb +2 -1
  260. data/app/services/spree/orders/discounts/create.rb +7 -4
  261. data/app/services/spree/orders/discounts/destroy.rb +1 -0
  262. data/app/services/spree/orders/discounts/update.rb +1 -0
  263. data/app/services/spree/orders/fees/create.rb +1 -0
  264. data/app/services/spree/orders/fees/destroy.rb +1 -0
  265. data/app/services/spree/orders/fees/update.rb +1 -0
  266. data/app/services/spree/orders/remove_line_item.rb +13 -3
  267. data/app/services/spree/orders/update.rb +10 -6
  268. data/app/services/spree/orders/update_item.rb +6 -3
  269. data/app/services/spree/orders/update_statuses.rb +3 -1
  270. data/app/services/spree/product_memberships.rb +61 -0
  271. data/app/services/spree/returns/eligibility_validator.rb +1 -1
  272. data/app/services/spree/search_provider/custom_field_schema.rb +2 -2
  273. data/app/services/spree/seeds/commission_rates.rb +1 -1
  274. data/app/services/spree/seeds/digital_delivery.rb +1 -1
  275. data/app/services/spree/seeds/payment_methods.rb +2 -2
  276. data/app/services/spree/seeds/saved_reports.rb +3 -3
  277. data/app/services/spree/sellers/requirements.rb +1 -2
  278. data/app/services/spree/shipping_labels/confirm_refund.rb +1 -1
  279. data/app/services/spree/shipping_labels/store_file.rb +3 -3
  280. data/app/services/spree/stock_levels/correct.rb +3 -3
  281. data/app/services/spree/stock_reservations/reserve.rb +2 -3
  282. data/app/services/spree/store_credits/apply.rb +15 -13
  283. data/app/services/spree/stores/provision_defaults.rb +15 -14
  284. data/app/subscribers/spree/seller_onboarding_subscriber.rb +1 -1
  285. data/app/views/layouts/spree/base_mailer.html.erb +2 -43
  286. data/app/views/layouts/spree/base_mailer.liquid +79 -0
  287. data/app/views/spree/admin_user_mailer/password_reset_email.liquid +14 -0
  288. data/app/views/spree/export_mailer/export_done.liquid +12 -0
  289. data/app/views/spree/import_mailer/import_done.liquid +15 -0
  290. data/app/views/spree/invitation_mailer/invitation_accepted.liquid +9 -0
  291. data/app/views/spree/invitation_mailer/invitation_email.liquid +13 -0
  292. data/app/views/spree/seller_user_mailer/password_reset_email.liquid +14 -0
  293. data/app/views/spree/shared/_mailer_button.html.erb +4 -5
  294. data/app/views/spree/shared/_mailer_hero.html.erb +6 -5
  295. data/app/views/spree/shared/_summary_row.liquid +8 -0
  296. data/app/views/spree/webhook_mailer/endpoint_disabled.liquid +21 -0
  297. data/app/workflows/spree/carts/add_item.rb +4 -4
  298. data/app/workflows/spree/carts/complete.rb +13 -3
  299. data/app/workflows/spree/carts/merge.rb +1 -1
  300. data/app/workflows/spree/carts/recalculate_totals.rb +5 -0
  301. data/app/workflows/spree/carts/upsert_items.rb +18 -9
  302. data/app/workflows/spree/claims/approve.rb +5 -22
  303. data/app/workflows/spree/claims/cancel.rb +5 -21
  304. data/app/workflows/spree/claims/create.rb +15 -0
  305. data/app/workflows/spree/claims/deny.rb +7 -0
  306. data/app/workflows/spree/claims/resolve.rb +34 -31
  307. data/app/workflows/spree/customers/anonymize.rb +3 -4
  308. data/app/workflows/spree/customers/create.rb +1 -1
  309. data/app/workflows/spree/data_requests/fulfill.rb +2 -2
  310. data/app/workflows/spree/deliveries/update_tracking.rb +1 -1
  311. data/app/workflows/spree/email_templates/discard_draft.rb +20 -0
  312. data/app/workflows/spree/email_templates/publish.rb +70 -0
  313. data/app/workflows/spree/email_templates/restore_revision.rb +33 -0
  314. data/app/workflows/spree/email_templates/revert.rb +29 -0
  315. data/app/workflows/spree/email_templates/save_draft.rb +85 -0
  316. data/app/workflows/spree/exchanges/approve.rb +5 -22
  317. data/app/workflows/spree/exchanges/cancel.rb +5 -21
  318. data/app/workflows/spree/exchanges/create.rb +6 -0
  319. data/app/workflows/spree/exchanges/fulfill.rb +75 -51
  320. data/app/workflows/spree/exchanges/receive.rb +6 -65
  321. data/app/workflows/spree/fulfillments/cancel.rb +1 -1
  322. data/app/workflows/spree/fulfillments/create.rb +51 -47
  323. data/app/workflows/spree/fulfillments/fulfill.rb +23 -27
  324. data/app/workflows/spree/fulfillments/mark_delivered.rb +1 -1
  325. data/app/workflows/spree/fulfillments/replacements.rb +59 -0
  326. data/app/workflows/spree/gift_cards/apply.rb +2 -2
  327. data/app/workflows/spree/orders/add_item.rb +5 -1
  328. data/app/workflows/spree/orders/cancel.rb +3 -3
  329. data/app/workflows/spree/orders/complete.rb +16 -8
  330. data/app/workflows/spree/payments/process.rb +2 -2
  331. data/app/workflows/spree/post_sale/approval.rb +35 -0
  332. data/app/workflows/spree/post_sale/cancellation.rb +44 -0
  333. data/app/workflows/spree/post_sale/receipt.rb +92 -0
  334. data/app/workflows/spree/products/nested_attributes.rb +1 -1
  335. data/app/workflows/spree/purchase_orders/cancel.rb +1 -1
  336. data/app/workflows/spree/purchase_orders/close.rb +3 -31
  337. data/app/workflows/spree/purchase_orders/mark_draft.rb +2 -2
  338. data/app/workflows/spree/purchase_orders/mark_ordered.rb +2 -2
  339. data/app/workflows/spree/purchase_orders/receive.rb +1 -1
  340. data/app/workflows/spree/purchase_orders/update.rb +5 -34
  341. data/app/workflows/spree/receivables/closing_short.rb +46 -0
  342. data/app/workflows/spree/receivables/draft_editing.rb +48 -0
  343. data/app/workflows/spree/refunds/create.rb +5 -2
  344. data/app/workflows/spree/refunds/order_payments.rb +14 -2
  345. data/app/workflows/spree/refunds/tax_credit.rb +67 -0
  346. data/app/workflows/spree/returns/approve.rb +5 -22
  347. data/app/workflows/spree/returns/cancel.rb +5 -22
  348. data/app/workflows/spree/returns/create.rb +8 -0
  349. data/app/workflows/spree/returns/receive.rb +5 -76
  350. data/app/workflows/spree/returns/refund.rb +31 -8
  351. data/app/workflows/spree/seller_requirement_submissions/create.rb +1 -2
  352. data/app/workflows/spree/seller_transfers/reverse.rb +28 -1
  353. data/app/workflows/spree/shipping_labels/purchase.rb +8 -8
  354. data/app/workflows/spree/shipping_labels/record.rb +5 -5
  355. data/app/workflows/spree/shipping_labels/refund.rb +2 -2
  356. data/app/workflows/spree/stock_receipts/recording.rb +4 -4
  357. data/app/workflows/spree/stock_transfers/cancel.rb +2 -2
  358. data/app/workflows/spree/stock_transfers/close.rb +3 -30
  359. data/app/workflows/spree/stock_transfers/mark_draft.rb +1 -1
  360. data/app/workflows/spree/stock_transfers/mark_in_transit.rb +3 -3
  361. data/app/workflows/spree/stock_transfers/mark_ready.rb +2 -2
  362. data/app/workflows/spree/stock_transfers/receive.rb +1 -1
  363. data/app/workflows/spree/stock_transfers/update.rb +5 -33
  364. data/config/i18n-tasks.yml +32 -18
  365. data/config/initializers/alba.rb +7 -0
  366. data/config/language_names.csv +264 -0
  367. data/config/locales/ar.yml +811 -0
  368. data/config/locales/be.yml +855 -0
  369. data/config/locales/bg.yml +723 -0
  370. data/config/locales/ca.yml +689 -0
  371. data/config/locales/cs.yml +855 -0
  372. data/config/locales/da.yml +771 -0
  373. data/config/locales/de-CH.yml +248 -0
  374. data/config/locales/de.yml +835 -0
  375. data/config/locales/el.yml +769 -0
  376. data/config/locales/en.yml +37 -503
  377. data/config/locales/es-CL.yml +301 -0
  378. data/config/locales/es-EC.yml +186 -0
  379. data/config/locales/es-MX.yml +334 -0
  380. data/config/locales/es.yml +831 -0
  381. data/config/locales/et.yml +644 -0
  382. data/config/locales/fa.yml +709 -0
  383. data/config/locales/fi.yml +771 -0
  384. data/config/locales/fr.yml +830 -0
  385. data/config/locales/hu.yml +751 -0
  386. data/config/locales/id.yml +756 -0
  387. data/config/locales/it.yml +833 -0
  388. data/config/locales/ja.yml +726 -0
  389. data/config/locales/km.yml +759 -0
  390. data/config/locales/ko.yml +575 -0
  391. data/config/locales/ku.yml +753 -0
  392. data/config/locales/lt.yml +821 -0
  393. data/config/locales/lv.yml +685 -0
  394. data/config/locales/nb.yml +824 -0
  395. data/config/locales/ne.yml +823 -0
  396. data/config/locales/nl.yml +826 -0
  397. data/config/locales/pl.yml +835 -0
  398. data/config/locales/pt-BR.yml +547 -0
  399. data/config/locales/pt.yml +636 -0
  400. data/config/locales/ro.yml +750 -0
  401. data/config/locales/ru.yml +834 -0
  402. data/config/locales/sk.yml +680 -0
  403. data/config/locales/sl-SI.yml +600 -0
  404. data/config/locales/sv.yml +827 -0
  405. data/config/locales/th.yml +745 -0
  406. data/config/locales/tr.yml +777 -0
  407. data/config/locales/uk.yml +836 -0
  408. data/config/locales/vi.yml +768 -0
  409. data/config/locales/zh-CN.yml +786 -0
  410. data/config/locales/zh-TW.yml +834 -0
  411. data/db/migrate/20260925000001_add_replacement_to_spree_fulfillment_items.rb +5 -0
  412. data/db/migrate/20261001000001_add_post_sale_tax.rb +30 -0
  413. data/db/migrate/20261001000001_convert_preferences_to_json.rb +166 -0
  414. data/db/migrate/20261001000001_create_spree_email_templates.rb +52 -0
  415. data/lib/generators/spree/dummy/dummy_generator.rb +0 -9
  416. data/lib/generators/spree/dummy/templates/rails/test.rb +7 -0
  417. data/lib/spree/checkout/default_requirements.rb +37 -13
  418. data/lib/spree/core/configuration.rb +1 -1
  419. data/lib/spree/core/controller_helpers/store.rb +1 -1
  420. data/lib/spree/core/dependencies.rb +5 -4
  421. data/lib/spree/core/emails/escaped_output.rb +60 -0
  422. data/lib/spree/core/engine.rb +48 -167
  423. data/lib/spree/core/permissions/default_catalog.rb +5 -0
  424. data/lib/spree/core/preferences/json_conversion.rb +182 -0
  425. data/lib/spree/core/preferences/masking.rb +4 -1
  426. data/lib/spree/core/preferences/preferable.rb +137 -83
  427. data/lib/spree/core/preferences/preferable_class_methods.rb +81 -146
  428. data/lib/spree/core/previews/import_preview.rb +10 -0
  429. data/lib/spree/core/tax/provider_error.rb +37 -0
  430. data/lib/spree/core/version.rb +1 -1
  431. data/lib/spree/core.rb +85 -370
  432. data/lib/spree/emails/editable_templates.rb +82 -0
  433. data/lib/spree/events/catalog.rb +189 -0
  434. data/lib/spree/events.rb +21 -1
  435. data/lib/spree/hooks.rb +0 -17
  436. data/lib/spree/i18n.rb +32 -15
  437. data/lib/spree/reporting/adapters/live.rb +6 -6
  438. data/lib/spree/reporting/default_vocabulary.rb +22 -10
  439. data/lib/spree/reporting/hydration.rb +1 -1
  440. data/lib/spree/reporting/query.rb +8 -0
  441. data/lib/spree/reporting/schema.rb +10 -10
  442. data/lib/spree/testing_support/editable_email_templates.rb +61 -0
  443. data/lib/spree/testing_support/emails.rb +22 -0
  444. data/lib/spree/testing_support/factories/cart_factory.rb +1 -1
  445. data/lib/spree/testing_support/factories/email_template_factory.rb +28 -0
  446. data/lib/spree/testing_support/factories/order_factory.rb +2 -2
  447. data/lib/spree/testing_support/factories/refund_factory.rb +1 -1
  448. data/lib/spree/testing_support/factories/stock_factory.rb +1 -1
  449. data/lib/spree/testing_support/factories/variant_factory.rb +1 -1
  450. data/lib/spree/testing_support/i18n.rb +3 -94
  451. data/lib/spree/testing_support/lifecycle_events.rb +1 -1
  452. data/lib/spree/testing_support/order_walkthrough.rb +1 -1
  453. data/lib/spree/testing_support/preferences.rb +0 -5
  454. data/lib/spree/testing_support/store.rb +9 -1
  455. data/lib/spree/upgrades/5_6_to_6_0/manifest.yml +45 -0
  456. data/lib/tasks/categories_and_collections.rake +3 -3
  457. data/lib/tasks/core.rake +6 -4
  458. data/lib/tasks/media.rake +3 -3
  459. data/lib/tasks/package_types.rake +1 -1
  460. data/lib/tasks/preferences_json.rake +101 -0
  461. data/spec/fixtures/mailer_views/custom_views_probe_mailer/welcome_email.html.erb +4 -0
  462. metadata +188 -44
  463. data/app/models/acts_as_taggable_on/tag_decorator.rb +0 -10
  464. data/app/models/concerns/spree/has_image_alt_text.rb +0 -18
  465. data/app/models/spree/base_analytics_event_handler.rb +0 -54
  466. data/app/models/spree/invitations/store.rb +0 -6
  467. data/app/models/spree/preference.rb +0 -14
  468. data/app/models/spree/promotion_handler/page.rb +0 -25
  469. data/app/models/spree/promotion_handler/promotion_duplicator.rb +0 -60
  470. data/app/models/spree/stock/differentiator.rb +0 -45
  471. data/app/presenters/spree/filters/options_presenter.rb +0 -47
  472. data/app/services/spree/carts/empty.rb +0 -42
  473. data/app/services/spree/country_to_timezone.rb +0 -273
  474. data/app/services/spree/payments/create.rb +0 -92
  475. data/app/validators/spree/url_validator.rb +0 -23
  476. data/app/views/active_storage/blobs/_blob.html.erb +0 -14
  477. data/app/views/spree/admin_user_mailer/password_reset_email.html.erb +0 -8
  478. data/app/views/spree/export_mailer/export_done.html.erb +0 -7
  479. data/app/views/spree/import_mailer/import_done.html.erb +0 -12
  480. data/app/views/spree/invitation_mailer/invitation_accepted.html.erb +0 -4
  481. data/app/views/spree/invitation_mailer/invitation_email.html.erb +0 -12
  482. data/app/views/spree/seller_user_mailer/password_reset_email.html.erb +0 -8
  483. data/app/views/spree/shared/_base_mailer_footer.html.erb +0 -19
  484. data/app/views/spree/shared/_base_mailer_header.html.erb +0 -8
  485. data/app/views/spree/shared/_base_mailer_stylesheets.html.erb +0 -399
  486. data/app/views/spree/shared/_mailer_logo.html.erb +0 -41
  487. data/app/views/spree/shared/_purchased_items_styles.html.erb +0 -119
  488. data/app/views/spree/webhook_mailer/endpoint_disabled.html.erb +0 -51
  489. data/app/views/spree/webhook_mailer/endpoint_disabled.text.erb +0 -10
  490. data/app/workflows/spree/fulfillments/purchase_label.rb +0 -40
  491. data/app/workflows/spree/returns/purchase_label.rb +0 -32
  492. data/lib/spree/analytics.rb +0 -11
  493. data/lib/spree/core/preferences/scoped_store.rb +0 -34
  494. data/lib/spree/core/preferences/store.rb +0 -105
  495. data/lib/spree/translation_migrations.rb +0 -52
@@ -1,5 +1,5 @@
1
1
  # A rule to apply to an order greater than (or greater than or equal to)
2
- # a specific amount
2
+ # a specific amount. Free gifts do not count toward it.
3
3
  module Spree
4
4
  class Promotion
5
5
  module Rules
@@ -12,12 +12,8 @@ module Spree
12
12
  OPERATORS_MIN = ['gt', 'gte']
13
13
  OPERATORS_MAX = ['lt', 'lte']
14
14
 
15
- def applicable?(promotable)
16
- promotable.is_a?(Spree::Order) || promotable.is_a?(Spree::Cart)
17
- end
18
-
19
- def eligible?(order, _options = {})
20
- item_total = order.item_total
15
+ def eligible?(order, options = {})
16
+ item_total = order.item_total - order.gift_amount(promotion: promotion, own_gift_only: options.fetch(:own_gift_only, false))
21
17
 
22
18
  lower_limit_condition = item_total.send(preferred_operator_min == 'gte' ? :>= : :>, BigDecimal(preferred_amount_min.to_s))
23
19
 
@@ -43,7 +39,7 @@ module Spree
43
39
  if preferred_amount_max.present?
44
40
  Spree::Money.new(preferred_amount_max, currency: order.currency).to_s
45
41
  else
46
- Spree.t('no_maximum')
42
+ I18n.t('spree.no_maximum')
47
43
  end
48
44
  end
49
45
 
@@ -14,10 +14,6 @@ module Spree
14
14
  scope: ->(rule) { rule.promotion.store.markets }
15
15
  )
16
16
 
17
- def applicable?(promotable)
18
- promotable.is_a?(Spree::Order) || promotable.is_a?(Spree::Cart)
19
- end
20
-
21
17
  def markets
22
18
  return Spree::Market.none if preferred_market_ids.blank?
23
19
 
@@ -2,10 +2,6 @@ module Spree
2
2
  class Promotion
3
3
  module Rules
4
4
  class OneUsePerUser < Spree::PromotionRule
5
- def applicable?(promotable)
6
- promotable.is_a?(Spree::Order) || promotable.is_a?(Spree::Cart)
7
- end
8
-
9
5
  def eligible?(order, _options = {})
10
6
  if order.customer.present?
11
7
  if promotion.used_by?(order.customer, [order])
@@ -13,9 +13,6 @@ module Spree
13
13
 
14
14
  # @param promotable [Object]
15
15
  # @return [Boolean]
16
- def applicable?(promotable)
17
- promotable.is_a?(Spree::Order) || promotable.is_a?(Spree::Cart)
18
- end
19
16
 
20
17
  # @return [ActiveRecord::Relation<Spree::OptionValue>]
21
18
  def option_values
@@ -49,10 +49,6 @@ module Spree
49
49
  @eligible_product_ids ||= product_promotion_rules.pluck(:product_id)
50
50
  end
51
51
 
52
- def applicable?(promotable)
53
- promotable.is_a?(Spree::Order) || promotable.is_a?(Spree::Cart)
54
- end
55
-
56
52
  def eligible?(order, _options = {})
57
53
  return true if eligible_product_ids.empty?
58
54
 
@@ -33,10 +33,6 @@ module Spree
33
33
  #
34
34
  after_save :add_users
35
35
 
36
- def applicable?(promotable)
37
- promotable.is_a?(Spree::Order) || promotable.is_a?(Spree::Cart)
38
- end
39
-
40
36
  def eligible_user_ids
41
37
  @eligible_user_ids ||= promotion_rule_users.pluck(:customer_id)
42
38
  end
@@ -8,10 +8,6 @@ module Spree
8
8
  'customer_logged_in'
9
9
  end
10
10
 
11
- def applicable?(promotable)
12
- promotable.is_a?(Spree::Order) || promotable.is_a?(Spree::Cart)
13
- end
14
-
15
11
  def eligible?(order, _options = {})
16
12
  unless order.customer.present?
17
13
  eligibility_errors.add(:base, eligibility_error_message(:no_user_specified))
@@ -106,6 +106,28 @@ module Spree
106
106
  where('spree_promotions.expires_at IS NULL OR spree_promotions.expires_at > ?', Time.current)
107
107
  end
108
108
 
109
+ # The promotion a purchase's PERSISTED coupon code names, which keeps it in
110
+ # candidacy even before it ever applied — the discount activates on the
111
+ # exact recalculation where the cart first qualifies, and deactivates the
112
+ # same way (Shopify-parity for cart-level discount codes). In-memory
113
+ # assignments deliberately don't participate: unsaved codes belong to
114
+ # the explicit PromotionHandler::Coupon path. A batch code counts only
115
+ # while the purchase holds it (see #eligible?).
116
+ #
117
+ # @param purchase [Spree::Cart, Spree::Order]
118
+ # @return [Array<Spree::Promotion>] empty, or the one promotion
119
+ def self.held_by_saved_coupon_code(purchase)
120
+ return [] unless purchase.class.respond_to?(:column_names) && purchase.class.column_names.include?('coupon_code')
121
+
122
+ code = purchase.read_attribute(:coupon_code)
123
+ return [] if code.blank?
124
+
125
+ promotion = purchase.store.promotions.active.with_coupon_code(code)
126
+ return [] if promotion.nil? || promotion.usage_limit_exceeded?(purchase)
127
+
128
+ [promotion]
129
+ end
130
+
109
131
  def self.order_activatable?(promotable)
110
132
  # Carts have no cancellation concept — only placed orders can be canceled.
111
133
  promotable && !promotable.completed? && !(promotable.is_a?(Spree::Order) && promotable.canceled?)
@@ -202,7 +224,7 @@ module Spree
202
224
 
203
225
  # called anytime order.recalculate_totals! happens
204
226
  def eligible?(promotable, options = {})
205
- return false if expired? || usage_limit_exceeded?(promotable) || blacklisted?(promotable)
227
+ return false if expired? || usage_limit_exceeded?(promotable) || blacklisted?(promotable) || !code_held_by?(promotable)
206
228
 
207
229
  !!eligible_rules(promotable, options)
208
230
  end
@@ -240,7 +262,7 @@ module Spree
240
262
  end
241
263
 
242
264
  def products
243
- rules.where(type: %w[Spree::Promotion::Rules::Product Spree::Promotion::Rules::Product]).map(&:products).flatten.uniq
265
+ rules.where(type: 'Spree::Promotion::Rules::Product').map(&:products).flatten.uniq
244
266
  end
245
267
 
246
268
  def usage_limit_exceeded?(promotable)
@@ -278,9 +300,9 @@ module Spree
278
300
  checkouts_credited(credits)
279
301
  end
280
302
 
281
- def line_item_actionable?(order, line_item)
282
- if eligible? order
283
- rules = eligible_rules(order)
303
+ def line_item_actionable?(order, line_item, options = {})
304
+ if eligible?(order, options)
305
+ rules = eligible_rules(order, options)
284
306
  if rules.blank?
285
307
  true
286
308
  else
@@ -325,6 +347,13 @@ module Spree
325
347
 
326
348
  private
327
349
 
350
+ # A batch code discounts only the cart or order holding it, which is what
351
+ # keeps each generated code to a single order. A cart's line item has no
352
+ # order, and the adjuster has asked with the cart before pricing its lines.
353
+ def code_held_by?(order)
354
+ !multi_codes? || order.nil? || coupon_codes.held_by(order).exists?
355
+ end
356
+
328
357
  # Counts distinct checkouts behind a set of discount rows.
329
358
  #
330
359
  # An order placed on its own is one checkout; the several orders a split
@@ -349,7 +378,7 @@ module Spree
349
378
  def not_used?
350
379
  return true if orders.empty?
351
380
 
352
- errors.add(:base, :promotion_already_used, message: Spree.t('promotion_already_used'))
381
+ errors.add(:base, :promotion_already_used, message: I18n.t('spree.promotion_already_used'))
353
382
  throw(:abort)
354
383
  end
355
384
 
@@ -2,6 +2,10 @@
2
2
  # PromotionActions perform the necessary tasks when a promotion is activated by an event and determined to be eligible.
3
3
  module Spree
4
4
  class PromotionAction < Spree.base_class
5
+ include Spree::TypeLabels
6
+
7
+ self.type_labels_scope = 'spree.promotion_action_types'
8
+
5
9
  has_prefix_id :pact
6
10
 
7
11
  registers_subclasses_via { Spree.promotions.actions }
@@ -66,25 +70,14 @@ module Spree
66
70
  when :line_item
67
71
  %w[Spree::Promotion::Actions::CreateItemAdjustments Spree::Promotion::Actions::CreateLineItems]
68
72
  when :fulfillment
69
- %w[Spree::Promotion::Actions::FreeShipping Spree::Promotion::Actions::FreeShipping]
73
+ %w[Spree::Promotion::Actions::FreeShipping]
70
74
  when :order
71
- %w[Spree::Promotion::Actions::CreateAdjustment Spree::Promotion::Actions::CreateAdjustment]
75
+ %w[Spree::Promotion::Actions::CreateAdjustment]
72
76
  else
73
77
  []
74
78
  end
75
79
  end
76
80
 
77
- def self.human_name
78
- Spree.t("promotion_action_types.#{api_type}.name", default: api_type.titleize)
79
- end
80
-
81
- def self.human_description
82
- Spree.t("promotion_action_types.#{api_type}.description", default: '')
83
- end
84
-
85
- def human_name = self.class.human_name
86
- def human_description = self.class.human_description
87
-
88
81
  # Returns the key of the promotion action
89
82
  #
90
83
  # @return [String] eg. free_shipping
@@ -109,7 +102,7 @@ module Spree
109
102
  end
110
103
 
111
104
  def label
112
- Spree.t(:promotion_label, name: promotion.name)
105
+ I18n.t('spree.promotion_label', name: promotion.name)
113
106
  end
114
107
  end
115
108
  end
@@ -4,6 +4,6 @@ module Spree
4
4
  belongs_to :variant, class_name: 'Spree::Variant'
5
5
 
6
6
  validates :quantity, presence: true
7
- validates :quantity, numericality: { only_integer: true, message: Spree.t('validation.must_be_int') }
7
+ validates :quantity, numericality: { only_integer: true, message: I18n.t('spree.validation.must_be_int') }
8
8
  end
9
9
  end
@@ -39,7 +39,7 @@ module Spree
39
39
  @status_code = :gift_card_not_applied
40
40
  # The rejection message is what the hook author wrote for the
41
41
  # shopper; fall back to the generic string if it says nothing.
42
- @error = result.error.to_s.presence || Spree.t(:gift_card_not_applied)
42
+ @error = result.error.to_s.presence || I18n.t('spree.gift_card_not_applied')
43
43
  end
44
44
  end
45
45
 
@@ -57,9 +57,25 @@ module Spree
57
57
  else
58
58
  set_error_code :coupon_code_not_found
59
59
  end
60
+ release_stale_coupon_codes
60
61
  self
61
62
  end
62
63
 
64
+ # Gives back every batch code this cart or order holds but no longer
65
+ # shows: anything other than the code it entered while that code waits
66
+ # to qualify, or the codes of promotions attached to it. Without this a
67
+ # replaced or dropped code stays held, and is spent with the order.
68
+ #
69
+ # @return [void]
70
+ def release_stale_coupon_codes
71
+ return unless order.persisted?
72
+
73
+ stale = Spree::CouponCode.unused.held_by(order).where.not(promotion_id: order.promotions.select(:id))
74
+ pending = order.coupon_code.to_s.downcase.presence if status_code == :coupon_code_not_eligible
75
+ stale = stale.where.not(code: pending) if pending
76
+ stale.find_each(&:remove_from_order)
77
+ end
78
+
63
79
  def remove(coupon_code)
64
80
  if gift_cards_enabled? && order.gift_card
65
81
  result = order.remove_gift_card
@@ -75,19 +91,13 @@ module Spree
75
91
 
76
92
  promotion = order.promotions.with_coupon_code(coupon_code)
77
93
  if promotion.present?
78
- # Order promotion has to be destroyed before line item removing
79
- order.promotions.delete(promotion)
80
94
  clear_persisted_coupon_code(coupon_code)
95
+ detach_promotion(promotion)
81
96
 
82
- if promotion.multi_codes?
83
- promotion.coupon_codes.held_by(order).first&.remove_from_order
84
- else
85
- promotion.touch
86
- end
87
-
88
- remove_promotion_adjustments(promotion)
89
- remove_promotion_line_items(promotion)
90
- order.recalculate_totals!
97
+ set_success_code :adjustments_deleted
98
+ elsif clear_persisted_coupon_code(coupon_code)
99
+ # Saved before the cart qualified, so no promotion is attached yet.
100
+ Spree::CouponCode.held_by(order).find_by(code: coupon_code.to_s.strip.downcase)&.remove_from_order
91
101
 
92
102
  set_success_code :adjustments_deleted
93
103
  else
@@ -96,14 +106,26 @@ module Spree
96
106
  self
97
107
  end
98
108
 
109
+ # Gives the code up to another cart or order that has taken it. Unlike
110
+ # #remove, the entered code stays on this one, so the shopper can be
111
+ # told it went (Spree::Cart#remove_unavailable_coupon_code!).
112
+ #
113
+ # @param coupon_code [String]
114
+ # @return [self]
115
+ def release(coupon_code)
116
+ promotion = order.promotions.with_coupon_code(coupon_code)
117
+ detach_promotion(promotion) if promotion.present?
118
+ self
119
+ end
120
+
99
121
  def set_success_code(code)
100
122
  @status_code = code
101
- @success = Spree.t(code)
123
+ @success = I18n.t("spree.#{code}")
102
124
  end
103
125
 
104
126
  def set_error_code(code, locale_options = {})
105
127
  @status_code = code
106
- @error = Spree.t(code, locale_options)
128
+ @error = I18n.t("spree.#{code}", **locale_options)
107
129
  end
108
130
 
109
131
  # Returns the promotion for the order
@@ -143,6 +165,21 @@ module Spree
143
165
  order.update_column(:coupon_code, nil)
144
166
  end
145
167
 
168
+ def detach_promotion(promotion)
169
+ # Order promotion has to be destroyed before line item removing
170
+ order.promotions.delete(promotion)
171
+
172
+ if promotion.multi_codes?
173
+ promotion.coupon_codes.held_by(order).first&.remove_from_order
174
+ else
175
+ promotion.touch
176
+ end
177
+
178
+ remove_promotion_adjustments(promotion)
179
+ remove_promotion_line_items(promotion)
180
+ order.recalculate_totals!
181
+ end
182
+
146
183
  def remove_promotion_adjustments(promotion)
147
184
  order.discounts.where(promotion_action_id: promotion.actions.pluck(:id)).destroy_all
148
185
  end
@@ -165,6 +202,11 @@ module Spree
165
202
  return set_error_code :coupon_code_used if promotion.coupon_codes.used.where(code: order.coupon_code).exists?
166
203
  return promotion_usage_limit_exceeded if promotion.usage_limit_exceeded?(order)
167
204
 
205
+ # Claimed before anything prices the code, since the adjuster only
206
+ # discounts the holder, and a cart that qualifies later activates the
207
+ # code on recalculation without coming back here.
208
+ return set_error_code :coupon_code_used unless claim_coupon_code(order.coupon_code.downcase)
209
+
168
210
  unless promotion.eligible?(order, options)
169
211
  # Rules that explain themselves keep their message, but the status
170
212
  # code is always :coupon_code_not_eligible so callers can park the
@@ -216,11 +258,6 @@ module Spree
216
258
  end
217
259
 
218
260
  if discount || created_line_items
219
- if discount && !claim_coupon_code(discount, coupon_code)
220
- release_coupon_code(order, coupon_code)
221
- return set_error_code :coupon_code_used
222
- end
223
-
224
261
  order.recalculate_totals!
225
262
  set_success_code :coupon_code_applied
226
263
  elsif order.promotions.with_coupon_code(order.coupon_code)
@@ -249,8 +286,8 @@ module Spree
249
286
  # them in, so the two serialize instead of deadlocking.
250
287
  #
251
288
  # @return [Boolean] false when the code can no longer be claimed
252
- def claim_coupon_code(discount, coupon_code)
253
- record = Spree::CouponCode.find_by(promotion_id: discount.promotion_id, code: coupon_code)
289
+ def claim_coupon_code(coupon_code)
290
+ record = promotion.coupon_codes.find_by(code: coupon_code)
254
291
  return true if record.nil?
255
292
 
256
293
  holder = record.holder
@@ -261,16 +298,11 @@ module Spree
261
298
  record.lock!
262
299
  next false if record.used? || !same_holder?(record.holder, holder) || !releasable?(holder)
263
300
 
264
- release_coupon_code(holder, coupon_code) if holder
301
+ self.class.new(holder).release(coupon_code) if holder
265
302
  record.apply_order!(order)
266
303
  end
267
304
  end
268
305
 
269
- # Gift cards are turned off so a holder paying with one keeps it.
270
- def release_coupon_code(holder, coupon_code)
271
- self.class.new(holder, enable_gift_cards: false).remove(coupon_code)
272
- end
273
-
274
306
  def releasable?(holder)
275
307
  return true if holder.nil?
276
308
 
@@ -1,6 +1,10 @@
1
1
  # Base class for all promotion rules
2
2
  module Spree
3
3
  class PromotionRule < Spree.base_class
4
+ include Spree::TypeLabels
5
+
6
+ self.type_labels_scope = 'spree.promotion_rule_types'
7
+
4
8
  has_prefix_id :prorule
5
9
 
6
10
  registers_subclasses_via { Spree.promotions.rules }
@@ -17,8 +21,8 @@ module Spree
17
21
  all.select { |rule| rule.applicable?(promotable) }
18
22
  end
19
23
 
20
- def applicable?(_promotable)
21
- raise 'applicable? should be implemented in a sub-class of Spree::PromotionRule'
24
+ def applicable?(promotable)
25
+ promotable.is_a?(Spree::Order) || promotable.is_a?(Spree::Cart)
22
26
  end
23
27
 
24
28
  def eligible?(_promotable, _options = {})
@@ -35,17 +39,6 @@ module Spree
35
39
  @eligibility_errors ||= ActiveModel::Errors.new(self)
36
40
  end
37
41
 
38
- def self.human_name
39
- Spree.t("promotion_rule_types.#{api_type}.name", default: api_type.titleize)
40
- end
41
-
42
- def self.human_description
43
- Spree.t("promotion_rule_types.#{api_type}.description", default: '')
44
- end
45
-
46
- def human_name = self.class.human_name
47
- def human_description = self.class.human_description
48
-
49
42
  # Returns the key of the promotion rule
50
43
  #
51
44
  # @return [String] eg. currency
@@ -70,7 +63,7 @@ module Spree
70
63
  end
71
64
 
72
65
  def eligibility_error_message(key, options = {})
73
- Spree.t(key, Hash[scope: [:eligibility_errors, :messages]].merge(options))
66
+ I18n.t("spree.eligibility_errors.messages.#{key}", **options)
74
67
  end
75
68
  end
76
69
  end
@@ -26,6 +26,7 @@ module Spree
26
26
  include Spree::Metadata
27
27
 
28
28
  publishes_lifecycle_events
29
+ publishes_events :canceled, :draft, :ordered, :partially_received, :over_received, :received
29
30
 
30
31
  has_status :draft, :ordered, :partially_received, :received, :over_received, :canceled,
31
32
  default: :draft
@@ -83,7 +83,7 @@ module Spree
83
83
  def violation_message(name, quantity)
84
84
  return nil if satisfied_by?(quantity)
85
85
 
86
- Spree.t('cart_line_item.quantity_rule_violated',
86
+ I18n.t('spree.cart_line_item.quantity_rule_violated',
87
87
  li_name: name, quantities: nearest_valid(quantity).to_sentence)
88
88
  end
89
89
 
@@ -33,6 +33,20 @@ module Spree
33
33
  validate :amount_is_less_than_or_equal_to_allowed_amount, on: :create, if: :amount
34
34
  validate :order_is_covered_by_payment, on: :create
35
35
 
36
+ # Refunds for goods that came back, which settle what the customer owed
37
+ # for them rather than leaving it owed again.
38
+ scope :for_returned_items, -> { where(originator_type: returned_item_originators) }
39
+
40
+ # @return [Array<String>]
41
+ def self.returned_item_originators
42
+ [Spree::Return, Spree::Claim, Spree::Exchange].map(&:polymorphic_name)
43
+ end
44
+
45
+ # @return [Boolean]
46
+ def for_returned_items?
47
+ self.class.returned_item_originators.include?(originator_type)
48
+ end
49
+
36
50
  attr_reader :response
37
51
 
38
52
  delegate :currency, to: :payment
@@ -76,6 +90,17 @@ module Spree
76
90
  originator.refunded_line_amounts
77
91
  end
78
92
 
93
+ # The tax inside {#refunded_line_amounts}, for the lines whose tax is known.
94
+ # A line opened before returns and claims carried tax is absent: its amount
95
+ # says nothing about how much of it was tax.
96
+ #
97
+ # @return [Hash{Integer => BigDecimal}] line item id => tax
98
+ def refunded_line_taxes
99
+ return {} unless originator.respond_to?(:refunded_line_taxes)
100
+
101
+ originator.refunded_line_taxes
102
+ end
103
+
79
104
  # Returns true if the refund is editable.
80
105
  #
81
106
  # Read through the refund's own order: a payment shared by a split checkout
@@ -155,15 +180,15 @@ module Spree
155
180
  if response.success?
156
181
  track_order_as_refunded(refund_total_in_cents)
157
182
  else
158
- Rails.logger.error(Spree.t(:gateway_error) + " #{response.to_yaml}")
183
+ Rails.logger.error(I18n.t('spree.gateway_error') + " #{response.to_yaml}")
159
184
  text = response.params['message'] || response.params['response_reason_text'] || response.message
160
185
  raise Core::GatewayError, text
161
186
  end
162
187
 
163
188
  response
164
189
  rescue Spree::PaymentConnectionError => e
165
- Rails.logger.error(Spree.t(:gateway_error) + " #{e.inspect}")
166
- raise Core::GatewayError, Spree.t(:unable_to_connect_to_gateway)
190
+ Rails.logger.error(I18n.t('spree.gateway_error') + " #{e.inspect}")
191
+ raise Core::GatewayError, I18n.t('spree.unable_to_connect_to_gateway')
167
192
  end
168
193
 
169
194
  def calculate_refund_amount(credit_cents)
@@ -20,13 +20,14 @@ module Spree
20
20
  include Spree::HasStatus
21
21
  include Spree::HasCustomFields
22
22
  include Spree::Metadata
23
+ include Spree::PostSale::Taxation
23
24
 
24
25
  publishes_lifecycle_events
26
+ publishes_events :approved, :canceled, :received, :refunded, :requested
25
27
 
26
28
  has_status :requested, :approved, :received, :refunded, :canceled,
27
29
  default: :requested
28
30
 
29
- belongs_to :store, class_name: 'Spree::Store'
30
31
  belongs_to :order, class_name: 'Spree::Order', inverse_of: :returns
31
32
  belongs_to :stock_location, class_name: 'Spree::StockLocation'
32
33
  belongs_to :reason, class_name: 'Spree::ReturnReason', optional: true, inverse_of: :returns
@@ -118,11 +119,16 @@ module Spree
118
119
  received? || refunded?
119
120
  end
120
121
 
121
- # What the customer is owed for the items being returned.
122
+ # What the customer is owed for the items being returned, tax included.
122
123
  def refund_total
123
124
  return_line_items.sum(&:refund_amount)
124
125
  end
125
126
 
127
+ # The tax inside {#refund_total}.
128
+ def refund_tax_total
129
+ return_line_items.sum(&:refund_tax_amount)
130
+ end
131
+
126
132
  # What a refund from this return paid for, line by line. Only ever what
127
133
  # the warehouse counted: the seller's clawback is built from this, and a
128
134
  # line nobody counted earned nothing back.
@@ -139,6 +145,20 @@ module Spree
139
145
  end
140
146
  end
141
147
 
148
+ # The tax inside {#refunded_line_amounts}, for the lines whose tax is
149
+ # recorded.
150
+ #
151
+ # @return [Hash{Integer => BigDecimal}] line item id => tax
152
+ def refunded_line_taxes
153
+ return {} unless counted?
154
+
155
+ return_line_items.each_with_object(Hash.new(0)) do |line, taxes|
156
+ next if line.refund_amount.zero? || !line.settles_tax?
157
+
158
+ taxes[line.line_item_id] += line.refund_tax_amount
159
+ end
160
+ end
161
+
142
162
  # What has actually been refunded so far — a return can be refunded in
143
163
  # more than one step (part to store credit, part to the card), and store
144
164
  # credit is its own ledger rather than a Spree::Refund row.
@@ -157,5 +177,13 @@ module Spree
157
177
  def display_refunded_total
158
178
  Spree::Money.new(refunded_total, currency: currency)
159
179
  end
180
+
181
+ def display_refund_tax_total
182
+ Spree::Money.new(refund_tax_total, currency: currency)
183
+ end
184
+
185
+ def taxed_lines
186
+ return_line_items
187
+ end
160
188
  end
161
189
  end