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,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)
@@ -14,11 +14,13 @@ module Spree
14
14
  hooks :validate, :before_capture, :after_capture
15
15
 
16
16
  # @param payment [Spree::Payment] a completed payment is a no-op
17
- # @param amount [Integer, nil] amount in cents; nil captures in full,
18
- # a smaller amount splits the remainder into a new pending payment
17
+ # @param amount [BigDecimal, nil] in the currency's own units; nil
18
+ # captures in full, a smaller amount splits the remainder into a new
19
+ # pending payment. An Integer is read as hundredths of the currency,
20
+ # the pre-6.0 contract (deprecated, removed in 6.1).
19
21
  def perform(payment:, amount: nil)
20
22
  super
21
- @amount = amount || payment.money.amount_in_cents
23
+ @amount = capture_amount(amount)
22
24
 
23
25
  step :ensure_capturable
24
26
  # Veto point — fraud holds, manual review. Before any money moves.
@@ -44,6 +46,17 @@ module Spree
44
46
 
45
47
  attr_reader :response, :remainder
46
48
 
49
+ def capture_amount(amount)
50
+ return payment.amount if amount.nil?
51
+ return Spree::Money::Rounding.to_currency(amount, payment.currency) unless amount.is_a?(Integer)
52
+
53
+ Spree::Deprecation.warn(
54
+ 'Passing an Integer amount to the payment capture workflow is deprecated and is read as hundredths of ' \
55
+ 'the currency. Pass the amount in the currency\'s own units as a BigDecimal; removed in Spree 6.1.'
56
+ )
57
+ Spree::Money::Rounding.from_hundredths(amount)
58
+ end
59
+
47
60
  # An already-captured payment returns success rather than failing:
48
61
  # capture is naturally idempotent and double-submitted admin actions
49
62
  # must not surface as errors. A failed payment is capturable — the
@@ -65,7 +78,9 @@ module Spree
65
78
  def capture_at_gateway
66
79
  @response = payment.protect_from_connection_error do
67
80
  instrument_gateway_call(:capture, payment.payment_method) do
68
- payment.payment_method.capture(@amount, payment.response_code, payment.gateway_options)
81
+ payment.payment_method.capture(
82
+ payment.payment_method.gateway_amount(@amount, payment.currency), payment.response_code, payment.gateway_options
83
+ )
69
84
  end
70
85
  end
71
86
 
@@ -86,7 +101,7 @@ module Spree
86
101
  if Spree::Payment.where(id: payment.id, status: 'completed').exists?
87
102
  already_captured = true
88
103
  else
89
- payment.capture_events.create!(amount: ::Money.new(@amount, payment.currency).to_f)
104
+ payment.capture_events.create!(amount: @amount)
90
105
  # Split before completing, so payment.completed publishes with
91
106
  # the captured amount and the order recomputes from correct rows.
92
107
  @remainder = payment.split_uncaptured_amount
@@ -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
 
@@ -57,7 +57,9 @@ module Spree
57
57
 
58
58
  response = payment.protect_from_connection_error do
59
59
  instrument_gateway_call(verb, payment.payment_method) do
60
- payment.payment_method.public_send(verb, payment.money.amount_in_cents, payment.source, payment.gateway_options)
60
+ payment.payment_method.public_send(
61
+ verb, payment.payment_method.gateway_amount(payment.amount, payment.currency), payment.source, payment.gateway_options
62
+ )
61
63
  end
62
64
  end
63
65
 
@@ -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
@@ -61,7 +61,9 @@ module Spree
61
61
  unless result.success?
62
62
  refusal = result.error&.value
63
63
  refusal = {} unless refusal.is_a?(Hash)
64
- if refusal[:invalid_amounts].present?
64
+ if refusal[:malformed_amounts].present?
65
+ price_list.errors.add(:base, :malformed_price)
66
+ elsif refusal[:invalid_amounts].present?
65
67
  price_list.errors.add(:base, :negative_price)
66
68
  elsif refusal[:invalid_quantities].present?
67
69
  price_list.errors.add(:base, :invalid_quantity)
@@ -76,12 +78,10 @@ module Spree
76
78
  end
77
79
  return failure(price_list)
78
80
  end
79
-
80
- touch_variants(rows.map { |row| row[:variant_id] }.uniq)
81
81
  end
82
82
 
83
83
  def clear_prices
84
- variant_ids = price_list.prices.distinct.pluck(:variant_id)
84
+ variant_ids = price_list.prices.where.not(amount: nil).distinct.pluck(:variant_id)
85
85
  price_list.prices.update_all(amount: nil, compare_at_amount: nil, updated_at: Time.current)
86
86
  touch_variants(variant_ids)
87
87
  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
@@ -19,9 +19,10 @@ module Spree
19
19
  # @param expected_at [Date, nil]
20
20
  # @param reference [String, nil] the supplier's own order number
21
21
  # @param notes [String, nil]
22
+ # @param metadata [Hash, nil]
22
23
  # @param created_by [Object, nil]
23
24
  def perform(store:, supplier:, destination_location:, items: [], currency: nil,
24
- expected_at: nil, cancel_by: nil, reference: nil, notes: nil, created_by: nil)
25
+ expected_at: nil, cancel_by: nil, reference: nil, notes: nil, metadata: nil, created_by: nil)
25
26
  super
26
27
 
27
28
  step :build_purchase_order
@@ -50,6 +51,7 @@ module Spree
50
51
  created_by: created_by,
51
52
  status: Spree::PurchaseOrder.default_status
52
53
  )
54
+ @purchase_order.metadata = metadata if metadata
53
55
 
54
56
  Array(items).each do |item|
55
57
  @purchase_order.items.build(
@@ -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