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
@@ -1,43 +1,2 @@
1
- <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
2
- <html lang="<%= I18n.locale %>">
3
- <head>
4
- <meta name="viewport" content="width=device-width, initial-scale=1.0"/>
5
- <meta name="x-apple-disable-message-reformatting"/>
6
- <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
7
- <meta name="color-scheme" content="light"/>
8
- <meta name="supported-color-schemes" content="light"/>
9
-
10
- <%= render partial: 'spree/shared/base_mailer_stylesheets' %>
11
- <%# Per-email styles — templates add their own via `content_for :head` %>
12
- <%= yield :head %>
13
- </head>
14
- <body class="email-bg" style="margin: 0; padding: 0; -webkit-text-size-adjust: none;" bgcolor="#FFFFFF">
15
- <table class="email-wrapper" width="100%" cellpadding="0" cellspacing="0" role="presentation" bgcolor="#FFFFFF">
16
- <tr>
17
- <td align="center">
18
- <table class="email-content" width="100%" cellpadding="0" cellspacing="0" role="presentation">
19
- <!-- Email Body -->
20
- <tr>
21
- <td class="email-card" bgcolor="#F6F6F6">
22
- <%= render partial: 'spree/shared/base_mailer_header' %>
23
- <table width="100%" cellpadding="0" cellspacing="0" role="presentation">
24
- <tr>
25
- <td class="content-cell f-fallback" align="left">
26
- <%= yield %>
27
- </td>
28
- </tr>
29
- </table>
30
- </td>
31
- </tr>
32
- <!-- Email Footer -->
33
- <tr>
34
- <td class="email-footer" align="center">
35
- <%= render partial: 'spree/shared/base_mailer_footer' %>
36
- </td>
37
- </tr>
38
- </table>
39
- </td>
40
- </tr>
41
- </table>
42
- </body>
43
- </html>
1
+ <%# Mailers that render their own ERB views get Spree's Liquid email layout around them. %>
2
+ <%= render_in_email_layout(yield) %>
@@ -0,0 +1,79 @@
1
+ {% assign brand = store.branding %}<mjml lang="{{ locale }}">
2
+ <mj-head>
3
+ <mj-title>{{ subject }}</mj-title>
4
+ {% if brand.font_url %}<mj-font name="{{ brand.font_family | split: ',' | first }}" href="{{ brand.font_url }}" />{% endif %}
5
+ <mj-attributes>
6
+ <mj-all font-family="{{ brand.font_family }}" />
7
+ <mj-section padding="0" />
8
+ <mj-column padding="0" />
9
+ <mj-text padding="0" color="{{ brand.text_color }}" font-size="14px" line-height="1.6" align="center" />
10
+ <mj-button background-color="{{ brand.button_color }}" border="{{ brand.button_border }}" border-radius="8px" color="{{ brand.button_text_color }}" font-size="15px" font-weight="500" line-height="1.6" inner-padding="12px 20px" padding="32px 0 0" />
11
+ <mj-table padding="0" color="{{ brand.text_color }}" font-size="14px" line-height="1.6" />
12
+ <mj-class name="heading" font-family="{{ brand.heading_font_family }}" font-size="40px" font-weight="700" line-height="1.2" letter-spacing="-0.8px" color="{{ brand.heading_color }}" css-class="heading" />
13
+ <mj-class name="greeting" padding="24px 0 0" font-size="15px" font-weight="500" color="{{ brand.heading_color }}" />
14
+ <mj-class name="lead" padding="12px 0 0" />
15
+ <mj-class name="note" padding="24px 0 0" font-size="13px" css-class="note" />
16
+ <mj-class name="section-heading" padding="48px 0 16px" font-family="{{ brand.heading_font_family }}" font-size="16px" font-weight="500" letter-spacing="-0.16px" align="left" />
17
+ <mj-class name="body" padding="16px 0 0" align="left" />
18
+ <mj-class name="footer" padding="12px 0 0" font-size="11px" line-height="1.5" />
19
+ </mj-attributes>
20
+ <mj-style>
21
+ {% if brand.font == 'inter' %}
22
+ @font-face { font-family: "Geist"; font-style: normal; font-weight: 500 700; src: url("https://fonts.gstatic.com/s/geist/v4/gyByhwUxId8gMEwcGFU.woff2") format("woff2"); }
23
+ @font-face { font-family: "Inter"; font-style: normal; font-weight: 400 450; src: url("https://fonts.gstatic.com/s/inter/v20/UcCO3FwrK3iLTeHuS_nVMrMxCp50SjIw2boKoduKmMEVuOKfMZg.ttf") format("truetype"); }
24
+ @font-face { font-family: "Inter"; font-style: normal; font-weight: 500; src: url("https://fonts.gstatic.com/s/inter/v20/UcCO3FwrK3iLTeHuS_nVMrMxCp50SjIw2boKoduKmMEVuI6fMZg.ttf") format("truetype"); }
25
+ @font-face { font-family: "Inter"; font-style: normal; font-weight: 600; src: url("https://fonts.gstatic.com/s/inter/v20/UcCO3FwrK3iLTeHuS_nVMrMxCp50SjIw2boKoduKmMEVuGKYMZg.ttf") format("truetype"); }
26
+ {% endif %}
27
+ a { color: {{ brand.link_color }}; }
28
+ .hero > table { max-width: 420px; margin: 0 auto; }
29
+ .note a { color: {{ brand.text_color }}; word-break: break-all; }
30
+ .footer a { color: {{ brand.text_color }}; }
31
+ .item { width: 100%; background-color: #F0F0F0; border-radius: 16px; margin: 0 0 8px; }
32
+ .item_image { padding: 8px; width: 96px; }
33
+ .item_thumb { display: block; width: 80px; height: auto; border-radius: 12px; }
34
+ .item_thumb_placeholder { width: 80px; height: 80px; background-color: #E4E4E4; border-radius: 12px; }
35
+ .item_info { padding: 8px 16px 8px 8px; }
36
+ .item_name { font-size: 15px; line-height: 1.5; font-weight: 500; color: {{ brand.heading_color }}; text-decoration: none; }
37
+ .item_meta { margin: 2px 0 0; font-size: 13px; line-height: 1.5; color: {{ brand.text_color }}; }
38
+ .item_amount { padding: 8px 16px 8px 8px; text-align: right; white-space: nowrap; }
39
+ .item_qty { margin: 0; font-size: 13px; line-height: 1.5; color: {{ brand.text_color }}; }
40
+ .item_price { margin: 2px 0 0; font-size: 15px; line-height: 1.5; font-weight: 500; color: {{ brand.heading_color }}; }
41
+ .summary td { padding: 6px 0; font-size: 14px; line-height: 1.6; color: {{ brand.text_color }}; }
42
+ .summary_value { font-size: 15px; font-weight: 500; text-align: right; white-space: nowrap; color: {{ brand.heading_color }}; }
43
+ .summary_sub { display: block; font-size: 13px; line-height: 1.5; }
44
+ .summary_total td { padding: 14px 0 0; border-top: 1px solid #E8E9E9; font-weight: 500; color: {{ brand.heading_color }}; }
45
+ .summary_total .summary_value { font-weight: 600; }
46
+ .mailer_hero h1 { margin: 0; font-family: {{ brand.heading_font_family }}; font-size: 40px; line-height: 1.2; letter-spacing: -0.8px; font-weight: 700; color: {{ brand.heading_color }}; }
47
+ .mailer_hero p { margin: 12px 0 0; }
48
+ .mailer_hero .greeting { margin-top: 24px; font-size: 15px; font-weight: 500; color: {{ brand.heading_color }}; }
49
+ .mailer_hero .sub { margin-top: 24px; font-size: 13px; }
50
+ @media only screen and (max-width: 600px) {
51
+ .heading div { font-size: 32px !important; letter-spacing: -0.64px !important; }
52
+ }
53
+ </mj-style>
54
+ </mj-head>
55
+ <mj-body background-color="{{ brand.background_color }}" width="640px">
56
+ <mj-wrapper background-color="{{ brand.card_color }}" padding="56px 24px">
57
+ <mj-section padding="0 0 32px">
58
+ <mj-column>
59
+ {% if store.logo_url and store.logo_width %}
60
+ <mj-image src="{{ store.logo_url }}" href="{{ store.url }}" alt="{{ store.name }}" width="{{ store.logo_width }}px" height="32px" padding="0" />
61
+ {% elsif store.logo_url %}
62
+ <mj-text><a href="{{ store.url }}"><img src="{{ store.logo_url }}" alt="{{ store.name }}" height="32" style="height: 32px; width: auto; border: 0;" /></a></mj-text>
63
+ {% else %}
64
+ <mj-text font-size="22px" font-weight="700" color="{{ brand.heading_color }}" text-transform="uppercase"><a href="{{ store.url }}" style="color: {{ brand.heading_color }}; text-decoration: none;">{{ store.name }}</a></mj-text>
65
+ {% endif %}
66
+ </mj-column>
67
+ </mj-section>
68
+ {{ content_for_layout }}
69
+ </mj-wrapper>
70
+ <mj-section padding="64px 24px">
71
+ <mj-column>
72
+ <mj-text font-size="13px" line-height="1.5" css-class="footer"><a href="{{ store.url }}" style="font-weight: 500; text-decoration: none;">{{ store.name }}</a></mj-text>
73
+ {% if store.address != blank %}<mj-text mj-class="footer">{{ store.address }}</mj-text>{% endif %}
74
+ {% if store.support_email != blank %}<mj-text mj-class="footer" css-class="footer"><a href="mailto:{{ store.support_email }}">{{ store.support_email }}</a></mj-text>{% endif %}
75
+ <mj-text mj-class="footer" padding="20px 0 0">&copy; {{ 'now' | date: '%Y' }} {{ store.name }}</mj-text>
76
+ </mj-column>
77
+ </mj-section>
78
+ </mj-body>
79
+ </mjml>
@@ -0,0 +1,14 @@
1
+ ---
2
+ subject: "{{ store.name }} {{ 'admin_user_mailer.password_reset_email.subject' | t }}"
3
+ ---
4
+ {% assign name = user.first_name | default: user.email %}
5
+ <mj-section>
6
+ <mj-column css-class="hero">
7
+ <mj-text mj-class="heading">{{ 'admin_user_mailer.password_reset_email.action' | t }}</mj-text>
8
+ <mj-text mj-class="greeting">{{ 'admin_user_mailer.password_reset_email.greeting' | t: name: name }}</mj-text>
9
+ <mj-text mj-class="lead">{{ 'admin_user_mailer.password_reset_email.instructions' | t: store_name: store.name }}</mj-text>
10
+ <mj-button href="{{ reset_url }}">{{ 'admin_user_mailer.password_reset_email.action' | t }}</mj-button>
11
+ <mj-text mj-class="note">{{ 'admin_user_mailer.password_reset_email.expiry_notice' | t }}</mj-text>
12
+ <mj-text mj-class="note" padding="16px 0 0">{{ 'admin_user_mailer.password_reset_email.ignore_notice' | t }}</mj-text>
13
+ </mj-column>
14
+ </mj-section>
@@ -0,0 +1,12 @@
1
+ ---
2
+ subject: "{{ 'export_mailer.export_done.subject' | t: export_number: export.number }}"
3
+ ---
4
+ <mj-section>
5
+ <mj-column css-class="hero">
6
+ <mj-text mj-class="heading">{{ 'export_mailer.greeting' | t: user_name: export.user.first_name }}</mj-text>
7
+ <mj-text mj-class="lead">{{ 'export_mailer.export_done.message' | t }}</mj-text>
8
+ {% if export.results_url != blank %}
9
+ <mj-button href="{{ export.results_url }}">{{ export.filename }}</mj-button>
10
+ {% endif %}
11
+ </mj-column>
12
+ </mj-section>
@@ -0,0 +1,15 @@
1
+ ---
2
+ subject: "{{ 'import_mailer.import_done.subject' | t: import_number: import.number }}"
3
+ ---
4
+ <mj-section>
5
+ <mj-column css-class="hero">
6
+ <mj-text mj-class="heading">{{ 'import_mailer.greeting' | t: user_name: import.user.first_name }}</mj-text>
7
+ <mj-text mj-class="lead">{{ 'import_mailer.import_done.message' | t: completed_count: import.completed_count }}</mj-text>
8
+ {% if import.failed_count > 0 %}
9
+ <mj-text mj-class="lead"><strong>{{ 'import_mailer.import_done.failed_message' | t: failed_count: import.failed_count }}</strong></mj-text>
10
+ {% endif %}
11
+ {% if import.results_url != blank %}
12
+ <mj-button href="{{ import.results_url }}">{{ 'import_mailer.import_done.view_results' | t }}</mj-button>
13
+ {% endif %}
14
+ </mj-column>
15
+ </mj-section>
@@ -0,0 +1,9 @@
1
+ ---
2
+ subject: "{{ 'invitation_mailer.invitation_accepted.subject' | t: invitee_name: invitation.invitee_name, resource_name: invitation.resource_name }}"
3
+ ---
4
+ <mj-section>
5
+ <mj-column css-class="hero">
6
+ <mj-text mj-class="heading">{{ 'invitation_mailer.greeting' | t: user_name: invitation.inviter_first_name }}</mj-text>
7
+ <mj-text mj-class="lead">{{ 'invitation_mailer.invitation_accepted.body' | t: invitee_name: invitation.invitee_name, resource_name: invitation.resource_name }}</mj-text>
8
+ </mj-column>
9
+ </mj-section>
@@ -0,0 +1,13 @@
1
+ ---
2
+ subject: "{{ 'invitation_mailer.invitation_email.subject' | t: resource_name: invitation.resource_name }}"
3
+ ---
4
+ {% assign name = invitation.invitee_first_name | default: invitation.email %}
5
+ <mj-section>
6
+ <mj-column css-class="hero">
7
+ <mj-text mj-class="heading">{{ 'invitation_mailer.invitation_email.subject' | t: resource_name: invitation.resource_name }}</mj-text>
8
+ <mj-text mj-class="greeting">{{ 'invitation_mailer.greeting' | t: user_name: name }}</mj-text>
9
+ <mj-text mj-class="lead">{{ 'invitation_mailer.invitation_email.body' | t: inviter_name: invitation.inviter_name, resource_name: invitation.resource_name }}</mj-text>
10
+ <mj-text mj-class="lead">{{ 'invitation_mailer.invitation_email.link_description' | t }}</mj-text>
11
+ <mj-button href="{{ accept_url }}">{{ 'accept' | t }}</mj-button>
12
+ </mj-column>
13
+ </mj-section>
@@ -0,0 +1,14 @@
1
+ ---
2
+ subject: "{{ store.name }} {{ 'seller_user_mailer.password_reset_email.subject' | t }}"
3
+ ---
4
+ {% assign name = user.first_name | default: user.email %}
5
+ <mj-section>
6
+ <mj-column css-class="hero">
7
+ <mj-text mj-class="heading">{{ 'seller_user_mailer.password_reset_email.action' | t }}</mj-text>
8
+ <mj-text mj-class="greeting">{{ 'seller_user_mailer.password_reset_email.greeting' | t: name: name }}</mj-text>
9
+ <mj-text mj-class="lead">{{ 'seller_user_mailer.password_reset_email.instructions' | t: store_name: store.name }}</mj-text>
10
+ <mj-button href="{{ reset_url }}">{{ 'seller_user_mailer.password_reset_email.action' | t }}</mj-button>
11
+ <mj-text mj-class="note">{{ 'seller_user_mailer.password_reset_email.expiry_notice' | t }}</mj-text>
12
+ <mj-text mj-class="note" padding="16px 0 0">{{ 'seller_user_mailer.password_reset_email.ignore_notice' | t }}</mj-text>
13
+ </mj-column>
14
+ </mj-section>
@@ -1,10 +1,9 @@
1
- <% button_class = local_assigns.fetch(:button_class, nil) %>
2
- <table class="body-action" align="center" width="100%" cellpadding="0" cellspacing="0" role="presentation">
1
+ <%# A call-to-action button for a custom mailer's ERB view. Locals: url, label. %>
2
+ <table align="center" width="100%" cellpadding="0" cellspacing="0" role="presentation" style="margin: 32px auto 0;">
3
3
  <tr>
4
4
  <td align="center">
5
- <a href="<%= url %>" class="f-fallback button <%= button_class %>" target="_blank">
6
- <%= label %>
7
- </a>
5
+ <a href="<%= url %>" target="_blank"
6
+ style="display: inline-block; background-color: #FFFFFF; border: 1px solid #E8E9E9; border-radius: 8px; padding: 12px 20px; font-size: 15px; line-height: 1.6; font-weight: 500; color: #1F2222; text-decoration: none;"><%= label %></a>
8
7
  </td>
9
8
  </tr>
10
9
  </table>
@@ -1,10 +1,11 @@
1
- <%# Centered hero block every mailer template opens with.
2
- Render as a layout so the template's headline/copy fills it:
3
- render layout: 'spree/shared/mailer_hero' do ... end %>
4
- <table class="hero" width="100%" cellpadding="0" cellspacing="0" role="presentation">
1
+ <%# Centered hero block for a custom mailer's ERB view. Render it as a layout so
2
+ the view's heading and copy fill it:
3
+ render layout: 'spree/shared/mailer_hero' do ... end
4
+ Style its contents with h1, p.greeting and p.sub. %>
5
+ <table class="mailer_hero" width="100%" cellpadding="0" cellspacing="0" role="presentation">
5
6
  <tr>
6
7
  <td align="center">
7
- <div class="hero_content f-fallback">
8
+ <div style="max-width: 420px; margin: 0 auto; text-align: center;">
8
9
  <%= yield %>
9
10
  </div>
10
11
  </td>
@@ -0,0 +1,8 @@
1
+ {%- comment -%}
2
+ One label/value row inside an <mj-table css-class="summary">.
3
+ Arguments: label (translation key when translate is true), value, optional sub (a second line under the label), optional total (the emphasized final row).
4
+ {%- endcomment -%}
5
+ <tr{% if total %} class="summary_total"{% endif %}>
6
+ <td valign="top">{% if translate %}{{ label | t }}{% else %}{{ label }}{% endif %}{% if sub != blank %} <span class="summary_sub">{{ sub }}</span>{% endif %}</td>
7
+ <td class="summary_value" align="right" valign="top">{{ value }}</td>
8
+ </tr>
@@ -0,0 +1,21 @@
1
+ ---
2
+ subject: "{{ 'webhook_mailer.endpoint_disabled.subject' | t: endpoint_name: endpoint.display_name }}"
3
+ ---
4
+ {% assign disabled_at = endpoint.disabled_at | date: '%Y-%m-%d %H:%M %Z' %}
5
+ <mj-section>
6
+ <mj-column css-class="hero">
7
+ <mj-text mj-class="heading">{{ 'webhook_mailer.endpoint_disabled.heading' | t }}</mj-text>
8
+ <mj-text mj-class="lead">{{ 'webhook_mailer.endpoint_disabled.message' | t: endpoint_name: endpoint.display_name }}</mj-text>
9
+ </mj-column>
10
+ </mj-section>
11
+ <mj-section padding="48px 0 0">
12
+ <mj-column>
13
+ <mj-table css-class="summary">
14
+ {% render 'spree/shared/summary_row', label: 'webhook_mailer.endpoint_disabled.url_label', value: endpoint.url, translate: true %}
15
+ {% if endpoint.name != blank %}{% render 'spree/shared/summary_row', label: 'webhook_mailer.endpoint_disabled.name_label', value: endpoint.name, translate: true %}{% endif %}
16
+ {% render 'spree/shared/summary_row', label: 'webhook_mailer.endpoint_disabled.reason_label', value: endpoint.disabled_reason, translate: true %}
17
+ {% render 'spree/shared/summary_row', label: 'webhook_mailer.endpoint_disabled.disabled_at_label', value: disabled_at, translate: true %}
18
+ </mj-table>
19
+ <mj-text mj-class="body" padding="48px 0 0">{{ 'webhook_mailer.endpoint_disabled.instructions' | t }}</mj-text>
20
+ </mj-column>
21
+ </mj-section>
@@ -61,17 +61,17 @@ module Spree
61
61
  # @return [Spree::ServiceModule::Result, nil] failure when the price is refused
62
62
  def parse_manual_price
63
63
  if cart.completed?
64
- errors.add(:base, :price_override_not_allowed, message: Spree.t('cart_line_item.price_override_not_allowed'))
64
+ errors.add(:base, :price_override_not_allowed, message: I18n.t('spree.cart_line_item.price_override_not_allowed'))
65
65
  return failure(cart, errors)
66
66
  end
67
67
 
68
68
  @manual_price = BigDecimal(price.to_s)
69
69
  if @manual_price.negative? || !@manual_price.finite?
70
- errors.add(:base, :invalid_price, message: Spree.t('cart_line_item.invalid_price'))
70
+ errors.add(:base, :invalid_price, message: I18n.t('spree.cart_line_item.invalid_price'))
71
71
  return failure(cart, errors)
72
72
  end
73
73
  rescue ArgumentError
74
- errors.add(:base, :invalid_price, message: Spree.t('cart_line_item.invalid_price'))
74
+ errors.add(:base, :invalid_price, message: I18n.t('spree.cart_line_item.invalid_price'))
75
75
  failure(cart, errors)
76
76
  end
77
77
 
@@ -158,7 +158,7 @@ module Spree
158
158
 
159
159
  return if result.value.blank?
160
160
 
161
- failure(variant, Spree.t(:selected_quantity_not_available, item: variant.name.inspect))
161
+ failure(variant, I18n.t('spree.selected_quantity_not_available', item: variant.name.inspect))
162
162
  end
163
163
 
164
164
  # Asked before the item exists, so the context carries the quantity the
@@ -47,6 +47,7 @@ module Spree
47
47
 
48
48
  cart.with_lock do
49
49
  step :guard_concurrent_completion
50
+ step :guard_coupon_code
50
51
  step :recalculate_in_lock
51
52
  step :verify_expected_total
52
53
  step :validate_cart
@@ -92,7 +93,7 @@ module Spree
92
93
  halt!(result) if result.canceled?
93
94
 
94
95
  if !result.placed? && result.payment_required? && !payment_covered?(result)
95
- failure(cart, code: 'payment_failed', message: Spree.t(:payment_processing_failed))
96
+ failure(cart, code: 'payment_failed', message: I18n.t('spree.payment_processing_failed'))
96
97
  end
97
98
 
98
99
  finalize!(cart, result)
@@ -113,6 +114,15 @@ module Spree
113
114
  failure(cart, code: 'completion_in_progress') if cart.completion_claimed?
114
115
  end
115
116
 
117
+ # A batch code this cart no longer holds no longer discounts it, so the
118
+ # shopper would be charged more than the total they last saw, and the
119
+ # order would name a code it never used.
120
+ def guard_coupon_code
121
+ return unless cart.coupon_code_unavailable?
122
+
123
+ failure(cart, code: 'coupon_code_unavailable', message: I18n.t('spree.coupon_code_unavailable'))
124
+ end
125
+
116
126
  # In-lock recalculation — the totals about to be charged are computed
117
127
  # here, not trusted from earlier requests.
118
128
  # Only external pricing is re-confirmed: Spree's own catalog is read
@@ -165,7 +175,7 @@ module Spree
165
175
  return if payment_covered?(order)
166
176
 
167
177
  failure(cart, code: 'payment_failed',
168
- message: order.errors.full_messages.to_sentence.presence || Spree.t(:payment_processing_failed))
178
+ message: order.errors.full_messages.to_sentence.presence || I18n.t('spree.payment_processing_failed'))
169
179
  rescue Spree::Core::GatewayError => e
170
180
  failure(cart, code: 'payment_failed', message: e.message)
171
181
  end
@@ -328,7 +338,7 @@ module Spree
328
338
  line_item_id_map = line_item_map.transform_keys(&:id).transform_values(&:id)
329
339
 
330
340
  cart.fulfillments.reload.each_with_object({}) do |cart_fulfillment, map|
331
- attributes = cart_fulfillment.attributes.except('id', 'cart_id', 'number', 'created_at', 'updated_at')
341
+ attributes = cart_fulfillment.attributes.except('id', 'cart_id', 'number', 'tracking', 'created_at', 'updated_at')
332
342
  fulfillment = order.fulfillments.create!(attributes.merge('order_id' => order.id, 'address_id' => order.ship_address_id))
333
343
 
334
344
  if (selected = cart_fulfillment.selected_delivery_rate)
@@ -42,7 +42,7 @@ module Spree
42
42
  def ensure_matching_currency
43
43
  return if other_cart.currency == cart.currency
44
44
 
45
- failure(cart, Spree.t('errors.messages.cart_currency_mismatch'))
45
+ failure(cart, I18n.t('spree.errors.messages.cart_currency_mismatch'))
46
46
  end
47
47
 
48
48
  # A matching item absorbs the quantity; everything else is re-pointed
@@ -40,6 +40,7 @@ module Spree
40
40
  step :fold_typed_rows
41
41
  step :refresh_grand_total
42
42
  step :persist_totals
43
+ step :resize_gift_card_payment unless money_frozen?
43
44
 
44
45
  success(cart)
45
46
  end
@@ -175,6 +176,10 @@ module Spree
175
176
  cart.update_columns(columns)
176
177
  end
177
178
 
179
+ def resize_gift_card_payment
180
+ cart.recalculate_gift_card
181
+ end
182
+
178
183
  def money_frozen?
179
184
  resum_only || (cart.is_a?(Spree::Order) && cart.completed?)
180
185
  end
@@ -33,8 +33,10 @@ module Spree
33
33
  # amount negotiates the line (+price_source: 'manual'+), an explicit
34
34
  # nil reverts a negotiated line to catalog pricing. Admin surface
35
35
  # only (draft orders); refused once the order is placed.
36
+ # @param orderable_variants [ActiveRecord::Relation<Spree::Variant>, nil]
37
+ # what an add may name; the store's variants when nil
36
38
  # @return [Spree::ServiceModule::Result] value is the cart
37
- def perform(cart:, items:)
39
+ def perform(cart:, items:, orderable_variants: nil)
38
40
  super
39
41
  @warnings = []
40
42
 
@@ -107,7 +109,7 @@ module Spree
107
109
  # Price overrides are pre-placement only. reject! rather than
108
110
  # failure(cart, message), which would drop the message.
109
111
  if cart.completed? && @resolved_items.any?(&:price_provided)
110
- errors.add(:base, :price_override_not_allowed, message: Spree.t('cart_line_item.price_override_not_allowed'))
112
+ errors.add(:base, :price_override_not_allowed, message: I18n.t('spree.cart_line_item.price_override_not_allowed'))
111
113
  reject!(nil, cart)
112
114
  end
113
115
  end
@@ -121,19 +123,19 @@ module Spree
121
123
  return nil if value.nil?
122
124
 
123
125
  if cart.completed?
124
- errors.add(:base, :price_override_not_allowed, message: Spree.t('cart_line_item.price_override_not_allowed'))
126
+ errors.add(:base, :price_override_not_allowed, message: I18n.t('spree.cart_line_item.price_override_not_allowed'))
125
127
  reject!(nil, cart)
126
128
  end
127
129
 
128
130
  parsed = BigDecimal(value.to_s)
129
131
  if parsed.negative? || !parsed.finite?
130
- errors.add(:base, :invalid_price, message: Spree.t('cart_line_item.invalid_price'))
132
+ errors.add(:base, :invalid_price, message: I18n.t('spree.cart_line_item.invalid_price'))
131
133
  reject!(nil, cart)
132
134
  end
133
135
 
134
136
  parsed
135
137
  rescue ArgumentError
136
- errors.add(:base, :invalid_price, message: Spree.t('cart_line_item.invalid_price'))
138
+ errors.add(:base, :invalid_price, message: I18n.t('spree.cart_line_item.invalid_price'))
137
139
  reject!(nil, cart)
138
140
  end
139
141
 
@@ -230,7 +232,7 @@ module Spree
230
232
  # external system can be the only source for a currency. A
231
233
  # negotiated price is a quote too, made by the merchant.
232
234
  if !item.manual_price? && resolved_prices[item.variant.id]&.at(1)&.amount.nil? && item.variant.amount_in(cart.currency).nil?
233
- message = Spree.t('cart_line_item.currency_unavailable', li_name: item.variant.name, currency: cart.currency)
235
+ message = I18n.t('spree.cart_line_item.currency_unavailable', li_name: item.variant.name, currency: cart.currency)
234
236
  failure(item.variant, message) unless partial_success?
235
237
 
236
238
  warn(index, item, :currency_unavailable, message)
@@ -238,7 +240,7 @@ module Spree
238
240
  end
239
241
 
240
242
  if unsupplyable?(item)
241
- message = Spree.t(:selected_quantity_not_available, item: item.variant.name.inspect)
243
+ message = I18n.t('spree.selected_quantity_not_available', item: item.variant.name.inspect)
242
244
  failure(item.variant, message) unless partial_success?
243
245
 
244
246
  warn(index, item, :selected_quantity_not_available, message)
@@ -355,8 +357,15 @@ module Spree
355
357
  cart.line_items.detect { |line_item| line_item.variant_id.to_s == variant_id.to_s }&.variant ||
356
358
  Spree::Variant.with_deleted.find_by_param(variant_id)
357
359
  else
358
- store.variants.find_by_param(variant_id) ||
359
- raise(ActiveRecord::RecordNotFound.new("Variant '#{variant_id}' not found in this store", 'Spree::Variant', 'id', variant_id))
360
+ variant = store.variants.find_by_param(variant_id)
361
+ # +orderable_variants+ narrows what a request may add. A line already
362
+ # in the cart is a quantity edit and resolves as before — checkout
363
+ # judges it — so one stale line cannot fail a whole cart's update.
364
+ if variant && orderable_variants && cart.line_items.none? { |line_item| line_item.variant_id == variant.id }
365
+ variant = orderable_variants.find_by(id: variant.id)
366
+ end
367
+
368
+ variant || raise(ActiveRecord::RecordNotFound.new("Variant '#{variant_id}' not found in this store", 'Spree::Variant', 'id', variant_id))
360
369
  end
361
370
  end
362
371
 
@@ -1,38 +1,21 @@
1
1
  module Spree
2
2
  module Claims
3
3
  class Approve < Spree::Workflow
4
+ include Spree::PostSale::Approval
5
+
4
6
  hooks :validate, :after_approve
5
7
 
6
8
  # @param claim [Spree::Claim]
7
9
  # @param approver [Object, nil]
8
10
  def perform(claim:, approver: nil)
9
11
  super
10
-
11
- step :ensure_approvable
12
- run_hooks :validate
13
-
14
- ApplicationRecord.transaction do
15
- step :mark_approved
16
- end
17
-
18
- run_hooks :after_approve
19
- claim.publish_event('claim.approved')
20
- success(claim.reload)
12
+ approve
21
13
  end
22
14
 
23
15
  private
24
16
 
25
- def ensure_approvable
26
- failure(claim, :not_open) unless claim.open?
27
- end
28
-
29
- def mark_approved
30
- claim.update!(
31
- status: 'approved',
32
- approved_at: Time.current,
33
- created_by: claim.created_by || approver
34
- )
35
- end
17
+ def post_sale_record = claim
18
+ def approvable_status = 'open'
36
19
  end
37
20
  end
38
21
  end
@@ -1,37 +1,21 @@
1
1
  module Spree
2
2
  module Claims
3
3
  class Cancel < Spree::Workflow
4
+ include Spree::PostSale::Cancellation
5
+
4
6
  hooks :validate, :after_cancel
5
7
 
6
8
  # @param claim [Spree::Claim]
7
9
  # @param reason [String, nil]
8
10
  def perform(claim:, reason: nil)
9
11
  super
10
-
11
- step :ensure_cancellable
12
- run_hooks :validate
13
-
14
- ApplicationRecord.transaction do
15
- step :mark_canceled
16
- end
17
-
18
- run_hooks :after_cancel
19
- claim.publish_event('claim.canceled')
20
- success(claim.reload)
12
+ cancel
21
13
  end
22
14
 
23
15
  private
24
16
 
25
- def ensure_cancellable
26
- return if claim.open? || claim.approved?
27
-
28
- failure(claim, :not_cancellable)
29
- end
30
-
31
- def mark_canceled
32
- memo = [claim.memo, reason].compact_blank.join("\n")
33
- claim.update!(status: 'canceled', canceled_at: Time.current, memo: memo.presence)
34
- end
17
+ def post_sale_record = claim
18
+ def cancellable_statuses = %w[open approved]
35
19
  end
36
20
  end
37
21
  end
@@ -5,6 +5,8 @@ module Spree
5
5
  # No goods come back — that is the point of a claim — so there is nothing
6
6
  # to receive and nothing to restock.
7
7
  class Create < Spree::Workflow
8
+ include Spree::Refunds::TaxCredit
9
+
8
10
  hooks :validate, :after_create
9
11
 
10
12
  attr_reader :claim
@@ -24,6 +26,8 @@ module Spree
24
26
 
25
27
  ApplicationRecord.transaction do
26
28
  step :build_claim
29
+ step :calculate_tax
30
+ step :ensure_refunds_within_paid
27
31
  end
28
32
 
29
33
  run_hooks :after_create
@@ -107,6 +111,17 @@ module Spree
107
111
 
108
112
  failure(@claim) unless @claim.save
109
113
  end
114
+
115
+ def calculate_tax
116
+ with_tax_provider(claim) { claim.calculate_tax! }
117
+ end
118
+
119
+ # Checked once the tax is known, because a line's refund is entered tax
120
+ # included and what it may not exceed includes the tax charged on it.
121
+ def ensure_refunds_within_paid
122
+ over = claim.claim_line_items.any? { |line| line.refund_amount.to_d > line.paid_amount }
123
+ failure(claim, :refund_exceeds_paid) if over
124
+ end
110
125
  end
111
126
  end
112
127
  end
@@ -1,6 +1,8 @@
1
1
  module Spree
2
2
  module Claims
3
3
  class Deny < Spree::Workflow
4
+ include Spree::Refunds::TaxCredit
5
+
4
6
  hooks :validate, :after_deny
5
7
 
6
8
  # @param claim [Spree::Claim]
@@ -13,6 +15,7 @@ module Spree
13
15
 
14
16
  ApplicationRecord.transaction do
15
17
  step :mark_denied
18
+ step :clear_tax
16
19
  end
17
20
 
18
21
  run_hooks :after_deny
@@ -30,6 +33,10 @@ module Spree
30
33
  memo = [claim.memo, reason].compact_blank.join("\n")
31
34
  claim.update!(status: 'denied', denied_at: Time.current, memo: memo.presence)
32
35
  end
36
+
37
+ def clear_tax
38
+ clear_tax_credit(claim)
39
+ end
33
40
  end
34
41
  end
35
42
  end