spree_core 6.0.0.beta3 → 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.
- checksums.yaml +4 -4
- data/README.md +2 -2
- data/app/helpers/spree/images_helper.rb +0 -57
- data/app/jobs/spree/base_job.rb +21 -0
- data/app/jobs/spree/carts/reap_expired_job.rb +2 -2
- data/app/jobs/spree/collections/regenerate_time_based_job.rb +6 -3
- data/app/jobs/spree/orders/finalize_stale_drafts_job.rb +8 -5
- data/app/jobs/spree/payments/cancel_unused_sessions_job.rb +61 -0
- data/app/jobs/spree/seller_payouts/sweep_seller_job.rb +2 -0
- data/app/mailers/spree/admin_user_mailer.rb +4 -19
- data/app/mailers/spree/base_mailer.rb +96 -13
- data/app/mailers/spree/email_template_mailer.rb +27 -0
- data/app/mailers/spree/export_mailer.rb +3 -4
- data/app/mailers/spree/import_mailer.rb +3 -4
- data/app/mailers/spree/invitation_mailer.rb +24 -7
- data/app/mailers/spree/seller_user_mailer.rb +4 -16
- data/app/mailers/spree/webhook_mailer.rb +2 -2
- data/app/models/concerns/spree/acted_by.rb +1 -1
- data/app/models/concerns/spree/admin_user_methods.rb +13 -1
- data/app/models/concerns/spree/api_typed.rb +91 -0
- data/app/models/concerns/spree/calculator/tiers.rb +76 -0
- data/app/models/concerns/spree/customer_methods.rb +15 -14
- data/app/models/concerns/spree/email_template_identity.rb +52 -0
- data/app/models/concerns/spree/parameterizable_name.rb +2 -0
- data/app/models/concerns/spree/payment_session_transitions.rb +8 -2
- data/app/models/concerns/spree/post_sale/taxation.rb +54 -0
- data/app/models/concerns/spree/post_sale/taxed_line.rb +51 -0
- data/app/models/concerns/spree/preference_schema.rb +4 -12
- data/app/models/concerns/spree/product_scopes.rb +11 -1
- data/app/models/concerns/spree/provider_listing.rb +41 -0
- data/app/models/concerns/spree/publishable.rb +82 -14
- data/app/models/concerns/spree/purchase/addresses.rb +23 -10
- data/app/models/concerns/spree/purchase/currency.rb +1 -1
- data/app/models/concerns/spree/purchase/deprecated_aliases.rb +87 -0
- data/app/models/concerns/spree/purchase/gift_cards.rb +10 -4
- data/app/models/concerns/spree/purchase/locale.rb +1 -1
- data/app/models/concerns/spree/purchase/payment_processing.rb +7 -3
- data/app/models/concerns/spree/purchase/purchase_order.rb +2 -2
- data/app/models/concerns/spree/purchase/totals.rb +49 -1
- data/app/models/concerns/spree/receivable.rb +1 -1
- data/app/models/concerns/spree/searchable.rb +0 -4
- data/app/models/concerns/spree/secret_preferences.rb +59 -0
- data/app/models/concerns/spree/single_store_resource.rb +1 -1
- data/app/models/concerns/spree/stores/email_branding.rb +31 -0
- data/app/models/concerns/spree/type_labels.rb +38 -0
- data/app/models/concerns/spree/typed_adjustment_line.rb +1 -1
- data/app/models/concerns/spree/typed_associations.rb +6 -0
- data/app/models/spree/address.rb +11 -1
- data/app/models/spree/adjusters/promotion.rb +1 -15
- data/app/models/spree/allowed_origin.rb +0 -1
- data/app/models/spree/api_key.rb +5 -6
- data/app/models/spree/authentication/strategies/oidc_strategy.rb +5 -5
- data/app/models/spree/base.rb +70 -22
- data/app/models/spree/calculator/flat_percent_item_total.rb +1 -3
- data/app/models/spree/calculator/flat_rate.rb +1 -3
- data/app/models/spree/calculator/flexi_rate.rb +1 -3
- data/app/models/spree/calculator/percent_on_line_item.rb +1 -1
- data/app/models/spree/calculator/price_sack.rb +1 -3
- data/app/models/spree/calculator/shipping/digital_delivery.rb +1 -3
- data/app/models/spree/calculator/shipping/flat_percent_item_total.rb +1 -3
- data/app/models/spree/calculator/shipping/flat_rate.rb +1 -3
- data/app/models/spree/calculator/shipping/flexi_rate.rb +1 -3
- data/app/models/spree/calculator/shipping/per_item.rb +1 -3
- data/app/models/spree/calculator/shipping/price_sack.rb +1 -3
- data/app/models/spree/calculator/tiered_flat_rate.rb +6 -23
- data/app/models/spree/calculator/tiered_percent.rb +7 -25
- data/app/models/spree/cart.rb +78 -18
- data/app/models/spree/channel/gating.rb +1 -1
- data/app/models/spree/channel.rb +4 -4
- data/app/models/spree/claim.rb +21 -1
- data/app/models/spree/claim_line_item.rb +41 -6
- data/app/models/spree/collection.rb +0 -1
- data/app/models/spree/collection_rule.rb +5 -21
- data/app/models/spree/commission_line.rb +1 -1
- data/app/models/spree/commission_rate.rb +2 -3
- data/app/models/spree/commission_rate_value.rb +1 -1
- data/app/models/spree/commission_rule.rb +4 -11
- data/app/models/spree/commission_rules/product_rule.rb +1 -1
- data/app/models/spree/company_invitation.rb +1 -0
- data/app/models/spree/consent_record.rb +0 -1
- data/app/models/spree/credit_card.rb +1 -1
- data/app/models/spree/current.rb +23 -0
- data/app/models/spree/custom_field.rb +2 -2
- data/app/models/spree/custom_field_definition.rb +9 -23
- data/app/models/spree/custom_fields/boolean.rb +1 -1
- data/app/models/spree/custom_fields/json.rb +1 -1
- data/app/models/spree/data_feed/google.rb +0 -2
- data/app/models/spree/data_request.rb +1 -1
- data/app/models/spree/delivery_method.rb +30 -13
- data/app/models/spree/delivery_method_rule.rb +4 -11
- data/app/models/spree/delivery_rate.rb +4 -5
- data/app/models/spree/delivery_rate_provider/base.rb +1 -0
- data/app/models/spree/delivery_zone.rb +0 -1
- data/app/models/spree/delivery_zone_member.rb +4 -4
- data/app/models/spree/digital_asset.rb +7 -0
- data/app/models/spree/digital_asset_provider/base.rb +2 -0
- data/app/models/spree/digital_link.rb +2 -5
- data/app/models/spree/discount.rb +1 -1
- data/app/models/spree/email_template.rb +25 -0
- data/app/models/spree/email_template_draft.rb +13 -0
- data/app/models/spree/email_template_revision.rb +17 -0
- data/app/models/spree/email_templates/entry.rb +118 -0
- data/app/models/spree/email_templates/sample_record.rb +16 -0
- data/app/models/spree/emails/branding.rb +80 -0
- data/app/models/spree/exchange.rb +15 -1
- data/app/models/spree/exchange_line_item.rb +121 -5
- data/app/models/spree/export.rb +0 -1
- data/app/models/spree/exports/products.rb +10 -1
- data/app/models/spree/exports/report.rb +1 -1
- data/app/models/spree/fee.rb +4 -2
- data/app/models/spree/fulfillment.rb +53 -27
- data/app/models/spree/fulfillment_changer.rb +2 -2
- data/app/models/spree/fulfillment_item.rb +0 -4
- data/app/models/spree/fulfillment_provider/base.rb +1 -0
- data/app/models/spree/gateway/bogus.rb +4 -0
- data/app/models/spree/gateway/custom_payment_source_method.rb +5 -0
- data/app/models/spree/gateway.rb +5 -1
- data/app/models/spree/gift_card.rb +12 -1
- data/app/models/spree/gift_card_batch.rb +0 -1
- data/app/models/spree/import.rb +2 -7
- data/app/models/spree/import_mapping.rb +1 -2
- data/app/models/spree/import_row.rb +2 -2
- data/app/models/spree/import_schemas/products.rb +1 -0
- data/app/models/spree/integration.rb +8 -18
- data/app/models/spree/invitation.rb +5 -5
- data/app/models/spree/line_item.rb +13 -2
- data/app/models/spree/locale.rb +11 -8
- data/app/models/spree/market.rb +1 -0
- data/app/models/spree/newsletter_subscriber.rb +3 -0
- data/app/models/spree/order.rb +56 -138
- data/app/models/spree/order_group.rb +11 -2
- data/app/models/spree/order_inventory.rb +10 -5
- data/app/models/spree/order_promotion.rb +1 -1
- data/app/models/spree/order_routing/has_strategy_preference.rb +1 -1
- data/app/models/spree/order_routing/strategy/base.rb +4 -2
- data/app/models/spree/order_routing/strategy/rules.rb +1 -1
- data/app/models/spree/order_routing_rule.rb +6 -22
- data/app/models/spree/package_type.rb +1 -2
- data/app/models/spree/payment/gateway_options.rb +14 -52
- data/app/models/spree/payment/processing.rb +2 -4
- data/app/models/spree/payment.rb +20 -5
- data/app/models/spree/payment_method/session_instrumentation.rb +1 -0
- data/app/models/spree/payment_method/store_credit.rb +4 -4
- data/app/models/spree/payment_method.rb +36 -4
- data/app/models/spree/payment_session.rb +5 -1
- data/app/models/spree/payment_setup_session.rb +1 -0
- data/app/models/spree/payout_provider/base.rb +3 -1
- data/app/models/spree/pickup_point_provider/base.rb +2 -0
- data/app/models/spree/price_list.rb +1 -2
- data/app/models/spree/price_rules/channel_rule.rb +1 -1
- data/app/models/spree/price_rules/customer_group_rule.rb +1 -1
- data/app/models/spree/product.rb +31 -7
- data/app/models/spree/product_submission.rb +0 -12
- data/app/models/spree/product_type.rb +0 -1
- data/app/models/spree/promotion/actions/create_line_items.rb +27 -17
- data/app/models/spree/promotion/rules/category.rb +0 -4
- data/app/models/spree/promotion/rules/channel.rb +0 -4
- data/app/models/spree/promotion/rules/country.rb +0 -4
- data/app/models/spree/promotion/rules/currency.rb +0 -4
- data/app/models/spree/promotion/rules/customer_group.rb +0 -4
- data/app/models/spree/promotion/rules/first_order.rb +0 -4
- data/app/models/spree/promotion/rules/item_total.rb +4 -8
- data/app/models/spree/promotion/rules/market.rb +0 -4
- data/app/models/spree/promotion/rules/one_use_per_user.rb +0 -4
- data/app/models/spree/promotion/rules/option_value.rb +0 -3
- data/app/models/spree/promotion/rules/product.rb +0 -4
- data/app/models/spree/promotion/rules/user.rb +0 -4
- data/app/models/spree/promotion/rules/user_logged_in.rb +0 -4
- data/app/models/spree/promotion.rb +35 -6
- data/app/models/spree/promotion_action.rb +7 -14
- data/app/models/spree/promotion_action_line_item.rb +1 -1
- data/app/models/spree/promotion_handler/coupon.rb +59 -27
- data/app/models/spree/promotion_rule.rb +7 -14
- data/app/models/spree/purchase_order.rb +1 -0
- data/app/models/spree/quantity_rule.rb +1 -1
- data/app/models/spree/refund.rb +28 -3
- data/app/models/spree/return.rb +30 -2
- data/app/models/spree/return_line_item.rb +65 -14
- data/app/models/spree/role.rb +7 -7
- data/app/models/spree/saved_report.rb +1 -1
- data/app/models/spree/seller.rb +2 -1
- data/app/models/spree/seller_payout.rb +7 -0
- data/app/models/spree/seller_requirement.rb +4 -12
- data/app/models/spree/seller_requirement_custom_field.rb +1 -2
- data/app/models/spree/seller_requirement_submission.rb +2 -2
- data/app/models/spree/seller_requirements/accept_terms.rb +2 -3
- data/app/models/spree/seller_transfer.rb +5 -0
- data/app/models/spree/shipping_label.rb +1 -0
- data/app/models/spree/stock/availability_validator.rb +2 -2
- data/app/models/spree/stock/inventory_unit_builder.rb +1 -1
- data/app/models/spree/stock/packer.rb +2 -2
- data/app/models/spree/stock/splitter/weight.rb +1 -1
- data/app/models/spree/stock_level/custom_events.rb +6 -0
- data/app/models/spree/stock_level.rb +1 -1
- data/app/models/spree/stock_movement.rb +1 -1
- data/app/models/spree/stock_receipt_item.rb +1 -1
- data/app/models/spree/stock_reservation.rb +2 -2
- data/app/models/spree/stock_transfer.rb +4 -2
- data/app/models/spree/store.rb +33 -7
- data/app/models/spree/store_credit.rb +8 -9
- data/app/models/spree/store_credit_event.rb +5 -5
- data/app/models/spree/tax_exemption_certificate.rb +2 -0
- data/app/models/spree/tax_identifier.rb +3 -1
- data/app/models/spree/tax_line.rb +53 -3
- data/app/models/spree/tax_provider/base.rb +71 -14
- data/app/models/spree/tax_provider/internal.rb +59 -33
- data/app/models/spree/tax_provider/recorded_share.rb +114 -0
- data/app/models/spree/tax_rate.rb +1 -2
- data/app/models/spree/taxonomy.rb +0 -1
- data/app/models/spree/variant.rb +4 -3
- data/app/models/spree/variant_media.rb +1 -1
- data/app/models/spree/webhook_endpoint.rb +10 -13
- data/app/models/spree/wishlist.rb +1 -6
- data/app/models/spree/wishlist_item/custom_events.rb +6 -0
- data/app/presenters/spree/csv/customer_presenter.rb +1 -1
- data/app/presenters/spree/csv/newsletter_subscriber_presenter.rb +1 -1
- data/app/presenters/spree/csv/order_line_item_presenter.rb +1 -1
- data/app/presenters/spree/csv/product_variant_presenter.rb +42 -12
- data/app/serializers/spree/emails/base_serializer.rb +14 -0
- data/app/serializers/spree/emails/export_serializer.rb +13 -0
- data/app/serializers/spree/emails/import_serializer.rb +19 -0
- data/app/serializers/spree/emails/invitation_serializer.rb +28 -0
- data/app/serializers/spree/emails/store_serializer.rb +31 -0
- data/app/serializers/spree/emails/user_serializer.rb +12 -0
- data/app/serializers/spree/emails/webhook_endpoint_serializer.rb +16 -0
- data/app/services/spree/carts/create.rb +8 -2
- data/app/services/spree/carts/destroy.rb +2 -2
- data/app/services/spree/carts/remove_out_of_stock_items.rb +2 -2
- data/app/services/spree/carts/split_by_seller.rb +1 -1
- data/app/services/spree/carts/update.rb +19 -5
- data/app/services/spree/catalogs/set_product_terms.rb +1 -1
- data/app/services/spree/categories/add_products.rb +13 -31
- data/app/services/spree/categories/remove_products.rb +12 -51
- data/app/services/spree/checkout/advance.rb +1 -4
- data/app/services/spree/collections/add_products.rb +10 -23
- data/app/services/spree/collections/remove_products.rb +7 -43
- data/app/services/spree/coupon_codes/bulk_generate.rb +8 -14
- data/app/services/spree/data_requests/create.rb +1 -1
- data/app/services/spree/deliveries/destroy.rb +2 -2
- data/app/services/spree/email_templates/check.rb +58 -0
- data/app/services/spree/email_templates/no_sample_record.rb +7 -0
- data/app/services/spree/email_templates/preview.rb +99 -0
- data/app/services/spree/emails/file_system.rb +23 -0
- data/app/services/spree/emails/filters.rb +84 -0
- data/app/services/spree/emails/liquid_context.rb +48 -0
- data/app/services/spree/emails/rendered_email.rb +14 -0
- data/app/services/spree/emails/renderer.rb +125 -0
- data/app/services/spree/emails/template.rb +51 -0
- data/app/services/spree/emails/template_data.rb +23 -0
- data/app/services/spree/emails/template_resolver.rb +109 -0
- data/app/services/spree/emails/text_converter.rb +72 -0
- data/app/services/spree/emails/translation_inliner.rb +134 -0
- data/app/services/spree/imports/row_processors/price_list_price.rb +10 -10
- data/app/services/spree/imports/row_processors/product_variant.rb +65 -34
- data/app/services/spree/imports/row_processors/purchase_order.rb +13 -13
- data/app/services/spree/locales.rb +4 -5
- data/app/services/spree/newsletter/subscribe.rb +8 -12
- data/app/services/spree/orders/build_fulfillments.rb +3 -2
- data/app/services/spree/orders/create.rb +2 -1
- data/app/services/spree/orders/discounts/create.rb +7 -4
- data/app/services/spree/orders/discounts/destroy.rb +1 -0
- data/app/services/spree/orders/discounts/update.rb +1 -0
- data/app/services/spree/orders/fees/create.rb +1 -0
- data/app/services/spree/orders/fees/destroy.rb +1 -0
- data/app/services/spree/orders/fees/update.rb +1 -0
- data/app/services/spree/orders/remove_line_item.rb +13 -3
- data/app/services/spree/orders/update.rb +10 -6
- data/app/services/spree/orders/update_item.rb +6 -3
- data/app/services/spree/orders/update_statuses.rb +3 -1
- data/app/services/spree/product_memberships.rb +61 -0
- data/app/services/spree/returns/eligibility_validator.rb +1 -1
- data/app/services/spree/search_provider/custom_field_schema.rb +2 -2
- data/app/services/spree/seeds/commission_rates.rb +1 -1
- data/app/services/spree/seeds/digital_delivery.rb +1 -1
- data/app/services/spree/seeds/payment_methods.rb +2 -2
- data/app/services/spree/seeds/saved_reports.rb +3 -3
- data/app/services/spree/sellers/requirements.rb +1 -2
- data/app/services/spree/shipping_labels/confirm_refund.rb +1 -1
- data/app/services/spree/shipping_labels/store_file.rb +3 -3
- data/app/services/spree/stock_levels/correct.rb +3 -3
- data/app/services/spree/stock_reservations/reserve.rb +2 -3
- data/app/services/spree/store_credits/apply.rb +15 -13
- data/app/services/spree/stores/provision_defaults.rb +15 -14
- data/app/subscribers/spree/seller_onboarding_subscriber.rb +1 -1
- data/app/views/layouts/spree/base_mailer.html.erb +2 -43
- data/app/views/layouts/spree/base_mailer.liquid +79 -0
- data/app/views/spree/admin_user_mailer/password_reset_email.liquid +14 -0
- data/app/views/spree/export_mailer/export_done.liquid +12 -0
- data/app/views/spree/import_mailer/import_done.liquid +15 -0
- data/app/views/spree/invitation_mailer/invitation_accepted.liquid +9 -0
- data/app/views/spree/invitation_mailer/invitation_email.liquid +13 -0
- data/app/views/spree/seller_user_mailer/password_reset_email.liquid +14 -0
- data/app/views/spree/shared/_mailer_button.html.erb +4 -5
- data/app/views/spree/shared/_mailer_hero.html.erb +6 -5
- data/app/views/spree/shared/_summary_row.liquid +8 -0
- data/app/views/spree/webhook_mailer/endpoint_disabled.liquid +21 -0
- data/app/workflows/spree/carts/add_item.rb +4 -4
- data/app/workflows/spree/carts/complete.rb +13 -3
- data/app/workflows/spree/carts/merge.rb +1 -1
- data/app/workflows/spree/carts/recalculate_totals.rb +5 -0
- data/app/workflows/spree/carts/upsert_items.rb +18 -9
- data/app/workflows/spree/claims/approve.rb +5 -22
- data/app/workflows/spree/claims/cancel.rb +5 -21
- data/app/workflows/spree/claims/create.rb +15 -0
- data/app/workflows/spree/claims/deny.rb +7 -0
- data/app/workflows/spree/claims/resolve.rb +34 -31
- data/app/workflows/spree/customers/anonymize.rb +3 -4
- data/app/workflows/spree/customers/create.rb +1 -1
- data/app/workflows/spree/data_requests/fulfill.rb +2 -2
- data/app/workflows/spree/deliveries/update_tracking.rb +1 -1
- data/app/workflows/spree/email_templates/discard_draft.rb +20 -0
- data/app/workflows/spree/email_templates/publish.rb +70 -0
- data/app/workflows/spree/email_templates/restore_revision.rb +33 -0
- data/app/workflows/spree/email_templates/revert.rb +29 -0
- data/app/workflows/spree/email_templates/save_draft.rb +85 -0
- data/app/workflows/spree/exchanges/approve.rb +5 -22
- data/app/workflows/spree/exchanges/cancel.rb +5 -21
- data/app/workflows/spree/exchanges/create.rb +6 -0
- data/app/workflows/spree/exchanges/fulfill.rb +75 -51
- data/app/workflows/spree/exchanges/receive.rb +6 -65
- data/app/workflows/spree/fulfillments/cancel.rb +1 -1
- data/app/workflows/spree/fulfillments/create.rb +51 -47
- data/app/workflows/spree/fulfillments/fulfill.rb +23 -27
- data/app/workflows/spree/fulfillments/mark_delivered.rb +1 -1
- data/app/workflows/spree/fulfillments/replacements.rb +59 -0
- data/app/workflows/spree/gift_cards/apply.rb +2 -2
- data/app/workflows/spree/orders/add_item.rb +5 -1
- data/app/workflows/spree/orders/cancel.rb +3 -3
- data/app/workflows/spree/orders/complete.rb +16 -8
- data/app/workflows/spree/payments/process.rb +2 -2
- data/app/workflows/spree/post_sale/approval.rb +35 -0
- data/app/workflows/spree/post_sale/cancellation.rb +44 -0
- data/app/workflows/spree/post_sale/receipt.rb +92 -0
- data/app/workflows/spree/products/nested_attributes.rb +1 -1
- data/app/workflows/spree/purchase_orders/cancel.rb +1 -1
- data/app/workflows/spree/purchase_orders/close.rb +3 -31
- data/app/workflows/spree/purchase_orders/mark_draft.rb +2 -2
- data/app/workflows/spree/purchase_orders/mark_ordered.rb +2 -2
- data/app/workflows/spree/purchase_orders/receive.rb +1 -1
- data/app/workflows/spree/purchase_orders/update.rb +5 -34
- data/app/workflows/spree/receivables/closing_short.rb +46 -0
- data/app/workflows/spree/receivables/draft_editing.rb +48 -0
- data/app/workflows/spree/refunds/create.rb +5 -2
- data/app/workflows/spree/refunds/order_payments.rb +14 -2
- data/app/workflows/spree/refunds/tax_credit.rb +67 -0
- data/app/workflows/spree/returns/approve.rb +5 -22
- data/app/workflows/spree/returns/cancel.rb +5 -22
- data/app/workflows/spree/returns/create.rb +8 -0
- data/app/workflows/spree/returns/receive.rb +5 -76
- data/app/workflows/spree/returns/refund.rb +31 -8
- data/app/workflows/spree/seller_requirement_submissions/create.rb +1 -2
- data/app/workflows/spree/seller_transfers/reverse.rb +28 -1
- data/app/workflows/spree/shipping_labels/purchase.rb +8 -8
- data/app/workflows/spree/shipping_labels/record.rb +5 -5
- data/app/workflows/spree/shipping_labels/refund.rb +2 -2
- data/app/workflows/spree/stock_receipts/recording.rb +4 -4
- data/app/workflows/spree/stock_transfers/cancel.rb +2 -2
- data/app/workflows/spree/stock_transfers/close.rb +3 -30
- data/app/workflows/spree/stock_transfers/mark_draft.rb +1 -1
- data/app/workflows/spree/stock_transfers/mark_in_transit.rb +3 -3
- data/app/workflows/spree/stock_transfers/mark_ready.rb +2 -2
- data/app/workflows/spree/stock_transfers/receive.rb +1 -1
- data/app/workflows/spree/stock_transfers/update.rb +5 -33
- data/config/i18n-tasks.yml +32 -18
- data/config/initializers/alba.rb +7 -0
- data/config/language_names.csv +264 -0
- data/config/locales/ar.yml +811 -0
- data/config/locales/be.yml +855 -0
- data/config/locales/bg.yml +723 -0
- data/config/locales/ca.yml +689 -0
- data/config/locales/cs.yml +855 -0
- data/config/locales/da.yml +771 -0
- data/config/locales/de-CH.yml +248 -0
- data/config/locales/de.yml +835 -0
- data/config/locales/el.yml +769 -0
- data/config/locales/en.yml +37 -503
- data/config/locales/es-CL.yml +301 -0
- data/config/locales/es-EC.yml +186 -0
- data/config/locales/es-MX.yml +334 -0
- data/config/locales/es.yml +831 -0
- data/config/locales/et.yml +644 -0
- data/config/locales/fa.yml +709 -0
- data/config/locales/fi.yml +771 -0
- data/config/locales/fr.yml +830 -0
- data/config/locales/hu.yml +751 -0
- data/config/locales/id.yml +756 -0
- data/config/locales/it.yml +833 -0
- data/config/locales/ja.yml +726 -0
- data/config/locales/km.yml +759 -0
- data/config/locales/ko.yml +575 -0
- data/config/locales/ku.yml +753 -0
- data/config/locales/lt.yml +821 -0
- data/config/locales/lv.yml +685 -0
- data/config/locales/nb.yml +824 -0
- data/config/locales/ne.yml +823 -0
- data/config/locales/nl.yml +826 -0
- data/config/locales/pl.yml +835 -0
- data/config/locales/pt-BR.yml +547 -0
- data/config/locales/pt.yml +636 -0
- data/config/locales/ro.yml +750 -0
- data/config/locales/ru.yml +834 -0
- data/config/locales/sk.yml +680 -0
- data/config/locales/sl-SI.yml +600 -0
- data/config/locales/sv.yml +827 -0
- data/config/locales/th.yml +745 -0
- data/config/locales/tr.yml +777 -0
- data/config/locales/uk.yml +836 -0
- data/config/locales/vi.yml +768 -0
- data/config/locales/zh-CN.yml +786 -0
- data/config/locales/zh-TW.yml +834 -0
- data/db/migrate/20260925000001_add_replacement_to_spree_fulfillment_items.rb +5 -0
- data/db/migrate/20261001000001_add_post_sale_tax.rb +30 -0
- data/db/migrate/20261001000001_convert_preferences_to_json.rb +166 -0
- data/db/migrate/20261001000001_create_spree_email_templates.rb +52 -0
- data/lib/generators/spree/dummy/dummy_generator.rb +0 -9
- data/lib/generators/spree/dummy/templates/rails/test.rb +7 -0
- data/lib/spree/checkout/default_requirements.rb +37 -13
- data/lib/spree/core/configuration.rb +1 -1
- data/lib/spree/core/controller_helpers/store.rb +1 -1
- data/lib/spree/core/dependencies.rb +5 -4
- data/lib/spree/core/emails/escaped_output.rb +60 -0
- data/lib/spree/core/engine.rb +48 -167
- data/lib/spree/core/permissions/default_catalog.rb +5 -0
- data/lib/spree/core/preferences/json_conversion.rb +182 -0
- data/lib/spree/core/preferences/masking.rb +4 -1
- data/lib/spree/core/preferences/preferable.rb +137 -83
- data/lib/spree/core/preferences/preferable_class_methods.rb +81 -146
- data/lib/spree/core/previews/import_preview.rb +10 -0
- data/lib/spree/core/tax/provider_error.rb +37 -0
- data/lib/spree/core/version.rb +1 -1
- data/lib/spree/core.rb +85 -370
- data/lib/spree/emails/editable_templates.rb +82 -0
- data/lib/spree/events/catalog.rb +189 -0
- data/lib/spree/events.rb +21 -1
- data/lib/spree/hooks.rb +0 -17
- data/lib/spree/i18n.rb +32 -15
- data/lib/spree/reporting/adapters/live.rb +6 -6
- data/lib/spree/reporting/default_vocabulary.rb +22 -10
- data/lib/spree/reporting/hydration.rb +1 -1
- data/lib/spree/reporting/query.rb +8 -0
- data/lib/spree/reporting/schema.rb +10 -10
- data/lib/spree/testing_support/editable_email_templates.rb +61 -0
- data/lib/spree/testing_support/emails.rb +22 -0
- data/lib/spree/testing_support/factories/cart_factory.rb +1 -1
- data/lib/spree/testing_support/factories/email_template_factory.rb +28 -0
- data/lib/spree/testing_support/factories/order_factory.rb +2 -2
- data/lib/spree/testing_support/factories/refund_factory.rb +1 -1
- data/lib/spree/testing_support/factories/stock_factory.rb +1 -1
- data/lib/spree/testing_support/factories/variant_factory.rb +1 -1
- data/lib/spree/testing_support/i18n.rb +3 -94
- data/lib/spree/testing_support/lifecycle_events.rb +1 -1
- data/lib/spree/testing_support/order_walkthrough.rb +1 -1
- data/lib/spree/testing_support/preferences.rb +0 -5
- data/lib/spree/testing_support/store.rb +9 -1
- data/lib/spree/upgrades/5_6_to_6_0/manifest.yml +45 -0
- data/lib/tasks/categories_and_collections.rake +3 -3
- data/lib/tasks/core.rake +6 -4
- data/lib/tasks/media.rake +3 -3
- data/lib/tasks/package_types.rake +1 -1
- data/lib/tasks/preferences_json.rake +101 -0
- data/spec/fixtures/mailer_views/custom_views_probe_mailer/welcome_email.html.erb +4 -0
- metadata +188 -44
- data/app/models/acts_as_taggable_on/tag_decorator.rb +0 -10
- data/app/models/concerns/spree/has_image_alt_text.rb +0 -18
- data/app/models/spree/base_analytics_event_handler.rb +0 -54
- data/app/models/spree/invitations/store.rb +0 -6
- data/app/models/spree/preference.rb +0 -14
- data/app/models/spree/promotion_handler/page.rb +0 -25
- data/app/models/spree/promotion_handler/promotion_duplicator.rb +0 -60
- data/app/models/spree/stock/differentiator.rb +0 -45
- data/app/presenters/spree/filters/options_presenter.rb +0 -47
- data/app/services/spree/carts/empty.rb +0 -42
- data/app/services/spree/country_to_timezone.rb +0 -273
- data/app/services/spree/payments/create.rb +0 -92
- data/app/validators/spree/url_validator.rb +0 -23
- data/app/views/active_storage/blobs/_blob.html.erb +0 -14
- data/app/views/spree/admin_user_mailer/password_reset_email.html.erb +0 -8
- data/app/views/spree/export_mailer/export_done.html.erb +0 -7
- data/app/views/spree/import_mailer/import_done.html.erb +0 -12
- data/app/views/spree/invitation_mailer/invitation_accepted.html.erb +0 -4
- data/app/views/spree/invitation_mailer/invitation_email.html.erb +0 -12
- data/app/views/spree/seller_user_mailer/password_reset_email.html.erb +0 -8
- data/app/views/spree/shared/_base_mailer_footer.html.erb +0 -19
- data/app/views/spree/shared/_base_mailer_header.html.erb +0 -8
- data/app/views/spree/shared/_base_mailer_stylesheets.html.erb +0 -399
- data/app/views/spree/shared/_mailer_logo.html.erb +0 -41
- data/app/views/spree/shared/_purchased_items_styles.html.erb +0 -119
- data/app/views/spree/webhook_mailer/endpoint_disabled.html.erb +0 -51
- data/app/views/spree/webhook_mailer/endpoint_disabled.text.erb +0 -10
- data/app/workflows/spree/fulfillments/purchase_label.rb +0 -40
- data/app/workflows/spree/returns/purchase_label.rb +0 -32
- data/lib/spree/analytics.rb +0 -11
- data/lib/spree/core/preferences/scoped_store.rb +0 -34
- data/lib/spree/core/preferences/store.rb +0 -105
- data/lib/spree/translation_migrations.rb +0 -52
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
module Spree
|
|
2
|
+
# Wire shorthand for the plain-Ruby strategy classes a merchant picks from a
|
|
3
|
+
# registry (fulfillment, delivery-rate, pickup-point, payout and digital
|
|
4
|
+
# asset providers, order routing strategies). Records keep storing the class
|
|
5
|
+
# name; the API reads and writes this shorthand, so the contract never
|
|
6
|
+
# carries a Ruby constant.
|
|
7
|
+
#
|
|
8
|
+
# class Spree::FulfillmentProvider::Base
|
|
9
|
+
# extend Spree::ApiTyped
|
|
10
|
+
# end
|
|
11
|
+
#
|
|
12
|
+
# Spree::FulfillmentProvider::Manual.api_type # => "manual"
|
|
13
|
+
# SpreeEasyPost::FulfillmentProvider.api_type # => "easy_post"
|
|
14
|
+
module ApiTyped
|
|
15
|
+
# The demodulized + underscored leaf. A gem names its class after the
|
|
16
|
+
# family (`SpreeEasyPost::FulfillmentProvider`, `SpreeAcmeOms::Strategy`),
|
|
17
|
+
# where the leaf would collapse every gem to one value — those derive it
|
|
18
|
+
# from the gem's module instead, matching {Spree::Integration.api_type}.
|
|
19
|
+
# Any other class keeps its leaf, so two providers nested in one module
|
|
20
|
+
# never share a value. Override to keep the value stable across a rename.
|
|
21
|
+
#
|
|
22
|
+
# @return [String]
|
|
23
|
+
def api_type
|
|
24
|
+
leaf = name.to_s.demodulize
|
|
25
|
+
outer = name.to_s.deconstantize.demodulize.delete_prefix('Spree')
|
|
26
|
+
return leaf.underscore unless leaf == api_typed_family && outer.present?
|
|
27
|
+
|
|
28
|
+
outer.underscore
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
# The family's own name — the module its base class sits in
|
|
32
|
+
# (`FulfillmentProvider` for `Spree::FulfillmentProvider::Base`).
|
|
33
|
+
#
|
|
34
|
+
# @return [String, nil]
|
|
35
|
+
def api_typed_family
|
|
36
|
+
base = ancestors.reverse.find { |ancestor| ancestor.is_a?(Class) && ancestor.singleton_class.include?(Spree::ApiTyped) }
|
|
37
|
+
base&.name.to_s.deconstantize.demodulize.presence
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
# The registered class answering to a wire shorthand, or nil.
|
|
41
|
+
#
|
|
42
|
+
# @param registry [Array<Class, String>]
|
|
43
|
+
# @param api_type [String, nil]
|
|
44
|
+
# @return [Class, nil]
|
|
45
|
+
def self.find(registry, api_type)
|
|
46
|
+
return nil if api_type.blank?
|
|
47
|
+
|
|
48
|
+
Array(registry).map { |entry| entry.is_a?(Class) ? entry : entry.to_s.safe_constantize }.
|
|
49
|
+
compact.find { |klass| klass.api_type == api_type.to_s }
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
# The registered class a record's stored class name points at, or nil
|
|
53
|
+
# when it is no longer registered (a typo, an uninstalled gem) — so a
|
|
54
|
+
# stored string is never constantized outside the registry.
|
|
55
|
+
#
|
|
56
|
+
# @param registry [Array<Class>]
|
|
57
|
+
# @param class_name [String, nil]
|
|
58
|
+
# @return [Class, nil]
|
|
59
|
+
def self.registered_class(registry, class_name)
|
|
60
|
+
return nil if class_name.blank?
|
|
61
|
+
|
|
62
|
+
Array(registry).find { |klass| klass.to_s == class_name.to_s }
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
# The class name a record stores for a value arriving from the API. A
|
|
66
|
+
# registered shorthand becomes its class name; anything else passes through
|
|
67
|
+
# unchanged so the record's own inclusion validation rejects it.
|
|
68
|
+
#
|
|
69
|
+
# @param registry [Array<Class, String>]
|
|
70
|
+
# @param value [String, nil]
|
|
71
|
+
# @return [String, nil]
|
|
72
|
+
def self.class_name_for(registry, value)
|
|
73
|
+
return value if value.blank?
|
|
74
|
+
|
|
75
|
+
find(registry, value)&.to_s || value
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
# The shorthand for a stored class name. A class that is no longer
|
|
79
|
+
# registered (an uninstalled gem) is shortened the same way rather than
|
|
80
|
+
# constantized.
|
|
81
|
+
#
|
|
82
|
+
# @param class_name [String, nil]
|
|
83
|
+
# @return [String, nil]
|
|
84
|
+
def self.api_type_for(class_name)
|
|
85
|
+
return nil if class_name.blank?
|
|
86
|
+
|
|
87
|
+
klass = class_name.to_s.safe_constantize
|
|
88
|
+
klass.respond_to?(:api_type) ? klass.api_type : class_name.to_s.demodulize.underscore
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
end
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
module Spree
|
|
2
|
+
class Calculator < Spree.base_class
|
|
3
|
+
# A tier ladder for the tiered calculators: a list of
|
|
4
|
+
# `{ "threshold" => "100.0", "value" => "10.0" }` objects, where the value
|
|
5
|
+
# is a percentage or an amount depending on the calculator. A list rather
|
|
6
|
+
# than a hash keyed by threshold, because JSON object keys are always
|
|
7
|
+
# strings and a number key would not survive storage.
|
|
8
|
+
module Tiers
|
|
9
|
+
extend ActiveSupport::Concern
|
|
10
|
+
|
|
11
|
+
included do
|
|
12
|
+
preference :tiers, :array, default: [], parse_on_set: ->(value) { Tiers.normalize(value) }
|
|
13
|
+
|
|
14
|
+
validate :preferred_tiers_content
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
# @param value [Object] tiers as an operator or API client sent them
|
|
18
|
+
# @return [Object] an array of string-keyed hashes with numbers as exact
|
|
19
|
+
# decimal strings — the form JSON stores, so a fresh value compares
|
|
20
|
+
# equal to one read back — or the value unchanged when it is not a list
|
|
21
|
+
# of hashes, so validation can name the problem
|
|
22
|
+
def self.normalize(value)
|
|
23
|
+
return value unless value.is_a?(Array) && value.all? { |tier| tier.respond_to?(:to_h) && !tier.is_a?(Array) }
|
|
24
|
+
|
|
25
|
+
value.map do |tier|
|
|
26
|
+
tier.to_h.stringify_keys.to_h { |key, number| [key, decimal(number)&.as_json || number] }
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
# @return [BigDecimal, nil] nil when the value is not a number
|
|
31
|
+
def self.decimal(value)
|
|
32
|
+
BigDecimal(value.to_s, exception: false)
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
# The value of the highest tier whose threshold the amount reaches.
|
|
36
|
+
#
|
|
37
|
+
# @param amount [Numeric]
|
|
38
|
+
# @return [BigDecimal, nil] nil when the amount reaches no tier
|
|
39
|
+
def tier_value_for(amount)
|
|
40
|
+
tier_pairs.reverse_each.detect { |threshold, _| amount >= threshold }&.last
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
private
|
|
44
|
+
|
|
45
|
+
# Tiers that are not numbers are skipped: validation refuses them on
|
|
46
|
+
# save, but an unsaved calculator can still be asked to compute. Tiers
|
|
47
|
+
# still in the pre-6.0 hash keyed by threshold, left by an upgrade that
|
|
48
|
+
# could not convert them, keep discounting until they are converted.
|
|
49
|
+
#
|
|
50
|
+
# @return [Array<Array(BigDecimal, BigDecimal)>] threshold and value, lowest threshold first
|
|
51
|
+
def tier_pairs
|
|
52
|
+
tiers = preferred_tiers
|
|
53
|
+
pairs = case tiers
|
|
54
|
+
when Hash then tiers.to_a
|
|
55
|
+
when Array then tiers.grep(Hash).map { |tier| tier.values_at('threshold', 'value') }
|
|
56
|
+
else []
|
|
57
|
+
end
|
|
58
|
+
pairs.map { |threshold, value| [Tiers.decimal(threshold), Tiers.decimal(value)] }.select(&:all?).sort_by(&:first)
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def preferred_tiers_content
|
|
62
|
+
tiers = preferred_tiers
|
|
63
|
+
unless tiers.is_a?(Array) && tiers.all? { |tier| tier.is_a?(Hash) }
|
|
64
|
+
return errors.add(:preferred_tiers, :should_be_list)
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
thresholds = tiers.map { |tier| Tiers.decimal(tier['threshold']) }
|
|
68
|
+
values = tiers.map { |tier| Tiers.decimal(tier['value']) }
|
|
69
|
+
|
|
70
|
+
errors.add(:preferred_tiers, :thresholds_should_be_positive_number) unless thresholds.all? { |threshold| threshold&.positive? }
|
|
71
|
+
errors.add(:preferred_tiers, :thresholds_should_be_unique) unless thresholds.uniq.size == thresholds.size
|
|
72
|
+
validate_tier_values(values)
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
end
|
|
@@ -20,8 +20,10 @@ module Spree
|
|
|
20
20
|
# 2026-03-16 "Consolidate metadata") — no public/private split.
|
|
21
21
|
attribute :metadata, default: -> { {} }
|
|
22
22
|
|
|
23
|
-
# Enable lifecycle events for user models
|
|
24
23
|
publishes_lifecycle_events
|
|
24
|
+
publishes_events 'customer.password_reset', 'customer.anonymized'
|
|
25
|
+
publishes_event 'customer.password_reset_requested',
|
|
26
|
+
serializer: 'Spree::Api::V3::PasswordResetRequestedEventSerializer', credential: 'write_customers'
|
|
25
27
|
|
|
26
28
|
# Password reset token (Rails 7.1+ signed token, no DB column needed)
|
|
27
29
|
# Token auto-invalidates when password changes (salt changes)
|
|
@@ -184,13 +186,10 @@ module Spree
|
|
|
184
186
|
where(conditions.reduce(:or))
|
|
185
187
|
end
|
|
186
188
|
|
|
187
|
-
# Backward compatibility alias — remove in Spree 6.0
|
|
188
|
-
def self.multi_search(query) = search(query)
|
|
189
|
-
|
|
190
189
|
self.whitelisted_ransackable_associations = %w[bill_address ship_address addresses tags spree_roles orders customer_groups]
|
|
191
190
|
self.whitelisted_ransackable_attributes = %w[id email first_name last_name phone accepts_email_marketing
|
|
192
191
|
created_at updated_at last_sign_in_at]
|
|
193
|
-
self.whitelisted_ransackable_scopes = %w[search
|
|
192
|
+
self.whitelisted_ransackable_scopes = %w[search with_min_total_spent with_standing_for_company
|
|
194
193
|
anonymized]
|
|
195
194
|
|
|
196
195
|
# Two-state scope: see Spree::Base.ransack_flag? for why the cast is
|
|
@@ -278,6 +277,16 @@ module Spree
|
|
|
278
277
|
end
|
|
279
278
|
end
|
|
280
279
|
|
|
280
|
+
class_methods do
|
|
281
|
+
# Customer lifecycle events are `user.*`; an installation whose staff
|
|
282
|
+
# class shares these methods publishes theirs as `admin.*`.
|
|
283
|
+
#
|
|
284
|
+
# @return [String]
|
|
285
|
+
def event_prefix
|
|
286
|
+
self == Spree.admin_user_class && Spree.admin_user_class != Spree.customer_class ? 'admin' : 'user'
|
|
287
|
+
end
|
|
288
|
+
end
|
|
289
|
+
|
|
281
290
|
# Returns the last incomplete spree order for the current store
|
|
282
291
|
# @deprecated Carts are {Spree::Cart} since 6.0, so an incomplete order is
|
|
283
292
|
# a backoffice draft rather than the customer's cart. Use
|
|
@@ -337,7 +346,7 @@ module Spree
|
|
|
337
346
|
# @return [Spree::Wishlist]
|
|
338
347
|
def default_wishlist_for_store(current_store)
|
|
339
348
|
wishlists.find_by(is_default: true, store_id: current_store.id) || ActiveRecord::Base.connected_to(role: :writing) do
|
|
340
|
-
wishlists.create!(store: current_store, is_default: true, name:
|
|
349
|
+
wishlists.create!(store: current_store, is_default: true, name: I18n.t('spree.default_wishlist_name'))
|
|
341
350
|
end
|
|
342
351
|
end
|
|
343
352
|
|
|
@@ -368,14 +377,6 @@ module Spree
|
|
|
368
377
|
'Spree::Api::V3::CustomerSerializer'.safe_constantize
|
|
369
378
|
end
|
|
370
379
|
|
|
371
|
-
def event_prefix
|
|
372
|
-
if self.class == Spree.admin_user_class && Spree.admin_user_class != Spree.customer_class
|
|
373
|
-
'admin'
|
|
374
|
-
else
|
|
375
|
-
'user'
|
|
376
|
-
end
|
|
377
|
-
end
|
|
378
|
-
|
|
379
380
|
# @param store [Spree::Store] store to scope the lookup to; defaults to the current store
|
|
380
381
|
# @return [Spree::NewsletterSubscriber, nil] this user's newsletter subscriber for the given store
|
|
381
382
|
def newsletter_subscriber(store = Spree::Current.store)
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
module Spree
|
|
2
|
+
# What a store's published email template and its draft share: which
|
|
3
|
+
# editable template they are, in which language, and the default they
|
|
4
|
+
# started from.
|
|
5
|
+
module EmailTemplateIdentity
|
|
6
|
+
extend ActiveSupport::Concern
|
|
7
|
+
|
|
8
|
+
# The version every language uses. Never NULL, so the unique index on
|
|
9
|
+
# store, key and locale holds for it too.
|
|
10
|
+
ANY_LOCALE = 'any'.freeze
|
|
11
|
+
LOCALE_FORMAT = /\A(any|[a-z]{2,3}(-[A-Za-z0-9]{2,8})*)\z/
|
|
12
|
+
|
|
13
|
+
included do
|
|
14
|
+
include Spree::SingleStoreResource
|
|
15
|
+
|
|
16
|
+
normalizes :locale, with: ->(locale) { locale.to_s.strip.presence || ANY_LOCALE }, apply_to_nil: true
|
|
17
|
+
|
|
18
|
+
validates :key, presence: true, uniqueness: { scope: spree_base_uniqueness_scope + [:store_id, :locale] }
|
|
19
|
+
validates :locale, format: { with: LOCALE_FORMAT }
|
|
20
|
+
validates :body, :base_body, presence: true
|
|
21
|
+
validate :key_editable
|
|
22
|
+
validate :locale_supported
|
|
23
|
+
|
|
24
|
+
scope :for_key, ->(key, locale = ANY_LOCALE) { where(key: key, locale: locale) }
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
# @return [Spree::Emails::EditableTemplates::Definition, nil]
|
|
28
|
+
def definition
|
|
29
|
+
Spree.editable_email_templates[key]
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
# @return [Boolean] whether Spree's default changed since this version started from it
|
|
33
|
+
# @param default [Spree::Emails::Template, nil] the current default
|
|
34
|
+
def default_changed?(default)
|
|
35
|
+
return false unless default
|
|
36
|
+
|
|
37
|
+
default.body != base_body || default.subject.to_s != base_subject.to_s
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
private
|
|
41
|
+
|
|
42
|
+
def key_editable
|
|
43
|
+
errors.add(:key, :invalid) if key.present? && definition.nil?
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def locale_supported
|
|
47
|
+
return if locale == ANY_LOCALE || store.nil?
|
|
48
|
+
|
|
49
|
+
errors.add(:locale, :inclusion) unless store.supported_locales_list.include?(locale)
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|
|
@@ -24,10 +24,16 @@ module Spree
|
|
|
24
24
|
pending?
|
|
25
25
|
end
|
|
26
26
|
|
|
27
|
-
%w[complete fail
|
|
27
|
+
%w[complete fail expire].each do |transition|
|
|
28
28
|
define_method(:"can_#{transition}?") { pending? || processing? }
|
|
29
29
|
end
|
|
30
30
|
|
|
31
|
+
# A failed confirm can leave the provider intent payable. Cancel is how
|
|
32
|
+
# placement retires it; the other transitions stay closed.
|
|
33
|
+
def can_cancel?
|
|
34
|
+
pending? || processing? || failed?
|
|
35
|
+
end
|
|
36
|
+
|
|
31
37
|
def process
|
|
32
38
|
return false unless transition_to('processing', from: %w[pending])
|
|
33
39
|
|
|
@@ -50,7 +56,7 @@ module Spree
|
|
|
50
56
|
end
|
|
51
57
|
|
|
52
58
|
def cancel
|
|
53
|
-
return false unless transition_to('canceled', from: %w[pending processing])
|
|
59
|
+
return false unless transition_to('canceled', from: %w[pending processing failed])
|
|
54
60
|
|
|
55
61
|
publish_canceled_event
|
|
56
62
|
true
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
module Spree
|
|
2
|
+
module PostSale
|
|
3
|
+
# Asks the order's tax provider what tax a return, claim or exchange gives
|
|
4
|
+
# back. Including models name their lines through +taxed_lines+.
|
|
5
|
+
#
|
|
6
|
+
# The provider is asked twice: when the record opens, for what was
|
|
7
|
+
# announced, and again before money moves, for what actually goes back.
|
|
8
|
+
# Only the first is copied onto the lines' columns — they keep what was
|
|
9
|
+
# announced, the way a return line's +pre_tax_amount+ does — while the rows
|
|
10
|
+
# always say what the customer is, or was, refunded.
|
|
11
|
+
module Taxation
|
|
12
|
+
extend ActiveSupport::Concern
|
|
13
|
+
|
|
14
|
+
# @return [void]
|
|
15
|
+
# @raise [Spree::Tax::ProviderError]
|
|
16
|
+
def calculate_tax!
|
|
17
|
+
lines = taxed_lines.to_a
|
|
18
|
+
estimate_tax(lines)
|
|
19
|
+
lines.each(&:fold_tax_lines!)
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
# @param amounts [Hash{Object => BigDecimal}, nil] the money refunded per
|
|
23
|
+
# line when it is less than the line is worth
|
|
24
|
+
# @return [void]
|
|
25
|
+
# @raise [Spree::Tax::ProviderError]
|
|
26
|
+
def settle_tax!(amounts: nil)
|
|
27
|
+
lines = taxed_lines.select(&:settles_tax?)
|
|
28
|
+
estimate_tax(lines, amounts: amounts) if lines.any?
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
# Drops every row on the record's lines, for a record withdrawn before
|
|
32
|
+
# anything went back. Nothing is worked out, so the provider is not asked
|
|
33
|
+
# — a cancellation must not wait on, or fail with, a tax service.
|
|
34
|
+
#
|
|
35
|
+
# @return [void]
|
|
36
|
+
def clear_tax!
|
|
37
|
+
Spree::TaxLine.post_sale.where(Spree::TaxLine.adjustable_key_for(taxed_lines.klass) => taxed_lines.select(:id)).delete_all
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
# The tax the record's lines give back, as their credit rows stand.
|
|
41
|
+
#
|
|
42
|
+
# @return [BigDecimal]
|
|
43
|
+
def credited_tax_total
|
|
44
|
+
taxed_lines.sum(0.to_d, &:credited_tax_total)
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
private
|
|
48
|
+
|
|
49
|
+
def estimate_tax(lines, amounts: nil)
|
|
50
|
+
order.tax_provider.estimate_refund(order, lines, amounts: amounts, tax_date: order.completed_at)
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
module Spree
|
|
2
|
+
module PostSale
|
|
3
|
+
# The tax side of a line on a return, claim or exchange: the rows the tax
|
|
4
|
+
# provider writes on it (see Spree::TaxProvider::Base#estimate_refund).
|
|
5
|
+
#
|
|
6
|
+
# Including models answer +credited_quantity+ and +credited_worth+, which
|
|
7
|
+
# the provider reads, and +fold_tax_lines!+, which copies the rows written
|
|
8
|
+
# at opening onto the line's own columns.
|
|
9
|
+
module TaxedLine
|
|
10
|
+
extend ActiveSupport::Concern
|
|
11
|
+
|
|
12
|
+
included do
|
|
13
|
+
has_many :tax_lines, class_name: 'Spree::TaxLine', dependent: :destroy
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
# Whether this line's tax goes through the provider when it is refunded.
|
|
17
|
+
# A line opened before refunds carried tax recorded none against a taxed
|
|
18
|
+
# sale line, and is refunded exactly as it was opened.
|
|
19
|
+
#
|
|
20
|
+
# Read from the columns, which are written once at opening, rather than
|
|
21
|
+
# the rows: settling rewrites or deletes the rows, and a settle whose
|
|
22
|
+
# money step then failed would otherwise leave the line looking like one
|
|
23
|
+
# that never carried tax.
|
|
24
|
+
#
|
|
25
|
+
# @return [Boolean]
|
|
26
|
+
def settles_tax?
|
|
27
|
+
recorded_tax_total.positive? || line_item.nil? || !line_item.tax_lines.exists?
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
# The tax this line gives back, as its credit rows stand now.
|
|
31
|
+
#
|
|
32
|
+
# @return [BigDecimal]
|
|
33
|
+
def credited_tax_total
|
|
34
|
+
tax_lines.credits.sum(:amount)
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
private
|
|
38
|
+
|
|
39
|
+
# The tax copied onto the line's columns when it opened.
|
|
40
|
+
def recorded_tax_total
|
|
41
|
+
tax_total
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
# @return [Array(BigDecimal, BigDecimal)] included and additional tax
|
|
45
|
+
def tax_totals_from(rows)
|
|
46
|
+
included, additional = rows.partition(&:included?)
|
|
47
|
+
[included.sum(0.to_d, &:amount), additional.sum(0.to_d, &:amount)]
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
|
@@ -93,17 +93,6 @@ module Spree
|
|
|
93
93
|
.freeze
|
|
94
94
|
end
|
|
95
95
|
|
|
96
|
-
# Declared order first, so a form reads the way its author grouped the
|
|
97
|
-
# fields; anything the macro did not record (an association writer that
|
|
98
|
-
# looks like a preference) keeps its existing place at the end.
|
|
99
|
-
def ordered_preferences(instance)
|
|
100
|
-
defined = instance.defined_preferences
|
|
101
|
-
return defined unless respond_to?(:declared_preference_order)
|
|
102
|
-
|
|
103
|
-
declared = declared_preference_order & defined
|
|
104
|
-
declared + (defined - declared)
|
|
105
|
-
end
|
|
106
|
-
|
|
107
96
|
def compute_preference_schema
|
|
108
97
|
# Only instantiation is guarded. `new` touches the database to read
|
|
109
98
|
# the column list, so it fails whenever the schema is asked for before
|
|
@@ -121,7 +110,7 @@ module Spree
|
|
|
121
110
|
return nil
|
|
122
111
|
end
|
|
123
112
|
|
|
124
|
-
|
|
113
|
+
instance.defined_preferences.filter_map do |pref|
|
|
125
114
|
next if instance.preference_deprecated(pref)
|
|
126
115
|
# Written by Spree, not supplied by the operator — a value a
|
|
127
116
|
# provider hands back after we register something with it. Offering
|
|
@@ -216,6 +205,9 @@ module Spree
|
|
|
216
205
|
# their wire shape byte-identical. Pickers stop offering a
|
|
217
206
|
# superseded kind; existing rows still render and match.
|
|
218
207
|
entry[:superseded] = true if klass.respond_to?(:superseded?) && klass.superseded?
|
|
208
|
+
# Provider families (payment methods) add their gallery logo and
|
|
209
|
+
# setup guide; other families keep their wire shape unchanged.
|
|
210
|
+
entry.merge!(klass.provider_listing) if klass.respond_to?(:provider_listing)
|
|
219
211
|
entry
|
|
220
212
|
end.sort_by { |entry| entry[:label] }
|
|
221
213
|
end
|
|
@@ -156,7 +156,8 @@ module Spree
|
|
|
156
156
|
where(Spree::ProductCategory.table_name => { category_id: taxon_ids }).
|
|
157
157
|
select("#{Product.table_name}.*", "#{min_position_sql} AS min_taxon_position").
|
|
158
158
|
group("#{Product.table_name}.id").
|
|
159
|
-
order
|
|
159
|
+
# Ties fall back to id, so the order holds across queries and pages.
|
|
160
|
+
order(Arel.sql("#{min_position_sql} ASC"), arel_table[:id].asc)
|
|
160
161
|
}
|
|
161
162
|
|
|
162
163
|
scope :with_option_value, ->(option, value) {
|
|
@@ -218,6 +219,15 @@ module Spree
|
|
|
218
219
|
channel = Spree::Current.channel
|
|
219
220
|
return where(discontinue_on: [nil, Time.current.beginning_of_minute..]) unless channel
|
|
220
221
|
|
|
222
|
+
not_discontinued_on(channel)
|
|
223
|
+
end
|
|
224
|
+
|
|
225
|
+
# Published on the channel and not yet unpublished from it — a channel
|
|
226
|
+
# publication's end date is the product's discontinuation there.
|
|
227
|
+
#
|
|
228
|
+
# @param channel [Spree::Channel]
|
|
229
|
+
# @return [ActiveRecord::Relation<Spree::Product>]
|
|
230
|
+
def self.not_discontinued_on(channel)
|
|
221
231
|
for_channel(channel).where(Spree::ProductPublication.table_name => { unpublished_at: [nil, Time.current.beginning_of_minute..] })
|
|
222
232
|
end
|
|
223
233
|
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
module Spree
|
|
2
|
+
# What a provider shows on its gallery card in the dashboard: a logo and a
|
|
3
|
+
# link to its setup guide. Shared by integrations and payment methods so a
|
|
4
|
+
# provider gem declares both the same way whichever family it extends.
|
|
5
|
+
#
|
|
6
|
+
# class SpreeStripe::Gateway < Spree::Gateway
|
|
7
|
+
# def self.logo_url = 'https://example.com/stripe.png'
|
|
8
|
+
# def self.docs_url = 'https://spreecommerce.org/docs/integrations/payments/stripe'
|
|
9
|
+
# end
|
|
10
|
+
module ProviderListing
|
|
11
|
+
extend ActiveSupport::Concern
|
|
12
|
+
|
|
13
|
+
class_methods do
|
|
14
|
+
# Logo shown on the gallery card: an absolute URL to publicly hosted
|
|
15
|
+
# brand assets, or a `data:` URI for gems that want to be self-contained
|
|
16
|
+
# (works air-gapped, no CSP domain to allowlist). Anything an `<img src>`
|
|
17
|
+
# accepts. Deliberately not an asset-pipeline path — provider gems must
|
|
18
|
+
# not force an asset pipeline onto headless API hosts. Hosted logos are a
|
|
19
|
+
# courtesy, not a guarantee: the dashboard falls back to a letter avatar
|
|
20
|
+
# when unset or unreachable.
|
|
21
|
+
#
|
|
22
|
+
# @return [String, nil]
|
|
23
|
+
def logo_url
|
|
24
|
+
nil
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
# Absolute URL of the provider's setup guide, linked from its gallery
|
|
28
|
+
# card. A third-party gem points at its own documentation.
|
|
29
|
+
#
|
|
30
|
+
# @return [String, nil]
|
|
31
|
+
def docs_url
|
|
32
|
+
nil
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
# @return [Hash] the listing attributes merged into a type-discovery entry
|
|
36
|
+
def provider_listing
|
|
37
|
+
{ logo_url: logo_url, docs_url: docs_url }
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
@@ -36,9 +36,12 @@ module Spree
|
|
|
36
36
|
module Publishable
|
|
37
37
|
extend ActiveSupport::Concern
|
|
38
38
|
|
|
39
|
+
LIFECYCLE_ACTIONS = { create: :created, update: :updated, delete: :deleted }.freeze
|
|
40
|
+
|
|
39
41
|
included do
|
|
40
42
|
class_attribute :publish_events, default: true
|
|
41
43
|
class_attribute :lifecycle_events_enabled, default: false
|
|
44
|
+
class_attribute :lifecycle_event_actions, default: []
|
|
42
45
|
|
|
43
46
|
after_update :mark_real_update_for_events
|
|
44
47
|
end
|
|
@@ -76,6 +79,9 @@ module Spree
|
|
|
76
79
|
events &= Array(options[:only]) if options[:only]
|
|
77
80
|
events -= Array(options[:except]) if options[:except]
|
|
78
81
|
|
|
82
|
+
self.lifecycle_event_actions = events.map { |event| LIFECYCLE_ACTIONS.fetch(event) }
|
|
83
|
+
lifecycle_event_actions.each { |action| publishes_event(action) }
|
|
84
|
+
|
|
79
85
|
if events.include?(:create)
|
|
80
86
|
after_commit :publish_create_event, on: :create, if: :should_publish_events?
|
|
81
87
|
end
|
|
@@ -90,6 +96,51 @@ module Spree
|
|
|
90
96
|
end
|
|
91
97
|
end
|
|
92
98
|
|
|
99
|
+
# A subclass publishes its lifecycle events under its own prefix, which may
|
|
100
|
+
# differ from its parent's, so they are declared again for it.
|
|
101
|
+
def inherited(subclass)
|
|
102
|
+
super
|
|
103
|
+
subclass.lifecycle_event_actions.each { |action| subclass.publishes_event(action) }
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
# Declare events this model publishes, each carrying this model's Store
|
|
107
|
+
# serializer as its payload.
|
|
108
|
+
#
|
|
109
|
+
# @param names [Array<Symbol, String>] actions (`:placed`, prefixed with
|
|
110
|
+
# {event_prefix}) or full dotted names (`'customer.password_reset'`)
|
|
111
|
+
# @param options [Hash] see {publishes_event}
|
|
112
|
+
# @return [void]
|
|
113
|
+
#
|
|
114
|
+
# @example
|
|
115
|
+
# publishes_events :placed, :paid, :shipped
|
|
116
|
+
def publishes_events(*names, **options)
|
|
117
|
+
names.each { |name| publishes_event(name, **options) }
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
# Declare one event this model publishes. Publishing a name no model
|
|
121
|
+
# declared raises in development and test.
|
|
122
|
+
#
|
|
123
|
+
# @param name [Symbol, String] an action or a full dotted name
|
|
124
|
+
# @param serializer [String, nil] payload serializer class name, when the
|
|
125
|
+
# payload is not this model's Store serializer
|
|
126
|
+
# @param credential [String, nil] for a payload carrying a live credential,
|
|
127
|
+
# the permission key needed to point a webhook endpoint at it; such an
|
|
128
|
+
# event reaches only endpoints that name it outright
|
|
129
|
+
# @param deprecated_alias_of [String, nil] the event this one duplicates
|
|
130
|
+
# @param webhook [Boolean] false for an event no webhook endpoint may
|
|
131
|
+
# receive, even one subscribed to `*`
|
|
132
|
+
# @return [void]
|
|
133
|
+
#
|
|
134
|
+
# @example
|
|
135
|
+
# publishes_event :canceled, serializer: 'Spree::Api::V3::OrderCanceledEventSerializer'
|
|
136
|
+
# publishes_event 'customer.password_reset_requested', serializer: '...', credential: 'write_customers'
|
|
137
|
+
def publishes_event(name, serializer: nil, credential: nil, deprecated_alias_of: nil, webhook: true)
|
|
138
|
+
Spree::Events.catalog.declare(
|
|
139
|
+
self, name,
|
|
140
|
+
serializer: serializer, credential: credential, deprecated_alias_of: deprecated_alias_of, webhook: webhook
|
|
141
|
+
)
|
|
142
|
+
end
|
|
143
|
+
|
|
93
144
|
# Disable lifecycle events for this model
|
|
94
145
|
#
|
|
95
146
|
# @example
|
|
@@ -145,10 +196,13 @@ module Spree
|
|
|
145
196
|
# order.publish_event('order.placed', metadata: { user_id: 1 })
|
|
146
197
|
#
|
|
147
198
|
def publish_event(event_name, payload = nil, metadata = {})
|
|
199
|
+
# Checked even while events are disabled, so every spec that reaches a
|
|
200
|
+
# publish also proves the event is declared.
|
|
201
|
+
Spree::Events.catalog.verify_declared!(event_name)
|
|
148
202
|
return unless Spree::Events.enabled?
|
|
149
203
|
|
|
150
204
|
@_current_event_name = event_name
|
|
151
|
-
payload ||=
|
|
205
|
+
payload ||= event_payload_for(event_name)
|
|
152
206
|
Spree::Events.publish(event_name, payload, metadata)
|
|
153
207
|
ensure
|
|
154
208
|
@_current_event_name = nil
|
|
@@ -161,20 +215,19 @@ module Spree
|
|
|
161
215
|
#
|
|
162
216
|
# @return [Hash]
|
|
163
217
|
def event_payload
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
unless serializer
|
|
167
|
-
return {
|
|
168
|
-
id: respond_to?(:prefixed_id) ? prefixed_id : id,
|
|
169
|
-
created_at: created_at&.iso8601,
|
|
170
|
-
updated_at: updated_at&.iso8601
|
|
171
|
-
}
|
|
172
|
-
end
|
|
218
|
+
build_event_payload(event_serializer_class)
|
|
219
|
+
end
|
|
173
220
|
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
221
|
+
# The payload for one event, built with the serializer the catalog declares
|
|
222
|
+
# for it, falling back to this model's Store serializer.
|
|
223
|
+
#
|
|
224
|
+
# @param event_name [String]
|
|
225
|
+
# @param params [Hash] extra serializer params the event serializer reads,
|
|
226
|
+
# e.g. the reset token of a password reset request
|
|
227
|
+
# @return [Hash]
|
|
228
|
+
def event_payload_for(event_name, **params)
|
|
229
|
+
serializer = Spree::Events.catalog.find(event_name)&.serializer_name&.safe_constantize
|
|
230
|
+
build_event_payload(serializer || event_serializer_class, params)
|
|
178
231
|
end
|
|
179
232
|
|
|
180
233
|
# Find the event serializer class for this model
|
|
@@ -223,6 +276,21 @@ module Spree
|
|
|
223
276
|
|
|
224
277
|
private
|
|
225
278
|
|
|
279
|
+
def build_event_payload(serializer, params = {})
|
|
280
|
+
unless serializer
|
|
281
|
+
return {
|
|
282
|
+
id: respond_to?(:prefixed_id) ? prefixed_id : id,
|
|
283
|
+
created_at: created_at&.iso8601,
|
|
284
|
+
updated_at: updated_at&.iso8601
|
|
285
|
+
}
|
|
286
|
+
end
|
|
287
|
+
|
|
288
|
+
# Use as_json to ensure all values are JSON-safe primitives.
|
|
289
|
+
# Alba's to_h can return raw Ruby objects (e.g., Spree::Money) which
|
|
290
|
+
# ActiveJob cannot serialize for async event subscribers.
|
|
291
|
+
serializer.new(self, params: event_serializer_params.merge(params)).to_h.as_json
|
|
292
|
+
end
|
|
293
|
+
|
|
226
294
|
# Build params for V3 serializers
|
|
227
295
|
#
|
|
228
296
|
# @return [Hash]
|