spree_core 6.0.0.beta4 → 6.0.0.rc1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (495) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +2 -2
  3. data/app/helpers/spree/images_helper.rb +0 -57
  4. data/app/jobs/spree/base_job.rb +21 -0
  5. data/app/jobs/spree/carts/reap_expired_job.rb +2 -2
  6. data/app/jobs/spree/collections/regenerate_time_based_job.rb +6 -3
  7. data/app/jobs/spree/orders/finalize_stale_drafts_job.rb +8 -5
  8. data/app/jobs/spree/payments/cancel_unused_sessions_job.rb +61 -0
  9. data/app/jobs/spree/seller_payouts/sweep_seller_job.rb +2 -0
  10. data/app/mailers/spree/admin_user_mailer.rb +4 -19
  11. data/app/mailers/spree/base_mailer.rb +96 -13
  12. data/app/mailers/spree/email_template_mailer.rb +27 -0
  13. data/app/mailers/spree/export_mailer.rb +3 -4
  14. data/app/mailers/spree/import_mailer.rb +3 -4
  15. data/app/mailers/spree/invitation_mailer.rb +24 -7
  16. data/app/mailers/spree/seller_user_mailer.rb +4 -16
  17. data/app/mailers/spree/webhook_mailer.rb +2 -2
  18. data/app/models/concerns/spree/acted_by.rb +1 -1
  19. data/app/models/concerns/spree/admin_user_methods.rb +13 -1
  20. data/app/models/concerns/spree/api_typed.rb +91 -0
  21. data/app/models/concerns/spree/calculator/tiers.rb +76 -0
  22. data/app/models/concerns/spree/customer_methods.rb +15 -14
  23. data/app/models/concerns/spree/email_template_identity.rb +52 -0
  24. data/app/models/concerns/spree/parameterizable_name.rb +2 -0
  25. data/app/models/concerns/spree/payment_session_transitions.rb +8 -2
  26. data/app/models/concerns/spree/post_sale/taxation.rb +54 -0
  27. data/app/models/concerns/spree/post_sale/taxed_line.rb +51 -0
  28. data/app/models/concerns/spree/preference_schema.rb +4 -12
  29. data/app/models/concerns/spree/product_scopes.rb +11 -1
  30. data/app/models/concerns/spree/provider_listing.rb +41 -0
  31. data/app/models/concerns/spree/publishable.rb +82 -14
  32. data/app/models/concerns/spree/purchase/addresses.rb +23 -10
  33. data/app/models/concerns/spree/purchase/currency.rb +1 -1
  34. data/app/models/concerns/spree/purchase/deprecated_aliases.rb +87 -0
  35. data/app/models/concerns/spree/purchase/gift_cards.rb +10 -4
  36. data/app/models/concerns/spree/purchase/locale.rb +1 -1
  37. data/app/models/concerns/spree/purchase/payment_processing.rb +7 -3
  38. data/app/models/concerns/spree/purchase/purchase_order.rb +2 -2
  39. data/app/models/concerns/spree/purchase/totals.rb +49 -1
  40. data/app/models/concerns/spree/receivable.rb +1 -1
  41. data/app/models/concerns/spree/searchable.rb +0 -4
  42. data/app/models/concerns/spree/secret_preferences.rb +59 -0
  43. data/app/models/concerns/spree/single_store_resource.rb +1 -1
  44. data/app/models/concerns/spree/stores/email_branding.rb +31 -0
  45. data/app/models/concerns/spree/type_labels.rb +38 -0
  46. data/app/models/concerns/spree/typed_adjustment_line.rb +1 -1
  47. data/app/models/concerns/spree/typed_associations.rb +6 -0
  48. data/app/models/spree/address.rb +11 -1
  49. data/app/models/spree/adjusters/promotion.rb +1 -15
  50. data/app/models/spree/allowed_origin.rb +0 -1
  51. data/app/models/spree/api_key.rb +5 -6
  52. data/app/models/spree/authentication/strategies/oidc_strategy.rb +5 -5
  53. data/app/models/spree/base.rb +70 -22
  54. data/app/models/spree/calculator/flat_percent_item_total.rb +1 -3
  55. data/app/models/spree/calculator/flat_rate.rb +1 -3
  56. data/app/models/spree/calculator/flexi_rate.rb +1 -3
  57. data/app/models/spree/calculator/percent_on_line_item.rb +1 -1
  58. data/app/models/spree/calculator/price_sack.rb +1 -3
  59. data/app/models/spree/calculator/shipping/digital_delivery.rb +1 -3
  60. data/app/models/spree/calculator/shipping/flat_percent_item_total.rb +1 -3
  61. data/app/models/spree/calculator/shipping/flat_rate.rb +1 -3
  62. data/app/models/spree/calculator/shipping/flexi_rate.rb +1 -3
  63. data/app/models/spree/calculator/shipping/per_item.rb +1 -3
  64. data/app/models/spree/calculator/shipping/price_sack.rb +1 -3
  65. data/app/models/spree/calculator/tiered_flat_rate.rb +6 -23
  66. data/app/models/spree/calculator/tiered_percent.rb +7 -25
  67. data/app/models/spree/cart.rb +78 -18
  68. data/app/models/spree/channel/gating.rb +1 -1
  69. data/app/models/spree/channel.rb +4 -4
  70. data/app/models/spree/claim.rb +21 -1
  71. data/app/models/spree/claim_line_item.rb +41 -6
  72. data/app/models/spree/collection.rb +0 -1
  73. data/app/models/spree/collection_rule.rb +5 -21
  74. data/app/models/spree/commission_line.rb +1 -1
  75. data/app/models/spree/commission_rate.rb +2 -3
  76. data/app/models/spree/commission_rate_value.rb +1 -1
  77. data/app/models/spree/commission_rule.rb +4 -11
  78. data/app/models/spree/commission_rules/product_rule.rb +1 -1
  79. data/app/models/spree/company_invitation.rb +1 -0
  80. data/app/models/spree/consent_record.rb +0 -1
  81. data/app/models/spree/credit_card.rb +1 -1
  82. data/app/models/spree/current.rb +23 -0
  83. data/app/models/spree/custom_field.rb +2 -2
  84. data/app/models/spree/custom_field_definition.rb +9 -23
  85. data/app/models/spree/custom_fields/boolean.rb +1 -1
  86. data/app/models/spree/custom_fields/json.rb +1 -1
  87. data/app/models/spree/data_feed/google.rb +0 -2
  88. data/app/models/spree/data_request.rb +1 -1
  89. data/app/models/spree/delivery_method.rb +30 -13
  90. data/app/models/spree/delivery_method_rule.rb +4 -11
  91. data/app/models/spree/delivery_rate.rb +4 -5
  92. data/app/models/spree/delivery_rate_provider/base.rb +1 -0
  93. data/app/models/spree/delivery_zone.rb +0 -1
  94. data/app/models/spree/delivery_zone_member.rb +4 -4
  95. data/app/models/spree/digital_asset.rb +7 -0
  96. data/app/models/spree/digital_asset_provider/base.rb +2 -0
  97. data/app/models/spree/digital_link.rb +2 -5
  98. data/app/models/spree/discount.rb +1 -1
  99. data/app/models/spree/email_template.rb +25 -0
  100. data/app/models/spree/email_template_draft.rb +13 -0
  101. data/app/models/spree/email_template_revision.rb +17 -0
  102. data/app/models/spree/email_templates/entry.rb +118 -0
  103. data/app/models/spree/email_templates/sample_record.rb +16 -0
  104. data/app/models/spree/emails/branding.rb +80 -0
  105. data/app/models/spree/exchange.rb +15 -1
  106. data/app/models/spree/exchange_line_item.rb +121 -5
  107. data/app/models/spree/export.rb +0 -1
  108. data/app/models/spree/exports/products.rb +10 -1
  109. data/app/models/spree/exports/report.rb +1 -1
  110. data/app/models/spree/fee.rb +4 -2
  111. data/app/models/spree/fulfillment.rb +53 -27
  112. data/app/models/spree/fulfillment_changer.rb +2 -2
  113. data/app/models/spree/fulfillment_item.rb +0 -4
  114. data/app/models/spree/fulfillment_provider/base.rb +1 -0
  115. data/app/models/spree/gateway/bogus.rb +4 -0
  116. data/app/models/spree/gateway/custom_payment_source_method.rb +5 -0
  117. data/app/models/spree/gateway.rb +5 -1
  118. data/app/models/spree/gift_card.rb +12 -1
  119. data/app/models/spree/gift_card_batch.rb +0 -1
  120. data/app/models/spree/import.rb +2 -7
  121. data/app/models/spree/import_mapping.rb +1 -2
  122. data/app/models/spree/import_row.rb +2 -2
  123. data/app/models/spree/import_schemas/products.rb +1 -0
  124. data/app/models/spree/integration.rb +8 -18
  125. data/app/models/spree/invitation.rb +5 -5
  126. data/app/models/spree/line_item.rb +13 -2
  127. data/app/models/spree/locale.rb +11 -8
  128. data/app/models/spree/market.rb +1 -0
  129. data/app/models/spree/newsletter_subscriber.rb +3 -0
  130. data/app/models/spree/order.rb +56 -138
  131. data/app/models/spree/order_group.rb +11 -2
  132. data/app/models/spree/order_inventory.rb +10 -5
  133. data/app/models/spree/order_promotion.rb +1 -1
  134. data/app/models/spree/order_routing/has_strategy_preference.rb +1 -1
  135. data/app/models/spree/order_routing/strategy/base.rb +4 -2
  136. data/app/models/spree/order_routing/strategy/rules.rb +1 -1
  137. data/app/models/spree/order_routing_rule.rb +6 -22
  138. data/app/models/spree/package_type.rb +1 -2
  139. data/app/models/spree/payment/gateway_options.rb +14 -52
  140. data/app/models/spree/payment/processing.rb +2 -4
  141. data/app/models/spree/payment.rb +20 -5
  142. data/app/models/spree/payment_method/session_instrumentation.rb +1 -0
  143. data/app/models/spree/payment_method/store_credit.rb +4 -4
  144. data/app/models/spree/payment_method.rb +36 -4
  145. data/app/models/spree/payment_session.rb +5 -1
  146. data/app/models/spree/payment_setup_session.rb +1 -0
  147. data/app/models/spree/payout_provider/base.rb +3 -1
  148. data/app/models/spree/pickup_point_provider/base.rb +2 -0
  149. data/app/models/spree/price_list.rb +1 -2
  150. data/app/models/spree/price_rules/channel_rule.rb +1 -1
  151. data/app/models/spree/price_rules/customer_group_rule.rb +1 -1
  152. data/app/models/spree/product.rb +31 -7
  153. data/app/models/spree/product_submission.rb +0 -12
  154. data/app/models/spree/product_type.rb +0 -1
  155. data/app/models/spree/promotion/actions/create_line_items.rb +27 -17
  156. data/app/models/spree/promotion/rules/category.rb +0 -4
  157. data/app/models/spree/promotion/rules/channel.rb +0 -4
  158. data/app/models/spree/promotion/rules/country.rb +0 -4
  159. data/app/models/spree/promotion/rules/currency.rb +0 -4
  160. data/app/models/spree/promotion/rules/customer_group.rb +0 -4
  161. data/app/models/spree/promotion/rules/first_order.rb +0 -4
  162. data/app/models/spree/promotion/rules/item_total.rb +4 -8
  163. data/app/models/spree/promotion/rules/market.rb +0 -4
  164. data/app/models/spree/promotion/rules/one_use_per_user.rb +0 -4
  165. data/app/models/spree/promotion/rules/option_value.rb +0 -3
  166. data/app/models/spree/promotion/rules/product.rb +0 -4
  167. data/app/models/spree/promotion/rules/user.rb +0 -4
  168. data/app/models/spree/promotion/rules/user_logged_in.rb +0 -4
  169. data/app/models/spree/promotion.rb +35 -6
  170. data/app/models/spree/promotion_action.rb +7 -14
  171. data/app/models/spree/promotion_action_line_item.rb +1 -1
  172. data/app/models/spree/promotion_handler/coupon.rb +59 -27
  173. data/app/models/spree/promotion_rule.rb +7 -14
  174. data/app/models/spree/purchase_order.rb +1 -0
  175. data/app/models/spree/quantity_rule.rb +1 -1
  176. data/app/models/spree/refund.rb +28 -3
  177. data/app/models/spree/return.rb +30 -2
  178. data/app/models/spree/return_line_item.rb +65 -14
  179. data/app/models/spree/role.rb +7 -7
  180. data/app/models/spree/saved_report.rb +1 -1
  181. data/app/models/spree/seller.rb +2 -1
  182. data/app/models/spree/seller_payout.rb +7 -0
  183. data/app/models/spree/seller_requirement.rb +4 -12
  184. data/app/models/spree/seller_requirement_custom_field.rb +1 -2
  185. data/app/models/spree/seller_requirement_submission.rb +2 -2
  186. data/app/models/spree/seller_requirements/accept_terms.rb +2 -3
  187. data/app/models/spree/seller_transfer.rb +5 -0
  188. data/app/models/spree/shipping_label.rb +1 -0
  189. data/app/models/spree/stock/availability_validator.rb +2 -2
  190. data/app/models/spree/stock/inventory_unit_builder.rb +1 -1
  191. data/app/models/spree/stock/packer.rb +2 -2
  192. data/app/models/spree/stock/splitter/weight.rb +1 -1
  193. data/app/models/spree/stock_level/custom_events.rb +6 -0
  194. data/app/models/spree/stock_level.rb +1 -1
  195. data/app/models/spree/stock_movement.rb +1 -1
  196. data/app/models/spree/stock_receipt_item.rb +1 -1
  197. data/app/models/spree/stock_reservation.rb +2 -2
  198. data/app/models/spree/stock_transfer.rb +4 -2
  199. data/app/models/spree/store.rb +33 -7
  200. data/app/models/spree/store_credit.rb +8 -9
  201. data/app/models/spree/store_credit_event.rb +5 -5
  202. data/app/models/spree/tax_exemption_certificate.rb +2 -0
  203. data/app/models/spree/tax_identifier.rb +3 -1
  204. data/app/models/spree/tax_line.rb +53 -3
  205. data/app/models/spree/tax_provider/base.rb +71 -14
  206. data/app/models/spree/tax_provider/internal.rb +59 -33
  207. data/app/models/spree/tax_provider/recorded_share.rb +114 -0
  208. data/app/models/spree/tax_rate.rb +1 -2
  209. data/app/models/spree/taxonomy.rb +0 -1
  210. data/app/models/spree/variant.rb +4 -3
  211. data/app/models/spree/variant_media.rb +1 -1
  212. data/app/models/spree/webhook_endpoint.rb +10 -13
  213. data/app/models/spree/wishlist.rb +1 -6
  214. data/app/models/spree/wishlist_item/custom_events.rb +6 -0
  215. data/app/presenters/spree/csv/customer_presenter.rb +1 -1
  216. data/app/presenters/spree/csv/newsletter_subscriber_presenter.rb +1 -1
  217. data/app/presenters/spree/csv/order_line_item_presenter.rb +1 -1
  218. data/app/presenters/spree/csv/product_variant_presenter.rb +42 -12
  219. data/app/serializers/spree/emails/base_serializer.rb +14 -0
  220. data/app/serializers/spree/emails/export_serializer.rb +13 -0
  221. data/app/serializers/spree/emails/import_serializer.rb +19 -0
  222. data/app/serializers/spree/emails/invitation_serializer.rb +28 -0
  223. data/app/serializers/spree/emails/store_serializer.rb +31 -0
  224. data/app/serializers/spree/emails/user_serializer.rb +12 -0
  225. data/app/serializers/spree/emails/webhook_endpoint_serializer.rb +16 -0
  226. data/app/services/spree/carts/create.rb +8 -2
  227. data/app/services/spree/carts/destroy.rb +2 -2
  228. data/app/services/spree/carts/remove_out_of_stock_items.rb +2 -2
  229. data/app/services/spree/carts/split_by_seller.rb +1 -1
  230. data/app/services/spree/carts/update.rb +19 -5
  231. data/app/services/spree/catalogs/set_product_terms.rb +1 -1
  232. data/app/services/spree/categories/add_products.rb +13 -31
  233. data/app/services/spree/categories/remove_products.rb +12 -51
  234. data/app/services/spree/checkout/advance.rb +1 -4
  235. data/app/services/spree/collections/add_products.rb +10 -23
  236. data/app/services/spree/collections/remove_products.rb +7 -43
  237. data/app/services/spree/coupon_codes/bulk_generate.rb +8 -14
  238. data/app/services/spree/data_requests/create.rb +1 -1
  239. data/app/services/spree/deliveries/destroy.rb +2 -2
  240. data/app/services/spree/email_templates/check.rb +58 -0
  241. data/app/services/spree/email_templates/no_sample_record.rb +7 -0
  242. data/app/services/spree/email_templates/preview.rb +99 -0
  243. data/app/services/spree/emails/file_system.rb +23 -0
  244. data/app/services/spree/emails/filters.rb +84 -0
  245. data/app/services/spree/emails/liquid_context.rb +48 -0
  246. data/app/services/spree/emails/rendered_email.rb +14 -0
  247. data/app/services/spree/emails/renderer.rb +125 -0
  248. data/app/services/spree/emails/template.rb +51 -0
  249. data/app/services/spree/emails/template_data.rb +23 -0
  250. data/app/services/spree/emails/template_resolver.rb +109 -0
  251. data/app/services/spree/emails/text_converter.rb +72 -0
  252. data/app/services/spree/emails/translation_inliner.rb +134 -0
  253. data/app/services/spree/imports/row_processors/price_list_price.rb +10 -10
  254. data/app/services/spree/imports/row_processors/product_variant.rb +65 -34
  255. data/app/services/spree/imports/row_processors/purchase_order.rb +13 -13
  256. data/app/services/spree/locales.rb +4 -5
  257. data/app/services/spree/newsletter/subscribe.rb +8 -12
  258. data/app/services/spree/orders/build_fulfillments.rb +3 -2
  259. data/app/services/spree/orders/create.rb +2 -1
  260. data/app/services/spree/orders/discounts/create.rb +7 -4
  261. data/app/services/spree/orders/discounts/destroy.rb +1 -0
  262. data/app/services/spree/orders/discounts/update.rb +1 -0
  263. data/app/services/spree/orders/fees/create.rb +1 -0
  264. data/app/services/spree/orders/fees/destroy.rb +1 -0
  265. data/app/services/spree/orders/fees/update.rb +1 -0
  266. data/app/services/spree/orders/remove_line_item.rb +13 -3
  267. data/app/services/spree/orders/update.rb +10 -6
  268. data/app/services/spree/orders/update_item.rb +6 -3
  269. data/app/services/spree/orders/update_statuses.rb +3 -1
  270. data/app/services/spree/product_memberships.rb +61 -0
  271. data/app/services/spree/returns/eligibility_validator.rb +1 -1
  272. data/app/services/spree/search_provider/custom_field_schema.rb +2 -2
  273. data/app/services/spree/seeds/commission_rates.rb +1 -1
  274. data/app/services/spree/seeds/digital_delivery.rb +1 -1
  275. data/app/services/spree/seeds/payment_methods.rb +2 -2
  276. data/app/services/spree/seeds/saved_reports.rb +3 -3
  277. data/app/services/spree/sellers/requirements.rb +1 -2
  278. data/app/services/spree/shipping_labels/confirm_refund.rb +1 -1
  279. data/app/services/spree/shipping_labels/store_file.rb +3 -3
  280. data/app/services/spree/stock_levels/correct.rb +3 -3
  281. data/app/services/spree/stock_reservations/reserve.rb +2 -3
  282. data/app/services/spree/store_credits/apply.rb +15 -13
  283. data/app/services/spree/stores/provision_defaults.rb +15 -14
  284. data/app/subscribers/spree/seller_onboarding_subscriber.rb +1 -1
  285. data/app/views/layouts/spree/base_mailer.html.erb +2 -43
  286. data/app/views/layouts/spree/base_mailer.liquid +79 -0
  287. data/app/views/spree/admin_user_mailer/password_reset_email.liquid +14 -0
  288. data/app/views/spree/export_mailer/export_done.liquid +12 -0
  289. data/app/views/spree/import_mailer/import_done.liquid +15 -0
  290. data/app/views/spree/invitation_mailer/invitation_accepted.liquid +9 -0
  291. data/app/views/spree/invitation_mailer/invitation_email.liquid +13 -0
  292. data/app/views/spree/seller_user_mailer/password_reset_email.liquid +14 -0
  293. data/app/views/spree/shared/_mailer_button.html.erb +4 -5
  294. data/app/views/spree/shared/_mailer_hero.html.erb +6 -5
  295. data/app/views/spree/shared/_summary_row.liquid +8 -0
  296. data/app/views/spree/webhook_mailer/endpoint_disabled.liquid +21 -0
  297. data/app/workflows/spree/carts/add_item.rb +4 -4
  298. data/app/workflows/spree/carts/complete.rb +13 -3
  299. data/app/workflows/spree/carts/merge.rb +1 -1
  300. data/app/workflows/spree/carts/recalculate_totals.rb +5 -0
  301. data/app/workflows/spree/carts/upsert_items.rb +18 -9
  302. data/app/workflows/spree/claims/approve.rb +5 -22
  303. data/app/workflows/spree/claims/cancel.rb +5 -21
  304. data/app/workflows/spree/claims/create.rb +15 -0
  305. data/app/workflows/spree/claims/deny.rb +7 -0
  306. data/app/workflows/spree/claims/resolve.rb +34 -31
  307. data/app/workflows/spree/customers/anonymize.rb +3 -4
  308. data/app/workflows/spree/customers/create.rb +1 -1
  309. data/app/workflows/spree/data_requests/fulfill.rb +2 -2
  310. data/app/workflows/spree/deliveries/update_tracking.rb +1 -1
  311. data/app/workflows/spree/email_templates/discard_draft.rb +20 -0
  312. data/app/workflows/spree/email_templates/publish.rb +70 -0
  313. data/app/workflows/spree/email_templates/restore_revision.rb +33 -0
  314. data/app/workflows/spree/email_templates/revert.rb +29 -0
  315. data/app/workflows/spree/email_templates/save_draft.rb +85 -0
  316. data/app/workflows/spree/exchanges/approve.rb +5 -22
  317. data/app/workflows/spree/exchanges/cancel.rb +5 -21
  318. data/app/workflows/spree/exchanges/create.rb +6 -0
  319. data/app/workflows/spree/exchanges/fulfill.rb +75 -51
  320. data/app/workflows/spree/exchanges/receive.rb +6 -65
  321. data/app/workflows/spree/fulfillments/cancel.rb +1 -1
  322. data/app/workflows/spree/fulfillments/create.rb +51 -47
  323. data/app/workflows/spree/fulfillments/fulfill.rb +23 -27
  324. data/app/workflows/spree/fulfillments/mark_delivered.rb +1 -1
  325. data/app/workflows/spree/fulfillments/replacements.rb +59 -0
  326. data/app/workflows/spree/gift_cards/apply.rb +2 -2
  327. data/app/workflows/spree/orders/add_item.rb +5 -1
  328. data/app/workflows/spree/orders/cancel.rb +3 -3
  329. data/app/workflows/spree/orders/complete.rb +16 -8
  330. data/app/workflows/spree/payments/process.rb +2 -2
  331. data/app/workflows/spree/post_sale/approval.rb +35 -0
  332. data/app/workflows/spree/post_sale/cancellation.rb +44 -0
  333. data/app/workflows/spree/post_sale/receipt.rb +92 -0
  334. data/app/workflows/spree/products/nested_attributes.rb +1 -1
  335. data/app/workflows/spree/purchase_orders/cancel.rb +1 -1
  336. data/app/workflows/spree/purchase_orders/close.rb +3 -31
  337. data/app/workflows/spree/purchase_orders/mark_draft.rb +2 -2
  338. data/app/workflows/spree/purchase_orders/mark_ordered.rb +2 -2
  339. data/app/workflows/spree/purchase_orders/receive.rb +1 -1
  340. data/app/workflows/spree/purchase_orders/update.rb +5 -34
  341. data/app/workflows/spree/receivables/closing_short.rb +46 -0
  342. data/app/workflows/spree/receivables/draft_editing.rb +48 -0
  343. data/app/workflows/spree/refunds/create.rb +5 -2
  344. data/app/workflows/spree/refunds/order_payments.rb +14 -2
  345. data/app/workflows/spree/refunds/tax_credit.rb +67 -0
  346. data/app/workflows/spree/returns/approve.rb +5 -22
  347. data/app/workflows/spree/returns/cancel.rb +5 -22
  348. data/app/workflows/spree/returns/create.rb +8 -0
  349. data/app/workflows/spree/returns/receive.rb +5 -76
  350. data/app/workflows/spree/returns/refund.rb +31 -8
  351. data/app/workflows/spree/seller_requirement_submissions/create.rb +1 -2
  352. data/app/workflows/spree/seller_transfers/reverse.rb +28 -1
  353. data/app/workflows/spree/shipping_labels/purchase.rb +8 -8
  354. data/app/workflows/spree/shipping_labels/record.rb +5 -5
  355. data/app/workflows/spree/shipping_labels/refund.rb +2 -2
  356. data/app/workflows/spree/stock_receipts/recording.rb +4 -4
  357. data/app/workflows/spree/stock_transfers/cancel.rb +2 -2
  358. data/app/workflows/spree/stock_transfers/close.rb +3 -30
  359. data/app/workflows/spree/stock_transfers/mark_draft.rb +1 -1
  360. data/app/workflows/spree/stock_transfers/mark_in_transit.rb +3 -3
  361. data/app/workflows/spree/stock_transfers/mark_ready.rb +2 -2
  362. data/app/workflows/spree/stock_transfers/receive.rb +1 -1
  363. data/app/workflows/spree/stock_transfers/update.rb +5 -33
  364. data/config/i18n-tasks.yml +32 -18
  365. data/config/initializers/alba.rb +7 -0
  366. data/config/language_names.csv +264 -0
  367. data/config/locales/ar.yml +811 -0
  368. data/config/locales/be.yml +855 -0
  369. data/config/locales/bg.yml +723 -0
  370. data/config/locales/ca.yml +689 -0
  371. data/config/locales/cs.yml +855 -0
  372. data/config/locales/da.yml +771 -0
  373. data/config/locales/de-CH.yml +248 -0
  374. data/config/locales/de.yml +835 -0
  375. data/config/locales/el.yml +769 -0
  376. data/config/locales/en.yml +37 -503
  377. data/config/locales/es-CL.yml +301 -0
  378. data/config/locales/es-EC.yml +186 -0
  379. data/config/locales/es-MX.yml +334 -0
  380. data/config/locales/es.yml +831 -0
  381. data/config/locales/et.yml +644 -0
  382. data/config/locales/fa.yml +709 -0
  383. data/config/locales/fi.yml +771 -0
  384. data/config/locales/fr.yml +830 -0
  385. data/config/locales/hu.yml +751 -0
  386. data/config/locales/id.yml +756 -0
  387. data/config/locales/it.yml +833 -0
  388. data/config/locales/ja.yml +726 -0
  389. data/config/locales/km.yml +759 -0
  390. data/config/locales/ko.yml +575 -0
  391. data/config/locales/ku.yml +753 -0
  392. data/config/locales/lt.yml +821 -0
  393. data/config/locales/lv.yml +685 -0
  394. data/config/locales/nb.yml +824 -0
  395. data/config/locales/ne.yml +823 -0
  396. data/config/locales/nl.yml +826 -0
  397. data/config/locales/pl.yml +835 -0
  398. data/config/locales/pt-BR.yml +547 -0
  399. data/config/locales/pt.yml +636 -0
  400. data/config/locales/ro.yml +750 -0
  401. data/config/locales/ru.yml +834 -0
  402. data/config/locales/sk.yml +680 -0
  403. data/config/locales/sl-SI.yml +600 -0
  404. data/config/locales/sv.yml +827 -0
  405. data/config/locales/th.yml +745 -0
  406. data/config/locales/tr.yml +777 -0
  407. data/config/locales/uk.yml +836 -0
  408. data/config/locales/vi.yml +768 -0
  409. data/config/locales/zh-CN.yml +786 -0
  410. data/config/locales/zh-TW.yml +834 -0
  411. data/db/migrate/20260925000001_add_replacement_to_spree_fulfillment_items.rb +5 -0
  412. data/db/migrate/20261001000001_add_post_sale_tax.rb +30 -0
  413. data/db/migrate/20261001000001_convert_preferences_to_json.rb +166 -0
  414. data/db/migrate/20261001000001_create_spree_email_templates.rb +52 -0
  415. data/lib/generators/spree/dummy/dummy_generator.rb +0 -9
  416. data/lib/generators/spree/dummy/templates/rails/test.rb +7 -0
  417. data/lib/spree/checkout/default_requirements.rb +37 -13
  418. data/lib/spree/core/configuration.rb +1 -1
  419. data/lib/spree/core/controller_helpers/store.rb +1 -1
  420. data/lib/spree/core/dependencies.rb +5 -4
  421. data/lib/spree/core/emails/escaped_output.rb +60 -0
  422. data/lib/spree/core/engine.rb +48 -167
  423. data/lib/spree/core/permissions/default_catalog.rb +5 -0
  424. data/lib/spree/core/preferences/json_conversion.rb +182 -0
  425. data/lib/spree/core/preferences/masking.rb +4 -1
  426. data/lib/spree/core/preferences/preferable.rb +137 -83
  427. data/lib/spree/core/preferences/preferable_class_methods.rb +81 -146
  428. data/lib/spree/core/previews/import_preview.rb +10 -0
  429. data/lib/spree/core/tax/provider_error.rb +37 -0
  430. data/lib/spree/core/version.rb +1 -1
  431. data/lib/spree/core.rb +85 -370
  432. data/lib/spree/emails/editable_templates.rb +82 -0
  433. data/lib/spree/events/catalog.rb +189 -0
  434. data/lib/spree/events.rb +21 -1
  435. data/lib/spree/hooks.rb +0 -17
  436. data/lib/spree/i18n.rb +32 -15
  437. data/lib/spree/reporting/adapters/live.rb +6 -6
  438. data/lib/spree/reporting/default_vocabulary.rb +22 -10
  439. data/lib/spree/reporting/hydration.rb +1 -1
  440. data/lib/spree/reporting/query.rb +8 -0
  441. data/lib/spree/reporting/schema.rb +10 -10
  442. data/lib/spree/testing_support/editable_email_templates.rb +61 -0
  443. data/lib/spree/testing_support/emails.rb +22 -0
  444. data/lib/spree/testing_support/factories/cart_factory.rb +1 -1
  445. data/lib/spree/testing_support/factories/email_template_factory.rb +28 -0
  446. data/lib/spree/testing_support/factories/order_factory.rb +2 -2
  447. data/lib/spree/testing_support/factories/refund_factory.rb +1 -1
  448. data/lib/spree/testing_support/factories/stock_factory.rb +1 -1
  449. data/lib/spree/testing_support/factories/variant_factory.rb +1 -1
  450. data/lib/spree/testing_support/i18n.rb +3 -94
  451. data/lib/spree/testing_support/lifecycle_events.rb +1 -1
  452. data/lib/spree/testing_support/order_walkthrough.rb +1 -1
  453. data/lib/spree/testing_support/preferences.rb +0 -5
  454. data/lib/spree/testing_support/store.rb +9 -1
  455. data/lib/spree/upgrades/5_6_to_6_0/manifest.yml +45 -0
  456. data/lib/tasks/categories_and_collections.rake +3 -3
  457. data/lib/tasks/core.rake +6 -4
  458. data/lib/tasks/media.rake +3 -3
  459. data/lib/tasks/package_types.rake +1 -1
  460. data/lib/tasks/preferences_json.rake +101 -0
  461. data/spec/fixtures/mailer_views/custom_views_probe_mailer/welcome_email.html.erb +4 -0
  462. metadata +188 -44
  463. data/app/models/acts_as_taggable_on/tag_decorator.rb +0 -10
  464. data/app/models/concerns/spree/has_image_alt_text.rb +0 -18
  465. data/app/models/spree/base_analytics_event_handler.rb +0 -54
  466. data/app/models/spree/invitations/store.rb +0 -6
  467. data/app/models/spree/preference.rb +0 -14
  468. data/app/models/spree/promotion_handler/page.rb +0 -25
  469. data/app/models/spree/promotion_handler/promotion_duplicator.rb +0 -60
  470. data/app/models/spree/stock/differentiator.rb +0 -45
  471. data/app/presenters/spree/filters/options_presenter.rb +0 -47
  472. data/app/services/spree/carts/empty.rb +0 -42
  473. data/app/services/spree/country_to_timezone.rb +0 -273
  474. data/app/services/spree/payments/create.rb +0 -92
  475. data/app/validators/spree/url_validator.rb +0 -23
  476. data/app/views/active_storage/blobs/_blob.html.erb +0 -14
  477. data/app/views/spree/admin_user_mailer/password_reset_email.html.erb +0 -8
  478. data/app/views/spree/export_mailer/export_done.html.erb +0 -7
  479. data/app/views/spree/import_mailer/import_done.html.erb +0 -12
  480. data/app/views/spree/invitation_mailer/invitation_accepted.html.erb +0 -4
  481. data/app/views/spree/invitation_mailer/invitation_email.html.erb +0 -12
  482. data/app/views/spree/seller_user_mailer/password_reset_email.html.erb +0 -8
  483. data/app/views/spree/shared/_base_mailer_footer.html.erb +0 -19
  484. data/app/views/spree/shared/_base_mailer_header.html.erb +0 -8
  485. data/app/views/spree/shared/_base_mailer_stylesheets.html.erb +0 -399
  486. data/app/views/spree/shared/_mailer_logo.html.erb +0 -41
  487. data/app/views/spree/shared/_purchased_items_styles.html.erb +0 -119
  488. data/app/views/spree/webhook_mailer/endpoint_disabled.html.erb +0 -51
  489. data/app/views/spree/webhook_mailer/endpoint_disabled.text.erb +0 -10
  490. data/app/workflows/spree/fulfillments/purchase_label.rb +0 -40
  491. data/app/workflows/spree/returns/purchase_label.rb +0 -32
  492. data/lib/spree/analytics.rb +0 -11
  493. data/lib/spree/core/preferences/scoped_store.rb +0 -34
  494. data/lib/spree/core/preferences/store.rb +0 -105
  495. data/lib/spree/translation_migrations.rb +0 -52
@@ -0,0 +1,59 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Spree
4
+ module Fulfillments
5
+ # Sends replacement goods for an exchange or a claim.
6
+ #
7
+ # Replacement units hang off the line they replace but are flagged, so an
8
+ # edit of that line counts only what the customer bought — otherwise the
9
+ # replacement reads as a unit the line already holds, and raising the
10
+ # quantity adds nothing.
11
+ module Replacements
12
+ extend ActiveSupport::Concern
13
+
14
+ private
15
+
16
+ def build_replacements(record, items)
17
+ order = record.order
18
+
19
+ # By id, not through the association writers: with has_many inversing
20
+ # each writer adds the template to its owner's collection, and the
21
+ # owner's next save persists it beside the packed copy.
22
+ units = items.map do |item|
23
+ Spree::FulfillmentItem.new(
24
+ order_id: order.id,
25
+ line_item_id: item[:line_item].id,
26
+ variant_id: item[:variant].id,
27
+ quantity: item[:quantity],
28
+ status: 'on_hand',
29
+ replacement: true
30
+ )
31
+ end
32
+
33
+ fulfillments = Spree::Stock::Coordinator.new(order, units).fulfillments
34
+ if fulfillments.flat_map(&:fulfillment_items).sum(&:quantity) != units.sum(&:quantity)
35
+ failure(record, :replacement_out_of_stock)
36
+ end
37
+
38
+ order.fulfillments += fulfillments
39
+ order.save!
40
+ fulfillments.each { |fulfillment| allocate_replacement_stock(fulfillment) }
41
+ fulfillments
42
+ end
43
+
44
+ # The replacement is promised the moment it exists, exactly as placement
45
+ # promises an order's own fulfillments. Without this the fulfillment holds
46
+ # nothing, and dispatch then writes no movement at all — an unallocated
47
+ # fulfillment is indistinguishable from one created before typed
48
+ # movements, so the goods would leave the shelf untouched and unrecorded.
49
+ def allocate_replacement_stock(fulfillment)
50
+ fulfillment.manifest.each do |item|
51
+ next unless item.variant.track_inventory?
52
+ next unless item.quantity.positive?
53
+
54
+ fulfillment.stock_location.allocate(item.variant, item.quantity, fulfillment)
55
+ end
56
+ end
57
+ end
58
+ end
59
+ end
@@ -185,7 +185,7 @@ module Spree
185
185
  end
186
186
 
187
187
  def amount
188
- @amount ||= [gift_card.amount_remaining, order.total].min
188
+ @amount ||= [gift_card.spendable_amount, order.total].min
189
189
  end
190
190
 
191
191
  def issue_store_credit
@@ -224,7 +224,7 @@ module Spree
224
224
  payment_method = order.store.payment_methods.find_or_initialize_by(
225
225
  type: 'Spree::PaymentMethod::StoreCredit'
226
226
  )
227
- payment_method.name ||= Spree.t(:store_credit_name)
227
+ payment_method.name ||= I18n.t('spree.store_credit_name')
228
228
  payment_method.active = true
229
229
  payment_method.save! if payment_method.new_record?
230
230
  payment_method
@@ -5,7 +5,11 @@ module Spree
5
5
  # behavior can diverge without branching one implementation.
6
6
  class AddItem < Spree::Carts::AddItem
7
7
  def perform(order:, **rest)
8
- super(cart: order, **rest)
8
+ result = super(cart: order, **rest)
9
+ # Placed orders take items through here too, and their payment
10
+ # status is measured against the total that just moved.
11
+ order.update_statuses!
12
+ result
9
13
  end
10
14
  end
11
15
  end
@@ -63,7 +63,7 @@ module Spree
63
63
  external_step :void_tax
64
64
  step :recompute_totals, with: -> { Spree.order_recalculate_totals_workflow }
65
65
  step :update_statuses, with: -> { Spree.order_update_statuses_service }
66
- order.publish_event('order.canceled', order.event_payload.merge(notify_customer: notify_customer))
66
+ order.publish_event('order.canceled', nil, notify_customer: notify_customer)
67
67
  success(order.reload)
68
68
  rescue ActiveRecord::RecordInvalid
69
69
  failure(order)
@@ -74,7 +74,7 @@ module Spree
74
74
  def ensure_cancellable
75
75
  return if order.allow_cancel?
76
76
 
77
- order.errors.add(:base, :not_cancellable, message: Spree.t('errors.messages.not_cancellable'))
77
+ order.errors.add(:base, :not_cancellable, message: I18n.t('spree.errors.messages.not_cancellable'))
78
78
  failure(order)
79
79
  end
80
80
 
@@ -86,7 +86,7 @@ module Spree
86
86
  def ensure_refund_amount_is_settleable
87
87
  return if refund_amount.blank? || order.grouped?
88
88
 
89
- order.errors.add(:base, :refund_amount_requires_shared_payment, message: Spree.t('errors.messages.refund_amount_requires_shared_payment'))
89
+ order.errors.add(:base, :refund_amount_requires_shared_payment, message: I18n.t('spree.errors.messages.refund_amount_requires_shared_payment'))
90
90
  failure(order)
91
91
  end
92
92
 
@@ -52,6 +52,7 @@ module Spree
52
52
 
53
53
  step :ensure_placed_status
54
54
  step :release_stock_reservations
55
+ step :cancel_unused_payment_sessions
55
56
  step :update_statuses
56
57
  step :publish_order_placed
57
58
  step :complete_sibling_orders
@@ -69,7 +70,7 @@ module Spree
69
70
  order.process_payments! unless payment_covered?
70
71
 
71
72
  failure(order, order.errors.full_messages.to_sentence) if order.errors.any?
72
- failure(order, Spree.t(:payment_processing_failed)) unless payment_covered?
73
+ failure(order, I18n.t('spree.payment_processing_failed')) unless payment_covered?
73
74
  end
74
75
 
75
76
  # Files the sale under whoever made it, dividing the order into one per
@@ -212,6 +213,15 @@ module Spree
212
213
  Spree::StockReservations::Release.call(owner: order)
213
214
  end
214
215
 
216
+ # A session left behind by a switched method or a second tab could still
217
+ # be paid, charging the buyer twice. One provider call per session, so
218
+ # it runs in a job rather than holding up placement.
219
+ def cancel_unused_payment_sessions
220
+ return unless order.payment_sessions.unused.exists?
221
+
222
+ Spree::Payments::CancelUnusedSessionsJob.perform_later(order.id)
223
+ end
224
+
215
225
  def update_statuses
216
226
  Spree::Orders::UpdateStatuses.call(order: order)
217
227
  end
@@ -221,9 +231,10 @@ module Spree
221
231
  # order.completed is a one-release alias for 5.x webhook consumers;
222
232
  # wildcard subscribers dedupe on the metadata marker.
223
233
  def publish_order_placed
224
- payload = order.event_payload.merge(notify_customer: order.notify_customer)
225
- order.publish_event('order.placed', payload)
226
- order.publish_event('order.completed', payload, { deprecated_alias_of: 'order.placed' })
234
+ metadata = { notify_customer: order.notify_customer }
235
+ payload = order.event_payload
236
+ order.publish_event('order.placed', payload, metadata)
237
+ order.publish_event('order.completed', payload, metadata.merge(deprecated_alias_of: 'order.placed'))
227
238
  end
228
239
 
229
240
  # Places the orders the division produced beside this one.
@@ -246,10 +257,7 @@ module Spree
246
257
 
247
258
  return unless order_group.orders.all?(&:placed?)
248
259
 
249
- order_group.publish_event(
250
- 'order_group.completed',
251
- order_group.event_payload.merge(notify_customer: @notify_customer_of_purchase)
252
- )
260
+ order_group.publish_event('order_group.completed', nil, notify_customer: @notify_customer_of_purchase)
253
261
  end
254
262
 
255
263
  def place_sibling(sibling)
@@ -36,11 +36,11 @@ module Spree
36
36
  halt!(payment) if payment.processing?
37
37
  return unless payment.payment_method&.source_required?
38
38
 
39
- failure(payment, Spree.t(:payment_processing_failed)) if payment.source.blank?
39
+ failure(payment, I18n.t('spree.payment_processing_failed')) if payment.source.blank?
40
40
 
41
41
  unless payment.payment_method.supports?(payment.source) || payment.token_based?
42
42
  payment.invalidate!
43
- failure(payment, Spree.t(:payment_method_not_supported))
43
+ failure(payment, I18n.t('spree.payment_method_not_supported'))
44
44
  end
45
45
  end
46
46
 
@@ -0,0 +1,35 @@
1
+ module Spree
2
+ module PostSale
3
+ # Approval shared by returns, exchanges and claims. The including
4
+ # workflow names the record it approves (`post_sale_record`) and the
5
+ # status that record must be in (`approvable_status`).
6
+ module Approval
7
+ private
8
+
9
+ def approve
10
+ step :ensure_approvable
11
+ run_hooks :validate
12
+
13
+ ApplicationRecord.transaction do
14
+ step :mark_approved
15
+ end
16
+
17
+ run_hooks :after_approve
18
+ post_sale_record.publish_event("#{post_sale_record.event_prefix}.approved")
19
+ success(post_sale_record.reload)
20
+ end
21
+
22
+ def ensure_approvable
23
+ failure(post_sale_record, :"not_#{approvable_status}") unless post_sale_record.status == approvable_status
24
+ end
25
+
26
+ def mark_approved
27
+ post_sale_record.update!(
28
+ status: 'approved',
29
+ approved_at: Time.current,
30
+ created_by: post_sale_record.created_by || approver
31
+ )
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,44 @@
1
+ module Spree
2
+ module PostSale
3
+ # Cancellation shared by returns, exchanges and claims. The including
4
+ # workflow names the record it cancels (`post_sale_record`) and the
5
+ # statuses it may be cancelled from (`cancellable_statuses`). A block
6
+ # runs after the cancellation commits, for work outside the database.
7
+ module Cancellation
8
+ include Spree::Refunds::TaxCredit
9
+
10
+ private
11
+
12
+ def cancel
13
+ step :ensure_cancellable
14
+ run_hooks :validate
15
+
16
+ ApplicationRecord.transaction do
17
+ step :mark_canceled
18
+ step :clear_tax
19
+ end
20
+
21
+ yield if block_given?
22
+
23
+ run_hooks :after_cancel
24
+ post_sale_record.publish_event("#{post_sale_record.event_prefix}.canceled")
25
+ success(post_sale_record.reload)
26
+ end
27
+
28
+ def ensure_cancellable
29
+ return if post_sale_record.status.in?(cancellable_statuses)
30
+
31
+ failure(post_sale_record, :not_cancellable)
32
+ end
33
+
34
+ def mark_canceled
35
+ memo = [post_sale_record.memo, reason].compact_blank.join("\n")
36
+ post_sale_record.update!(status: 'canceled', canceled_at: Time.current, memo: memo.presence)
37
+ end
38
+
39
+ def clear_tax
40
+ clear_tax_credit(post_sale_record)
41
+ end
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,92 @@
1
+ module Spree
2
+ module PostSale
3
+ # Records what the warehouse actually received for a return or an
4
+ # exchange. The including workflow names the record (`post_sale_record`);
5
+ # its lines, the item key callers pass (`return_line_item:` or
6
+ # `exchange_line_item:`) and the error codes follow from the record's
7
+ # event prefix. `restock_variant` says which variant a line puts back.
8
+ #
9
+ # Partial and damaged receipt is the normal case, not an edge case: the
10
+ # customer said three items were coming, two arrived, one of those is not
11
+ # resellable. Quantities and resellable flags therefore come from the
12
+ # caller rather than from the request.
13
+ module Receipt
14
+ private
15
+
16
+ def receive
17
+ step :ensure_receivable
18
+ step :normalize_items
19
+ run_hooks :validate
20
+
21
+ ApplicationRecord.transaction do
22
+ step :record_receipt
23
+ run_hooks :before_restock
24
+ step :restock_resellable_items
25
+ step :mark_received
26
+ end
27
+
28
+ run_hooks :after_receive
29
+ post_sale_record.publish_event("#{post_sale_kind}.received")
30
+ success(post_sale_record.reload)
31
+ end
32
+
33
+ def post_sale_kind
34
+ post_sale_record.event_prefix
35
+ end
36
+
37
+ def restock_variant(line)
38
+ line.variant
39
+ end
40
+
41
+ def ensure_receivable
42
+ failure(post_sale_record, :not_approved) unless post_sale_record.approved?
43
+ end
44
+
45
+ def normalize_items
46
+ @normalized_items =
47
+ if items.nil?
48
+ post_sale_record.public_send(:"#{post_sale_kind}_line_items").map do |line|
49
+ { line: line, quantity: line.quantity, resellable: true }
50
+ end
51
+ else
52
+ items.map { |item| normalize_item(item) }
53
+ end
54
+
55
+ failure(post_sale_record, :no_items_received) if @normalized_items.sum { |item| item[:quantity] }.zero?
56
+ end
57
+
58
+ def normalize_item(item)
59
+ line = item[:"#{post_sale_kind}_line_item"]
60
+ quantity = item[:quantity].to_i
61
+
62
+ unless line&.public_send(:"#{post_sale_kind}_id") == post_sale_record.id
63
+ failure(post_sale_record, :"item_not_on_#{post_sale_kind}")
64
+ end
65
+ failure(post_sale_record, :invalid_quantity) if quantity.negative? || quantity > line.quantity
66
+
67
+ { line: line, quantity: quantity, resellable: item.fetch(:resellable, true) }
68
+ end
69
+
70
+ def record_receipt
71
+ @normalized_items.each do |item|
72
+ item[:line].update!(received_quantity: item[:quantity], resellable: item[:resellable])
73
+ end
74
+ end
75
+
76
+ # Only resellable goods go back into sellable stock — a damaged item
77
+ # is received but never restocked.
78
+ def restock_resellable_items
79
+ @normalized_items.each do |item|
80
+ next unless item[:resellable]
81
+ next unless item[:quantity].positive?
82
+
83
+ post_sale_record.stock_location.restock(restock_variant(item[:line]), item[:quantity], post_sale_record)
84
+ end
85
+ end
86
+
87
+ def mark_received
88
+ post_sale_record.update!(status: 'received', received_at: Time.current)
89
+ end
90
+ end
91
+ end
92
+ end
@@ -228,7 +228,7 @@ module Spree
228
228
  # it rather than attaching it wrongly.
229
229
  blob = ActiveStorage::Blob.find_signed(signed_id)
230
230
  unless blob && blob.service_name.to_s == Spree.private_storage_service_name.to_s
231
- asset.errors.add(:attachment, :digital_assets_attachment_must_be_private, message: Spree.t(:digital_assets_attachment_must_be_private))
231
+ asset.errors.add(:attachment, :digital_assets_attachment_must_be_private, message: I18n.t('spree.digital_assets_attachment_must_be_private'))
232
232
  raise ActiveRecord::RecordInvalid.new(asset)
233
233
  end
234
234
 
@@ -40,7 +40,7 @@ module Spree
40
40
  def ensure_cancelable
41
41
  return unless purchase_order.closed?
42
42
 
43
- failure(purchase_order, Spree.t('purchase_order.errors.already_closed'))
43
+ failure(purchase_order, I18n.t('spree.purchase_order.errors.already_closed'))
44
44
  end
45
45
 
46
46
  def uncount_awaited_units
@@ -8,7 +8,7 @@ module Spree
8
8
  # lets the outstanding count stand on each line as the record of the gap.
9
9
  # No stock moves — the units were never here.
10
10
  class Close < Spree::Workflow
11
- include Spree::Receivables::IncomingCounter
11
+ include Spree::Receivables::ClosingShort
12
12
 
13
13
  hooks :validate, :after_close
14
14
 
@@ -18,40 +18,12 @@ module Spree
18
18
  # @param reason [String, nil] why the balance is not expected
19
19
  def perform(purchase_order:, reason: nil)
20
20
  super
21
-
22
- purchase_order.with_lock do
23
- step :ensure_closable
24
- run_hooks :validate
25
- step :uncount_awaited_units
26
- step :close_short
27
- end
28
-
29
- run_hooks :after_close
30
- purchase_order.publish_event('purchase_order.received')
31
- success(purchase_order.reload)
21
+ close
32
22
  end
33
23
 
34
24
  private
35
25
 
36
- def ensure_closable
37
- return if purchase_order.partially_received?
38
-
39
- failure(purchase_order, Spree.t('purchase_order.errors.not_partially_received'))
40
- end
41
-
42
- # The merchant has just said the balance is not coming, so it leaves
43
- # the destination's incoming figure; the outstanding count on each line
44
- # keeps the record of what never arrived.
45
- def uncount_awaited_units
46
- uncount_incoming(purchase_order)
47
- end
48
-
49
- def close_short
50
- now = Time.current
51
- attributes = { status: 'received', received_at: now, closed_short_at: now, close_reason: reason.presence }
52
-
53
- failure(purchase_order) unless purchase_order.update(attributes)
54
- end
26
+ def receivable = purchase_order
55
27
  end
56
28
  end
57
29
  end
@@ -32,8 +32,8 @@ module Spree
32
32
  private
33
33
 
34
34
  def ensure_returnable
35
- failure(purchase_order, Spree.t('purchase_order.errors.already_receiving')) if purchase_order.stock_receipts.exists?
36
- failure(purchase_order, Spree.t('purchase_order.errors.not_ordered_for_draft')) unless purchase_order.ordered?
35
+ failure(purchase_order, I18n.t('spree.purchase_order.errors.already_receiving')) if purchase_order.stock_receipts.exists?
36
+ failure(purchase_order, I18n.t('spree.purchase_order.errors.not_ordered_for_draft')) unless purchase_order.ordered?
37
37
  end
38
38
 
39
39
  # Nothing has been received, so this is every unit the order placed.
@@ -32,8 +32,8 @@ module Spree
32
32
  private
33
33
 
34
34
  def ensure_draft
35
- failure(purchase_order, Spree.t('purchase_order.errors.not_draft')) unless purchase_order.draft?
36
- failure(purchase_order, Spree.t('purchase_order.errors.must_have_variant')) if purchase_order.items.empty?
35
+ failure(purchase_order, I18n.t('spree.purchase_order.errors.not_draft')) unless purchase_order.draft?
36
+ failure(purchase_order, I18n.t('spree.purchase_order.errors.must_have_variant')) if purchase_order.items.empty?
37
37
  end
38
38
 
39
39
  def mark_ordered
@@ -50,7 +50,7 @@ module Spree
50
50
  def ensure_receivable
51
51
  return if purchase_order.ordered? || purchase_order.partially_received?
52
52
 
53
- failure(purchase_order, Spree.t('purchase_order.errors.not_ordered'))
53
+ failure(purchase_order, I18n.t('spree.purchase_order.errors.not_ordered'))
54
54
  end
55
55
 
56
56
  def line_belongs?(line)
@@ -4,6 +4,8 @@ module Spree
4
4
  # ordered is a matter of record between two businesses — so this refuses
5
5
  # anything past `draft`.
6
6
  class Update < Spree::Workflow
7
+ include Spree::Receivables::DraftEditing
8
+
7
9
  hooks :validate, :after_update
8
10
 
9
11
  # @param purchase_order [Spree::PurchaseOrder]
@@ -13,44 +15,13 @@ module Spree
13
15
  # unit_cost: }]` replacing the current lines; nil leaves them alone
14
16
  def perform(purchase_order:, attributes: {}, items: nil)
15
17
  super
16
-
17
- step :ensure_editable
18
- run_hooks :validate
19
-
20
- ApplicationRecord.transaction do
21
- step :apply_changes
22
- step :save_purchase_order
23
- end
24
-
25
- run_hooks :after_update
26
- success(purchase_order.reload)
18
+ edit
27
19
  end
28
20
 
29
21
  private
30
22
 
31
- def ensure_editable
32
- return if purchase_order.editable?
33
-
34
- failure(purchase_order, Spree.t('purchase_order.errors.not_editable'))
35
- end
36
-
37
- def apply_changes
38
- purchase_order.assign_attributes(attributes) if attributes.present?
39
- return if items.nil?
40
-
41
- purchase_order.items.destroy_all
42
- Array(items).each do |item|
43
- purchase_order.items.build(
44
- variant: item[:variant],
45
- quantity_ordered: item[:quantity_ordered],
46
- unit_cost: item[:unit_cost]
47
- )
48
- end
49
- end
50
-
51
- def save_purchase_order
52
- failure(purchase_order) unless purchase_order.save
53
- end
23
+ def receivable = purchase_order
24
+ def item_attributes = %i[quantity_ordered unit_cost]
54
25
  end
55
26
  end
56
27
  end
@@ -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