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,82 @@
|
|
|
1
|
+
module Spree
|
|
2
|
+
module Emails
|
|
3
|
+
# The email templates merchants may edit in the dashboard: the emails
|
|
4
|
+
# customers receive, plus the layout and shared partials those emails are
|
|
5
|
+
# built from. Staff, store-owner and seller emails are never registered, so
|
|
6
|
+
# they always render from files. `spree_emails` registers its own; an
|
|
7
|
+
# extension adding a customer email registers it the same way.
|
|
8
|
+
#
|
|
9
|
+
# @example
|
|
10
|
+
# Spree.editable_email_templates.register(
|
|
11
|
+
# 'spree/loyalty_mailer/points_earned_email',
|
|
12
|
+
# kind: :email, sample: 'MyApp::EmailSamples::PointsEarned'
|
|
13
|
+
# )
|
|
14
|
+
class EditableTemplates
|
|
15
|
+
include Enumerable
|
|
16
|
+
|
|
17
|
+
KINDS = %i[email layout partial].freeze
|
|
18
|
+
|
|
19
|
+
# One editable template.
|
|
20
|
+
class Definition
|
|
21
|
+
attr_reader :key, :kind, :sample
|
|
22
|
+
|
|
23
|
+
# @param key [String] the template's view path, e.g. "spree/order_mailer/confirm_email"
|
|
24
|
+
# @param kind [Symbol] :email, :layout or :partial
|
|
25
|
+
# @param sample [String, nil] class building sample variables for an email (see Spree::Emails::Samples::Base)
|
|
26
|
+
def initialize(key:, kind:, sample: nil)
|
|
27
|
+
raise ArgumentError, "Unknown email template kind: #{kind.inspect}" unless KINDS.include?(kind)
|
|
28
|
+
|
|
29
|
+
@key = key
|
|
30
|
+
@kind = kind
|
|
31
|
+
@sample = sample
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def email?
|
|
35
|
+
kind == :email
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
# @return [Class, nil]
|
|
39
|
+
def sample_class
|
|
40
|
+
sample&.constantize
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def initialize
|
|
45
|
+
@definitions = {}
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
# @param key [String]
|
|
49
|
+
# @param kind [Symbol]
|
|
50
|
+
# @param sample [String, nil]
|
|
51
|
+
# @return [Spree::Emails::EditableTemplates::Definition]
|
|
52
|
+
def register(key, kind: :email, sample: nil)
|
|
53
|
+
@definitions[key] = Definition.new(key: key, kind: kind, sample: sample)
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
# @param key [String]
|
|
57
|
+
def delete(key)
|
|
58
|
+
@definitions.delete(key)
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
# @param key [String]
|
|
62
|
+
# @return [Spree::Emails::EditableTemplates::Definition, nil]
|
|
63
|
+
def [](key)
|
|
64
|
+
@definitions[key]
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
# @param key [String]
|
|
68
|
+
def include?(key)
|
|
69
|
+
@definitions.key?(key)
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def each(&block)
|
|
73
|
+
@definitions.each_value(&block)
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
# @return [Array<Spree::Emails::EditableTemplates::Definition>] the editable emails, without layout and partials
|
|
77
|
+
def emails
|
|
78
|
+
select(&:email?)
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
end
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Spree
|
|
4
|
+
module Events
|
|
5
|
+
# Raised in development and test when code publishes an event that no model
|
|
6
|
+
# declared with `publishes_event`.
|
|
7
|
+
class UndeclaredEventError < StandardError; end
|
|
8
|
+
|
|
9
|
+
# Every event Spree publishes, declared once on the model whose record is the
|
|
10
|
+
# payload (`publishes_events` / `publishes_event` / `publishes_lifecycle_events`).
|
|
11
|
+
#
|
|
12
|
+
# The catalog is the single source for the webhook event types generated into
|
|
13
|
+
# `@spree/sdk/webhooks`, the Admin API list of subscribable events and the
|
|
14
|
+
# rule that keeps credential-carrying events off wildcard webhook endpoints.
|
|
15
|
+
class Catalog
|
|
16
|
+
# One declared event.
|
|
17
|
+
#
|
|
18
|
+
# @!attribute name [String] the full event name, e.g. `order.placed`
|
|
19
|
+
# @!attribute model_name [String] the declaring model's class name
|
|
20
|
+
# @!attribute serializer_name [String, nil] the serializer named in the declaration, if any
|
|
21
|
+
# @!attribute credential_permission [String, nil] for an event carrying a live
|
|
22
|
+
# credential, the permission needed to point a webhook endpoint at it
|
|
23
|
+
# @!attribute deprecated_alias_of [String, nil] the event this one duplicates, when deprecated
|
|
24
|
+
# @!attribute webhook [Boolean] false for an event never forwarded to webhook endpoints
|
|
25
|
+
Entry = Data.define(:name, :model_name, :serializer_name, :credential_permission, :deprecated_alias_of, :webhook) do
|
|
26
|
+
# The resource segment of the name, used to group events for display.
|
|
27
|
+
#
|
|
28
|
+
# @return [String] e.g. `order` for `order.placed`
|
|
29
|
+
def group
|
|
30
|
+
name.split('.').first
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
# Whether the payload carries a live credential (a password reset token),
|
|
34
|
+
# which a webhook endpoint receives only when it names the event outright.
|
|
35
|
+
#
|
|
36
|
+
# @return [Boolean]
|
|
37
|
+
def credential?
|
|
38
|
+
credential_permission.present?
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
alias_method :webhook?, :webhook
|
|
42
|
+
|
|
43
|
+
# @return [Boolean]
|
|
44
|
+
def deprecated?
|
|
45
|
+
deprecated_alias_of.present?
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
# The serializer that builds the payload: the declared one, or the model's
|
|
49
|
+
# Store serializer resolved the way `Spree::Publishable#event_payload` does.
|
|
50
|
+
# Nil when there is none — the payload is then only the record's `id`,
|
|
51
|
+
# `created_at` and `updated_at` — or when `spree_api` is not installed.
|
|
52
|
+
#
|
|
53
|
+
# @return [Class, nil]
|
|
54
|
+
def payload_serializer
|
|
55
|
+
serializer_name&.safe_constantize || model_name.constantize.allocate.event_serializer_class
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def initialize
|
|
60
|
+
@declarations = {}
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
# Declare an event for a model. A bare action (`:placed`) is prefixed with
|
|
64
|
+
# the model's event prefix when the catalog is read, so a prefix assigned
|
|
65
|
+
# later in the class body still applies; a dotted name is used as given.
|
|
66
|
+
#
|
|
67
|
+
# Re-declaring the same model and name replaces the earlier declaration,
|
|
68
|
+
# which keeps the catalog correct across code reloads.
|
|
69
|
+
#
|
|
70
|
+
# @param model [Class] the model whose record is the payload
|
|
71
|
+
# @param name [Symbol, String] an action or a full event name
|
|
72
|
+
# @param options [Hash] `serializer:`, `credential:` (the permission key
|
|
73
|
+
# required to receive it), `deprecated_alias_of:`, `webhook:` (false to
|
|
74
|
+
# never forward it to webhook endpoints)
|
|
75
|
+
# @return [void]
|
|
76
|
+
def declare(model, name, **options)
|
|
77
|
+
# A named class is keyed by its name so a reloaded class replaces its
|
|
78
|
+
# stale copy; an anonymous one (a spec's `Class.new`) by the class.
|
|
79
|
+
@declarations[[model.name || model, name.to_s]] = options
|
|
80
|
+
@entries = @sorted = nil
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
# @param name [String]
|
|
84
|
+
# @return [Entry, nil]
|
|
85
|
+
def find(name)
|
|
86
|
+
entries[name.to_s]
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
# Every declared event, sorted by name.
|
|
90
|
+
#
|
|
91
|
+
# @return [Array<Entry>]
|
|
92
|
+
def all
|
|
93
|
+
load!
|
|
94
|
+
@sorted ||= entries.values.sort_by(&:name)
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
# Events a webhook endpoint can subscribe to.
|
|
98
|
+
#
|
|
99
|
+
# @return [Array<Entry>]
|
|
100
|
+
def webhook_events
|
|
101
|
+
all.select(&:webhook?)
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
# Whether an event may be forwarded to webhook endpoints. An undeclared
|
|
105
|
+
# event may, so a production store keeps delivering it while the missing
|
|
106
|
+
# declaration is only logged. A miss loads every model first: where
|
|
107
|
+
# classes load lazily, the model declaring `webhook: false` may not be
|
|
108
|
+
# loaded in the process delivering webhooks yet.
|
|
109
|
+
#
|
|
110
|
+
# @param name [String]
|
|
111
|
+
# @return [Boolean]
|
|
112
|
+
def webhook?(name)
|
|
113
|
+
entry = find(name)
|
|
114
|
+
unless entry
|
|
115
|
+
load!
|
|
116
|
+
entry = find(name)
|
|
117
|
+
end
|
|
118
|
+
entry.nil? || entry.webhook?
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
# @param name [String]
|
|
122
|
+
# @return [Boolean]
|
|
123
|
+
def credential?(name)
|
|
124
|
+
find(name)&.credential? || false
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
# Events whose payload carries a live credential, optionally only those
|
|
128
|
+
# among the given names.
|
|
129
|
+
#
|
|
130
|
+
# @param names [Array<String>, nil]
|
|
131
|
+
# @return [Array<Entry>]
|
|
132
|
+
def credential_events(names = nil)
|
|
133
|
+
events = all.select(&:credential?)
|
|
134
|
+
names ? events.select { |entry| names.include?(entry.name) } : events
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
# Checks that an event about to be published is declared. Raises
|
|
138
|
+
# {UndeclaredEventError} in development and test, logs a warning otherwise.
|
|
139
|
+
#
|
|
140
|
+
# @param name [String]
|
|
141
|
+
# @return [void]
|
|
142
|
+
def verify_declared!(name)
|
|
143
|
+
return if find(name)
|
|
144
|
+
|
|
145
|
+
# A class declared before it had a name is only listed once it has one,
|
|
146
|
+
# so a miss rebuilds once — and only while anonymous declarations exist.
|
|
147
|
+
if !@loaded || @declarations.each_key.any? { |model_key, _| model_key.is_a?(Class) }
|
|
148
|
+
@entries = @sorted = nil
|
|
149
|
+
load!
|
|
150
|
+
return if find(name)
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
message = "Spree event #{name.inspect} is not declared. Declare it with `publishes_event` on the model whose record is the payload."
|
|
154
|
+
raise UndeclaredEventError, message if Spree::Events.raise_on_undeclared_events
|
|
155
|
+
|
|
156
|
+
Rails.logger.warn("[Spree Events] #{message}")
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
# Loads every model so the catalog holds all declarations, which only
|
|
160
|
+
# matters where Rails does not eager load (development and test).
|
|
161
|
+
#
|
|
162
|
+
# @return [void]
|
|
163
|
+
def load!
|
|
164
|
+
return if @loaded
|
|
165
|
+
|
|
166
|
+
@loaded = true
|
|
167
|
+
Rails.application.eager_load! unless Rails.application.config.eager_load
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
private
|
|
171
|
+
|
|
172
|
+
def entries
|
|
173
|
+
@entries ||= @declarations.each_with_object({}) do |((model_key, name), options), result|
|
|
174
|
+
model = model_key.is_a?(Class) ? model_key : model_key.safe_constantize
|
|
175
|
+
next unless model&.name
|
|
176
|
+
|
|
177
|
+
# A parent and its subclass can declare the same name; the parent,
|
|
178
|
+
# declared first, stays the owner.
|
|
179
|
+
full_name = name.include?('.') ? name : "#{model.event_prefix}.#{name}"
|
|
180
|
+
result[full_name] ||= Entry.new(
|
|
181
|
+
name: full_name, model_name: model.name, serializer_name: options[:serializer],
|
|
182
|
+
credential_permission: options[:credential], deprecated_alias_of: options[:deprecated_alias_of],
|
|
183
|
+
webhook: options.fetch(:webhook, true)
|
|
184
|
+
)
|
|
185
|
+
end
|
|
186
|
+
end
|
|
187
|
+
end
|
|
188
|
+
end
|
|
189
|
+
end
|
data/lib/spree/events.rb
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require_relative 'events/registry'
|
|
4
|
+
require_relative 'events/catalog'
|
|
4
5
|
require_relative 'events/adapters/base'
|
|
5
6
|
require_relative 'events/adapters/active_support_notifications'
|
|
6
7
|
|
|
@@ -34,6 +35,24 @@ module Spree
|
|
|
34
35
|
# be logged multiple times.
|
|
35
36
|
attr_accessor :log_subscription
|
|
36
37
|
|
|
38
|
+
# Whether publishing an undeclared event raises (development and test) or
|
|
39
|
+
# only logs a warning (production), see {Catalog#verify_declared!}.
|
|
40
|
+
attr_writer :raise_on_undeclared_events
|
|
41
|
+
|
|
42
|
+
# @return [Boolean]
|
|
43
|
+
def raise_on_undeclared_events
|
|
44
|
+
return @raise_on_undeclared_events unless @raise_on_undeclared_events.nil?
|
|
45
|
+
|
|
46
|
+
Rails.env.development? || Rails.env.test?
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
# Every event Spree publishes, see {Catalog}.
|
|
50
|
+
#
|
|
51
|
+
# @return [Spree::Events::Catalog]
|
|
52
|
+
def catalog
|
|
53
|
+
@catalog ||= Catalog.new
|
|
54
|
+
end
|
|
55
|
+
|
|
37
56
|
# Publish an event to all matching subscribers
|
|
38
57
|
#
|
|
39
58
|
# @param name [String] The event name (e.g., 'order.completed')
|
|
@@ -151,11 +170,12 @@ module Spree
|
|
|
151
170
|
# In development, Zeitwerk may have reloaded the class, creating a new
|
|
152
171
|
# class object while the old one is still referenced in Spree.subscribers.
|
|
153
172
|
# This method resolves the constant fresh to get the current class.
|
|
173
|
+
# A class name given as a String is resolved in every environment.
|
|
154
174
|
#
|
|
155
175
|
# @param subscriber [Class, String] The subscriber class or class name
|
|
156
176
|
# @return [Class, nil] The resolved class or nil if not found
|
|
157
177
|
def resolve_subscriber(subscriber)
|
|
158
|
-
return subscriber unless Rails.env.development? || Rails.env.test?
|
|
178
|
+
return subscriber unless subscriber.is_a?(String) || Rails.env.development? || Rails.env.test?
|
|
159
179
|
|
|
160
180
|
class_name = subscriber.is_a?(String) ? subscriber : subscriber.name
|
|
161
181
|
return nil unless class_name
|
data/lib/spree/hooks.rb
CHANGED
|
@@ -98,23 +98,6 @@ module Spree
|
|
|
98
98
|
@handlers.clear
|
|
99
99
|
end
|
|
100
100
|
|
|
101
|
-
# Introspection facade: Spree.hooks.carts => { 'carts.add_item' => [...] }
|
|
102
|
-
def method_missing(name, *args, &block)
|
|
103
|
-
prefix = "#{name}."
|
|
104
|
-
matching = @workflows.keys.select { |workflow_key| workflow_key.start_with?(prefix) || workflow_key == name.to_s }
|
|
105
|
-
return super if matching.empty? && !@workflows.keys.any? { |k| k.start_with?(prefix) }
|
|
106
|
-
|
|
107
|
-
matching.index_with do |workflow_key|
|
|
108
|
-
workflow = @workflows[workflow_key]&.safe_constantize
|
|
109
|
-
workflow ? workflow.declared_hooks : []
|
|
110
|
-
end
|
|
111
|
-
end
|
|
112
|
-
|
|
113
|
-
def respond_to_missing?(name, include_private = false)
|
|
114
|
-
prefix = "#{name}."
|
|
115
|
-
@workflows.keys.any? { |workflow_key| workflow_key.start_with?(prefix) } || super
|
|
116
|
-
end
|
|
117
|
-
|
|
118
101
|
private
|
|
119
102
|
|
|
120
103
|
# Last writer wins on a collision, which is only defensible if it's
|
data/lib/spree/i18n.rb
CHANGED
|
@@ -1,30 +1,47 @@
|
|
|
1
1
|
require 'i18n'
|
|
2
|
-
require 'active_support/core_ext/array/extract_options'
|
|
3
|
-
require 'action_view'
|
|
4
2
|
|
|
5
3
|
module Spree
|
|
6
|
-
|
|
7
|
-
include ActionView::Helpers::TranslationHelper
|
|
8
|
-
end
|
|
4
|
+
SHIPPED_LOCALES = Dir[File.expand_path('../../config/locales/*.yml', __dir__)].map { |file| File.basename(file, '.yml').to_sym }.freeze
|
|
9
5
|
|
|
10
6
|
class << self
|
|
11
|
-
#
|
|
12
|
-
#
|
|
7
|
+
# @deprecated Use +I18n.t+ with the full key, e.g. +I18n.t('spree.free')+.
|
|
8
|
+
# Will be removed in Spree 6.1.
|
|
13
9
|
def translate(key, options = {})
|
|
14
|
-
|
|
10
|
+
Spree::Deprecation.warn('Spree.t is deprecated and will be removed in Spree 6.1. Use I18n.t with the full key, e.g. I18n.t("spree.free").') if defined?(Spree::Deprecation)
|
|
15
11
|
|
|
16
|
-
|
|
12
|
+
I18n.t(key, **options.symbolize_keys, scope: [:spree, *Array(options[:scope]).map(&:to_sym)].uniq)
|
|
17
13
|
end
|
|
14
|
+
alias t translate
|
|
18
15
|
|
|
16
|
+
# Locales Spree ships translations for that the app allows, plus English
|
|
17
|
+
# and the app's default and current locale.
|
|
18
|
+
#
|
|
19
|
+
# @return [Array<Symbol>]
|
|
19
20
|
def available_locales
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
locales << I18n.locale
|
|
23
|
-
locales << Rails.application.config.i18n.default_locale
|
|
21
|
+
(configured_locales & I18n.available_locales.map(&:to_sym)) | [:en, I18n.locale, Rails.application.config.i18n.default_locale].compact
|
|
22
|
+
end
|
|
24
23
|
|
|
25
|
-
|
|
24
|
+
# {available_locales} as read from configuration alone, without loading
|
|
25
|
+
# any translations, for use while the app boots.
|
|
26
|
+
#
|
|
27
|
+
# @return [Array<Symbol>]
|
|
28
|
+
def configured_locales
|
|
29
|
+
allowed = Array(Rails.application.config.i18n.available_locales).map(&:to_sym)
|
|
30
|
+
allowed.empty? ? SHIPPED_LOCALES : SHIPPED_LOCALES & allowed
|
|
26
31
|
end
|
|
27
32
|
|
|
28
|
-
|
|
33
|
+
# The available locale a store should use for a language: the language
|
|
34
|
+
# itself when Spree ships it, otherwise its first regional variant ("zh"
|
|
35
|
+
# becomes "zh-CN", since Spree ships Chinese only by region).
|
|
36
|
+
#
|
|
37
|
+
# @param language [String] ISO 639-1 code, e.g. "de"
|
|
38
|
+
# @param locales [Array<Symbol>] the locales to choose from
|
|
39
|
+
# @return [String, nil] nil when the language is not available at all
|
|
40
|
+
def locale_for_language(language, locales = available_locales)
|
|
41
|
+
codes = locales.map(&:to_s)
|
|
42
|
+
return language if codes.include?(language)
|
|
43
|
+
|
|
44
|
+
codes.sort.find { |code| code.start_with?("#{language}-") }
|
|
45
|
+
end
|
|
29
46
|
end
|
|
30
47
|
end
|
|
@@ -642,12 +642,12 @@ module Spree
|
|
|
642
642
|
end.to_h
|
|
643
643
|
end
|
|
644
644
|
|
|
645
|
-
# Both periods span the same number of days
|
|
646
|
-
# differ by at most one partial edge. Pairing runs
|
|
647
|
-
# the nth bucket of this period with the nth of the
|
|
648
|
-
# the ranges share a start offset: a range
|
|
649
|
-
# its partial first bucket with the previous
|
|
650
|
-
# bucket. A bucket past the end of the previous list has no
|
|
645
|
+
# Both periods span the same number of days (or of whole months), so
|
|
646
|
+
# their bucket counts differ by at most one partial edge. Pairing runs
|
|
647
|
+
# from the start — the nth bucket of this period with the nth of the
|
|
648
|
+
# previous — because the ranges share a start offset: a range
|
|
649
|
+
# beginning mid-month pairs its partial first bucket with the previous
|
|
650
|
+
# period's partial first bucket. A bucket past the end of the previous list has no
|
|
651
651
|
# counterpart and pairs with nothing rather than wrapping around.
|
|
652
652
|
def bucket_pairs(grain)
|
|
653
653
|
current = expected_buckets(query.time_range, grain)
|
|
@@ -113,16 +113,27 @@ module Spree
|
|
|
113
113
|
SUM(COALESCE((SELECT SUM(r.amount) FROM %{refunds} r WHERE r.order_id = %{orders}.id), 0))
|
|
114
114
|
SQL
|
|
115
115
|
|
|
116
|
-
# A refund
|
|
117
|
-
#
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
116
|
+
# A refund that gives tax back carries it in its own column, so the goods
|
|
117
|
+
# and the tax it returned are netted from goods and tax respectively.
|
|
118
|
+
REFUNDED_GOODS_SUBQUERY = <<~SQL.squish.freeze
|
|
119
|
+
SUM(COALESCE((SELECT SUM(r.amount - r.tax_amount) FROM %{refunds} r WHERE r.order_id = %{orders}.id), 0))
|
|
120
|
+
SQL
|
|
121
|
+
|
|
122
|
+
REFUNDED_TAX_SUBQUERY = <<~SQL.squish.freeze
|
|
123
|
+
SUM(COALESCE((SELECT SUM(r.tax_amount) FROM %{refunds} r WHERE r.order_id = %{orders}.id), 0))
|
|
124
|
+
SQL
|
|
125
|
+
|
|
126
|
+
# A refund is issued against the order, not a line, so its goods are
|
|
127
|
+
# apportioned by the line's share of the order's pre-tax value. The
|
|
128
|
+
# denominator is summed over the order's own lines rather than read from
|
|
129
|
+
# item_total: item_total is priced before discounts, so on a discounted
|
|
130
|
+
# order the shares would not add up to one and part of the refund would
|
|
131
|
+
# vanish. The `* 1.0` forces real division: SQLite divides two
|
|
132
|
+
# integer-valued decimals as integers, which silently rounds every share
|
|
133
|
+
# to zero.
|
|
123
134
|
LINE_ITEM_REFUNDS_SUBQUERY = <<~SQL.squish.freeze
|
|
124
135
|
SUM(
|
|
125
|
-
COALESCE((SELECT SUM(r.amount) FROM %{refunds} r WHERE r.order_id = %{orders}.id), 0)
|
|
136
|
+
COALESCE((SELECT SUM(r.amount - r.tax_amount) FROM %{refunds} r WHERE r.order_id = %{orders}.id), 0)
|
|
126
137
|
* CASE
|
|
127
138
|
WHEN COALESCE((SELECT SUM(sibling.pre_tax_amount) FROM %{line_items} sibling
|
|
128
139
|
WHERE sibling.order_id = %{orders}.id), 0) > 0
|
|
@@ -293,7 +304,7 @@ module Spree
|
|
|
293
304
|
metric :gross_sales, sql: 'SUM(%{line_items}.price * %{line_items}.quantity)',
|
|
294
305
|
base: :line_items, format: :money
|
|
295
306
|
metric :discounts, sql: 'SUM(%{line_items}.discount_total)', base: :line_items, format: :money
|
|
296
|
-
metric :returns, sql:
|
|
307
|
+
metric :returns, sql: REFUNDED_GOODS_SUBQUERY, base: :orders, format: :money
|
|
297
308
|
# Net of returns as well as discounts, so this is the figure a
|
|
298
309
|
# merchant recognises as "what we actually sold".
|
|
299
310
|
metric :net_sales, sql: "SUM(%{line_items}.pre_tax_amount) - #{LINE_ITEM_REFUNDS_SUBQUERY}",
|
|
@@ -303,7 +314,8 @@ module Spree
|
|
|
303
314
|
# Every shopper-visible fee except duties, which the chain counts separately.
|
|
304
315
|
metric :fees, sql: "SUM(COALESCE(%{orders}.fee_total, 0)) - #{DUTIES_SUBQUERY}",
|
|
305
316
|
base: :orders, format: :money, suggests: 'net_sales'
|
|
306
|
-
|
|
317
|
+
# Net of the tax given back, as net_sales is net of the goods.
|
|
318
|
+
metric :taxes, sql: "SUM(%{orders}.additional_tax_total + %{orders}.included_tax_total) - #{REFUNDED_TAX_SUBQUERY}",
|
|
307
319
|
base: :orders, format: :money, suggests: 'net_sales'
|
|
308
320
|
# The order grand total, net of what came back.
|
|
309
321
|
metric :total_sales, sql: "SUM(%{orders}.total) - #{REFUNDS_SUBQUERY}",
|
|
@@ -21,7 +21,7 @@ module Spree
|
|
|
21
21
|
dimension = definition(name)
|
|
22
22
|
# A row whose key is NULL is a real group — an order with no market or
|
|
23
23
|
# channel — and needs a name a person can read, not a blank cell.
|
|
24
|
-
return { id: nil, label:
|
|
24
|
+
return { id: nil, label: I18n.t('spree.reporting.unassigned'), meta: {} } if raw.nil? && !plain?(dimension)
|
|
25
25
|
return { id: raw.to_s, label: Schema.value_label(dimension, raw), meta: {} } if dimension.enumerated_values
|
|
26
26
|
return raw unless dimension.hydrate
|
|
27
27
|
|
|
@@ -126,12 +126,20 @@ module Spree
|
|
|
126
126
|
# year" means February to February, and an equal-day-count shift off a
|
|
127
127
|
# 28-day February would land in January. ActiveSupport maps February 29
|
|
128
128
|
# to February 28 in a non-leap year.
|
|
129
|
+
#
|
|
130
|
+
# A range of whole calendar months (a full quarter, `last_month`) shifts
|
|
131
|
+
# by the same number of months for the same reason: July – September is
|
|
132
|
+
# 92 days, and shifting it by days would start on March 31, putting a
|
|
133
|
+
# one-day March bucket at the front of the previous period.
|
|
129
134
|
def previous_time_range
|
|
130
135
|
first = time_range.first.in_time_zone(time_zone)
|
|
131
136
|
last = time_range.last.in_time_zone(time_zone)
|
|
132
137
|
|
|
133
138
|
if compare == 'previous_year'
|
|
134
139
|
(first - 1.year)..(last - 1.year)
|
|
140
|
+
elsif first == first.beginning_of_month && last == last.end_of_month
|
|
141
|
+
months = (last.year * 12 + last.month) - (first.year * 12 + first.month) + 1
|
|
142
|
+
(first - months.months)..(last - months.months).end_of_month
|
|
135
143
|
else
|
|
136
144
|
days = (last.to_date - first.to_date).to_i + 1
|
|
137
145
|
(first - days.days)..(last - days.days)
|
|
@@ -32,7 +32,7 @@ module Spree
|
|
|
32
32
|
metrics: metrics.map { |m| metric_entry(m) },
|
|
33
33
|
dimensions: dimensions.map { |d| dimension_entry(d, metrics) },
|
|
34
34
|
time_range: {
|
|
35
|
-
presets: (Query::PRESETS + Query::RELATIVE_PRESETS).map { |p| { name: p, label:
|
|
35
|
+
presets: (Query::PRESETS + Query::RELATIVE_PRESETS).map { |p| { name: p, label: catalog_text('presets', p, :label) } },
|
|
36
36
|
relative: %w[last_<n>_days last_<n>_weeks last_<n>_months],
|
|
37
37
|
absolute: 'ISO 8601 dates or datetimes in `since` / `until`, resolved in the store timezone'
|
|
38
38
|
},
|
|
@@ -50,7 +50,7 @@ module Spree
|
|
|
50
50
|
# @param group [Symbol] :metrics | :dimensions
|
|
51
51
|
# @return [String] localized label (humanized name when none registered)
|
|
52
52
|
def label_for(group, name)
|
|
53
|
-
|
|
53
|
+
catalog_text(group, name, :label)
|
|
54
54
|
end
|
|
55
55
|
|
|
56
56
|
private
|
|
@@ -61,7 +61,7 @@ module Spree
|
|
|
61
61
|
metrics.group_by { |metric| @registry.family_of(metric) }.map do |family, family_metrics|
|
|
62
62
|
{
|
|
63
63
|
name: family,
|
|
64
|
-
label:
|
|
64
|
+
label: catalog_text('families', family, :label),
|
|
65
65
|
metrics: family_metrics.map(&:name),
|
|
66
66
|
dimensions: @registry.dimensions.values.
|
|
67
67
|
select { |d| @allowed.call(d) && @registry.base!(d.base).family == family }.map(&:name)
|
|
@@ -73,8 +73,8 @@ module Spree
|
|
|
73
73
|
{
|
|
74
74
|
name: metric.name,
|
|
75
75
|
family: @registry.family_of(metric),
|
|
76
|
-
label:
|
|
77
|
-
description:
|
|
76
|
+
label: catalog_text('metrics', metric.name, :label),
|
|
77
|
+
description: catalog_text('metrics', metric.name, :description),
|
|
78
78
|
format: metric.format,
|
|
79
79
|
currency: (metric.money? ? @store.default_currency : nil),
|
|
80
80
|
derived: metric.derived?,
|
|
@@ -88,8 +88,8 @@ module Spree
|
|
|
88
88
|
def dimension_entry(dimension, metrics)
|
|
89
89
|
{
|
|
90
90
|
name: dimension.name,
|
|
91
|
-
label:
|
|
92
|
-
description:
|
|
91
|
+
label: catalog_text('dimensions', dimension.name, :label),
|
|
92
|
+
description: catalog_text('dimensions', dimension.name, :description),
|
|
93
93
|
type: dimension.type,
|
|
94
94
|
grains: dimension.grains,
|
|
95
95
|
lookup: dimension.lookup,
|
|
@@ -104,8 +104,8 @@ module Spree
|
|
|
104
104
|
}.compact
|
|
105
105
|
end
|
|
106
106
|
|
|
107
|
-
def
|
|
108
|
-
|
|
107
|
+
def catalog_text(group, name, facet)
|
|
108
|
+
I18n.t("spree.reporting.#{group}.#{name}.#{facet}", default: (facet == :label ? name.to_s.humanize : nil))
|
|
109
109
|
end
|
|
110
110
|
|
|
111
111
|
|
|
@@ -117,7 +117,7 @@ module Spree
|
|
|
117
117
|
# @param value [String]
|
|
118
118
|
# @return [String]
|
|
119
119
|
def self.value_label(dimension, value)
|
|
120
|
-
|
|
120
|
+
I18n.t("spree.reporting.values.#{dimension.name}.#{value}", default: value.to_s.humanize)
|
|
121
121
|
end
|
|
122
122
|
end
|
|
123
123
|
end
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
# Registers core's staff password reset as an editable email for the length
|
|
2
|
+
# of an example, with a fixed sample, so specs in any engine can exercise the
|
|
3
|
+
# template editor without spree_emails' customer emails. Restores whatever was
|
|
4
|
+
# registered under the key before.
|
|
5
|
+
#
|
|
6
|
+
# RSpec.describe '...' do
|
|
7
|
+
# include_context 'with an editable email template'
|
|
8
|
+
# end
|
|
9
|
+
module Spree
|
|
10
|
+
module TestingSupport
|
|
11
|
+
class EmailTemplateSample
|
|
12
|
+
def self.record_type
|
|
13
|
+
nil
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def initialize(store:, record_id: nil)
|
|
17
|
+
@store = store
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def currency
|
|
21
|
+
@store.default_currency
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def recent_records(limit: 5)
|
|
25
|
+
@store.orders.order(created_at: :desc).limit(limit).map do |order|
|
|
26
|
+
Spree::EmailTemplates::SampleRecord.new(id: order.prefixed_id, label: order.number, created_at: order.created_at)
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def variables
|
|
31
|
+
{
|
|
32
|
+
user: { 'first_name' => 'Ann', 'email' => 'ann@example.com' },
|
|
33
|
+
order: { 'number' => 'R123456789', 'customer_name' => 'Ann Lee' },
|
|
34
|
+
reset_url: "#{@store.storefront_url}/reset-password?token=preview"
|
|
35
|
+
}
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
RSpec.shared_context 'with an editable email template' do
|
|
42
|
+
let(:editable_key) { 'spree/admin_user_mailer/password_reset_email' }
|
|
43
|
+
|
|
44
|
+
# Templates have per-language versions only in the store's languages.
|
|
45
|
+
before { allow_any_instance_of(Spree::Store).to receive(:supported_locales_list).and_return(%w[de en fr]) }
|
|
46
|
+
|
|
47
|
+
around do |example|
|
|
48
|
+
registry = Spree.editable_email_templates
|
|
49
|
+
previous = registry[editable_key]
|
|
50
|
+
previous_layout = registry['layouts/spree/base_mailer']
|
|
51
|
+
registry.register(editable_key, kind: :email, sample: 'Spree::TestingSupport::EmailTemplateSample')
|
|
52
|
+
registry.register('layouts/spree/base_mailer', kind: :layout)
|
|
53
|
+
|
|
54
|
+
begin
|
|
55
|
+
example.run
|
|
56
|
+
ensure
|
|
57
|
+
previous ? registry.register(previous.key, kind: previous.kind, sample: previous.sample) : registry.delete(editable_key)
|
|
58
|
+
previous_layout ? registry.register(previous_layout.key, kind: :layout) : registry.delete('layouts/spree/base_mailer')
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
end
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
module Spree
|
|
2
|
+
module TestingSupport
|
|
3
|
+
module Emails
|
|
4
|
+
# The decoded text of every part of an email, HTML and plain text
|
|
5
|
+
# together. Rendered emails are quoted-printable on the wire, so the raw
|
|
6
|
+
# encoded body splits long lines and turns "=" into "=3D".
|
|
7
|
+
#
|
|
8
|
+
# @param message [Mail::Message, ActionMailer::MessageDelivery]
|
|
9
|
+
# @return [String]
|
|
10
|
+
def email_body(message)
|
|
11
|
+
message = message.message if message.respond_to?(:message) && !message.is_a?(Mail::Message)
|
|
12
|
+
parts = message.multipart? ? message.parts : [message]
|
|
13
|
+
|
|
14
|
+
parts.map(&:decoded).join("\n")
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
RSpec.configure do |config|
|
|
21
|
+
config.include Spree::TestingSupport::Emails
|
|
22
|
+
end
|