spree_core 6.0.0.rc1 → 6.0.0.rc2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +3 -3
- data/app/jobs/spree/search_provider/index_job.rb +4 -0
- data/app/jobs/spree/uploads/purge_unattached_job.rb +49 -0
- data/app/mailers/spree/base_mailer.rb +4 -3
- data/app/models/concerns/spree/admin_user_methods.rb +2 -0
- data/app/models/concerns/spree/calculated_adjustments.rb +41 -13
- data/app/models/concerns/spree/calculator/currency_amounts.rb +1 -1
- data/app/models/concerns/spree/calculator/tiers.rb +1 -14
- data/app/models/concerns/spree/canonical_decimals.rb +61 -0
- data/app/models/concerns/spree/customer_methods.rb +8 -4
- data/app/models/concerns/spree/derived_number.rb +11 -13
- data/app/models/concerns/spree/preference_schema/json_schema.rb +139 -0
- data/app/models/concerns/spree/preference_schema.rb +59 -21
- data/app/models/concerns/spree/product_scopes.rb +5 -1
- data/app/models/concerns/spree/purchase/payment_processing.rb +1 -1
- data/app/models/concerns/spree/purchase/purchase_order.rb +7 -2
- data/app/models/concerns/spree/purchase/store_credits.rb +36 -1
- data/app/models/concerns/spree/purchase/totals.rb +1 -8
- data/app/models/concerns/spree/ransackable_attributes.rb +46 -0
- data/app/models/concerns/spree/signed_id_attachments.rb +54 -0
- data/app/models/concerns/spree/stores/email_branding.rb +2 -1
- data/app/models/concerns/spree/stores/markets.rb +5 -2
- data/app/models/concerns/spree/typed_adjustment_line.rb +4 -5
- data/app/models/concerns/spree/typed_associations.rb +6 -18
- data/app/models/concerns/spree/vat_price_calculation.rb +5 -6
- data/app/models/spree/address.rb +1 -1
- data/app/models/spree/adjusters/largest_remainder.rb +14 -1
- data/app/models/spree/adjusters/promotion.rb +3 -4
- data/app/models/spree/allowed_origin.rb +3 -0
- data/app/models/spree/base.rb +29 -0
- data/app/models/spree/calculator/flat_percent_item_total.rb +1 -1
- data/app/models/spree/calculator/flat_rate.rb +2 -2
- data/app/models/spree/calculator/flexi_rate.rb +3 -3
- data/app/models/spree/calculator/percent_on_line_item.rb +1 -1
- data/app/models/spree/calculator/price_sack.rb +4 -4
- data/app/models/spree/calculator/shipping/digital_delivery.rb +2 -2
- data/app/models/spree/calculator/shipping/flat_percent_item_total.rb +6 -4
- data/app/models/spree/calculator/shipping/flat_rate.rb +4 -4
- data/app/models/spree/calculator/shipping/flexi_rate.rb +3 -3
- data/app/models/spree/calculator/shipping/per_item.rb +2 -2
- data/app/models/spree/calculator/shipping/price_sack.rb +4 -4
- data/app/models/spree/calculator/tiered_flat_rate.rb +2 -2
- data/app/models/spree/calculator/tiered_percent.rb +1 -1
- data/app/models/spree/catalog.rb +1 -0
- data/app/models/spree/catalog_price.rb +0 -7
- data/app/models/spree/catalog_price_tier.rb +0 -8
- data/app/models/spree/category.rb +3 -1
- data/app/models/spree/channel/gating.rb +2 -1
- data/app/models/spree/channel.rb +2 -0
- data/app/models/spree/claim.rb +1 -0
- data/app/models/spree/collection.rb +2 -0
- data/app/models/spree/commission_rate.rb +5 -1
- data/app/models/spree/commission_rules/category_rule.rb +2 -5
- data/app/models/spree/commission_rules/item_total_rule.rb +2 -2
- data/app/models/spree/commission_rules/seller_rule.rb +2 -5
- data/app/models/spree/company.rb +1 -0
- data/app/models/spree/custom_field_definition.rb +11 -12
- data/app/models/spree/customer_group.rb +1 -0
- data/app/models/spree/data_request.rb +1 -0
- data/app/models/spree/delivery_method.rb +6 -1
- data/app/models/spree/delivery_method_rules/channel_rule.rb +2 -5
- data/app/models/spree/delivery_method_rules/item_total_rule.rb +2 -2
- data/app/models/spree/delivery_profile.rb +1 -0
- data/app/models/spree/delivery_rate.rb +3 -3
- data/app/models/spree/delivery_zone.rb +1 -1
- data/app/models/spree/exchange.rb +1 -0
- data/app/models/spree/exports/report.rb +2 -2
- data/app/models/spree/file_upload.rb +155 -0
- data/app/models/spree/fulfillment.rb +13 -3
- data/app/models/spree/fulfillment_group.rb +6 -1
- data/app/models/spree/gateway/bogus.rb +2 -0
- data/app/models/spree/gateway.rb +1 -1
- data/app/models/spree/gift_card.rb +6 -7
- data/app/models/spree/gift_card_batch.rb +3 -4
- data/app/models/spree/import.rb +3 -0
- data/app/models/spree/integration.rb +1 -1
- data/app/models/spree/line_item.rb +2 -2
- data/app/models/spree/market.rb +7 -2
- data/app/models/spree/media.rb +1 -9
- data/app/models/spree/newsletter_subscriber.rb +1 -0
- data/app/models/spree/option_type.rb +2 -1
- data/app/models/spree/option_value.rb +2 -1
- data/app/models/spree/order.rb +5 -5
- data/app/models/spree/order_group.rb +7 -2
- data/app/models/spree/package_type.rb +1 -0
- data/app/models/spree/payment/gateway_options.rb +1 -1
- data/app/models/spree/payment.rb +3 -10
- data/app/models/spree/payment_method/check.rb +2 -0
- data/app/models/spree/payment_method/store_credit.rb +11 -9
- data/app/models/spree/payment_method.rb +42 -2
- data/app/models/spree/policy.rb +1 -0
- data/app/models/spree/price.rb +17 -14
- data/app/models/spree/price_adjustment_tier.rb +4 -0
- data/app/models/spree/price_list.rb +13 -7
- data/app/models/spree/price_rules/channel_rule.rb +2 -5
- data/app/models/spree/price_rules/customer_group_rule.rb +2 -5
- data/app/models/spree/price_rules/market_rule.rb +2 -5
- data/app/models/spree/price_rules/user_rule.rb +1 -3
- data/app/models/spree/product/channels.rb +9 -2
- data/app/models/spree/product.rb +23 -23
- data/app/models/spree/product_type.rb +1 -0
- data/app/models/spree/promotion/actions/create_line_items.rb +1 -1
- data/app/models/spree/promotion/rules/category.rb +1 -1
- data/app/models/spree/promotion/rules/channel.rb +2 -5
- data/app/models/spree/promotion/rules/country.rb +3 -4
- data/app/models/spree/promotion/rules/currency.rb +1 -1
- data/app/models/spree/promotion/rules/customer_group.rb +2 -5
- data/app/models/spree/promotion/rules/item_total.rb +2 -2
- data/app/models/spree/promotion/rules/market.rb +2 -5
- data/app/models/spree/promotion/rules/option_value.rb +2 -3
- data/app/models/spree/promotion/rules/product.rb +1 -1
- data/app/models/spree/promotion.rb +1 -0
- data/app/models/spree/purchase_order.rb +2 -0
- data/app/models/spree/refund.rb +13 -15
- data/app/models/spree/return.rb +1 -0
- data/app/models/spree/saved_report.rb +1 -0
- data/app/models/spree/search_provider/base.rb +4 -0
- data/app/models/spree/seller.rb +8 -1
- data/app/models/spree/seller_payout.rb +1 -0
- data/app/models/spree/seller_requirement.rb +1 -0
- data/app/models/spree/seller_requirement_submission.rb +1 -0
- data/app/models/spree/seller_transfer.rb +1 -0
- data/app/models/spree/shipping_label.rb +1 -0
- data/app/models/spree/stock/estimator.rb +1 -1
- data/app/models/spree/stock_level.rb +2 -0
- data/app/models/spree/stock_location.rb +1 -0
- data/app/models/spree/stock_transfer.rb +2 -0
- data/app/models/spree/store.rb +37 -12
- data/app/models/spree/store_credit.rb +5 -4
- data/app/models/spree/supplier.rb +1 -0
- data/app/models/spree/tax_category.rb +1 -0
- data/app/models/spree/tax_exemption_certificate.rb +1 -0
- data/app/models/spree/tax_provider/internal.rb +12 -9
- data/app/models/spree/tax_rate.rb +29 -6
- data/app/models/spree/taxon.rb +1 -1
- data/app/models/spree/update_status.rb +25 -0
- data/app/models/spree/variant.rb +6 -9
- data/app/models/spree/webhook_delivery.rb +1 -0
- data/app/models/spree/webhook_endpoint.rb +36 -0
- data/app/presenters/spree/csv/order_line_item_presenter.rb +10 -6
- data/app/presenters/spree/csv/price_list_price_presenter.rb +3 -8
- data/app/presenters/spree/csv/product_variant_presenter.rb +17 -8
- data/app/presenters/spree/csv/purchase_order_item_presenter.rb +1 -3
- data/app/services/spree/carts/update.rb +3 -3
- data/app/services/spree/imports/row_processors/product_variant.rb +12 -0
- data/app/services/spree/locales.rb +39 -5
- data/app/services/spree/orders/create.rb +3 -3
- data/app/services/spree/orders/discounts/create.rb +8 -8
- data/app/services/spree/prices/bulk_upsert.rb +32 -25
- data/app/services/spree/seeds/stores.rb +1 -1
- data/app/services/spree/stores/provision_defaults.rb +13 -16
- data/app/services/spree/update_check.rb +160 -0
- data/app/services/spree/uploads/assign_stores.rb +92 -0
- data/app/validators/spree/attachment_content_type_validator.rb +19 -2
- data/app/validators/spree/locale_codes_validator.rb +22 -0
- data/app/workflows/spree/fulfillments/create.rb +2 -2
- data/app/workflows/spree/payments/capture.rb +20 -5
- data/app/workflows/spree/payments/process.rb +3 -1
- data/app/workflows/spree/price_lists/update.rb +4 -4
- data/app/workflows/spree/purchase_orders/create.rb +3 -1
- data/app/workflows/spree/shipping_labels/record.rb +2 -2
- data/app/workflows/spree/stock_transfers/create.rb +3 -1
- data/config/currency_exponents.json +180 -0
- data/config/locales/content_locales.rb +9 -0
- data/config/locales/en.yml +10 -2
- data/db/migrate/20261009000001_change_money_columns_to_decimal_19_4.rb +116 -0
- data/db/migrate/20261010000001_add_store_id_to_active_storage_blobs.rb +12 -0
- data/lib/generators/spree/dummy/templates/rails/test.rb +21 -2
- data/lib/spree/core/configuration.rb +18 -0
- data/lib/spree/core/engine.rb +26 -0
- data/lib/spree/core/preferences/invalid_preferences.rb +18 -0
- data/lib/spree/core/preferences/json_conversion.rb +5 -1
- data/lib/spree/core/preferences/masking.rb +51 -1
- data/lib/spree/core/preferences/preferable.rb +212 -26
- data/lib/spree/core/preferences/preferable_class_methods.rb +121 -4
- data/lib/spree/core/ransack_configuration.rb +13 -2
- data/lib/spree/core/version.rb +1 -1
- data/lib/spree/core.rb +6 -3
- data/lib/spree/iso_data.rb +7 -0
- data/lib/spree/localized_number.rb +6 -0
- data/lib/spree/migrations.rb +4 -1
- data/lib/spree/money.rb +196 -15
- data/lib/spree/reporting/adapters/base.rb +7 -6
- data/lib/spree/reporting/registry.rb +13 -0
- data/lib/spree/testing_support/factories/product_factory.rb +9 -0
- data/lib/spree/upgrades/5_6_to_6_0/manifest.yml +22 -1
- data/lib/spree/uploads.rb +101 -0
- data/lib/tasks/markets.rake +2 -1
- data/lib/tasks/migrate_users_to_customers.rake +23 -18
- data/lib/tasks/money.rake +46 -0
- data/lib/tasks/seller_sample_data.rake +2 -2
- data/lib/tasks/store_settings.rake +13 -6
- data/lib/tasks/typed_adjustments_migration.rake +1 -1
- data/lib/tasks/uploads.rake +19 -0
- metadata +35 -6
- data/app/jobs/spree/products/touch_categories_job.rb +0 -15
- data/app/jobs/spree/products/touch_taxons_job.rb +0 -13
data/app/models/spree/store.rb
CHANGED
|
@@ -41,11 +41,13 @@ module Spree
|
|
|
41
41
|
#
|
|
42
42
|
# Preferences
|
|
43
43
|
#
|
|
44
|
+
UNIT_SYSTEMS = %w[metric imperial].freeze
|
|
45
|
+
|
|
44
46
|
# general preferences
|
|
45
|
-
preference :admin_locale, :string
|
|
46
|
-
preference :timezone, :string, default: Time.zone.name
|
|
47
|
-
preference :weight_unit, :string, default: 'lb'
|
|
48
|
-
preference :unit_system, :string, default: 'imperial'
|
|
47
|
+
preference :admin_locale, :string, format: :locale
|
|
48
|
+
preference :timezone, :string, format: :timezone, default: Time.zone.name
|
|
49
|
+
preference :weight_unit, :string, default: 'lb', choices: -> { Spree::Variant::WEIGHT_UNITS }
|
|
50
|
+
preference :unit_system, :string, default: 'imperial', choices: -> { UNIT_SYSTEMS }
|
|
49
51
|
# Anonymous identifier of this installation, kept on the default store
|
|
50
52
|
# (see Spree.install_id). Written by Spree, never by the operator.
|
|
51
53
|
preference :install_id, :string, internal: true, nullable: true
|
|
@@ -83,22 +85,22 @@ module Spree
|
|
|
83
85
|
# How often sellers are settled, unless one carries its own schedule.
|
|
84
86
|
# Monthly by default because it is the interval that needs least of an
|
|
85
87
|
# operator paying by hand, which is what the built-in provider expects.
|
|
86
|
-
preference :default_payouts_schedule_interval, :string, default: 'monthly'
|
|
88
|
+
preference :default_payouts_schedule_interval, :string, default: 'monthly', choices: -> { Spree::Seller::PAYOUT_INTERVALS }
|
|
87
89
|
validates :preferred_default_payouts_schedule_interval,
|
|
88
90
|
inclusion: { in: Spree::Seller::PAYOUT_INTERVALS }, allow_blank: true
|
|
89
91
|
# What a seller's balance must reach before a settlement is worth sending;
|
|
90
92
|
# below it the balance carries to the next period. Zero pays whatever is
|
|
91
93
|
# owed, which is right for a provider that moves money for free.
|
|
92
|
-
preference :default_minimum_payout_amount, :
|
|
94
|
+
preference :default_minimum_payout_amount, :money, default: 0
|
|
93
95
|
# Checkout preferences
|
|
94
96
|
# Store-level fallback for the channel-owned `guest_checkout` preference
|
|
95
97
|
# (see Spree::Channel::Gating). Retained so existing accessors keep working.
|
|
96
98
|
preference :guest_checkout, :boolean, default: true
|
|
97
99
|
# Store-level fallback for the channel-owned `storefront_access` posture.
|
|
98
|
-
preference :storefront_access, :string, default: 'public'
|
|
100
|
+
preference :storefront_access, :string, default: 'public', choices: -> { Spree::Channel::Gating::STOREFRONT_ACCESS }
|
|
99
101
|
# Canonical storefront origin used in customer-facing emails and links,
|
|
100
102
|
# e.g. "https://myshop.com" — see #storefront_url for the fallback chain.
|
|
101
|
-
preference :storefront_url, :string
|
|
103
|
+
preference :storefront_url, :string, format: :url
|
|
102
104
|
preference :special_instructions_enabled, :boolean, default: false
|
|
103
105
|
# Advertises a confirm/review step for every checkout, even when no
|
|
104
106
|
# payment method asks for one. Payment methods that require confirmation
|
|
@@ -108,7 +110,8 @@ module Spree
|
|
|
108
110
|
# Store-wide default for when a customer is charged rather than only
|
|
109
111
|
# authorized. A payment method's own capture_method wins when set.
|
|
110
112
|
# See Spree::CaptureMethod.
|
|
111
|
-
preference :capture_method, :string, default: Spree::CaptureMethod::DEFAULT_CAPTURE_METHOD
|
|
113
|
+
preference :capture_method, :string, default: Spree::CaptureMethod::DEFAULT_CAPTURE_METHOD,
|
|
114
|
+
choices: -> { Spree::CaptureMethod::CAPTURE_METHODS }
|
|
112
115
|
preference :stock_reservations_enabled, :boolean, default: true
|
|
113
116
|
# Which address a sale's tax is computed against. Stores selling where tax
|
|
114
117
|
# follows the destination keep this on; billing-address jurisdictions turn
|
|
@@ -120,9 +123,9 @@ module Spree
|
|
|
120
123
|
preference :inventory_provider, :string, default: 'internal'
|
|
121
124
|
# What happens when an external source cannot be reached.
|
|
122
125
|
preference :pricing_provider_failure_policy, :string,
|
|
123
|
-
default: Spree::ProviderFailurePolicy::DEFAULT_PRICING_POLICY
|
|
126
|
+
default: Spree::ProviderFailurePolicy::DEFAULT_PRICING_POLICY, choices: -> { Spree::ProviderFailurePolicy::VALUES }
|
|
124
127
|
preference :inventory_provider_failure_policy, :string,
|
|
125
|
-
default: Spree::ProviderFailurePolicy::DEFAULT_INVENTORY_POLICY
|
|
128
|
+
default: Spree::ProviderFailurePolicy::DEFAULT_INVENTORY_POLICY, choices: -> { Spree::ProviderFailurePolicy::VALUES }
|
|
126
129
|
# Catalog preferences
|
|
127
130
|
preference :track_inventory_levels, :boolean, default: true
|
|
128
131
|
# On-hand quantity at or below which a tracked variant counts as running
|
|
@@ -163,11 +166,27 @@ module Spree
|
|
|
163
166
|
# applies to every numbered document; prefix, suffix and starting value
|
|
164
167
|
# are order-only — other document types keep their code-level prefixes.
|
|
165
168
|
# Changes affect future numbers only; existing numbers are permanent.
|
|
166
|
-
preference :document_number_format, :string, default: 'sequential'
|
|
169
|
+
preference :document_number_format, :string, default: 'sequential', choices: -> { Spree::NumberGenerators::Registry::FORMATS.keys }
|
|
167
170
|
preference :order_number_prefix, :string, default: 'R'
|
|
168
171
|
preference :order_number_suffix, :string, default: ''
|
|
169
172
|
preference :order_number_sequence_start, :integer, default: 1001
|
|
170
173
|
|
|
174
|
+
# The settings the API reads and writes under their plain names.
|
|
175
|
+
exposes_preferences :admin_locale, :timezone, :storefront_url, :weight_unit, :unit_system, :storefront_access, :guest_checkout,
|
|
176
|
+
:always_include_confirm_step, :company_field_enabled, :address_requires_company,
|
|
177
|
+
:address_requires_phone, :capture_method, :track_inventory_levels, :stock_reservations_enabled,
|
|
178
|
+
:low_stock_threshold, :tax_using_ship_address, :track_price_history,
|
|
179
|
+
:show_products_without_price, :disable_sku_validation, :order_routing_strategy,
|
|
180
|
+
:pricing_provider, :inventory_provider, :pricing_provider_failure_policy,
|
|
181
|
+
:inventory_provider_failure_policy, :payout_provider, :default_payouts_schedule_interval,
|
|
182
|
+
:default_minimum_payout_amount, :auto_approve_sellers, :auto_approve_seller_products,
|
|
183
|
+
:send_seller_transactional_emails, :default_commission_tax_rate, :document_number_format,
|
|
184
|
+
:order_number_prefix, :order_number_suffix, :order_number_sequence_start,
|
|
185
|
+
:limit_digital_download_count, :digital_asset_authorized_clicks,
|
|
186
|
+
:limit_digital_download_days, :digital_asset_authorized_days,
|
|
187
|
+
:send_consumer_transactional_emails, :email_accent_color, :email_background_color,
|
|
188
|
+
:email_card_color, :email_text_color, :email_heading_color, :email_font
|
|
189
|
+
|
|
171
190
|
#
|
|
172
191
|
# Associations
|
|
173
192
|
#
|
|
@@ -343,6 +362,10 @@ module Spree
|
|
|
343
362
|
validates :preferred_default_commission_tax_rate,
|
|
344
363
|
numericality: { greater_than_or_equal_to: 0, less_than_or_equal_to: 1 }
|
|
345
364
|
validates :preferred_storefront_access, inclusion: { in: Spree::Channel::Gating::STOREFRONT_ACCESS }
|
|
365
|
+
# Blank falls back to the defaults where they are read, so a store saved
|
|
366
|
+
# with one before these checks existed stays savable.
|
|
367
|
+
validates :preferred_unit_system, inclusion: { in: UNIT_SYSTEMS }, allow_blank: true
|
|
368
|
+
validates :preferred_weight_unit, inclusion: { in: Spree::Variant::WEIGHT_UNITS }, allow_blank: true
|
|
346
369
|
validates :preferred_document_number_format,
|
|
347
370
|
inclusion: { in: Spree::NumberGenerators::Registry::FORMATS.keys }
|
|
348
371
|
validates :preferred_order_number_sequence_start,
|
|
@@ -370,6 +393,7 @@ module Spree
|
|
|
370
393
|
#
|
|
371
394
|
has_one_attached :logo, service: Spree.public_storage_service_name
|
|
372
395
|
has_one_attached :mailer_logo, service: Spree.public_storage_service_name
|
|
396
|
+
signed_id_attachments :logo, :mailer_logo
|
|
373
397
|
|
|
374
398
|
# First-run setup credential (docs/plans/6.0-store-context-and-first-run-setup.md):
|
|
375
399
|
# printed by the installer, only consulted by the setup endpoint while no
|
|
@@ -528,6 +552,7 @@ module Spree
|
|
|
528
552
|
end
|
|
529
553
|
|
|
530
554
|
# Returns the storefront origin URL for use in customer-facing emails and links.
|
|
555
|
+
# Replaces the plain `storefront_url` reader `exposes_preferences` defines.
|
|
531
556
|
# Uses the `storefront_url` preference when set, then the oldest non-loopback
|
|
532
557
|
# allowed origin (the `http://localhost` origin seeded on install must never
|
|
533
558
|
# leak into customer emails), otherwise falls back to formatted_url.
|
|
@@ -5,6 +5,9 @@ module Spree
|
|
|
5
5
|
include Spree::SingleStoreResource
|
|
6
6
|
include Spree::HasCustomFields
|
|
7
7
|
include Spree::Metadata
|
|
8
|
+
include Spree::CanonicalDecimals
|
|
9
|
+
|
|
10
|
+
canonical_decimals :amount
|
|
8
11
|
|
|
9
12
|
publishes_lifecycle_events
|
|
10
13
|
|
|
@@ -105,13 +108,11 @@ module Spree
|
|
|
105
108
|
extend Spree::DisplayMoney
|
|
106
109
|
money_methods :amount, :amount_used, :amount_remaining, :amount_authorized
|
|
107
110
|
|
|
108
|
-
def amount=(amount)
|
|
109
|
-
self[:amount] = Spree::LocalizedNumber.parse(amount)
|
|
110
|
-
end
|
|
111
|
-
|
|
112
111
|
self.whitelisted_ransackable_attributes = %w[customer_id created_by_id amount currency memo]
|
|
112
|
+
search_by :memo
|
|
113
113
|
self.whitelisted_ransackable_associations = %w[customer created_by]
|
|
114
114
|
self.whitelisted_ransackable_scopes = %w[outstanding from_gift_card]
|
|
115
|
+
self.ransackable_scope_types = %w[outstanding from_gift_card].index_with('boolean')
|
|
115
116
|
# Staff notes and who issued the credit are back-office data.
|
|
116
117
|
self.private_ransackable_attributes = { store: %w[memo created_by_id] }
|
|
117
118
|
|
|
@@ -32,6 +32,7 @@ module Spree
|
|
|
32
32
|
normalizes :name, :contact_name, :phone, with: ->(value) { value.strip }
|
|
33
33
|
|
|
34
34
|
self.whitelisted_ransackable_attributes = %w[name contact_name email phone city country_code created_at]
|
|
35
|
+
search_by :name, :contact_name, :email
|
|
35
36
|
|
|
36
37
|
# The postal address, built from this row's own columns the way
|
|
37
38
|
# {Spree::StockLocation#address} does. Unsaved, and blank when nothing has
|
|
@@ -27,6 +27,7 @@ module Spree
|
|
|
27
27
|
# Confidential, so the private service rather than the public one that
|
|
28
28
|
# serves product images.
|
|
29
29
|
has_one_attached :document, service: Spree.private_storage_service_name
|
|
30
|
+
signed_id_attachments :document
|
|
30
31
|
|
|
31
32
|
validates :certificate_number, :reason_code, presence: true
|
|
32
33
|
# Certificates exist only on legal-entity nodes — a division's purchases
|
|
@@ -136,7 +136,7 @@ module Spree
|
|
|
136
136
|
write_tax_line(owner, item, rate, 0, 'customer_exempt', jurisdiction,
|
|
137
137
|
data: exemption_data(exemption, exemption_subject))
|
|
138
138
|
else
|
|
139
|
-
write_tax_line(owner, item, rate, compute_tax(rate, item, relevant_rates),
|
|
139
|
+
write_tax_line(owner, item, rate, compute_tax(rate, item, relevant_rates, owner.currency),
|
|
140
140
|
reason_for(rate), jurisdiction)
|
|
141
141
|
end
|
|
142
142
|
end
|
|
@@ -169,16 +169,19 @@ module Spree
|
|
|
169
169
|
item.respond_to?(:taxable_basis) ? item.taxable_basis : item.amount
|
|
170
170
|
end
|
|
171
171
|
|
|
172
|
-
|
|
172
|
+
# Rounded per line, half up, to the order's currency — the internal
|
|
173
|
+
# provider's choice; other providers round as their jurisdictions require.
|
|
174
|
+
def compute_tax(rate, item, relevant_rates, currency)
|
|
173
175
|
basis = taxable_basis(item)
|
|
174
176
|
|
|
175
|
-
if rate.included_in_price
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
177
|
+
tax = if rate.included_in_price
|
|
178
|
+
included_sum = relevant_rates.select(&:included_in_price).sum(&:amount)
|
|
179
|
+
included_sum = rate.amount if included_sum.zero?
|
|
180
|
+
basis / (1 + included_sum) * rate.amount
|
|
181
|
+
else
|
|
182
|
+
basis * rate.amount
|
|
183
|
+
end
|
|
184
|
+
Spree::Money::Rounding.to_currency(tax, currency)
|
|
182
185
|
end
|
|
183
186
|
|
|
184
187
|
# Pre-tax amounts are stored so refund and reporting code can weigh
|
|
@@ -15,6 +15,10 @@ module Spree
|
|
|
15
15
|
include Spree::HasCustomFields
|
|
16
16
|
include Spree::Metadata
|
|
17
17
|
include Spree::SingleStoreResource
|
|
18
|
+
include Spree::CanonicalDecimals
|
|
19
|
+
|
|
20
|
+
# A fraction (0.07125), held to the column's five decimals.
|
|
21
|
+
canonical_decimals :amount, places: 5
|
|
18
22
|
|
|
19
23
|
with_options inverse_of: :tax_rates do
|
|
20
24
|
belongs_to :tax_category,
|
|
@@ -56,17 +60,36 @@ module Spree
|
|
|
56
60
|
->(category) { where(tax_category_id: category.try(:id)) }
|
|
57
61
|
scope :included_in_price, -> { where(included_in_price: true) }
|
|
58
62
|
|
|
59
|
-
self.whitelisted_ransackable_attributes = %w[amount country_code state_code tax_category_id included_in_price name]
|
|
63
|
+
self.whitelisted_ransackable_attributes = %w[amount rate country_code state_code tax_category_id included_in_price name]
|
|
64
|
+
ransack_alias :rate, :amount
|
|
65
|
+
search_by :name
|
|
60
66
|
|
|
61
|
-
#
|
|
62
|
-
|
|
63
|
-
|
|
67
|
+
# The rate as a fraction (0.23); the column is +amount+.
|
|
68
|
+
alias_attribute :rate, :amount
|
|
69
|
+
|
|
70
|
+
# The rate as a percentage (23), what a merchant types.
|
|
71
|
+
#
|
|
72
|
+
# @return [BigDecimal, nil]
|
|
73
|
+
def rate_percent
|
|
74
|
+
amount && amount * 100
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
# @param value [String, Numeric, nil]
|
|
78
|
+
def rate_percent=(value)
|
|
79
|
+
parsed = Spree::Money::Rounding.parse_decimal(value)
|
|
80
|
+
self.amount = parsed && parsed / 100
|
|
81
|
+
end
|
|
64
82
|
|
|
65
|
-
|
|
83
|
+
# @deprecated Use {#rate_percent}; removed in 6.1.
|
|
84
|
+
def amount_percentage
|
|
85
|
+
Spree::Deprecation.warn('Spree::TaxRate#amount_percentage is deprecated and will be removed in Spree 6.1. Use #rate_percent instead.')
|
|
86
|
+
rate_percent
|
|
66
87
|
end
|
|
67
88
|
|
|
89
|
+
# @deprecated Use {#rate_percent=}; removed in 6.1.
|
|
68
90
|
def amount_percentage=(value)
|
|
69
|
-
|
|
91
|
+
Spree::Deprecation.warn('Spree::TaxRate#amount_percentage= is deprecated and will be removed in Spree 6.1. Use #rate_percent= instead.')
|
|
92
|
+
self.rate_percent = value
|
|
70
93
|
end
|
|
71
94
|
|
|
72
95
|
# The included-in-price rate to back out of a gross price.
|
data/app/models/spree/taxon.rb
CHANGED
|
@@ -10,7 +10,7 @@ module Spree
|
|
|
10
10
|
# prefix (ctg), and model_name are all Spree::Category. Only the constant differs,
|
|
11
11
|
# so is_a?, STI, polymorphic *_type strings, and class_name: references keep
|
|
12
12
|
# resolving to Spree::Category. The warning below fires through Spree's deprecator
|
|
13
|
-
# when this file loads (
|
|
13
|
+
# when this file loads (on first reference; it is kept out of eager loading).
|
|
14
14
|
Taxon = Category
|
|
15
15
|
|
|
16
16
|
Spree::Deprecation.warn('Spree::Taxon is deprecated and will be removed in Spree 6.1. Use Spree::Category instead.')
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
module Spree
|
|
2
|
+
# What {Spree::UpdateCheck} last learned about newer releases. Never
|
|
3
|
+
# persisted.
|
|
4
|
+
class UpdateStatus
|
|
5
|
+
include ActiveModel::Model
|
|
6
|
+
include ActiveModel::Attributes
|
|
7
|
+
|
|
8
|
+
attribute :current_version, :string
|
|
9
|
+
attribute :enabled, :boolean, default: true
|
|
10
|
+
# Nil until the first check has finished.
|
|
11
|
+
attribute :checked_at, :datetime
|
|
12
|
+
# Newer releases, newest first: hashes with +version+, +url+ and +published_at+.
|
|
13
|
+
attribute :releases, default: -> { [] }
|
|
14
|
+
|
|
15
|
+
# @return [Hash, nil]
|
|
16
|
+
def latest_release
|
|
17
|
+
releases.first
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
# @return [Boolean]
|
|
21
|
+
def update_available?
|
|
22
|
+
latest_release.present?
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
data/app/models/spree/variant.rb
CHANGED
|
@@ -8,6 +8,7 @@ module Spree
|
|
|
8
8
|
include Spree::MemoizedData
|
|
9
9
|
include Spree::HasCustomFields
|
|
10
10
|
include Spree::Metadata
|
|
11
|
+
include Spree::CanonicalDecimals
|
|
11
12
|
include Spree::HasExternalReferences
|
|
12
13
|
include Spree::Searchable
|
|
13
14
|
include Spree::StorePreferences
|
|
@@ -289,14 +290,9 @@ module Spree
|
|
|
289
290
|
relation.where(conditions.reduce(:or)).distinct
|
|
290
291
|
end
|
|
291
292
|
|
|
292
|
-
#
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
LOCALIZED_NUMBERS.each do |m|
|
|
296
|
-
define_method("#{m}=") do |argument|
|
|
297
|
-
self[m] = Spree::LocalizedNumber.parse(argument) if argument.present?
|
|
298
|
-
end
|
|
299
|
-
end
|
|
293
|
+
# A blank value leaves these as they were rather than clearing them.
|
|
294
|
+
canonical_decimals :cost_price, places: Spree::Money::Rounding::UNIT_PRICE_DECIMALS, ignore_blank: true
|
|
295
|
+
canonical_decimals :weight, :depth, :width, :height, places: nil, ignore_blank: true
|
|
300
296
|
|
|
301
297
|
accepts_nested_attributes_for(
|
|
302
298
|
:stock_levels,
|
|
@@ -331,6 +327,7 @@ module Spree
|
|
|
331
327
|
carton_package_type_id carton_weight cartons_per_pallet]
|
|
332
328
|
self.whitelisted_ransackable_scopes = %i(product_name_or_sku_cont search_by_product_name_or_sku search
|
|
333
329
|
available_at_stock_location)
|
|
330
|
+
self.ransackable_scope_types = { 'available_at_stock_location' => 'id' }
|
|
334
331
|
self.private_ransackable_attributes = { store: %w[cost_price cost_currency deleted_at] }
|
|
335
332
|
|
|
336
333
|
def self.product_name_or_sku_cont(query)
|
|
@@ -666,7 +663,7 @@ module Spree
|
|
|
666
663
|
def price_in(currency)
|
|
667
664
|
currency = currency&.upcase
|
|
668
665
|
|
|
669
|
-
price = if prices
|
|
666
|
+
price = if read_association_in_memory?(:prices) && prices.any?
|
|
670
667
|
prices.detect { |p| p.currency == currency && p.price_list_id.nil? }
|
|
671
668
|
else
|
|
672
669
|
prices.base_prices.find_by(currency: currency)
|
|
@@ -25,6 +25,7 @@ module Spree
|
|
|
25
25
|
|
|
26
26
|
# Ransack configuration
|
|
27
27
|
self.whitelisted_ransackable_attributes = %w[event_name response_code execution_time success delivered_at created_at]
|
|
28
|
+
search_by :event_name
|
|
28
29
|
|
|
29
30
|
# Event subjects whose permission is not found by class name alone.
|
|
30
31
|
PAYLOAD_SUBJECT_CLASSES = {
|
|
@@ -30,11 +30,45 @@ module Spree
|
|
|
30
30
|
before_save :clear_disabled_state_when_reactivated
|
|
31
31
|
|
|
32
32
|
self.whitelisted_ransackable_attributes = %w[name url active]
|
|
33
|
+
search_by :url
|
|
33
34
|
|
|
34
35
|
scope :active, -> { where(active: true) }
|
|
35
36
|
scope :inactive, -> { where(active: false) }
|
|
36
37
|
scope :enabled, -> { active.where(disabled_at: nil) }
|
|
37
38
|
|
|
39
|
+
# Whether +url+ targets a host the operator trusts despite it resolving to
|
|
40
|
+
# a private address — e.g. a storefront Service in the same Kubernetes
|
|
41
|
+
# cluster. Set via +Spree::Api::Config.webhooks_allowed_internal_hosts+
|
|
42
|
+
# (SPREE_WEBHOOKS_ALLOWED_INTERNAL_HOSTS): an entry matches that exact
|
|
43
|
+
# host, or with a leading dot (".svc.cluster.local") any host under it.
|
|
44
|
+
# Matching is case-insensitive. Operator config only, so a store admin
|
|
45
|
+
# can never point a webhook at an internal address.
|
|
46
|
+
#
|
|
47
|
+
# @param url [String]
|
|
48
|
+
# @return [Boolean]
|
|
49
|
+
def self.allowed_internal_host?(url)
|
|
50
|
+
host = URI.parse(url.to_s).hostname&.downcase
|
|
51
|
+
return false if host.blank?
|
|
52
|
+
|
|
53
|
+
allowed_internal_hosts.any? do |entry|
|
|
54
|
+
entry.start_with?('.') ? host.end_with?(entry) : host == entry
|
|
55
|
+
end
|
|
56
|
+
rescue URI::InvalidURIError
|
|
57
|
+
false
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
# Spree::Api::Config only exists when spree_api is loaded; without it no
|
|
61
|
+
# webhook is ever delivered, so there is nothing to allow.
|
|
62
|
+
def self.allowed_internal_hosts
|
|
63
|
+
return [] unless defined?(Spree::Api::Config)
|
|
64
|
+
|
|
65
|
+
Array(Spree::Api::Config.webhooks_allowed_internal_hosts).filter_map do |entry|
|
|
66
|
+
entry = entry.to_s.strip.downcase
|
|
67
|
+
entry if entry.present? && entry != '.'
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
private_class_method :allowed_internal_hosts
|
|
71
|
+
|
|
38
72
|
# Returns the plaintext `secret_key` only on the create response.
|
|
39
73
|
#
|
|
40
74
|
# `@reveal_secret_in_response` is set by the `after_create` callback above
|
|
@@ -165,6 +199,8 @@ module Spree
|
|
|
165
199
|
end
|
|
166
200
|
|
|
167
201
|
def url_must_not_resolve_to_private_ip
|
|
202
|
+
return if self.class.allowed_internal_host?(url)
|
|
203
|
+
|
|
168
204
|
uri = URI.parse(url)
|
|
169
205
|
blacklist = SsrfFilter::IPV4_BLACKLIST + SsrfFilter::IPV6_BLACKLIST
|
|
170
206
|
addresses = Resolv.getaddresses(uri.host)
|
|
@@ -96,16 +96,16 @@ module Spree
|
|
|
96
96
|
index.zero? ? order.email : nil,
|
|
97
97
|
index.zero? ? order.status : nil,
|
|
98
98
|
index.zero? ? order.currency : nil,
|
|
99
|
-
index.zero? ? order.item_total
|
|
100
|
-
index.zero? ? order.delivery_total
|
|
101
|
-
index.zero? ? order.tax_total
|
|
99
|
+
index.zero? ? money(order.item_total) : nil,
|
|
100
|
+
index.zero? ? money(order.delivery_total) : nil,
|
|
101
|
+
index.zero? ? money(order.tax_total) : nil,
|
|
102
102
|
index.zero? ? order.included_tax_total.positive? : nil,
|
|
103
103
|
index.zero? ? (order.discount_total.negative? || line_item.discount_total.negative?) : nil,
|
|
104
104
|
index.zero? ? order.has_free_shipping? : nil,
|
|
105
|
-
index.zero? ? order.discount_total.abs : nil,
|
|
105
|
+
index.zero? ? money(order.discount_total.abs) : nil,
|
|
106
106
|
index.zero? ? order.promo_code : nil,
|
|
107
|
-
index.zero? ? order.payments.store_credits.sum(:amount).abs : nil,
|
|
108
|
-
index.zero? ? order.total
|
|
107
|
+
index.zero? ? money(order.payments.store_credits.sum(:amount).abs) : nil,
|
|
108
|
+
index.zero? ? money(order.total) : nil,
|
|
109
109
|
index.zero? ? order.delivery_method&.name : nil,
|
|
110
110
|
index.zero? ? order.total_weight.to_f : nil,
|
|
111
111
|
index.zero? ? order.payments.valid&.first&.display_source_name : nil,
|
|
@@ -160,6 +160,10 @@ module Spree
|
|
|
160
160
|
|
|
161
161
|
private
|
|
162
162
|
|
|
163
|
+
def money(amount)
|
|
164
|
+
Spree::Money::Rounding.format(amount, order.currency)
|
|
165
|
+
end
|
|
166
|
+
|
|
163
167
|
# Drops the cells whose headers were omitted, matching each value to its
|
|
164
168
|
# header by position so the row and the header list stay aligned.
|
|
165
169
|
def reject_omitted(csv)
|
|
@@ -26,15 +26,10 @@ module Spree
|
|
|
26
26
|
|
|
27
27
|
private
|
|
28
28
|
|
|
29
|
-
#
|
|
30
|
-
#
|
|
31
|
-
# localized because the file is read back by the import under the
|
|
32
|
-
# store's locale, not the exporting admin's, and a comma written here
|
|
33
|
-
# would re-import as a hundred times the price.
|
|
29
|
+
# A plain decimal ("18.00", never "$18.00" or "18,00"): the import reads
|
|
30
|
+
# the file back and accepts nothing else.
|
|
34
31
|
def amount_string(amount)
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
format("%.#{Spree::Money::Rounding.precision(price.currency)}f", amount)
|
|
32
|
+
Spree::Money::Rounding.format(amount, price.currency, unit_price: true)
|
|
38
33
|
end
|
|
39
34
|
end
|
|
40
35
|
end
|
|
@@ -98,17 +98,17 @@ module Spree
|
|
|
98
98
|
index.zero? ? product.meta_keywords : nil,
|
|
99
99
|
index.zero? ? product.tag_list.to_s : nil,
|
|
100
100
|
index.zero? ? product.label_list.to_s : nil,
|
|
101
|
-
variant.amount_in(currency)
|
|
102
|
-
variant.compare_at_amount_in(currency)
|
|
101
|
+
unit_price(variant.amount_in(currency) || 0),
|
|
102
|
+
unit_price(variant.compare_at_amount_in(currency) || 0),
|
|
103
103
|
currency,
|
|
104
|
-
variant.width,
|
|
105
|
-
variant.height,
|
|
106
|
-
variant.depth,
|
|
104
|
+
decimal(variant.width),
|
|
105
|
+
decimal(variant.height),
|
|
106
|
+
decimal(variant.depth),
|
|
107
107
|
# The stored columns, not the resolved readers: importing this file
|
|
108
108
|
# into a store on another unit system must not silently write this
|
|
109
109
|
# store's units onto every variant.
|
|
110
110
|
variant[:dimensions_unit],
|
|
111
|
-
variant.weight,
|
|
111
|
+
decimal(variant.weight),
|
|
112
112
|
variant[:weight_unit],
|
|
113
113
|
publication_available_on&.strftime('%Y-%m-%d %H:%M:%S'),
|
|
114
114
|
(variant.discontinue_on || publication_discontinue_on)&.strftime('%Y-%m-%d %H:%M:%S'),
|
|
@@ -151,6 +151,15 @@ module Spree
|
|
|
151
151
|
|
|
152
152
|
private
|
|
153
153
|
|
|
154
|
+
def unit_price(amount)
|
|
155
|
+
Spree::Money::Rounding.format(amount, currency, unit_price: true)
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
# "1.5", never Ruby's "0.15e1", which the import refuses.
|
|
159
|
+
def decimal(value)
|
|
160
|
+
Spree::Money::Rounding.format_decimal(value)
|
|
161
|
+
end
|
|
162
|
+
|
|
154
163
|
# Default-channel publication for the export's store. 5.5 transitional:
|
|
155
164
|
# fall back to the legacy Product columns when the publication dates are
|
|
156
165
|
# NULL (pre-backfill). 6.0 drops the Product-column fallback.
|
|
@@ -175,8 +184,8 @@ module Spree
|
|
|
175
184
|
csv = Array.new(CSV_HEADERS.size)
|
|
176
185
|
csv[CSV_HEADERS.index('sku')] = variant.sku
|
|
177
186
|
csv[CSV_HEADERS.index('slug')] = product.slug
|
|
178
|
-
csv[CSV_HEADERS.index('price')] = variant.amount_in(currency)
|
|
179
|
-
csv[CSV_HEADERS.index('compare_at_price')] = variant.compare_at_amount_in(currency)
|
|
187
|
+
csv[CSV_HEADERS.index('price')] = unit_price(variant.amount_in(currency))
|
|
188
|
+
csv[CSV_HEADERS.index('compare_at_price')] = unit_price(variant.compare_at_amount_in(currency))
|
|
180
189
|
csv[CSV_HEADERS.index('currency')] = currency
|
|
181
190
|
csv
|
|
182
191
|
end
|
|
@@ -41,9 +41,7 @@ module Spree
|
|
|
41
41
|
private
|
|
42
42
|
|
|
43
43
|
def amount_string(amount, currency)
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
format("%.#{Spree::Money::Rounding.precision(currency)}f", amount)
|
|
44
|
+
Spree::Money::Rounding.format(amount, currency, unit_price: true)
|
|
47
45
|
end
|
|
48
46
|
end
|
|
49
47
|
end
|
|
@@ -89,7 +89,7 @@ module Spree
|
|
|
89
89
|
cart.use_shipping = params[:use_shipping] if params.key?(:use_shipping)
|
|
90
90
|
assign_preferred_stock_location if params.key?(:preferred_stock_location_id)
|
|
91
91
|
assign_company if params.key?(:company_id)
|
|
92
|
-
assign_po_document if params.key?(:
|
|
92
|
+
assign_po_document if params.key?(:po_document_signed_id)
|
|
93
93
|
end
|
|
94
94
|
|
|
95
95
|
# The buyer's purchase order as an ActiveStorage signed blob id — the
|
|
@@ -97,12 +97,12 @@ module Spree
|
|
|
97
97
|
# value detaches, so a buyer can withdraw a document they attached in
|
|
98
98
|
# error.
|
|
99
99
|
#
|
|
100
|
-
# An abandoned
|
|
100
|
+
# An abandoned presigned upload leaves the blob row behind without its
|
|
101
101
|
# bytes, and the buyer's client may still send back the signed id it was
|
|
102
102
|
# handed. ActiveStorage raises a message-less FileNotFoundError for that,
|
|
103
103
|
# which would otherwise reach the storefront as a bare class name.
|
|
104
104
|
def assign_po_document
|
|
105
|
-
value = params[:
|
|
105
|
+
value = params[:po_document_signed_id]
|
|
106
106
|
|
|
107
107
|
if value.blank?
|
|
108
108
|
cart.po_document.detach
|
|
@@ -4,9 +4,13 @@ module Spree
|
|
|
4
4
|
class ProductVariant < Base
|
|
5
5
|
OPTION_TYPES_COUNT = 3
|
|
6
6
|
CATEGORY_FIELDS = %w[category1 category2 category3].freeze
|
|
7
|
+
# Written as plain decimals ("16.50"): the models store text as sent,
|
|
8
|
+
# and "1,599.99" or "16,50" would otherwise be saved as 1 or 16.
|
|
9
|
+
DECIMAL_FIELDS = %w[price compare_at_price cost_price weight height width depth carton_weight].freeze
|
|
7
10
|
|
|
8
11
|
def initialize(row, **)
|
|
9
12
|
super
|
|
13
|
+
ensure_decimals!
|
|
10
14
|
@store = row.store
|
|
11
15
|
@seller = import.seller
|
|
12
16
|
# Before the product is saved: rows are not wrapped in a transaction,
|
|
@@ -83,6 +87,14 @@ module Spree
|
|
|
83
87
|
|
|
84
88
|
private
|
|
85
89
|
|
|
90
|
+
def ensure_decimals!
|
|
91
|
+
DECIMAL_FIELDS.each do |column|
|
|
92
|
+
Spree::Money::Rounding.parse_decimal(attributes[column])
|
|
93
|
+
rescue Spree::Money::InvalidFormat
|
|
94
|
+
raise ArgumentError, I18n.t('spree.import_invalid_number', column: column, value: attributes[column].to_s.strip)
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
|
|
86
98
|
# A product-header (options-empty) row creates an option-less default
|
|
87
99
|
# variant to hold product-level attributes. Once an option-bearing
|
|
88
100
|
# variant exists, that placeholder is a leftover — remove it and
|
|
@@ -16,15 +16,15 @@ module Spree
|
|
|
16
16
|
# English, Brazilian vs. European Portuguese, Simplified vs. Traditional
|
|
17
17
|
# Chinese, etc.).
|
|
18
18
|
#
|
|
19
|
-
#
|
|
20
|
-
#
|
|
21
|
-
#
|
|
19
|
+
# Markets and stores only accept codes from this list when their locales
|
|
20
|
+
# change, so an order's or cart's locale (which must be one its store
|
|
21
|
+
# supports) is one too, unless the store kept a value saved before that.
|
|
22
22
|
ALL = %w[
|
|
23
23
|
aa ab ae af ak am an ar as av ay az
|
|
24
24
|
ba be bg bh bi bm bn bo br bs
|
|
25
25
|
ca ce ch co cr cs cu cv cy
|
|
26
26
|
da de de-AT de-CH dv dz
|
|
27
|
-
ee el en en-AU en-CA en-GB en-IN en-NZ eo es es-419 es-MX et eu
|
|
27
|
+
ee el en en-AU en-CA en-GB en-IN en-NZ en-US eo es es-419 es-CL es-EC es-MX et eu
|
|
28
28
|
fa ff fi fj fo fr fr-CA fy
|
|
29
29
|
ga gd gl gn gu gv
|
|
30
30
|
ha he hi ho hr ht hu hy hz
|
|
@@ -38,7 +38,7 @@ module Spree
|
|
|
38
38
|
pa pi pl ps pt pt-BR pt-PT
|
|
39
39
|
qu
|
|
40
40
|
rm rn ro ru rw
|
|
41
|
-
sa sc sd se sg si sk sl sm sn so sq sr ss st su sv sw
|
|
41
|
+
sa sc sd se sg si sk sl sl-SI sm sn so sq sr ss st su sv sw
|
|
42
42
|
ta te tg th ti tk tl tn to tr ts tt tw ty
|
|
43
43
|
ug uk ur uz
|
|
44
44
|
ve vi vo
|
|
@@ -47,5 +47,39 @@ module Spree
|
|
|
47
47
|
yi yo
|
|
48
48
|
za zh-CN zh-HK zh-TW zu
|
|
49
49
|
].freeze
|
|
50
|
+
|
|
51
|
+
# The codes in a comma-separated list that are not listed, for a
|
|
52
|
+
# validation to name.
|
|
53
|
+
#
|
|
54
|
+
# @param codes [String, nil] e.g. "en,pt-BR"
|
|
55
|
+
# @return [Array<String>]
|
|
56
|
+
def self.unlisted(codes)
|
|
57
|
+
codes.to_s.split(',').map(&:strip).reject(&:empty?) - ALL
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
# A listed locale for a code from elsewhere (a host app's I18n config, a
|
|
61
|
+
# country's official language, a legacy column): the listed spelling of
|
|
62
|
+
# the code itself (`en_US` and `pt-br` give `en-US` and `pt-BR`),
|
|
63
|
+
# otherwise the listed one for its language (`en-IE` gives `en`, `zh`
|
|
64
|
+
# gives `zh-CN`), or nil when Spree knows neither.
|
|
65
|
+
#
|
|
66
|
+
# @param code [String, Symbol, nil]
|
|
67
|
+
# @return [String, nil]
|
|
68
|
+
def self.listed(code)
|
|
69
|
+
code = code.to_s.strip.tr('_', '-')
|
|
70
|
+
return if code.empty?
|
|
71
|
+
|
|
72
|
+
ALL.find { |listed| listed.casecmp?(code) } || for_language(code.split('-').first)
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
# The listed locale for an ISO 639-1 language: the language itself, or
|
|
76
|
+
# its first regional variant when only those are listed (`zh` gives
|
|
77
|
+
# `zh-CN`).
|
|
78
|
+
#
|
|
79
|
+
# @param language [String]
|
|
80
|
+
# @return [String, nil]
|
|
81
|
+
def self.for_language(language)
|
|
82
|
+
Spree.locale_for_language(language.to_s.downcase, ALL)
|
|
83
|
+
end
|
|
50
84
|
end
|
|
51
85
|
end
|