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
@@ -0,0 +1,46 @@
1
+ module Spree
2
+ module Receivables
3
+ # Ends a purchase order or transfer whose missing units are not going to
4
+ # turn up. Nothing is written to any shelf: closing short keeps what was
5
+ # received, records why the rest never arrived, and leaves the outstanding
6
+ # count on each line as the record of the gap. The including workflow
7
+ # names the document (`receivable`).
8
+ module ClosingShort
9
+ include IncomingCounter
10
+
11
+ private
12
+
13
+ def close
14
+ receivable.with_lock do
15
+ step :ensure_closable
16
+ run_hooks :validate
17
+ step :uncount_awaited_units
18
+ step :close_short
19
+ end
20
+
21
+ run_hooks :after_close
22
+ receivable.publish_event("#{receivable.event_prefix}.received")
23
+ success(receivable.reload)
24
+ end
25
+
26
+ def ensure_closable
27
+ return if receivable.partially_received?
28
+
29
+ failure(receivable, I18n.t("spree.#{receivable.event_prefix}.errors.not_partially_received"))
30
+ end
31
+
32
+ # The balance is not coming, so it leaves the destination's incoming
33
+ # figure; each line's outstanding count keeps the record.
34
+ def uncount_awaited_units
35
+ uncount_incoming(receivable)
36
+ end
37
+
38
+ def close_short
39
+ now = Time.current
40
+ attributes = { status: 'received', received_at: now, closed_short_at: now, close_reason: reason.presence }
41
+
42
+ failure(receivable) unless receivable.update(attributes)
43
+ end
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,48 @@
1
+ module Spree
2
+ module Receivables
3
+ # Edits a draft purchase order or transfer. Past `draft` the document is a
4
+ # matter of record, so the edit is refused rather than quietly rewriting
5
+ # something a supplier or warehouse is already acting on. The including
6
+ # workflow names the document (`receivable`) and the line columns callers
7
+ # may set (`item_attributes`).
8
+ module DraftEditing
9
+ private
10
+
11
+ def edit
12
+ step :ensure_editable
13
+ run_hooks :validate
14
+
15
+ ApplicationRecord.transaction do
16
+ step :apply_changes
17
+ step :save_document
18
+ end
19
+
20
+ run_hooks :after_update
21
+ success(receivable.reload)
22
+ end
23
+
24
+ def ensure_editable
25
+ return if receivable.editable?
26
+
27
+ failure(receivable, I18n.t("spree.#{receivable.event_prefix}.errors.not_editable"))
28
+ end
29
+
30
+ # A whole-list replacement rather than a per-line diff: a draft's lines
31
+ # are what the merchant is still deciding, and sending the list they
32
+ # want is simpler for a client than working out which rows to delete.
33
+ def apply_changes
34
+ receivable.assign_attributes(attributes) if attributes.present?
35
+ return if items.nil?
36
+
37
+ receivable.items.destroy_all
38
+ Array(items).each do |item|
39
+ receivable.items.build(variant: item[:variant], **item_attributes.index_with { |key| item[key] })
40
+ end
41
+ end
42
+
43
+ def save_document
44
+ failure(receivable) unless receivable.save
45
+ end
46
+ end
47
+ end
48
+ end
@@ -26,7 +26,9 @@ module Spree
26
26
  # Required only when the payment is shared by a split checkout, where
27
27
  # it covers several orders and the payment cannot say which one this
28
28
  # refund is for; otherwise the payment's own order is taken.
29
- def perform(payment:, amount: nil, reason: nil, refunder: nil, originator: nil, order: nil)
29
+ # @param tax_amount [BigDecimal, Numeric] the tax inside +amount+, when
30
+ # what is being refunded carried tax (see Spree::Refund#tax_amount)
31
+ def perform(payment:, amount: nil, reason: nil, refunder: nil, originator: nil, order: nil, tax_amount: 0)
30
32
  super
31
33
 
32
34
  step :ensure_refundable
@@ -96,7 +98,8 @@ module Spree
96
98
  order: order || payment.order,
97
99
  reason: reason || Spree::RefundReason.return_processing_reason(refund_store),
98
100
  refunder: refunder,
99
- originator: originator
101
+ originator: originator,
102
+ tax_amount: tax_amount
100
103
  )
101
104
  end
102
105
  end
@@ -91,9 +91,12 @@ module Spree
91
91
  # @param record [Spree::Return, Spree::Claim, Spree::Exchange] what asked
92
92
  # for the refund; it originates the rows and carries any failure
93
93
  # @param refunder [Object, nil] whoever is issuing it
94
+ # @param tax_amount [BigDecimal] the tax inside +amount+, divided across
95
+ # the rows in proportion to what each refunds
94
96
  # @return [Array<Spree::Refund>] the refunds written, in drain order
95
- def refund_order_payments(order:, amount:, record:, refunder: nil)
97
+ def refund_order_payments(order:, amount:, record:, refunder: nil, tax_amount: 0)
96
98
  remaining = amount.to_d
99
+ tax_remaining = tax_amount.to_d
97
100
  refunds = []
98
101
  shares = refundable_shares(order)
99
102
  # With no payment to draw on the caller reports that instead.
@@ -109,13 +112,21 @@ module Spree
109
112
  creditable = [refundable, remaining].min
110
113
  next unless creditable.positive?
111
114
 
115
+ # The last row takes what is left, so the rows' tax adds up exactly.
116
+ row_tax = if creditable == remaining
117
+ tax_remaining
118
+ else
119
+ Spree::Money::Rounding.to_currency(tax_amount.to_d * creditable / amount.to_d, order.currency)
120
+ end
121
+
112
122
  result = Spree.refund_create_workflow.call(
113
123
  payment: payment,
114
124
  amount: creditable,
115
125
  order: order,
116
126
  reason: reason,
117
127
  refunder: refunder,
118
- originator: record
128
+ originator: record,
129
+ tax_amount: row_tax
119
130
  )
120
131
  # `failure` raises, so this ends the drain — the caller's own error
121
132
  # vocabulary decorates the 422.
@@ -123,6 +134,7 @@ module Spree
123
134
 
124
135
  refunds << result.value
125
136
  remaining -= creditable
137
+ tax_remaining -= row_tax
126
138
  end
127
139
 
128
140
  refunds
@@ -0,0 +1,67 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Spree
4
+ module Refunds
5
+ # The tax half of putting money back: asking the order's tax provider what
6
+ # tax goes back with it, and filing the credit once the money has moved.
7
+ #
8
+ # Shared by returns, claims and exchanges, which give back tax for the same
9
+ # reason they give back money (see Spree::PostSale::Taxation).
10
+ module TaxCredit
11
+ extend ActiveSupport::Concern
12
+
13
+ private
14
+
15
+ # Runs a provider calculation, refusing the step when the provider could
16
+ # not answer. Going on would refund tax nobody worked out — too little,
17
+ # and the customer is short; none, and the merchant finds out in a filing.
18
+ def with_tax_provider(record)
19
+ yield
20
+ rescue Spree::Tax::ProviderError => error
21
+ Rails.error.report(error, handled: true, context: tax_report_context(record), source: 'spree.post_sale')
22
+
23
+ # A refusal names its own problem; an outage is worth retrying, and its
24
+ # raw message names the endpoint rather than anything the caller can fix.
25
+ failure(record, error.is_a?(Spree::Tax::CalculationRefused) ? error.message.to_s : :tax_provider_unavailable)
26
+ end
27
+
28
+ # A record withdrawn before anything went back loses its rows — left in
29
+ # place they would count against what a later return of the same units
30
+ # may credit.
31
+ def clear_tax_credit(record)
32
+ record.clear_tax!
33
+ end
34
+
35
+ # Files the credit with the provider after the money has moved.
36
+ # Reported rather than raised: the refund has gone through and the record
37
+ # is already settled, so failing here would fail a refund that happened.
38
+ def file_tax_credit(record, lines, amount)
39
+ order = record.order
40
+ order.tax_provider.refund(order, lines, amount: amount, tax_date: order.completed_at)
41
+ rescue Spree::Tax::ProviderError => error
42
+ Rails.error.report(error, handled: true, context: tax_report_context(record), source: 'spree.post_sale')
43
+ end
44
+
45
+ # Divides a refund across lines in proportion to what each is worth, to
46
+ # the cent, so the tax given back on each line follows the money on it.
47
+ #
48
+ # @param amount [BigDecimal]
49
+ # @param worth [Hash{Object => BigDecimal}] line => what it is worth
50
+ # @param currency [String]
51
+ # @return [Hash{Object => BigDecimal}]
52
+ def allocate_refund(amount, worth, currency)
53
+ weights = worth.values.map { |value| Spree::Money::Rounding.to_minor_units(value, currency) }
54
+ return worth.transform_values { 0.to_d } if weights.sum.zero?
55
+
56
+ shares = Spree::Adjusters::LargestRemainder.largest_remainder_shares(
57
+ Spree::Money::Rounding.to_minor_units(amount, currency), weights
58
+ )
59
+ worth.keys.zip(shares).to_h { |line, cents| [line, Spree::Money::Rounding.from_minor_units(cents, currency)] }
60
+ end
61
+
62
+ def tax_report_context(record)
63
+ { record_type: record.class.name, record_id: record.id, order_id: record.order_id }
64
+ end
65
+ end
66
+ end
67
+ end
@@ -10,38 +10,21 @@ module Spree
10
10
  # 6.0-delivery-rate-provider.md; a store needing one before then can put
11
11
  # the URL in the return's `metadata`.
12
12
  class Approve < Spree::Workflow
13
+ include Spree::PostSale::Approval
14
+
13
15
  hooks :validate, :after_approve
14
16
 
15
17
  # @param return_record [Spree::Return]
16
18
  # @param approver [Object, nil] who is approving it (see Spree.actor_classes)
17
19
  def perform(return_record:, approver: nil)
18
20
  super
19
-
20
- step :ensure_approvable
21
- run_hooks :validate
22
-
23
- ApplicationRecord.transaction do
24
- step :mark_approved
25
- end
26
-
27
- run_hooks :after_approve
28
- return_record.publish_event('return.approved')
29
- success(return_record.reload)
21
+ approve
30
22
  end
31
23
 
32
24
  private
33
25
 
34
- def ensure_approvable
35
- failure(return_record, :not_requested) unless return_record.requested?
36
- end
37
-
38
- def mark_approved
39
- return_record.update!(
40
- status: 'approved',
41
- approved_at: Time.current,
42
- created_by: return_record.created_by || approver
43
- )
44
- end
26
+ def post_sale_record = return_record
27
+ def approvable_status = 'requested'
45
28
  end
46
29
  end
47
30
  end
@@ -7,6 +7,8 @@ module Spree
7
7
  # holding the customer's items, and the way out is a refund, not a
8
8
  # cancellation.
9
9
  class Cancel < Spree::Workflow
10
+ include Spree::PostSale::Cancellation
11
+
10
12
  hooks :validate, :after_cancel
11
13
 
12
14
  # @param return_record [Spree::Return]
@@ -14,39 +16,20 @@ module Spree
14
16
  def perform(return_record:, reason: nil)
15
17
  super
16
18
 
17
- step :ensure_cancellable
18
- run_hooks :validate
19
-
20
- ApplicationRecord.transaction do
21
- step :mark_canceled
22
- end
23
-
24
19
  # Carrier I/O, after the status is committed: a customer who abandons
25
20
  # a return must not be left holding live prepaid postage the merchant
26
21
  # is still paying for.
27
- external_step :refund_prepaid_label
28
-
29
- run_hooks :after_cancel
30
- return_record.publish_event('return.canceled')
31
- success(return_record.reload)
22
+ cancel { external_step :refund_prepaid_label }
32
23
  end
33
24
 
34
25
  private
35
26
 
36
- def ensure_cancellable
37
- return if return_record.requested? || return_record.approved?
38
-
39
- failure(return_record, :not_cancellable)
40
- end
27
+ def post_sale_record = return_record
28
+ def cancellable_statuses = %w[requested approved]
41
29
 
42
30
  def refund_prepaid_label
43
31
  Spree::Fulfillments::StandDownProvider.refund_labels(return_record)
44
32
  end
45
-
46
- def mark_canceled
47
- memo = [return_record.memo, reason].compact_blank.join("\n")
48
- return_record.update!(status: 'canceled', canceled_at: Time.current, memo: memo.presence)
49
- end
50
33
  end
51
34
  end
52
35
  end
@@ -8,6 +8,7 @@ module Spree
8
8
  # commerce and deliberately does not live in core.
9
9
  class Create < Spree::Workflow
10
10
  include Spree::Returns::ReturnableQuantity
11
+ include Spree::Refunds::TaxCredit
11
12
 
12
13
  hooks :validate, :after_create
13
14
 
@@ -32,8 +33,11 @@ module Spree
32
33
  # is written.
33
34
  run_hooks :validate
34
35
 
36
+ # The tax is worked out with the return rather than after it, so no
37
+ # return ever offers a refund whose tax nobody calculated.
35
38
  ApplicationRecord.transaction do
36
39
  step :build_return
40
+ step :calculate_tax
37
41
  end
38
42
 
39
43
  run_hooks :after_create
@@ -93,6 +97,10 @@ module Spree
93
97
  failure(@return_record) unless @return_record.save
94
98
  end
95
99
 
100
+ def calculate_tax
101
+ with_tax_provider(return_record) { return_record.calculate_tax! }
102
+ end
103
+
96
104
  # Where goods come back to when nobody said. Goods return where they
97
105
  # shipped from, which is right until a merchant runs a dedicated
98
106
  # returns centre — so a location that has opted out of receiving them
@@ -1,13 +1,9 @@
1
1
  module Spree
2
2
  module Returns
3
- # Records what the warehouse actually received.
4
- #
5
- # Partial and damaged receipt is the normal case, not an edge case: the
6
- # customer said three items were coming, two arrived, one of those is not
7
- # resellable. Quantities and resellable flags therefore come from the
8
- # caller rather than from the request, which is precisely what a state
9
- # machine transition could not express.
3
+ # Records what the warehouse actually received (see Spree::PostSale::Receipt).
10
4
  class Receive < Spree::Workflow
5
+ include Spree::PostSale::Receipt
6
+
11
7
  hooks :validate, :before_restock, :after_receive
12
8
 
13
9
  # @param return_record [Spree::Return]
@@ -16,79 +12,12 @@ module Spree
16
12
  # @param received_by [Object, nil]
17
13
  def perform(return_record:, items: nil, received_by: nil)
18
14
  super
19
-
20
- step :ensure_receivable
21
- step :normalize_items
22
- run_hooks :validate
23
-
24
- ApplicationRecord.transaction do
25
- step :record_receipt
26
- run_hooks :before_restock
27
- step :restock_resellable_items
28
- step :mark_received
29
- end
30
-
31
- run_hooks :after_receive
32
- return_record.publish_event('return.received')
33
- success(return_record.reload)
15
+ receive
34
16
  end
35
17
 
36
18
  private
37
19
 
38
- def ensure_receivable
39
- failure(return_record, :not_approved) unless return_record.approved?
40
- end
41
-
42
- def normalize_items
43
- @normalized_items =
44
- if items.nil?
45
- return_record.return_line_items.map do |line|
46
- { return_line_item: line, quantity: line.quantity, resellable: true }
47
- end
48
- else
49
- items.map { |item| normalize_item(item) }
50
- end
51
-
52
- failure(return_record, :no_items_received) if @normalized_items.sum { |item| item[:quantity] }.zero?
53
- end
54
-
55
- def normalize_item(item)
56
- line = item[:return_line_item]
57
- quantity = item[:quantity].to_i
58
-
59
- failure(return_record, :item_not_on_return) unless line&.return_id == return_record.id
60
- failure(return_record, :invalid_quantity) if quantity.negative? || quantity > line.quantity
61
-
62
- { return_line_item: line, quantity: quantity, resellable: item.fetch(:resellable, true) }
63
- end
64
-
65
- def record_receipt
66
- @normalized_items.each do |item|
67
- item[:return_line_item].update!(
68
- received_quantity: item[:quantity],
69
- resellable: item[:resellable]
70
- )
71
- end
72
- end
73
-
74
- # Only resellable goods go back into sellable stock — a damaged return
75
- # is received but never restocked.
76
- def restock_resellable_items
77
- @normalized_items.each do |item|
78
- next unless item[:resellable]
79
- next unless item[:quantity].positive?
80
-
81
- return_record.stock_location.restock(
82
- item[:return_line_item].variant,
83
- item[:quantity],
84
- return_record
85
- )
86
- end
87
- end
88
-
89
- def mark_received
90
- return_record.update!(status: 'received', received_at: Time.current)
91
- end
20
+ def post_sale_record = return_record
92
21
  end
93
22
  end
94
23
  end
@@ -9,6 +9,7 @@ module Spree
9
9
  # a crash leaves a refunded return with no credit issued.
10
10
  class Refund < Spree::Workflow
11
11
  include Spree::Refunds::OrderPayments
12
+ include Spree::Refunds::TaxCredit
12
13
 
13
14
  hooks :validate, :before_refund, :after_refund
14
15
 
@@ -30,7 +31,13 @@ module Spree
30
31
  run_hooks :validate
31
32
  run_hooks :before_refund
32
33
 
33
- if internal_refund?
34
+ step :settle_tax unless @amount_to_refund.zero?
35
+
36
+ # Nothing is owed (a free gift sent back), so the return closes
37
+ # without a refund row, store credit or tax credit.
38
+ if @amount_to_refund.zero?
39
+ step :mark_refunded
40
+ elsif internal_refund?
34
41
  ApplicationRecord.transaction do
35
42
  step :issue_store_credit
36
43
  step :mark_refunded
@@ -40,7 +47,7 @@ module Spree
40
47
  step :mark_refunded
41
48
  end
42
49
 
43
- external_step :refund_tax
50
+ external_step :refund_tax unless @amount_to_refund.zero?
44
51
  run_hooks :after_refund
45
52
  return_record.publish_event('return.refunded')
46
53
  success(return_record.reload)
@@ -62,8 +69,20 @@ module Spree
62
69
  received = return_record.return_line_items.select { |line| line.refund_amount.positive? }
63
70
  return if received.empty?
64
71
 
65
- order = return_record.order
66
- order.tax_provider.refund(order, received, amount: @amount_to_refund, tax_date: order.completed_at)
72
+ file_tax_credit(return_record, received, @amount_to_refund)
73
+ end
74
+
75
+ # Rewrites the credit rows for what actually goes back — the units that
76
+ # arrived, and of those only the part of their worth being refunded —
77
+ # before any money moves, so the refund carries exactly the tax the rows
78
+ # record.
79
+ def settle_tax
80
+ lines = return_record.return_line_items.to_a
81
+ short = @amount_to_refund < return_record.refund_total
82
+ amounts = allocate_refund(@amount_to_refund, lines.index_with(&:refund_amount), return_record.currency) if short
83
+
84
+ with_tax_provider(return_record) { return_record.settle_tax!(amounts: amounts) }
85
+ @tax_amount = return_record.credited_tax_total
67
86
  end
68
87
 
69
88
  def internal_refund?
@@ -76,7 +95,7 @@ module Spree
76
95
  # attribute, and ActiveModel raises when an error names one that
77
96
  # does not exist on the record.
78
97
  return_record.errors.add(:base, :invalid_refund_method,
79
- message: Spree.t('errors.messages.invalid_refund_method'))
98
+ message: I18n.t('spree.errors.messages.invalid_refund_method'))
80
99
  failure(return_record)
81
100
  end
82
101
  failure(return_record, :not_received) unless return_record.received?
@@ -85,12 +104,15 @@ module Spree
85
104
  # Only what actually came back is refundable — a customer who sent two of
86
105
  # three items gets two items' worth. One figure serves as both the
87
106
  # default and the ceiling, so a caller naming an amount cannot ask for
88
- # more than a caller who names none would get.
107
+ # more than a caller who names none would get. Zero is accepted only when
108
+ # zero is owed, so a return the customer is owed money on cannot be
109
+ # closed without paying them.
89
110
  def resolve_amount
90
111
  refundable = return_record.refundable_total.to_d
91
112
  @amount_to_refund = amount ? amount.to_d : refundable
92
113
 
93
- failure(return_record, :nothing_to_refund) unless @amount_to_refund.positive?
114
+ failure(return_record, :refund_amount_negative) if @amount_to_refund.negative?
115
+ failure(return_record, :refund_amount_required) if @amount_to_refund.zero? && refundable.positive?
94
116
  failure(return_record, :refund_exceeds_balance) if @amount_to_refund > refundable
95
117
  end
96
118
 
@@ -111,7 +133,8 @@ module Spree
111
133
  order: return_record.order,
112
134
  amount: @amount_to_refund,
113
135
  record: return_record,
114
- refunder: refunder
136
+ refunder: refunder,
137
+ tax_amount: @tax_amount
115
138
  )
116
139
 
117
140
  failure(return_record, :no_refundable_payments) if @refunds.empty?
@@ -82,8 +82,7 @@ module Spree
82
82
  return unless requirement.class.requires_file?
83
83
  return if submission.file.attached?
84
84
 
85
- errors.add(:file, :blank, message: Spree.t('seller_requirements.file_required',
86
- default: 'A file is required for this requirement'))
85
+ errors.add(:file, :blank, message: I18n.t('spree.seller_requirements.file_required'))
87
86
  failure(submission, errors)
88
87
  end
89
88
 
@@ -102,9 +102,17 @@ module Spree
102
102
  gross = amounts.values.sum.to_d
103
103
  return if gross <= 0
104
104
 
105
+ taxes = refund.refunded_line_taxes
106
+
105
107
  # Nil from any line means the attribution cannot be trusted whole, so
106
108
  # the blend answers for the refund rather than part of it.
107
- earnings = amounts.map { |line_item_id, amount| line_earning(line_item_id, amount.to_d) }
109
+ earnings = amounts.map do |line_item_id, amount|
110
+ if taxes.key?(line_item_id)
111
+ line_goods_earning(line_item_id, amount.to_d, taxes[line_item_id].to_d)
112
+ else
113
+ line_earning(line_item_id, amount.to_d)
114
+ end
115
+ end
108
116
  return if earnings.any?(&:nil?)
109
117
 
110
118
  quantize(earnings.sum * (refunded.to_d.abs / gross))
@@ -124,6 +132,9 @@ module Spree
124
132
  # The commission comes from the row written against that line at
125
133
  # placement, not from today's rate: rates change, a clamped fee is not the
126
134
  # rate times the base, and a fixed rate is charged per unit.
135
+ #
136
+ # This is the reading for a refund that cannot say how much tax it gave
137
+ # back — a return or claim opened before they carried tax.
127
138
  def line_earning(line_item_id, amount)
128
139
  line_item = order_line_items[line_item_id]
129
140
  paid = line_item&.amount.to_d
@@ -141,6 +152,22 @@ module Spree
141
152
  earned
142
153
  end
143
154
 
155
+ # The same for a refund that knows the tax it gave back on the line. The
156
+ # goods in it are measured against the line's worth before tax, after
157
+ # discounts, and carry their share of the commission; the tax goes back
158
+ # only when the seller collected it — when the marketplace remits it, the
159
+ # seller never had it to return.
160
+ def line_goods_earning(line_item_id, amount, tax)
161
+ line_item = order_line_items[line_item_id]
162
+ worth = line_item ? [line_item.discounted_amount.to_d, 0].max - line_item.included_tax_total.to_d : 0.to_d
163
+ return unless worth.positive?
164
+
165
+ goods = amount - tax
166
+ earned = goods - (commission_totals[line_item_id].to_d * goods / worth)
167
+ earned += tax unless order.seller&.tax_remittance == 'platform'
168
+ earned
169
+ end
170
+
144
171
  def order_line_items
145
172
  @order_line_items ||= order.line_items.index_by(&:id)
146
173
  end
@@ -51,15 +51,15 @@ module Spree
51
51
  end
52
52
 
53
53
  def ensure_purchasable
54
- failure(owner, Spree.t('shipping_labels.errors.provider_has_no_labels')) unless provider.class.generates_labels?
55
- failure(owner, Spree.t('shipping_labels.errors.already_purchased')) if owner.shipping_labels.active.exists?
54
+ failure(owner, I18n.t('spree.shipping_labels.errors.provider_has_no_labels')) unless provider.class.generates_labels?
55
+ failure(owner, I18n.t('spree.shipping_labels.errors.already_purchased')) if owner.shipping_labels.active.exists?
56
56
 
57
57
  case owner
58
58
  when Spree::Fulfillment
59
- failure(owner, Spree.t('fulfillments.errors.cannot_purchase_label')) unless owner.unfulfilled?
60
- failure(owner, Spree.t('fulfillments.errors.order_draft')) if owner.order&.draft?
59
+ failure(owner, I18n.t('spree.fulfillments.errors.cannot_purchase_label')) unless owner.unfulfilled?
60
+ failure(owner, I18n.t('spree.fulfillments.errors.order_draft')) if owner.order&.draft?
61
61
  when Spree::Return
62
- failure(owner, Spree.t('shipping_labels.errors.return_closed')) if owner.received? || owner.refunded? || owner.canceled?
62
+ failure(owner, I18n.t('spree.shipping_labels.errors.return_closed')) if owner.received? || owner.refunded? || owner.canceled?
63
63
  end
64
64
  end
65
65
 
@@ -71,7 +71,7 @@ module Spree
71
71
  # re-read inside it.
72
72
  def claim_purchase
73
73
  owner.with_lock do
74
- failure(owner, Spree.t('shipping_labels.errors.already_purchased')) if owner.shipping_labels.active.exists?
74
+ failure(owner, I18n.t('spree.shipping_labels.errors.already_purchased')) if owner.shipping_labels.active.exists?
75
75
 
76
76
  @shipping_label = owner.shipping_labels.create!(
77
77
  store: owner.store,
@@ -81,7 +81,7 @@ module Spree
81
81
  )
82
82
  end
83
83
  rescue ActiveRecord::RecordNotUnique
84
- failure(owner, Spree.t('shipping_labels.errors.already_purchased'))
84
+ failure(owner, I18n.t('spree.shipping_labels.errors.already_purchased'))
85
85
  end
86
86
 
87
87
  # Undoes the claim when the carrier refuses, so a failed purchase does
@@ -105,7 +105,7 @@ module Spree
105
105
 
106
106
  return if @purchase.is_a?(Spree::LabelPurchase) && @purchase.valid?
107
107
 
108
- failure(owner, Spree.t('fulfillments.errors.label_purchase_failed'))
108
+ failure(owner, I18n.t('spree.fulfillments.errors.label_purchase_failed'))
109
109
  rescue Spree::Core::LabelPurchaseRefused => e
110
110
  failure(owner, e.message)
111
111
  end