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
|
@@ -3,14 +3,14 @@ module Spree
|
|
|
3
3
|
class FlatRate < ShippingCalculator
|
|
4
4
|
include Spree::Calculator::CurrencyAmounts
|
|
5
5
|
|
|
6
|
-
preference :amount, :
|
|
6
|
+
preference :amount, :money, default: 0
|
|
7
7
|
# @deprecated Together with the single `amount`, superseded by the
|
|
8
8
|
# per-currency `amounts` hash; kept as the fallback for its own
|
|
9
9
|
# currency so upgraded stores quote unchanged.
|
|
10
|
-
preference :currency, :string, default: -> { Spree::Store.default.default_currency }
|
|
10
|
+
preference :currency, :string, format: :currency, default: -> { Spree::Store.default.default_currency }
|
|
11
11
|
|
|
12
|
-
preference :minimum_item_total, :
|
|
13
|
-
preference :maximum_item_total, :
|
|
12
|
+
preference :minimum_item_total, :money, default: nil, nullable: true, deprecated: 'Use a Spree::DeliveryMethodRules rule on the delivery method instead; removed in Spree 6.1.'
|
|
13
|
+
preference :maximum_item_total, :money, default: nil, nullable: true, deprecated: 'Use a Spree::DeliveryMethodRules rule on the delivery method instead; removed in Spree 6.1.'
|
|
14
14
|
|
|
15
15
|
preference :minimum_weight, :decimal, default: nil, nullable: true, deprecated: 'Use a Spree::DeliveryMethodRules rule on the delivery method instead; removed in Spree 6.1.'
|
|
16
16
|
preference :maximum_weight, :decimal, default: nil, nullable: true, deprecated: 'Use a Spree::DeliveryMethodRules rule on the delivery method instead; removed in Spree 6.1.'
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
module Spree
|
|
2
2
|
module Calculator::Shipping
|
|
3
3
|
class FlexiRate < ShippingCalculator
|
|
4
|
-
preference :first_item, :
|
|
5
|
-
preference :additional_item, :
|
|
4
|
+
preference :first_item, :money, default: 0.0
|
|
5
|
+
preference :additional_item, :money, default: 0.0
|
|
6
6
|
preference :max_items, :integer, default: 0
|
|
7
|
-
preference :currency, :string, default: -> { Spree::Store.default.default_currency }
|
|
7
|
+
preference :currency, :string, format: :currency, default: -> { Spree::Store.default.default_currency }
|
|
8
8
|
|
|
9
9
|
# Single-currency amounts: quoting another currency would mislabel
|
|
10
10
|
# the configured numbers, so the method stays hidden there.
|
|
@@ -3,8 +3,8 @@ module Spree
|
|
|
3
3
|
class PerItem < ShippingCalculator
|
|
4
4
|
include Spree::Calculator::CurrencyAmounts
|
|
5
5
|
|
|
6
|
-
preference :amount, :
|
|
7
|
-
preference :currency, :string, default: -> { Spree::Store.default.default_currency }
|
|
6
|
+
preference :amount, :money, default: 0
|
|
7
|
+
preference :currency, :string, format: :currency, default: -> { Spree::Store.default.default_currency }
|
|
8
8
|
|
|
9
9
|
def self.description
|
|
10
10
|
I18n.t('spree.shipping_flat_rate_per_item')
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
module Spree
|
|
2
2
|
module Calculator::Shipping
|
|
3
3
|
class PriceSack < ShippingCalculator
|
|
4
|
-
preference :minimal_amount, :
|
|
5
|
-
preference :normal_amount, :
|
|
6
|
-
preference :discount_amount, :
|
|
7
|
-
preference :currency, :string, default: -> { Spree::Store.default.default_currency }
|
|
4
|
+
preference :minimal_amount, :money, default: 0
|
|
5
|
+
preference :normal_amount, :money, default: 0
|
|
6
|
+
preference :discount_amount, :money, default: 0
|
|
7
|
+
preference :currency, :string, format: :currency, default: -> { Spree::Store.default.default_currency }
|
|
8
8
|
|
|
9
9
|
# Single-currency amounts: quoting another currency would mislabel
|
|
10
10
|
# the configured numbers, so the method stays hidden there.
|
|
@@ -2,8 +2,8 @@ module Spree
|
|
|
2
2
|
class Calculator::TieredFlatRate < Calculator
|
|
3
3
|
include Spree::Calculator::Tiers
|
|
4
4
|
|
|
5
|
-
preference :base_amount, :
|
|
6
|
-
preference :currency, :string, default: -> { Spree::Store.default.default_currency }
|
|
5
|
+
preference :base_amount, :money, default: 0
|
|
6
|
+
preference :currency, :string, format: :currency, default: -> { Spree::Store.default.default_currency }
|
|
7
7
|
|
|
8
8
|
def self.description
|
|
9
9
|
I18n.t('spree.tiered_flat_rate')
|
|
@@ -15,7 +15,7 @@ module Spree
|
|
|
15
15
|
|
|
16
16
|
def compute(object)
|
|
17
17
|
percent = tier_value_for(object.amount) || preferred_base_percent
|
|
18
|
-
(object.amount * percent / 100
|
|
18
|
+
Spree::Money::Rounding.to_currency(object.amount * percent / 100, object.try(:currency))
|
|
19
19
|
end
|
|
20
20
|
|
|
21
21
|
private
|
data/app/models/spree/catalog.rb
CHANGED
|
@@ -86,6 +86,7 @@ module Spree
|
|
|
86
86
|
scope :by_position, -> { ordered }
|
|
87
87
|
|
|
88
88
|
self.whitelisted_ransackable_attributes = %w[name active]
|
|
89
|
+
search_by :name
|
|
89
90
|
|
|
90
91
|
# The catalogs that apply to a purchase node, nearest node first: the
|
|
91
92
|
# node's own assignments before its parent's, catalog position breaking
|
|
@@ -80,13 +80,6 @@ module Spree
|
|
|
80
80
|
break_count.to_i.positive?
|
|
81
81
|
end
|
|
82
82
|
|
|
83
|
-
# The amount as the wire carries it — see CatalogPriceTier#display_value.
|
|
84
|
-
#
|
|
85
|
-
# @return [String]
|
|
86
|
-
def display_value
|
|
87
|
-
amount.to_s
|
|
88
|
-
end
|
|
89
|
-
|
|
90
83
|
# @return [Spree::Money]
|
|
91
84
|
def money
|
|
92
85
|
Spree::Money.new(amount, currency: currency)
|
|
@@ -15,14 +15,6 @@ module Spree
|
|
|
15
15
|
|
|
16
16
|
validates :min_quantity, numericality: { greater_than: 1 }
|
|
17
17
|
|
|
18
|
-
# The amount as the wire carries it — a string, like every other
|
|
19
|
-
# money-shaped value, since a float would round the merchant's figure.
|
|
20
|
-
#
|
|
21
|
-
# @return [String]
|
|
22
|
-
def display_value
|
|
23
|
-
amount.to_s
|
|
24
|
-
end
|
|
25
|
-
|
|
26
18
|
# @return [Spree::Money]
|
|
27
19
|
def money
|
|
28
20
|
Spree::Money.new(amount, currency: currency)
|
|
@@ -61,6 +61,7 @@ module Spree
|
|
|
61
61
|
#
|
|
62
62
|
has_one_attached :image, service: Spree.public_storage_service_name
|
|
63
63
|
has_one_attached :square_image, service: Spree.public_storage_service_name
|
|
64
|
+
signed_id_attachments :image, :square_image
|
|
64
65
|
# The slots double as media-library placements, so an upload here is
|
|
65
66
|
# visible and reusable there (Spree::HasLibraryMedia).
|
|
66
67
|
has_library_media :image, :square_image
|
|
@@ -133,8 +134,9 @@ module Spree
|
|
|
133
134
|
# Ransack
|
|
134
135
|
#
|
|
135
136
|
self.whitelisted_ransackable_associations = %w[parent]
|
|
136
|
-
self.whitelisted_ransackable_attributes = %w[name permalink automatic depth
|
|
137
|
+
self.whitelisted_ransackable_attributes = %w[name permalink automatic depth children_count
|
|
137
138
|
products_count pretty_name parent_id]
|
|
139
|
+
search_by :name, :pretty_name
|
|
138
140
|
|
|
139
141
|
#
|
|
140
142
|
# Translations
|
|
@@ -17,8 +17,9 @@ module Spree
|
|
|
17
17
|
# Empty -> falls back to the Store-level preference. Both are `nullable`
|
|
18
18
|
# so a blank write stays nil (inherit) rather than coercing to "" or
|
|
19
19
|
# false, while an explicit value remains a channel override.
|
|
20
|
-
preference :storefront_access, :string, default: nil, nullable: true
|
|
20
|
+
preference :storefront_access, :string, default: nil, nullable: true, choices: STOREFRONT_ACCESS
|
|
21
21
|
preference :guest_checkout, :boolean, default: nil, nullable: true
|
|
22
|
+
exposes_preferences :storefront_access, :guest_checkout
|
|
22
23
|
|
|
23
24
|
validate :storefront_access_must_be_valid
|
|
24
25
|
end
|
data/app/models/spree/channel.rb
CHANGED
|
@@ -16,6 +16,7 @@ module Spree
|
|
|
16
16
|
# Empty -> falls back to the Store-level preference.
|
|
17
17
|
preference :order_routing_strategy, :string, default: nil,
|
|
18
18
|
parse_on_set: ->(value) { Spree::ApiTyped.class_name_for(Spree.order_routing.strategies, value) }
|
|
19
|
+
exposes_preferences :order_routing_strategy
|
|
19
20
|
|
|
20
21
|
|
|
21
22
|
has_many :orders, class_name: 'Spree::Order', inverse_of: :channel, dependent: :nullify
|
|
@@ -59,6 +60,7 @@ module Spree
|
|
|
59
60
|
scope :default, -> { where(default: true) }
|
|
60
61
|
|
|
61
62
|
self.whitelisted_ransackable_attributes = %w[name code active default store_id]
|
|
63
|
+
search_by :name
|
|
62
64
|
|
|
63
65
|
# Publishes the given products on this channel by creating/upserting ProductPublications.
|
|
64
66
|
# Optionally sets the publication window; if not given, the products will be published immediately
|
data/app/models/spree/claim.rb
CHANGED
|
@@ -72,6 +72,7 @@ module Spree
|
|
|
72
72
|
#
|
|
73
73
|
has_one_attached :image, service: Spree.public_storage_service_name
|
|
74
74
|
has_one_attached :square_image, service: Spree.public_storage_service_name
|
|
75
|
+
signed_id_attachments :image, :square_image
|
|
75
76
|
# The slots double as media-library placements, so an upload here is
|
|
76
77
|
# visible and reusable there (Spree::HasLibraryMedia).
|
|
77
78
|
has_library_media :image, :square_image
|
|
@@ -101,6 +102,7 @@ module Spree
|
|
|
101
102
|
# Ransack
|
|
102
103
|
#
|
|
103
104
|
self.whitelisted_ransackable_attributes = %w[permalink automatic sort_order products_count]
|
|
105
|
+
search_by :name
|
|
104
106
|
|
|
105
107
|
#
|
|
106
108
|
# Automatic (rule-based) membership
|
|
@@ -36,6 +36,9 @@ module Spree
|
|
|
36
36
|
include Spree::SingleStoreResource
|
|
37
37
|
include Spree::HasListPosition
|
|
38
38
|
include Spree::Metadata
|
|
39
|
+
include Spree::CanonicalDecimals
|
|
40
|
+
|
|
41
|
+
canonical_decimals :value, places: 5
|
|
39
42
|
include Spree::TypedAssociations
|
|
40
43
|
|
|
41
44
|
KINDS = %w[percentage fixed].freeze
|
|
@@ -103,6 +106,7 @@ module Spree
|
|
|
103
106
|
|
|
104
107
|
self.whitelisted_ransackable_attributes = %w[name code kind enabled position value
|
|
105
108
|
tax_inclusive include_shipping]
|
|
109
|
+
search_by :name
|
|
106
110
|
self.whitelisted_ransackable_associations = %w[commission_rules]
|
|
107
111
|
|
|
108
112
|
# @return [Boolean]
|
|
@@ -244,7 +248,7 @@ module Spree
|
|
|
244
248
|
# shape a price list's rules use.
|
|
245
249
|
#
|
|
246
250
|
# Ids inside a rule's preferences are scope-checked against this rate's own
|
|
247
|
-
# store as they are written (see
|
|
251
|
+
# store as they are written (see `of: :id, scope:`), so a rule can
|
|
248
252
|
# never be pointed at another marketplace's records.
|
|
249
253
|
#
|
|
250
254
|
# @param rows [Array<Hash>, nil]
|
|
@@ -9,11 +9,8 @@ module Spree
|
|
|
9
9
|
# restate the rule on every new leaf. The walk is done once per order by
|
|
10
10
|
# Spree::Commissions::Context, not per rule.
|
|
11
11
|
class CategoryRule < Spree::CommissionRule
|
|
12
|
-
preference :category_ids, :array, default: [],
|
|
13
|
-
|
|
14
|
-
klass: Spree::Category,
|
|
15
|
-
scope: ->(rule) { rule.store.categories }
|
|
16
|
-
)
|
|
12
|
+
preference :category_ids, :array, of: :id, model: 'Spree::Category', default: [],
|
|
13
|
+
scope: ->(rule) { rule.store.categories }
|
|
17
14
|
|
|
18
15
|
# @return [Array<Spree::Category>]
|
|
19
16
|
def categories
|
|
@@ -14,8 +14,8 @@ module Spree
|
|
|
14
14
|
# This rule is the reason commission rules became classes. It could not
|
|
15
15
|
# exist while a rule was only able to name a record.
|
|
16
16
|
class ItemTotalRule < Spree::CommissionRule
|
|
17
|
-
preference :min_amount, :
|
|
18
|
-
preference :max_amount, :
|
|
17
|
+
preference :min_amount, :money, nullable: true
|
|
18
|
+
preference :max_amount, :money, nullable: true
|
|
19
19
|
|
|
20
20
|
def applicable?(context)
|
|
21
21
|
# Weighed exactly as the fee will weigh it, per this rate's own base.
|
|
@@ -7,11 +7,8 @@ module Spree
|
|
|
7
7
|
# Ids are checked against the rate's own store on write, so a rule can
|
|
8
8
|
# never be pointed at another marketplace's seller — and the client is
|
|
9
9
|
# told, rather than having the id quietly dropped.
|
|
10
|
-
preference :seller_ids, :array, default: [],
|
|
11
|
-
|
|
12
|
-
klass: Spree::Seller,
|
|
13
|
-
scope: ->(rule) { rule.store.sellers }
|
|
14
|
-
)
|
|
10
|
+
preference :seller_ids, :array, of: :id, model: 'Spree::Seller', default: [],
|
|
11
|
+
scope: ->(rule) { rule.store.sellers }
|
|
15
12
|
|
|
16
13
|
# @return [Array<Spree::Seller>]
|
|
17
14
|
def sellers
|
data/app/models/spree/company.rb
CHANGED
|
@@ -101,6 +101,7 @@ module Spree
|
|
|
101
101
|
}
|
|
102
102
|
|
|
103
103
|
self.whitelisted_ransackable_attributes = %w[name kind parent_id po_number_required]
|
|
104
|
+
search_by :name
|
|
104
105
|
self.whitelisted_ransackable_associations = %w[parent children memberships external_references]
|
|
105
106
|
|
|
106
107
|
# The node a buyer browses and buys as when they name none: the single
|
|
@@ -25,16 +25,6 @@ module Spree
|
|
|
25
25
|
scope :for_resource_type, ->(resource_type) { where(resource_type: resource_type) }
|
|
26
26
|
scope :storefront_visible, -> { where(storefront_visible: true) }
|
|
27
27
|
scope :admin_only, -> { where(storefront_visible: false) }
|
|
28
|
-
scope :search, ->(query) do
|
|
29
|
-
return all if query.blank?
|
|
30
|
-
|
|
31
|
-
search_term = "%#{query.downcase}%"
|
|
32
|
-
namespace_condition = arel_table[:namespace].lower.matches(search_term)
|
|
33
|
-
key_condition = arel_table[:key].lower.matches(search_term)
|
|
34
|
-
label_condition = arel_table[:label].lower.matches(search_term)
|
|
35
|
-
|
|
36
|
-
where(namespace_condition.or(key_condition).or(label_condition))
|
|
37
|
-
end
|
|
38
28
|
|
|
39
29
|
#
|
|
40
30
|
# Callbacks
|
|
@@ -52,8 +42,17 @@ module Spree
|
|
|
52
42
|
#
|
|
53
43
|
# Ransack
|
|
54
44
|
#
|
|
55
|
-
self.whitelisted_ransackable_attributes = %w[key namespace label resource_type storefront_visible searchable sortable]
|
|
56
|
-
|
|
45
|
+
self.whitelisted_ransackable_attributes = %w[key namespace label resource_type field_type storefront_visible searchable sortable]
|
|
46
|
+
|
|
47
|
+
# Both columns store class names; filters take the short names clients see.
|
|
48
|
+
def self.api_type_resolver(attribute)
|
|
49
|
+
case attribute
|
|
50
|
+
when 'resource_type' then ->(api_type) { normalize_value_for(:resource_type, api_type) }
|
|
51
|
+
when 'field_type' then ->(api_type) { Spree::CustomField::TYPE_TOKENS[api_type] }
|
|
52
|
+
else super
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
search_by :namespace, :key, :label
|
|
57
56
|
|
|
58
57
|
# API-facing token for the STI subclass name stored in the `field_type`
|
|
59
58
|
# column. Reader returns the registered token (`short_text`); writer
|
|
@@ -56,6 +56,7 @@ module Spree
|
|
|
56
56
|
|
|
57
57
|
self.whitelisted_ransackable_attributes = %w[number kind status email requested_at completed_at]
|
|
58
58
|
self.whitelisted_ransackable_scopes = %w[access erasure in_progress]
|
|
59
|
+
self.ransackable_scope_types = %w[access erasure in_progress].index_with('boolean')
|
|
59
60
|
|
|
60
61
|
before_validation :set_defaults, on: :create
|
|
61
62
|
|
|
@@ -122,6 +122,7 @@ module Spree
|
|
|
122
122
|
# needed. `seller_id` is what the operator's list filters by to see one
|
|
123
123
|
# seller's methods, or (blank) the marketplace's own.
|
|
124
124
|
self.whitelisted_ransackable_attributes = %w[storefront_visible available_to_sellers seller_id]
|
|
125
|
+
search_by :name
|
|
125
126
|
self.whitelisted_ransackable_associations = %w[seller]
|
|
126
127
|
|
|
127
128
|
validates :name, presence: true
|
|
@@ -227,7 +228,11 @@ module Spree
|
|
|
227
228
|
end
|
|
228
229
|
|
|
229
230
|
def ensure_calculator
|
|
230
|
-
self.calculator ||=
|
|
231
|
+
self.calculator ||= default_calculator
|
|
232
|
+
end
|
|
233
|
+
|
|
234
|
+
def default_calculator
|
|
235
|
+
Spree::Calculator::Shipping::FlatRate.new(preferred_amount: 0)
|
|
231
236
|
end
|
|
232
237
|
|
|
233
238
|
def assign_default_delivery_profile
|
|
@@ -13,11 +13,8 @@ module Spree
|
|
|
13
13
|
# decodes them on write, so eligibility compares against raw
|
|
14
14
|
# `channel_id` rows. The scope confines the existence check to the
|
|
15
15
|
# method's store, so cross-store channel IDs can't sneak in.
|
|
16
|
-
preference :channel_ids, :array, default: [],
|
|
17
|
-
|
|
18
|
-
klass: Spree::Channel,
|
|
19
|
-
scope: ->(rule) { rule.store.channels }
|
|
20
|
-
)
|
|
16
|
+
preference :channel_ids, :array, of: :id, model: 'Spree::Channel', default: [],
|
|
17
|
+
scope: ->(rule) { rule.store.channels }
|
|
21
18
|
|
|
22
19
|
def channels
|
|
23
20
|
return [] if preferred_channel_ids.blank?
|
|
@@ -4,8 +4,8 @@ module Spree
|
|
|
4
4
|
# owner's currency). Empty preferences pass — half-configured rules fail
|
|
5
5
|
# open per-rule, matching the PromotionRule/PriceRule convention.
|
|
6
6
|
class ItemTotalRule < Spree::DeliveryMethodRule
|
|
7
|
-
preference :minimum_amount, :
|
|
8
|
-
preference :maximum_amount, :
|
|
7
|
+
preference :minimum_amount, :money, default: nil, nullable: true
|
|
8
|
+
preference :maximum_amount, :money, default: nil, nullable: true
|
|
9
9
|
|
|
10
10
|
def eligible?(package)
|
|
11
11
|
total = package.item_total
|
|
@@ -80,9 +80,9 @@ module Spree
|
|
|
80
80
|
@tax_amount ||= if tax_rate.nil?
|
|
81
81
|
BigDecimal(0)
|
|
82
82
|
elsif tax_rate.included_in_price?
|
|
83
|
-
(cost / (1 + tax_rate.amount) * tax_rate.amount)
|
|
83
|
+
Spree::Money::Rounding.to_currency(cost / (1 + tax_rate.amount) * tax_rate.amount, currency)
|
|
84
84
|
else
|
|
85
|
-
(cost * tax_rate.amount)
|
|
85
|
+
Spree::Money::Rounding.to_currency(cost * tax_rate.amount, currency)
|
|
86
86
|
end
|
|
87
87
|
end
|
|
88
88
|
|
|
@@ -157,7 +157,7 @@ module Spree
|
|
|
157
157
|
end
|
|
158
158
|
|
|
159
159
|
def discount_amount
|
|
160
|
-
fulfillment.
|
|
160
|
+
fulfillment.promotion_discount_total
|
|
161
161
|
end
|
|
162
162
|
end
|
|
163
163
|
end
|
|
@@ -33,7 +33,7 @@ module Spree
|
|
|
33
33
|
validate :origin_group_must_belong_to_profile,
|
|
34
34
|
if: -> { delivery_origin_group_id_changed? || delivery_profile_id_changed? }
|
|
35
35
|
|
|
36
|
-
self.whitelisted_ransackable_attributes = %w[name]
|
|
36
|
+
self.whitelisted_ransackable_attributes = %w[name delivery_profile_id]
|
|
37
37
|
|
|
38
38
|
# @param address [Spree::Address, nil]
|
|
39
39
|
# @return [Boolean] whether any member of this zone matches the address
|
|
@@ -42,6 +42,7 @@ module Spree
|
|
|
42
42
|
delegate :currency, to: :order
|
|
43
43
|
|
|
44
44
|
self.whitelisted_ransackable_attributes = %w[number status created_at]
|
|
45
|
+
search_by :number
|
|
45
46
|
self.whitelisted_ransackable_associations = %w[order reason]
|
|
46
47
|
|
|
47
48
|
# Positive when the replacements cost more than what came back (the
|
|
@@ -46,10 +46,10 @@ module Spree
|
|
|
46
46
|
result.rows.each do |row|
|
|
47
47
|
csv << Spree::CSV::FormulaSanitizer.row(
|
|
48
48
|
row[:dimensions].map { |name, raw| hydration.label(name, raw) } +
|
|
49
|
-
query.metrics.map { |m| row[:metrics][m.name][:value] }
|
|
49
|
+
query.metrics.map { |m| m.format_value(row[:metrics][m.name][:value], result.meta[:currency]) }
|
|
50
50
|
)
|
|
51
51
|
end
|
|
52
|
-
totals = query.metrics.map { |m| result.totals[m.name][:value] }
|
|
52
|
+
totals = query.metrics.map { |m| m.format_value(result.totals[m.name][:value], result.meta[:currency]) }
|
|
53
53
|
# With a breakdown the totals close the file under a "Total" label in
|
|
54
54
|
# the first dimension column; without one the totals are the only row.
|
|
55
55
|
label = query.dimensions.empty? ? [] : [I18n.t('spree.reporting.export.total')] + Array.new(query.dimensions.size - 1, '')
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
module Spree
|
|
2
|
+
# A file sent to `POST /files` (docs/plans/6.0-uploads-and-file-ownership.md),
|
|
3
|
+
# before anything uses it.
|
|
4
|
+
#
|
|
5
|
+
# Two ways in, one result. A presigned upload carries only the file's
|
|
6
|
+
# metadata and gets back a storage target the client sends the bytes to; a
|
|
7
|
+
# multipart upload carries the bytes, which are stored here. Either way the
|
|
8
|
+
# result is a blob owned by +store+ and an expiring, opaque `signed_id` the
|
|
9
|
+
# client passes to whichever endpoint uses the file.
|
|
10
|
+
#
|
|
11
|
+
# Never persisted: the blob is the record.
|
|
12
|
+
class FileUpload
|
|
13
|
+
include ActiveModel::Model
|
|
14
|
+
include ActiveModel::Attributes
|
|
15
|
+
|
|
16
|
+
VISIBILITIES = %w[public private].freeze
|
|
17
|
+
|
|
18
|
+
attribute :filename, :string
|
|
19
|
+
attribute :content_type, :string
|
|
20
|
+
attribute :byte_size, :integer
|
|
21
|
+
attribute :checksum, :string
|
|
22
|
+
attribute :visibility, :string, default: 'public'
|
|
23
|
+
# The bytes of a multipart upload (an uploaded file or any IO).
|
|
24
|
+
attribute :file
|
|
25
|
+
attribute :store
|
|
26
|
+
# Narrower limits for a caller with its own rules (a buyer's purchase
|
|
27
|
+
# order document); the installation-wide ones apply otherwise.
|
|
28
|
+
attribute :max_byte_size, :integer
|
|
29
|
+
attribute :allowed_content_types
|
|
30
|
+
|
|
31
|
+
attr_reader :blob, :signed_id, :expires_at
|
|
32
|
+
|
|
33
|
+
validates :store, presence: true
|
|
34
|
+
validates :visibility, inclusion: { in: VISIBILITIES }
|
|
35
|
+
validates :filename, :content_type, presence: true
|
|
36
|
+
validates :checksum, presence: true, unless: :multipart?
|
|
37
|
+
validates :byte_size, numericality: { only_integer: true, greater_than: 0 }
|
|
38
|
+
validate :byte_size_within_limit
|
|
39
|
+
validate :content_type_allowed
|
|
40
|
+
|
|
41
|
+
# The content types a public upload may have. Private uploads accept any
|
|
42
|
+
# type: digital products are ebooks, archives and audio, and each
|
|
43
|
+
# attachment validates its own file.
|
|
44
|
+
#
|
|
45
|
+
# @return [Array<String>]
|
|
46
|
+
def self.public_content_types
|
|
47
|
+
Rails.application.config.active_storage.web_image_content_types +
|
|
48
|
+
Spree::Config.video_content_types +
|
|
49
|
+
%w[text/csv]
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
# @return [Boolean] whether the bytes travel in the request
|
|
53
|
+
def multipart?
|
|
54
|
+
file.respond_to?(:read)
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
# @return [Boolean] whether the file lands on private storage
|
|
58
|
+
def private?
|
|
59
|
+
visibility == 'private'
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
# Validates and creates the blob. A multipart upload is stored here; a
|
|
63
|
+
# presigned one waits for the client to send its bytes to {#upload_target}.
|
|
64
|
+
#
|
|
65
|
+
# @return [Boolean]
|
|
66
|
+
def save
|
|
67
|
+
read_file_facts if multipart?
|
|
68
|
+
return false unless valid?
|
|
69
|
+
|
|
70
|
+
@blob = multipart? ? store_bytes : reserve_blob
|
|
71
|
+
@expires_at = Spree::Uploads::SIGNED_ID_EXPIRY.from_now
|
|
72
|
+
@signed_id = Spree::Uploads.signed_id_for(@blob)
|
|
73
|
+
true
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
# Where a presigned upload's bytes go, or nil once the file is stored.
|
|
77
|
+
#
|
|
78
|
+
# @return [Spree::FileUpload::Target, nil]
|
|
79
|
+
def upload_target
|
|
80
|
+
return if multipart? || blob.nil?
|
|
81
|
+
|
|
82
|
+
Target.new(
|
|
83
|
+
http_method: 'PUT',
|
|
84
|
+
url: blob.service_url_for_direct_upload,
|
|
85
|
+
headers: blob.service_headers_for_direct_upload
|
|
86
|
+
)
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
# The storage target a presigned upload sends its bytes to.
|
|
90
|
+
class Target
|
|
91
|
+
include ActiveModel::Model
|
|
92
|
+
include ActiveModel::Attributes
|
|
93
|
+
|
|
94
|
+
# Not `method`, which every Ruby object already answers.
|
|
95
|
+
attribute :http_method, :string
|
|
96
|
+
attribute :url, :string
|
|
97
|
+
attribute :headers
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
private
|
|
101
|
+
|
|
102
|
+
# The size of a multipart upload comes from the bytes, and so does its type
|
|
103
|
+
# wherever the bytes carry a recognisable signature: Marcel reads the
|
|
104
|
+
# content first, so a renamed executable is not accepted as an image. Only
|
|
105
|
+
# bytes it cannot place fall back to the declared type and the file name.
|
|
106
|
+
def read_file_facts
|
|
107
|
+
self.filename = filename.presence || file.try(:original_filename)
|
|
108
|
+
self.byte_size = io.size
|
|
109
|
+
self.content_type = Marcel::MimeType.for(io, name: filename, declared_type: content_type.presence || file.try(:content_type))
|
|
110
|
+
io.rewind
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
def reserve_blob
|
|
114
|
+
ActiveStorage::Blob.create!(
|
|
115
|
+
filename: filename, byte_size: byte_size, checksum: checksum, content_type: content_type,
|
|
116
|
+
service_name: service_name, store_id: store.id
|
|
117
|
+
)
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
def store_bytes
|
|
121
|
+
ActiveStorage::Blob.build_after_unfurling(
|
|
122
|
+
io: io, filename: filename, content_type: content_type, service_name: service_name, identify: false
|
|
123
|
+
).tap do |blob|
|
|
124
|
+
blob.store_id = store.id
|
|
125
|
+
blob.save!
|
|
126
|
+
blob.upload_without_unfurling(io)
|
|
127
|
+
end
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
def io
|
|
131
|
+
@io ||= file.respond_to?(:to_io) ? file.to_io : file
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
def service_name
|
|
135
|
+
private? ? Spree.private_storage_service_name : Spree.public_storage_service_name
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
def size_limit
|
|
139
|
+
max_byte_size || (multipart? ? Spree::Config.max_multipart_upload_size : Spree::Config.max_upload_size)
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
def byte_size_within_limit
|
|
143
|
+
return if byte_size.nil? || byte_size <= size_limit
|
|
144
|
+
|
|
145
|
+
errors.add(:byte_size, :less_than_or_equal_to, count: ActiveSupport::NumberHelper.number_to_human_size(size_limit))
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
def content_type_allowed
|
|
149
|
+
allowed = allowed_content_types || (self.class.public_content_types unless private?)
|
|
150
|
+
return if allowed.nil? || content_type.blank? || allowed.include?(content_type)
|
|
151
|
+
|
|
152
|
+
errors.add(:content_type, :inclusion, value: content_type)
|
|
153
|
+
end
|
|
154
|
+
end
|
|
155
|
+
end
|
|
@@ -374,9 +374,20 @@ module Spree
|
|
|
374
374
|
#
|
|
375
375
|
# @return [Boolean]
|
|
376
376
|
def with_free_shipping_promotion?
|
|
377
|
+
return discounts.any?(&:promotion?) if read_association_in_memory?(:discounts)
|
|
378
|
+
|
|
377
379
|
discounts.promotion.exists?
|
|
378
380
|
end
|
|
379
381
|
|
|
382
|
+
# What promotions took off this shipment's cost, as a negative amount.
|
|
383
|
+
#
|
|
384
|
+
# @return [BigDecimal]
|
|
385
|
+
def promotion_discount_total
|
|
386
|
+
return discounts.select(&:promotion?).sum(BigDecimal('0'), &:amount) if read_association_in_memory?(:discounts)
|
|
387
|
+
|
|
388
|
+
discounts.promotion.sum(:amount)
|
|
389
|
+
end
|
|
390
|
+
|
|
380
391
|
# Placement no longer moves physical stock — the units are promised in
|
|
381
392
|
# Spree::Orders::Complete and only leave the shelf when the parcel does.
|
|
382
393
|
def finalize!
|
|
@@ -701,7 +712,7 @@ module Spree
|
|
|
701
712
|
#
|
|
702
713
|
# @return [Spree::Delivery, nil]
|
|
703
714
|
def primary_delivery
|
|
704
|
-
deliveries.first
|
|
715
|
+
read_association_in_memory?(:deliveries) ? deliveries.to_a.first : deliveries.first
|
|
705
716
|
end
|
|
706
717
|
|
|
707
718
|
# The primary delivery's tracking number. Read-only since 6.0: tracking is
|
|
@@ -895,8 +906,7 @@ module Spree
|
|
|
895
906
|
def capture_at_gateway(payment, amount)
|
|
896
907
|
return 0 if amount <= 0
|
|
897
908
|
|
|
898
|
-
|
|
899
|
-
result = Spree.payment_capture_workflow.call(payment: payment, amount: units)
|
|
909
|
+
result = Spree.payment_capture_workflow.call(payment: payment, amount: amount)
|
|
900
910
|
raise Spree::Core::GatewayError, result.error.value.to_s if result.failure?
|
|
901
911
|
|
|
902
912
|
amount
|