spree_core 6.0.0.beta4 → 6.0.0.rc2

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 (575) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +5 -5
  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/search_provider/index_job.rb +4 -0
  10. data/app/jobs/spree/seller_payouts/sweep_seller_job.rb +2 -0
  11. data/app/jobs/spree/uploads/purge_unattached_job.rb +49 -0
  12. data/app/mailers/spree/admin_user_mailer.rb +4 -19
  13. data/app/mailers/spree/base_mailer.rb +97 -13
  14. data/app/mailers/spree/email_template_mailer.rb +27 -0
  15. data/app/mailers/spree/export_mailer.rb +3 -4
  16. data/app/mailers/spree/import_mailer.rb +3 -4
  17. data/app/mailers/spree/invitation_mailer.rb +24 -7
  18. data/app/mailers/spree/seller_user_mailer.rb +4 -16
  19. data/app/mailers/spree/webhook_mailer.rb +2 -2
  20. data/app/models/concerns/spree/acted_by.rb +1 -1
  21. data/app/models/concerns/spree/admin_user_methods.rb +15 -1
  22. data/app/models/concerns/spree/api_typed.rb +91 -0
  23. data/app/models/concerns/spree/calculated_adjustments.rb +41 -13
  24. data/app/models/concerns/spree/calculator/currency_amounts.rb +1 -1
  25. data/app/models/concerns/spree/calculator/tiers.rb +63 -0
  26. data/app/models/concerns/spree/canonical_decimals.rb +61 -0
  27. data/app/models/concerns/spree/customer_methods.rb +23 -18
  28. data/app/models/concerns/spree/derived_number.rb +11 -13
  29. data/app/models/concerns/spree/email_template_identity.rb +52 -0
  30. data/app/models/concerns/spree/parameterizable_name.rb +2 -0
  31. data/app/models/concerns/spree/payment_session_transitions.rb +8 -2
  32. data/app/models/concerns/spree/post_sale/taxation.rb +54 -0
  33. data/app/models/concerns/spree/post_sale/taxed_line.rb +51 -0
  34. data/app/models/concerns/spree/preference_schema/json_schema.rb +139 -0
  35. data/app/models/concerns/spree/preference_schema.rb +62 -32
  36. data/app/models/concerns/spree/product_scopes.rb +16 -2
  37. data/app/models/concerns/spree/provider_listing.rb +41 -0
  38. data/app/models/concerns/spree/publishable.rb +82 -14
  39. data/app/models/concerns/spree/purchase/addresses.rb +23 -10
  40. data/app/models/concerns/spree/purchase/currency.rb +1 -1
  41. data/app/models/concerns/spree/purchase/deprecated_aliases.rb +87 -0
  42. data/app/models/concerns/spree/purchase/gift_cards.rb +10 -4
  43. data/app/models/concerns/spree/purchase/locale.rb +1 -1
  44. data/app/models/concerns/spree/purchase/payment_processing.rb +8 -4
  45. data/app/models/concerns/spree/purchase/purchase_order.rb +9 -4
  46. data/app/models/concerns/spree/purchase/store_credits.rb +36 -1
  47. data/app/models/concerns/spree/purchase/totals.rb +49 -8
  48. data/app/models/concerns/spree/ransackable_attributes.rb +46 -0
  49. data/app/models/concerns/spree/receivable.rb +1 -1
  50. data/app/models/concerns/spree/searchable.rb +0 -4
  51. data/app/models/concerns/spree/secret_preferences.rb +59 -0
  52. data/app/models/concerns/spree/signed_id_attachments.rb +54 -0
  53. data/app/models/concerns/spree/single_store_resource.rb +1 -1
  54. data/app/models/concerns/spree/stores/email_branding.rb +32 -0
  55. data/app/models/concerns/spree/stores/markets.rb +5 -2
  56. data/app/models/concerns/spree/type_labels.rb +38 -0
  57. data/app/models/concerns/spree/typed_adjustment_line.rb +5 -6
  58. data/app/models/concerns/spree/typed_associations.rb +12 -18
  59. data/app/models/concerns/spree/vat_price_calculation.rb +5 -6
  60. data/app/models/spree/address.rb +12 -2
  61. data/app/models/spree/adjusters/largest_remainder.rb +14 -1
  62. data/app/models/spree/adjusters/promotion.rb +4 -19
  63. data/app/models/spree/allowed_origin.rb +3 -1
  64. data/app/models/spree/api_key.rb +5 -6
  65. data/app/models/spree/authentication/strategies/oidc_strategy.rb +5 -5
  66. data/app/models/spree/base.rb +99 -22
  67. data/app/models/spree/calculator/flat_percent_item_total.rb +2 -4
  68. data/app/models/spree/calculator/flat_rate.rb +3 -5
  69. data/app/models/spree/calculator/flexi_rate.rb +4 -6
  70. data/app/models/spree/calculator/percent_on_line_item.rb +2 -2
  71. data/app/models/spree/calculator/price_sack.rb +5 -7
  72. data/app/models/spree/calculator/shipping/digital_delivery.rb +3 -5
  73. data/app/models/spree/calculator/shipping/flat_percent_item_total.rb +7 -7
  74. data/app/models/spree/calculator/shipping/flat_rate.rb +5 -7
  75. data/app/models/spree/calculator/shipping/flexi_rate.rb +4 -6
  76. data/app/models/spree/calculator/shipping/per_item.rb +3 -5
  77. data/app/models/spree/calculator/shipping/price_sack.rb +5 -7
  78. data/app/models/spree/calculator/tiered_flat_rate.rb +7 -24
  79. data/app/models/spree/calculator/tiered_percent.rb +7 -25
  80. data/app/models/spree/cart.rb +78 -18
  81. data/app/models/spree/catalog.rb +1 -0
  82. data/app/models/spree/catalog_price.rb +0 -7
  83. data/app/models/spree/catalog_price_tier.rb +0 -8
  84. data/app/models/spree/category.rb +3 -1
  85. data/app/models/spree/channel/gating.rb +3 -2
  86. data/app/models/spree/channel.rb +6 -4
  87. data/app/models/spree/claim.rb +22 -1
  88. data/app/models/spree/claim_line_item.rb +41 -6
  89. data/app/models/spree/collection.rb +2 -1
  90. data/app/models/spree/collection_rule.rb +5 -21
  91. data/app/models/spree/commission_line.rb +1 -1
  92. data/app/models/spree/commission_rate.rb +7 -4
  93. data/app/models/spree/commission_rate_value.rb +1 -1
  94. data/app/models/spree/commission_rule.rb +4 -11
  95. data/app/models/spree/commission_rules/category_rule.rb +2 -5
  96. data/app/models/spree/commission_rules/item_total_rule.rb +2 -2
  97. data/app/models/spree/commission_rules/product_rule.rb +1 -1
  98. data/app/models/spree/commission_rules/seller_rule.rb +2 -5
  99. data/app/models/spree/company.rb +1 -0
  100. data/app/models/spree/company_invitation.rb +1 -0
  101. data/app/models/spree/consent_record.rb +0 -1
  102. data/app/models/spree/credit_card.rb +1 -1
  103. data/app/models/spree/current.rb +23 -0
  104. data/app/models/spree/custom_field.rb +2 -2
  105. data/app/models/spree/custom_field_definition.rb +20 -35
  106. data/app/models/spree/custom_fields/boolean.rb +1 -1
  107. data/app/models/spree/custom_fields/json.rb +1 -1
  108. data/app/models/spree/customer_group.rb +1 -0
  109. data/app/models/spree/data_feed/google.rb +0 -2
  110. data/app/models/spree/data_request.rb +2 -1
  111. data/app/models/spree/delivery_method.rb +36 -14
  112. data/app/models/spree/delivery_method_rule.rb +4 -11
  113. data/app/models/spree/delivery_method_rules/channel_rule.rb +2 -5
  114. data/app/models/spree/delivery_method_rules/item_total_rule.rb +2 -2
  115. data/app/models/spree/delivery_profile.rb +1 -0
  116. data/app/models/spree/delivery_rate.rb +7 -8
  117. data/app/models/spree/delivery_rate_provider/base.rb +1 -0
  118. data/app/models/spree/delivery_zone.rb +1 -2
  119. data/app/models/spree/delivery_zone_member.rb +4 -4
  120. data/app/models/spree/digital_asset.rb +7 -0
  121. data/app/models/spree/digital_asset_provider/base.rb +2 -0
  122. data/app/models/spree/digital_link.rb +2 -5
  123. data/app/models/spree/discount.rb +1 -1
  124. data/app/models/spree/email_template.rb +25 -0
  125. data/app/models/spree/email_template_draft.rb +13 -0
  126. data/app/models/spree/email_template_revision.rb +17 -0
  127. data/app/models/spree/email_templates/entry.rb +118 -0
  128. data/app/models/spree/email_templates/sample_record.rb +16 -0
  129. data/app/models/spree/emails/branding.rb +80 -0
  130. data/app/models/spree/exchange.rb +16 -1
  131. data/app/models/spree/exchange_line_item.rb +121 -5
  132. data/app/models/spree/export.rb +0 -1
  133. data/app/models/spree/exports/products.rb +10 -1
  134. data/app/models/spree/exports/report.rb +3 -3
  135. data/app/models/spree/fee.rb +4 -2
  136. data/app/models/spree/file_upload.rb +155 -0
  137. data/app/models/spree/fulfillment.rb +66 -30
  138. data/app/models/spree/fulfillment_changer.rb +2 -2
  139. data/app/models/spree/fulfillment_group.rb +6 -1
  140. data/app/models/spree/fulfillment_item.rb +0 -4
  141. data/app/models/spree/fulfillment_provider/base.rb +1 -0
  142. data/app/models/spree/gateway/bogus.rb +6 -0
  143. data/app/models/spree/gateway/custom_payment_source_method.rb +5 -0
  144. data/app/models/spree/gateway.rb +6 -2
  145. data/app/models/spree/gift_card.rb +18 -8
  146. data/app/models/spree/gift_card_batch.rb +3 -5
  147. data/app/models/spree/import.rb +5 -7
  148. data/app/models/spree/import_mapping.rb +1 -2
  149. data/app/models/spree/import_row.rb +2 -2
  150. data/app/models/spree/import_schemas/products.rb +1 -0
  151. data/app/models/spree/integration.rb +9 -19
  152. data/app/models/spree/invitation.rb +5 -5
  153. data/app/models/spree/line_item.rb +15 -4
  154. data/app/models/spree/locale.rb +11 -8
  155. data/app/models/spree/market.rb +8 -2
  156. data/app/models/spree/media.rb +1 -9
  157. data/app/models/spree/newsletter_subscriber.rb +4 -0
  158. data/app/models/spree/option_type.rb +2 -1
  159. data/app/models/spree/option_value.rb +2 -1
  160. data/app/models/spree/order.rb +60 -142
  161. data/app/models/spree/order_group.rb +18 -4
  162. data/app/models/spree/order_inventory.rb +10 -5
  163. data/app/models/spree/order_promotion.rb +1 -1
  164. data/app/models/spree/order_routing/has_strategy_preference.rb +1 -1
  165. data/app/models/spree/order_routing/strategy/base.rb +4 -2
  166. data/app/models/spree/order_routing/strategy/rules.rb +1 -1
  167. data/app/models/spree/order_routing_rule.rb +6 -22
  168. data/app/models/spree/package_type.rb +2 -2
  169. data/app/models/spree/payment/gateway_options.rb +15 -53
  170. data/app/models/spree/payment/processing.rb +2 -4
  171. data/app/models/spree/payment.rb +21 -13
  172. data/app/models/spree/payment_method/check.rb +2 -0
  173. data/app/models/spree/payment_method/session_instrumentation.rb +1 -0
  174. data/app/models/spree/payment_method/store_credit.rb +15 -13
  175. data/app/models/spree/payment_method.rb +78 -6
  176. data/app/models/spree/payment_session.rb +5 -1
  177. data/app/models/spree/payment_setup_session.rb +1 -0
  178. data/app/models/spree/payout_provider/base.rb +3 -1
  179. data/app/models/spree/pickup_point_provider/base.rb +2 -0
  180. data/app/models/spree/policy.rb +1 -0
  181. data/app/models/spree/price.rb +17 -14
  182. data/app/models/spree/price_adjustment_tier.rb +4 -0
  183. data/app/models/spree/price_list.rb +14 -9
  184. data/app/models/spree/price_rules/channel_rule.rb +3 -6
  185. data/app/models/spree/price_rules/customer_group_rule.rb +3 -6
  186. data/app/models/spree/price_rules/market_rule.rb +2 -5
  187. data/app/models/spree/price_rules/user_rule.rb +1 -3
  188. data/app/models/spree/product/channels.rb +9 -2
  189. data/app/models/spree/product.rb +54 -30
  190. data/app/models/spree/product_submission.rb +0 -12
  191. data/app/models/spree/product_type.rb +1 -1
  192. data/app/models/spree/promotion/actions/create_line_items.rb +28 -18
  193. data/app/models/spree/promotion/rules/category.rb +1 -5
  194. data/app/models/spree/promotion/rules/channel.rb +2 -9
  195. data/app/models/spree/promotion/rules/country.rb +3 -8
  196. data/app/models/spree/promotion/rules/currency.rb +1 -5
  197. data/app/models/spree/promotion/rules/customer_group.rb +2 -9
  198. data/app/models/spree/promotion/rules/first_order.rb +0 -4
  199. data/app/models/spree/promotion/rules/item_total.rb +6 -10
  200. data/app/models/spree/promotion/rules/market.rb +2 -9
  201. data/app/models/spree/promotion/rules/one_use_per_user.rb +0 -4
  202. data/app/models/spree/promotion/rules/option_value.rb +2 -6
  203. data/app/models/spree/promotion/rules/product.rb +1 -5
  204. data/app/models/spree/promotion/rules/user.rb +0 -4
  205. data/app/models/spree/promotion/rules/user_logged_in.rb +0 -4
  206. data/app/models/spree/promotion.rb +36 -6
  207. data/app/models/spree/promotion_action.rb +7 -14
  208. data/app/models/spree/promotion_action_line_item.rb +1 -1
  209. data/app/models/spree/promotion_handler/coupon.rb +59 -27
  210. data/app/models/spree/promotion_rule.rb +7 -14
  211. data/app/models/spree/purchase_order.rb +3 -0
  212. data/app/models/spree/quantity_rule.rb +1 -1
  213. data/app/models/spree/refund.rb +41 -18
  214. data/app/models/spree/return.rb +31 -2
  215. data/app/models/spree/return_line_item.rb +65 -14
  216. data/app/models/spree/role.rb +7 -7
  217. data/app/models/spree/saved_report.rb +2 -1
  218. data/app/models/spree/search_provider/base.rb +4 -0
  219. data/app/models/spree/seller.rb +10 -2
  220. data/app/models/spree/seller_payout.rb +8 -0
  221. data/app/models/spree/seller_requirement.rb +5 -12
  222. data/app/models/spree/seller_requirement_custom_field.rb +1 -2
  223. data/app/models/spree/seller_requirement_submission.rb +3 -2
  224. data/app/models/spree/seller_requirements/accept_terms.rb +2 -3
  225. data/app/models/spree/seller_transfer.rb +6 -0
  226. data/app/models/spree/shipping_label.rb +2 -0
  227. data/app/models/spree/stock/availability_validator.rb +2 -2
  228. data/app/models/spree/stock/estimator.rb +1 -1
  229. data/app/models/spree/stock/inventory_unit_builder.rb +1 -1
  230. data/app/models/spree/stock/packer.rb +2 -2
  231. data/app/models/spree/stock/splitter/weight.rb +1 -1
  232. data/app/models/spree/stock_level/custom_events.rb +6 -0
  233. data/app/models/spree/stock_level.rb +3 -1
  234. data/app/models/spree/stock_location.rb +1 -0
  235. data/app/models/spree/stock_movement.rb +1 -1
  236. data/app/models/spree/stock_receipt_item.rb +1 -1
  237. data/app/models/spree/stock_reservation.rb +2 -2
  238. data/app/models/spree/stock_transfer.rb +6 -2
  239. data/app/models/spree/store.rb +70 -19
  240. data/app/models/spree/store_credit.rb +13 -13
  241. data/app/models/spree/store_credit_event.rb +5 -5
  242. data/app/models/spree/supplier.rb +1 -0
  243. data/app/models/spree/tax_category.rb +1 -0
  244. data/app/models/spree/tax_exemption_certificate.rb +3 -0
  245. data/app/models/spree/tax_identifier.rb +3 -1
  246. data/app/models/spree/tax_line.rb +53 -3
  247. data/app/models/spree/tax_provider/base.rb +71 -14
  248. data/app/models/spree/tax_provider/internal.rb +70 -41
  249. data/app/models/spree/tax_provider/recorded_share.rb +114 -0
  250. data/app/models/spree/tax_rate.rb +30 -8
  251. data/app/models/spree/taxon.rb +1 -1
  252. data/app/models/spree/taxonomy.rb +0 -1
  253. data/app/models/spree/update_status.rb +25 -0
  254. data/app/models/spree/variant.rb +10 -12
  255. data/app/models/spree/variant_media.rb +1 -1
  256. data/app/models/spree/webhook_delivery.rb +1 -0
  257. data/app/models/spree/webhook_endpoint.rb +46 -13
  258. data/app/models/spree/wishlist.rb +1 -6
  259. data/app/models/spree/wishlist_item/custom_events.rb +6 -0
  260. data/app/presenters/spree/csv/customer_presenter.rb +1 -1
  261. data/app/presenters/spree/csv/newsletter_subscriber_presenter.rb +1 -1
  262. data/app/presenters/spree/csv/order_line_item_presenter.rb +10 -6
  263. data/app/presenters/spree/csv/price_list_price_presenter.rb +3 -8
  264. data/app/presenters/spree/csv/product_variant_presenter.rb +59 -20
  265. data/app/presenters/spree/csv/purchase_order_item_presenter.rb +1 -3
  266. data/app/serializers/spree/emails/base_serializer.rb +14 -0
  267. data/app/serializers/spree/emails/export_serializer.rb +13 -0
  268. data/app/serializers/spree/emails/import_serializer.rb +19 -0
  269. data/app/serializers/spree/emails/invitation_serializer.rb +28 -0
  270. data/app/serializers/spree/emails/store_serializer.rb +31 -0
  271. data/app/serializers/spree/emails/user_serializer.rb +12 -0
  272. data/app/serializers/spree/emails/webhook_endpoint_serializer.rb +16 -0
  273. data/app/services/spree/carts/create.rb +8 -2
  274. data/app/services/spree/carts/destroy.rb +2 -2
  275. data/app/services/spree/carts/remove_out_of_stock_items.rb +2 -2
  276. data/app/services/spree/carts/split_by_seller.rb +1 -1
  277. data/app/services/spree/carts/update.rb +22 -8
  278. data/app/services/spree/catalogs/set_product_terms.rb +1 -1
  279. data/app/services/spree/categories/add_products.rb +13 -31
  280. data/app/services/spree/categories/remove_products.rb +12 -51
  281. data/app/services/spree/checkout/advance.rb +1 -4
  282. data/app/services/spree/collections/add_products.rb +10 -23
  283. data/app/services/spree/collections/remove_products.rb +7 -43
  284. data/app/services/spree/coupon_codes/bulk_generate.rb +8 -14
  285. data/app/services/spree/data_requests/create.rb +1 -1
  286. data/app/services/spree/deliveries/destroy.rb +2 -2
  287. data/app/services/spree/email_templates/check.rb +58 -0
  288. data/app/services/spree/email_templates/no_sample_record.rb +7 -0
  289. data/app/services/spree/email_templates/preview.rb +99 -0
  290. data/app/services/spree/emails/file_system.rb +23 -0
  291. data/app/services/spree/emails/filters.rb +84 -0
  292. data/app/services/spree/emails/liquid_context.rb +48 -0
  293. data/app/services/spree/emails/rendered_email.rb +14 -0
  294. data/app/services/spree/emails/renderer.rb +125 -0
  295. data/app/services/spree/emails/template.rb +51 -0
  296. data/app/services/spree/emails/template_data.rb +23 -0
  297. data/app/services/spree/emails/template_resolver.rb +109 -0
  298. data/app/services/spree/emails/text_converter.rb +72 -0
  299. data/app/services/spree/emails/translation_inliner.rb +134 -0
  300. data/app/services/spree/imports/row_processors/price_list_price.rb +10 -10
  301. data/app/services/spree/imports/row_processors/product_variant.rb +77 -34
  302. data/app/services/spree/imports/row_processors/purchase_order.rb +13 -13
  303. data/app/services/spree/locales.rb +43 -10
  304. data/app/services/spree/newsletter/subscribe.rb +8 -12
  305. data/app/services/spree/orders/build_fulfillments.rb +3 -2
  306. data/app/services/spree/orders/create.rb +5 -4
  307. data/app/services/spree/orders/discounts/create.rb +14 -11
  308. data/app/services/spree/orders/discounts/destroy.rb +1 -0
  309. data/app/services/spree/orders/discounts/update.rb +1 -0
  310. data/app/services/spree/orders/fees/create.rb +1 -0
  311. data/app/services/spree/orders/fees/destroy.rb +1 -0
  312. data/app/services/spree/orders/fees/update.rb +1 -0
  313. data/app/services/spree/orders/remove_line_item.rb +13 -3
  314. data/app/services/spree/orders/update.rb +10 -6
  315. data/app/services/spree/orders/update_item.rb +6 -3
  316. data/app/services/spree/orders/update_statuses.rb +3 -1
  317. data/app/services/spree/prices/bulk_upsert.rb +32 -25
  318. data/app/services/spree/product_memberships.rb +61 -0
  319. data/app/services/spree/returns/eligibility_validator.rb +1 -1
  320. data/app/services/spree/search_provider/custom_field_schema.rb +2 -2
  321. data/app/services/spree/seeds/commission_rates.rb +1 -1
  322. data/app/services/spree/seeds/digital_delivery.rb +1 -1
  323. data/app/services/spree/seeds/payment_methods.rb +2 -2
  324. data/app/services/spree/seeds/saved_reports.rb +3 -3
  325. data/app/services/spree/seeds/stores.rb +1 -1
  326. data/app/services/spree/sellers/requirements.rb +1 -2
  327. data/app/services/spree/shipping_labels/confirm_refund.rb +1 -1
  328. data/app/services/spree/shipping_labels/store_file.rb +3 -3
  329. data/app/services/spree/stock_levels/correct.rb +3 -3
  330. data/app/services/spree/stock_reservations/reserve.rb +2 -3
  331. data/app/services/spree/store_credits/apply.rb +15 -13
  332. data/app/services/spree/stores/provision_defaults.rb +23 -25
  333. data/app/services/spree/update_check.rb +160 -0
  334. data/app/services/spree/uploads/assign_stores.rb +92 -0
  335. data/app/subscribers/spree/seller_onboarding_subscriber.rb +1 -1
  336. data/app/validators/spree/attachment_content_type_validator.rb +19 -2
  337. data/app/validators/spree/locale_codes_validator.rb +22 -0
  338. data/app/views/layouts/spree/base_mailer.html.erb +2 -43
  339. data/app/views/layouts/spree/base_mailer.liquid +79 -0
  340. data/app/views/spree/admin_user_mailer/password_reset_email.liquid +14 -0
  341. data/app/views/spree/export_mailer/export_done.liquid +12 -0
  342. data/app/views/spree/import_mailer/import_done.liquid +15 -0
  343. data/app/views/spree/invitation_mailer/invitation_accepted.liquid +9 -0
  344. data/app/views/spree/invitation_mailer/invitation_email.liquid +13 -0
  345. data/app/views/spree/seller_user_mailer/password_reset_email.liquid +14 -0
  346. data/app/views/spree/shared/_mailer_button.html.erb +4 -5
  347. data/app/views/spree/shared/_mailer_hero.html.erb +6 -5
  348. data/app/views/spree/shared/_summary_row.liquid +8 -0
  349. data/app/views/spree/webhook_mailer/endpoint_disabled.liquid +21 -0
  350. data/app/workflows/spree/carts/add_item.rb +4 -4
  351. data/app/workflows/spree/carts/complete.rb +13 -3
  352. data/app/workflows/spree/carts/merge.rb +1 -1
  353. data/app/workflows/spree/carts/recalculate_totals.rb +5 -0
  354. data/app/workflows/spree/carts/upsert_items.rb +18 -9
  355. data/app/workflows/spree/claims/approve.rb +5 -22
  356. data/app/workflows/spree/claims/cancel.rb +5 -21
  357. data/app/workflows/spree/claims/create.rb +15 -0
  358. data/app/workflows/spree/claims/deny.rb +7 -0
  359. data/app/workflows/spree/claims/resolve.rb +34 -31
  360. data/app/workflows/spree/customers/anonymize.rb +3 -4
  361. data/app/workflows/spree/customers/create.rb +1 -1
  362. data/app/workflows/spree/data_requests/fulfill.rb +2 -2
  363. data/app/workflows/spree/deliveries/update_tracking.rb +1 -1
  364. data/app/workflows/spree/email_templates/discard_draft.rb +20 -0
  365. data/app/workflows/spree/email_templates/publish.rb +70 -0
  366. data/app/workflows/spree/email_templates/restore_revision.rb +33 -0
  367. data/app/workflows/spree/email_templates/revert.rb +29 -0
  368. data/app/workflows/spree/email_templates/save_draft.rb +85 -0
  369. data/app/workflows/spree/exchanges/approve.rb +5 -22
  370. data/app/workflows/spree/exchanges/cancel.rb +5 -21
  371. data/app/workflows/spree/exchanges/create.rb +6 -0
  372. data/app/workflows/spree/exchanges/fulfill.rb +75 -51
  373. data/app/workflows/spree/exchanges/receive.rb +6 -65
  374. data/app/workflows/spree/fulfillments/cancel.rb +1 -1
  375. data/app/workflows/spree/fulfillments/create.rb +53 -49
  376. data/app/workflows/spree/fulfillments/fulfill.rb +23 -27
  377. data/app/workflows/spree/fulfillments/mark_delivered.rb +1 -1
  378. data/app/workflows/spree/fulfillments/replacements.rb +59 -0
  379. data/app/workflows/spree/gift_cards/apply.rb +2 -2
  380. data/app/workflows/spree/orders/add_item.rb +5 -1
  381. data/app/workflows/spree/orders/cancel.rb +3 -3
  382. data/app/workflows/spree/orders/complete.rb +16 -8
  383. data/app/workflows/spree/payments/capture.rb +20 -5
  384. data/app/workflows/spree/payments/process.rb +5 -3
  385. data/app/workflows/spree/post_sale/approval.rb +35 -0
  386. data/app/workflows/spree/post_sale/cancellation.rb +44 -0
  387. data/app/workflows/spree/post_sale/receipt.rb +92 -0
  388. data/app/workflows/spree/price_lists/update.rb +4 -4
  389. data/app/workflows/spree/products/nested_attributes.rb +1 -1
  390. data/app/workflows/spree/purchase_orders/cancel.rb +1 -1
  391. data/app/workflows/spree/purchase_orders/close.rb +3 -31
  392. data/app/workflows/spree/purchase_orders/create.rb +3 -1
  393. data/app/workflows/spree/purchase_orders/mark_draft.rb +2 -2
  394. data/app/workflows/spree/purchase_orders/mark_ordered.rb +2 -2
  395. data/app/workflows/spree/purchase_orders/receive.rb +1 -1
  396. data/app/workflows/spree/purchase_orders/update.rb +5 -34
  397. data/app/workflows/spree/receivables/closing_short.rb +46 -0
  398. data/app/workflows/spree/receivables/draft_editing.rb +48 -0
  399. data/app/workflows/spree/refunds/create.rb +5 -2
  400. data/app/workflows/spree/refunds/order_payments.rb +14 -2
  401. data/app/workflows/spree/refunds/tax_credit.rb +67 -0
  402. data/app/workflows/spree/returns/approve.rb +5 -22
  403. data/app/workflows/spree/returns/cancel.rb +5 -22
  404. data/app/workflows/spree/returns/create.rb +8 -0
  405. data/app/workflows/spree/returns/receive.rb +5 -76
  406. data/app/workflows/spree/returns/refund.rb +31 -8
  407. data/app/workflows/spree/seller_requirement_submissions/create.rb +1 -2
  408. data/app/workflows/spree/seller_transfers/reverse.rb +28 -1
  409. data/app/workflows/spree/shipping_labels/purchase.rb +8 -8
  410. data/app/workflows/spree/shipping_labels/record.rb +7 -7
  411. data/app/workflows/spree/shipping_labels/refund.rb +2 -2
  412. data/app/workflows/spree/stock_receipts/recording.rb +4 -4
  413. data/app/workflows/spree/stock_transfers/cancel.rb +2 -2
  414. data/app/workflows/spree/stock_transfers/close.rb +3 -30
  415. data/app/workflows/spree/stock_transfers/create.rb +3 -1
  416. data/app/workflows/spree/stock_transfers/mark_draft.rb +1 -1
  417. data/app/workflows/spree/stock_transfers/mark_in_transit.rb +3 -3
  418. data/app/workflows/spree/stock_transfers/mark_ready.rb +2 -2
  419. data/app/workflows/spree/stock_transfers/receive.rb +1 -1
  420. data/app/workflows/spree/stock_transfers/update.rb +5 -33
  421. data/config/currency_exponents.json +180 -0
  422. data/config/i18n-tasks.yml +32 -18
  423. data/config/initializers/alba.rb +7 -0
  424. data/config/language_names.csv +264 -0
  425. data/config/locales/ar.yml +811 -0
  426. data/config/locales/be.yml +855 -0
  427. data/config/locales/bg.yml +723 -0
  428. data/config/locales/ca.yml +689 -0
  429. data/config/locales/content_locales.rb +9 -0
  430. data/config/locales/cs.yml +855 -0
  431. data/config/locales/da.yml +771 -0
  432. data/config/locales/de-CH.yml +248 -0
  433. data/config/locales/de.yml +835 -0
  434. data/config/locales/el.yml +769 -0
  435. data/config/locales/en.yml +47 -505
  436. data/config/locales/es-CL.yml +301 -0
  437. data/config/locales/es-EC.yml +186 -0
  438. data/config/locales/es-MX.yml +334 -0
  439. data/config/locales/es.yml +831 -0
  440. data/config/locales/et.yml +644 -0
  441. data/config/locales/fa.yml +709 -0
  442. data/config/locales/fi.yml +771 -0
  443. data/config/locales/fr.yml +830 -0
  444. data/config/locales/hu.yml +751 -0
  445. data/config/locales/id.yml +756 -0
  446. data/config/locales/it.yml +833 -0
  447. data/config/locales/ja.yml +726 -0
  448. data/config/locales/km.yml +759 -0
  449. data/config/locales/ko.yml +575 -0
  450. data/config/locales/ku.yml +753 -0
  451. data/config/locales/lt.yml +821 -0
  452. data/config/locales/lv.yml +685 -0
  453. data/config/locales/nb.yml +824 -0
  454. data/config/locales/ne.yml +823 -0
  455. data/config/locales/nl.yml +826 -0
  456. data/config/locales/pl.yml +835 -0
  457. data/config/locales/pt-BR.yml +547 -0
  458. data/config/locales/pt.yml +636 -0
  459. data/config/locales/ro.yml +750 -0
  460. data/config/locales/ru.yml +834 -0
  461. data/config/locales/sk.yml +680 -0
  462. data/config/locales/sl-SI.yml +600 -0
  463. data/config/locales/sv.yml +827 -0
  464. data/config/locales/th.yml +745 -0
  465. data/config/locales/tr.yml +777 -0
  466. data/config/locales/uk.yml +836 -0
  467. data/config/locales/vi.yml +768 -0
  468. data/config/locales/zh-CN.yml +786 -0
  469. data/config/locales/zh-TW.yml +834 -0
  470. data/db/migrate/20260925000001_add_replacement_to_spree_fulfillment_items.rb +5 -0
  471. data/db/migrate/20261001000001_add_post_sale_tax.rb +30 -0
  472. data/db/migrate/20261001000001_convert_preferences_to_json.rb +166 -0
  473. data/db/migrate/20261001000001_create_spree_email_templates.rb +52 -0
  474. data/db/migrate/20261009000001_change_money_columns_to_decimal_19_4.rb +116 -0
  475. data/db/migrate/20261010000001_add_store_id_to_active_storage_blobs.rb +12 -0
  476. data/lib/generators/spree/dummy/dummy_generator.rb +0 -9
  477. data/lib/generators/spree/dummy/templates/rails/test.rb +26 -0
  478. data/lib/spree/checkout/default_requirements.rb +37 -13
  479. data/lib/spree/core/configuration.rb +19 -1
  480. data/lib/spree/core/controller_helpers/store.rb +1 -1
  481. data/lib/spree/core/dependencies.rb +5 -4
  482. data/lib/spree/core/emails/escaped_output.rb +60 -0
  483. data/lib/spree/core/engine.rb +72 -165
  484. data/lib/spree/core/permissions/default_catalog.rb +5 -0
  485. data/lib/spree/core/preferences/invalid_preferences.rb +18 -0
  486. data/lib/spree/core/preferences/json_conversion.rb +186 -0
  487. data/lib/spree/core/preferences/masking.rb +55 -2
  488. data/lib/spree/core/preferences/preferable.rb +343 -103
  489. data/lib/spree/core/preferences/preferable_class_methods.rb +188 -136
  490. data/lib/spree/core/previews/import_preview.rb +10 -0
  491. data/lib/spree/core/ransack_configuration.rb +13 -2
  492. data/lib/spree/core/tax/provider_error.rb +37 -0
  493. data/lib/spree/core/version.rb +1 -1
  494. data/lib/spree/core.rb +91 -373
  495. data/lib/spree/emails/editable_templates.rb +82 -0
  496. data/lib/spree/events/catalog.rb +189 -0
  497. data/lib/spree/events.rb +21 -1
  498. data/lib/spree/hooks.rb +0 -17
  499. data/lib/spree/i18n.rb +32 -15
  500. data/lib/spree/iso_data.rb +7 -0
  501. data/lib/spree/localized_number.rb +6 -0
  502. data/lib/spree/migrations.rb +4 -1
  503. data/lib/spree/money.rb +196 -15
  504. data/lib/spree/reporting/adapters/base.rb +7 -6
  505. data/lib/spree/reporting/adapters/live.rb +6 -6
  506. data/lib/spree/reporting/default_vocabulary.rb +22 -10
  507. data/lib/spree/reporting/hydration.rb +1 -1
  508. data/lib/spree/reporting/query.rb +8 -0
  509. data/lib/spree/reporting/registry.rb +13 -0
  510. data/lib/spree/reporting/schema.rb +10 -10
  511. data/lib/spree/testing_support/editable_email_templates.rb +61 -0
  512. data/lib/spree/testing_support/emails.rb +22 -0
  513. data/lib/spree/testing_support/factories/cart_factory.rb +1 -1
  514. data/lib/spree/testing_support/factories/email_template_factory.rb +28 -0
  515. data/lib/spree/testing_support/factories/order_factory.rb +2 -2
  516. data/lib/spree/testing_support/factories/product_factory.rb +9 -0
  517. data/lib/spree/testing_support/factories/refund_factory.rb +1 -1
  518. data/lib/spree/testing_support/factories/stock_factory.rb +1 -1
  519. data/lib/spree/testing_support/factories/variant_factory.rb +1 -1
  520. data/lib/spree/testing_support/i18n.rb +3 -94
  521. data/lib/spree/testing_support/lifecycle_events.rb +1 -1
  522. data/lib/spree/testing_support/order_walkthrough.rb +1 -1
  523. data/lib/spree/testing_support/preferences.rb +0 -5
  524. data/lib/spree/testing_support/store.rb +9 -1
  525. data/lib/spree/upgrades/5_6_to_6_0/manifest.yml +67 -1
  526. data/lib/spree/uploads.rb +101 -0
  527. data/lib/tasks/categories_and_collections.rake +3 -3
  528. data/lib/tasks/core.rake +6 -4
  529. data/lib/tasks/markets.rake +2 -1
  530. data/lib/tasks/media.rake +3 -3
  531. data/lib/tasks/migrate_users_to_customers.rake +23 -18
  532. data/lib/tasks/money.rake +46 -0
  533. data/lib/tasks/package_types.rake +1 -1
  534. data/lib/tasks/preferences_json.rake +101 -0
  535. data/lib/tasks/seller_sample_data.rake +2 -2
  536. data/lib/tasks/store_settings.rake +13 -6
  537. data/lib/tasks/typed_adjustments_migration.rake +1 -1
  538. data/lib/tasks/uploads.rake +19 -0
  539. data/spec/fixtures/mailer_views/custom_views_probe_mailer/welcome_email.html.erb +4 -0
  540. metadata +219 -46
  541. data/app/jobs/spree/products/touch_categories_job.rb +0 -15
  542. data/app/jobs/spree/products/touch_taxons_job.rb +0 -13
  543. data/app/models/acts_as_taggable_on/tag_decorator.rb +0 -10
  544. data/app/models/concerns/spree/has_image_alt_text.rb +0 -18
  545. data/app/models/spree/base_analytics_event_handler.rb +0 -54
  546. data/app/models/spree/invitations/store.rb +0 -6
  547. data/app/models/spree/preference.rb +0 -14
  548. data/app/models/spree/promotion_handler/page.rb +0 -25
  549. data/app/models/spree/promotion_handler/promotion_duplicator.rb +0 -60
  550. data/app/models/spree/stock/differentiator.rb +0 -45
  551. data/app/presenters/spree/filters/options_presenter.rb +0 -47
  552. data/app/services/spree/carts/empty.rb +0 -42
  553. data/app/services/spree/country_to_timezone.rb +0 -273
  554. data/app/services/spree/payments/create.rb +0 -92
  555. data/app/validators/spree/url_validator.rb +0 -23
  556. data/app/views/active_storage/blobs/_blob.html.erb +0 -14
  557. data/app/views/spree/admin_user_mailer/password_reset_email.html.erb +0 -8
  558. data/app/views/spree/export_mailer/export_done.html.erb +0 -7
  559. data/app/views/spree/import_mailer/import_done.html.erb +0 -12
  560. data/app/views/spree/invitation_mailer/invitation_accepted.html.erb +0 -4
  561. data/app/views/spree/invitation_mailer/invitation_email.html.erb +0 -12
  562. data/app/views/spree/seller_user_mailer/password_reset_email.html.erb +0 -8
  563. data/app/views/spree/shared/_base_mailer_footer.html.erb +0 -19
  564. data/app/views/spree/shared/_base_mailer_header.html.erb +0 -8
  565. data/app/views/spree/shared/_base_mailer_stylesheets.html.erb +0 -399
  566. data/app/views/spree/shared/_mailer_logo.html.erb +0 -41
  567. data/app/views/spree/shared/_purchased_items_styles.html.erb +0 -119
  568. data/app/views/spree/webhook_mailer/endpoint_disabled.html.erb +0 -51
  569. data/app/views/spree/webhook_mailer/endpoint_disabled.text.erb +0 -10
  570. data/app/workflows/spree/fulfillments/purchase_label.rb +0 -40
  571. data/app/workflows/spree/returns/purchase_label.rb +0 -32
  572. data/lib/spree/analytics.rb +0 -11
  573. data/lib/spree/core/preferences/scoped_store.rb +0 -34
  574. data/lib/spree/core/preferences/store.rb +0 -105
  575. data/lib/spree/translation_migrations.rb +0 -52
@@ -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))
@@ -87,6 +87,7 @@ module Spree
87
87
  # `name` is whitelisted so the admin global search / command palette can
88
88
  # filter via the `name_or_code_cont` predicate without a dedicated scope.
89
89
  self.whitelisted_ransackable_attributes = ['name', 'path', 'promotion_category_id', 'code', 'kind', 'starts_at', 'expires_at']
90
+ search_by :name, :code
90
91
  self.whitelisted_ransackable_associations = %w[coupon_codes]
91
92
 
92
93
  def self.with_coupon_code(coupon_code)
@@ -106,6 +107,28 @@ module Spree
106
107
  where('spree_promotions.expires_at IS NULL OR spree_promotions.expires_at > ?', Time.current)
107
108
  end
108
109
 
110
+ # The promotion a purchase's PERSISTED coupon code names, which keeps it in
111
+ # candidacy even before it ever applied — the discount activates on the
112
+ # exact recalculation where the cart first qualifies, and deactivates the
113
+ # same way (Shopify-parity for cart-level discount codes). In-memory
114
+ # assignments deliberately don't participate: unsaved codes belong to
115
+ # the explicit PromotionHandler::Coupon path. A batch code counts only
116
+ # while the purchase holds it (see #eligible?).
117
+ #
118
+ # @param purchase [Spree::Cart, Spree::Order]
119
+ # @return [Array<Spree::Promotion>] empty, or the one promotion
120
+ def self.held_by_saved_coupon_code(purchase)
121
+ return [] unless purchase.class.respond_to?(:column_names) && purchase.class.column_names.include?('coupon_code')
122
+
123
+ code = purchase.read_attribute(:coupon_code)
124
+ return [] if code.blank?
125
+
126
+ promotion = purchase.store.promotions.active.with_coupon_code(code)
127
+ return [] if promotion.nil? || promotion.usage_limit_exceeded?(purchase)
128
+
129
+ [promotion]
130
+ end
131
+
109
132
  def self.order_activatable?(promotable)
110
133
  # Carts have no cancellation concept — only placed orders can be canceled.
111
134
  promotable && !promotable.completed? && !(promotable.is_a?(Spree::Order) && promotable.canceled?)
@@ -202,7 +225,7 @@ module Spree
202
225
 
203
226
  # called anytime order.recalculate_totals! happens
204
227
  def eligible?(promotable, options = {})
205
- return false if expired? || usage_limit_exceeded?(promotable) || blacklisted?(promotable)
228
+ return false if expired? || usage_limit_exceeded?(promotable) || blacklisted?(promotable) || !code_held_by?(promotable)
206
229
 
207
230
  !!eligible_rules(promotable, options)
208
231
  end
@@ -240,7 +263,7 @@ module Spree
240
263
  end
241
264
 
242
265
  def products
243
- rules.where(type: %w[Spree::Promotion::Rules::Product Spree::Promotion::Rules::Product]).map(&:products).flatten.uniq
266
+ rules.where(type: 'Spree::Promotion::Rules::Product').map(&:products).flatten.uniq
244
267
  end
245
268
 
246
269
  def usage_limit_exceeded?(promotable)
@@ -278,9 +301,9 @@ module Spree
278
301
  checkouts_credited(credits)
279
302
  end
280
303
 
281
- def line_item_actionable?(order, line_item)
282
- if eligible? order
283
- rules = eligible_rules(order)
304
+ def line_item_actionable?(order, line_item, options = {})
305
+ if eligible?(order, options)
306
+ rules = eligible_rules(order, options)
284
307
  if rules.blank?
285
308
  true
286
309
  else
@@ -325,6 +348,13 @@ module Spree
325
348
 
326
349
  private
327
350
 
351
+ # A batch code discounts only the cart or order holding it, which is what
352
+ # keeps each generated code to a single order. A cart's line item has no
353
+ # order, and the adjuster has asked with the cart before pricing its lines.
354
+ def code_held_by?(order)
355
+ !multi_codes? || order.nil? || coupon_codes.held_by(order).exists?
356
+ end
357
+
328
358
  # Counts distinct checkouts behind a set of discount rows.
329
359
  #
330
360
  # An order placed on its own is one checkout; the several orders a split
@@ -349,7 +379,7 @@ module Spree
349
379
  def not_used?
350
380
  return true if orders.empty?
351
381
 
352
- errors.add(:base, :promotion_already_used, message: Spree.t('promotion_already_used'))
382
+ errors.add(:base, :promotion_already_used, message: I18n.t('spree.promotion_already_used'))
353
383
  throw(:abort)
354
384
  end
355
385
 
@@ -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
@@ -57,7 +58,9 @@ module Spree
57
58
  self.whitelisted_ransackable_attributes = %w[number status currency reference expected_at
58
59
  cancel_by ordered_at received_at closed_short_at
59
60
  supplier_id destination_location_id created_at]
61
+ search_by :number, :reference
60
62
  self.whitelisted_ransackable_scopes = %w[open closed overdue past_cancel_by]
63
+ self.ransackable_scope_types = %w[open closed overdue past_cancel_by].index_with('boolean')
61
64
  self.whitelisted_ransackable_associations = %w[supplier destination_location items]
62
65
 
63
66
  # What the supplier will invoice for everything ordered.
@@ -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
 
@@ -5,6 +5,9 @@ module Spree
5
5
  include Spree::ActedBy
6
6
  include Spree::HasCustomFields
7
7
  include Spree::Metadata
8
+ include Spree::CanonicalDecimals
9
+
10
+ canonical_decimals :amount
8
11
  include Spree::InstrumentsGatewayCalls
9
12
  if defined?(Spree::Security::Refunds)
10
13
  include Spree::Security::Refunds
@@ -33,6 +36,20 @@ module Spree
33
36
  validate :amount_is_less_than_or_equal_to_allowed_amount, on: :create, if: :amount
34
37
  validate :order_is_covered_by_payment, on: :create
35
38
 
39
+ # Refunds for goods that came back, which settle what the customer owed
40
+ # for them rather than leaving it owed again.
41
+ scope :for_returned_items, -> { where(originator_type: returned_item_originators) }
42
+
43
+ # @return [Array<String>]
44
+ def self.returned_item_originators
45
+ [Spree::Return, Spree::Claim, Spree::Exchange].map(&:polymorphic_name)
46
+ end
47
+
48
+ # @return [Boolean]
49
+ def for_returned_items?
50
+ self.class.returned_item_originators.include?(originator_type)
51
+ end
52
+
36
53
  attr_reader :response
37
54
 
38
55
  delegate :currency, to: :payment
@@ -42,10 +59,6 @@ module Spree
42
59
  # back through a payment that may belong to several orders.
43
60
  before_validation :assign_order_from_payment, on: :create
44
61
 
45
- def amount=(amount)
46
- self[:amount] = Spree::LocalizedNumber.parse(amount)
47
- end
48
-
49
62
  def money
50
63
  Spree::Money.new(amount, currency: currency)
51
64
  end
@@ -76,6 +89,17 @@ module Spree
76
89
  originator.refunded_line_amounts
77
90
  end
78
91
 
92
+ # The tax inside {#refunded_line_amounts}, for the lines whose tax is known.
93
+ # A line opened before returns and claims carried tax is absent: its amount
94
+ # says nothing about how much of it was tax.
95
+ #
96
+ # @return [Hash{Integer => BigDecimal}] line item id => tax
97
+ def refunded_line_taxes
98
+ return {} unless originator.respond_to?(:refunded_line_taxes)
99
+
100
+ originator.refunded_line_taxes
101
+ end
102
+
79
103
  # Returns true if the refund is editable.
80
104
  #
81
105
  # Read through the refund's own order: a payment shared by a split checkout
@@ -96,9 +120,7 @@ module Spree
96
120
  def perform!
97
121
  return true if transaction_id.present?
98
122
 
99
- credit_cents = Spree::Money.new(amount.to_f, currency: currency).amount_in_cents
100
-
101
- @response = process!(credit_cents)
123
+ @response = process!(payment.payment_method.gateway_amount(amount, currency))
102
124
 
103
125
  self.transaction_id = @response.authorization
104
126
  update_columns(transaction_id: transaction_id)
@@ -141,37 +163,38 @@ module Spree
141
163
  end
142
164
 
143
165
  # return a payment response object if successful or else raise an error
144
- def process!(credit_cents)
145
- refund_total_in_cents = calculate_refund_amount(credit_cents)
166
+ # @param credit_amount [Spree::Money, Integer] as PaymentMethod#gateway_amount returns it
167
+ def process!(credit_amount)
168
+ refund_total = calculate_refund_amount(credit_amount)
146
169
 
147
170
  response = instrument_gateway_call(:credit, payment.payment_method) do
148
171
  if payment.payment_method.payment_profiles_supported?
149
- payment.payment_method.credit(refund_total_in_cents, payment.source, payment.transaction_id, originator: self)
172
+ payment.payment_method.credit(refund_total, payment.source, payment.transaction_id, originator: self)
150
173
  else
151
- payment.payment_method.credit(refund_total_in_cents, payment.transaction_id, originator: self)
174
+ payment.payment_method.credit(refund_total, payment.transaction_id, originator: self)
152
175
  end
153
176
  end
154
177
 
155
178
  if response.success?
156
- track_order_as_refunded(refund_total_in_cents)
179
+ track_order_as_refunded(refund_total)
157
180
  else
158
- Rails.logger.error(Spree.t(:gateway_error) + " #{response.to_yaml}")
181
+ Rails.logger.error(I18n.t('spree.gateway_error') + " #{response.to_yaml}")
159
182
  text = response.params['message'] || response.params['response_reason_text'] || response.message
160
183
  raise Core::GatewayError, text
161
184
  end
162
185
 
163
186
  response
164
187
  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)
188
+ Rails.logger.error(I18n.t('spree.gateway_error') + " #{e.inspect}")
189
+ raise Core::GatewayError, I18n.t('spree.unable_to_connect_to_gateway')
167
190
  end
168
191
 
169
- def calculate_refund_amount(credit_cents)
192
+ def calculate_refund_amount(credit_amount)
170
193
  # Overwrite this for more complex calculations
171
- credit_cents
194
+ credit_amount
172
195
  end
173
196
 
174
- def track_order_as_refunded(credit_cents)
197
+ def track_order_as_refunded(credit_amount)
175
198
  # You can track refunds here
176
199
  end
177
200
 
@@ -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
@@ -51,6 +52,7 @@ module Spree
51
52
  delegate :currency, to: :order
52
53
 
53
54
  self.whitelisted_ransackable_attributes = %w[number status created_at]
55
+ search_by :number
54
56
  self.whitelisted_ransackable_associations = %w[order reason]
55
57
 
56
58
  # The label that currently binds the inbound parcel — bought or uploaded,
@@ -118,11 +120,16 @@ module Spree
118
120
  received? || refunded?
119
121
  end
120
122
 
121
- # What the customer is owed for the items being returned.
123
+ # What the customer is owed for the items being returned, tax included.
122
124
  def refund_total
123
125
  return_line_items.sum(&:refund_amount)
124
126
  end
125
127
 
128
+ # The tax inside {#refund_total}.
129
+ def refund_tax_total
130
+ return_line_items.sum(&:refund_tax_amount)
131
+ end
132
+
126
133
  # What a refund from this return paid for, line by line. Only ever what
127
134
  # the warehouse counted: the seller's clawback is built from this, and a
128
135
  # line nobody counted earned nothing back.
@@ -139,6 +146,20 @@ module Spree
139
146
  end
140
147
  end
141
148
 
149
+ # The tax inside {#refunded_line_amounts}, for the lines whose tax is
150
+ # recorded.
151
+ #
152
+ # @return [Hash{Integer => BigDecimal}] line item id => tax
153
+ def refunded_line_taxes
154
+ return {} unless counted?
155
+
156
+ return_line_items.each_with_object(Hash.new(0)) do |line, taxes|
157
+ next if line.refund_amount.zero? || !line.settles_tax?
158
+
159
+ taxes[line.line_item_id] += line.refund_tax_amount
160
+ end
161
+ end
162
+
142
163
  # What has actually been refunded so far — a return can be refunded in
143
164
  # more than one step (part to store credit, part to the card), and store
144
165
  # credit is its own ledger rather than a Spree::Refund row.
@@ -157,5 +178,13 @@ module Spree
157
178
  def display_refunded_total
158
179
  Spree::Money.new(refunded_total, currency: currency)
159
180
  end
181
+
182
+ def display_refund_tax_total
183
+ Spree::Money.new(refund_tax_total, currency: currency)
184
+ end
185
+
186
+ def taxed_lines
187
+ return_line_items
188
+ end
160
189
  end
161
190
  end