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
|
@@ -7,6 +7,9 @@ module Spree
|
|
|
7
7
|
|
|
8
8
|
include Spree::SingleStoreResource
|
|
9
9
|
include Spree::HasListPosition
|
|
10
|
+
include Spree::CanonicalDecimals
|
|
11
|
+
|
|
12
|
+
canonical_decimals :price_adjustment_percentage, places: 3
|
|
10
13
|
|
|
11
14
|
MATCH_POLICIES = %w[all any].freeze
|
|
12
15
|
|
|
@@ -94,6 +97,7 @@ module Spree
|
|
|
94
97
|
# `catalog_id` is queryable so a picker can offer the lists that are
|
|
95
98
|
# actually available — unowned, plus the one the catalog already holds.
|
|
96
99
|
self.whitelisted_ransackable_attributes = %w[status match_policy starts_at ends_at catalog_id]
|
|
100
|
+
search_by :name
|
|
97
101
|
|
|
98
102
|
scope :by_position, -> { ordered }
|
|
99
103
|
scope :for_store, ->(store) { where(store: store) }
|
|
@@ -333,9 +337,9 @@ module Spree
|
|
|
333
337
|
|
|
334
338
|
return if prices_to_insert.empty?
|
|
335
339
|
|
|
336
|
-
# Use upsert_all with on_duplicate: :skip to handle race conditions
|
|
340
|
+
# Use upsert_all with on_duplicate: :skip to handle race conditions.
|
|
341
|
+
# Placeholders charge nothing, so the variants are not touched.
|
|
337
342
|
Spree::Price.upsert_all(prices_to_insert, on_duplicate: :skip)
|
|
338
|
-
touch_variants(variant_ids)
|
|
339
343
|
touch
|
|
340
344
|
end
|
|
341
345
|
|
|
@@ -354,10 +358,13 @@ module Spree
|
|
|
354
358
|
variant_ids = Spree::Variant.where(product_id: product_ids).distinct.pluck(:id)
|
|
355
359
|
return if variant_ids.empty?
|
|
356
360
|
|
|
361
|
+
removed_prices = prices.where(variant_id: variant_ids)
|
|
362
|
+
priced_variant_ids = removed_prices.where.not(amount: nil).distinct.pluck(:variant_id)
|
|
363
|
+
|
|
357
364
|
# Use delete_all for hard delete - this bypasses acts_as_paranoid
|
|
358
365
|
# which is required for the unique index to work when re-adding products
|
|
359
|
-
|
|
360
|
-
touch_variants(
|
|
366
|
+
removed_prices.delete_all
|
|
367
|
+
touch_variants(priced_variant_ids)
|
|
361
368
|
touch
|
|
362
369
|
end
|
|
363
370
|
|
|
@@ -386,9 +393,8 @@ module Spree
|
|
|
386
393
|
|
|
387
394
|
current = current_values[price_id]
|
|
388
395
|
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
compare_at_amount = attrs[:compare_at_amount].present? ? Spree::LocalizedNumber.parse(attrs[:compare_at_amount]) : nil
|
|
396
|
+
amount = Spree::Money::Rounding.parse_decimal(attrs[:amount])
|
|
397
|
+
compare_at_amount = Spree::Money::Rounding.parse_decimal(attrs[:compare_at_amount])
|
|
392
398
|
|
|
393
399
|
# Clear compare_at_amount if it equals amount
|
|
394
400
|
compare_at_amount = nil if compare_at_amount == amount
|
|
@@ -9,11 +9,8 @@ module Spree
|
|
|
9
9
|
# against raw `channel_id` rows directly. Scope confines the
|
|
10
10
|
# existence check to the price-list's store so cross-store channel
|
|
11
11
|
# IDs can't sneak in.
|
|
12
|
-
preference :channel_ids, :array, default: [],
|
|
13
|
-
|
|
14
|
-
klass: Spree::Channel,
|
|
15
|
-
scope: ->(rule) { rule.store.channels }
|
|
16
|
-
)
|
|
12
|
+
preference :channel_ids, :array, of: :id, model: 'Spree::Channel', default: [],
|
|
13
|
+
scope: ->(rule) { rule.store.channels }
|
|
17
14
|
|
|
18
15
|
def channels
|
|
19
16
|
return [] if preferred_channel_ids.blank?
|
|
@@ -10,11 +10,8 @@ module Spree
|
|
|
10
10
|
# callers and decodes them on write so eligibility checks compare
|
|
11
11
|
# against raw `customer_group_id` rows directly. Scope confines
|
|
12
12
|
# the existence check to the price-list's store.
|
|
13
|
-
preference :customer_group_ids, :array, default: [],
|
|
14
|
-
|
|
15
|
-
klass: Spree::CustomerGroup,
|
|
16
|
-
scope: ->(rule) { rule.store.customer_groups }
|
|
17
|
-
)
|
|
13
|
+
preference :customer_group_ids, :array, of: :id, model: 'Spree::CustomerGroup', default: [],
|
|
14
|
+
scope: ->(rule) { rule.store.customer_groups }
|
|
18
15
|
|
|
19
16
|
def customer_groups
|
|
20
17
|
return [] if preferred_customer_group_ids.blank?
|
|
@@ -6,11 +6,8 @@ module Spree
|
|
|
6
6
|
# against raw `market_id` rows directly. Scope confines the
|
|
7
7
|
# existence check to the price-list's store so cross-store market
|
|
8
8
|
# IDs can't sneak in.
|
|
9
|
-
preference :market_ids, :array, default: [],
|
|
10
|
-
|
|
11
|
-
klass: Spree::Market,
|
|
12
|
-
scope: ->(rule) { rule.store.markets }
|
|
13
|
-
)
|
|
9
|
+
preference :market_ids, :array, of: :id, model: 'Spree::Market', default: [],
|
|
10
|
+
scope: ->(rule) { rule.store.markets }
|
|
14
11
|
|
|
15
12
|
def markets
|
|
16
13
|
return [] if preferred_market_ids.blank?
|
|
@@ -10,9 +10,7 @@ module Spree
|
|
|
10
10
|
# e.g. `usr_…`) from API callers and decodes them on write. Resolves
|
|
11
11
|
# `Spree.customer_class` lazily — the user class is configured at boot,
|
|
12
12
|
# and class-body evaluation runs before that on cold loads.
|
|
13
|
-
preference :user_ids, :array,
|
|
14
|
-
normalize_id_preference(klass: Spree.customer_class).call(values)
|
|
15
|
-
}
|
|
13
|
+
preference :user_ids, :array, of: :id, model: -> { Spree.customer_class }, default: []
|
|
16
14
|
|
|
17
15
|
def users
|
|
18
16
|
return [] if preferred_user_ids.blank?
|
|
@@ -43,8 +43,7 @@ module Spree
|
|
|
43
43
|
"Spree::Product##{legacy_attr}= is deprecated; set #{publication_attr} on " \
|
|
44
44
|
"ProductPublication instead (writes to every channel's publication). "
|
|
45
45
|
)
|
|
46
|
-
|
|
47
|
-
product_publications.each { |publication| publication.public_send("#{publication_attr}=", value) }
|
|
46
|
+
write_publication_date(legacy_attr, value)
|
|
48
47
|
end
|
|
49
48
|
|
|
50
49
|
# Reading +available_on+/+discontinue_on+ prefers the current-channel
|
|
@@ -92,6 +91,14 @@ module Spree
|
|
|
92
91
|
|
|
93
92
|
private
|
|
94
93
|
|
|
94
|
+
# Writes a legacy date column together with the matching date on every
|
|
95
|
+
# channel's publication, so the two never disagree.
|
|
96
|
+
def write_publication_date(legacy_attr, value)
|
|
97
|
+
write_attribute(legacy_attr, value)
|
|
98
|
+
publication_attr = DEPRECATED_DATE_TO_PUBLICATION_FIELD.fetch(legacy_attr.to_sym)
|
|
99
|
+
product_publications.each { |publication| publication.public_send("#{publication_attr}=", value) }
|
|
100
|
+
end
|
|
101
|
+
|
|
95
102
|
def assign_default_store
|
|
96
103
|
self.store ||= Spree::Current.store || Spree::Store.default
|
|
97
104
|
end
|
data/app/models/spree/product.rb
CHANGED
|
@@ -40,8 +40,7 @@ module Spree
|
|
|
40
40
|
publishes_lifecycle_events
|
|
41
41
|
publishes_events :activated, :approved, :archived, :back_in_stock, :drafted, :out_of_stock, :proposed, :rejected
|
|
42
42
|
|
|
43
|
-
MEMOIZED_METHODS = %w[total_on_hand
|
|
44
|
-
default_variant_id tax_category default_variant variant_for_images
|
|
43
|
+
MEMOIZED_METHODS = %w[total_on_hand default_variant_id tax_category default_variant variant_for_images
|
|
45
44
|
primary_category buy_box_variants resolved_delivery_profile
|
|
46
45
|
purchasable? in_stock? backorderable? digital?]
|
|
47
46
|
|
|
@@ -243,8 +242,6 @@ module Spree
|
|
|
243
242
|
after_create :set_default_variant
|
|
244
243
|
|
|
245
244
|
after_save :auto_promote_default_variant
|
|
246
|
-
after_save :run_touch_callbacks, if: :saved_changes?
|
|
247
|
-
after_touch :touch_categories
|
|
248
245
|
|
|
249
246
|
after_commit :auto_match_collections, if: :eligible_for_collection_matching?
|
|
250
247
|
|
|
@@ -364,7 +361,21 @@ module Spree
|
|
|
364
361
|
super(manual_ids | collections.automatic.ids)
|
|
365
362
|
end
|
|
366
363
|
|
|
367
|
-
self.whitelisted_ransackable_attributes = %w[description name slug discontinue_on status available_on created_at updated_at seller_id]
|
|
364
|
+
self.whitelisted_ransackable_attributes = %w[description name slug discontinue_on status available_on created_at updated_at seller_id price]
|
|
365
|
+
|
|
366
|
+
# The default variant's base price in the current currency — what the
|
|
367
|
+
# product lists show — so a list can filter on it like a column.
|
|
368
|
+
ransacker :price, type: :decimal do |parent|
|
|
369
|
+
prices = Spree::Price.arel_table
|
|
370
|
+
Arel::Nodes::Grouping.new(
|
|
371
|
+
prices.project(prices[:amount]).
|
|
372
|
+
where(prices[:variant_id].eq(parent.table[:default_variant_id])).
|
|
373
|
+
where(prices[:currency].eq(Spree::Current.currency)).
|
|
374
|
+
where(prices[:price_list_id].eq(nil)).
|
|
375
|
+
where(prices[:deleted_at].eq(nil)).
|
|
376
|
+
take(1)
|
|
377
|
+
)
|
|
378
|
+
end
|
|
368
379
|
self.whitelisted_ransackable_associations = %w[categories collections store channels variants default_variant tags labels
|
|
369
380
|
product_type product_categories option_types seller]
|
|
370
381
|
# The storefront filters by tag, category and collection; every other hop
|
|
@@ -376,6 +387,12 @@ module Spree
|
|
|
376
387
|
search in_stock out_of_stock with_option_value_ids
|
|
377
388
|
|
|
378
389
|
ascend_by_price descend_by_price]
|
|
390
|
+
self.ransackable_scope_types = {
|
|
391
|
+
'in_taxon' => 'id', 'in_category' => 'id', 'in_collection' => 'id',
|
|
392
|
+
'in_categories' => { list: 'id' }, 'with_option_value_ids' => { list: 'id' },
|
|
393
|
+
'price_between' => %w[decimal decimal], 'price_lte' => 'decimal', 'price_gte' => 'decimal',
|
|
394
|
+
'ascend_by_price' => 'boolean', 'descend_by_price' => 'boolean'
|
|
395
|
+
}
|
|
379
396
|
|
|
380
397
|
# All product-level convenience attributes delegate to the default variant —
|
|
381
398
|
# one target, no branching. Reads resolve the existing default variant
|
|
@@ -696,7 +713,7 @@ module Spree
|
|
|
696
713
|
end
|
|
697
714
|
|
|
698
715
|
def discontinue!
|
|
699
|
-
|
|
716
|
+
write_publication_date(:discontinue_on, Time.current)
|
|
700
717
|
self.status = 'archived'
|
|
701
718
|
save(validate: false)
|
|
702
719
|
end
|
|
@@ -996,23 +1013,6 @@ module Spree
|
|
|
996
1013
|
errors.add(:seller, :invalid)
|
|
997
1014
|
end
|
|
998
1015
|
|
|
999
|
-
def run_touch_callbacks
|
|
1000
|
-
run_callbacks(:touch)
|
|
1001
|
-
end
|
|
1002
|
-
|
|
1003
|
-
def category_and_ancestors
|
|
1004
|
-
@category_and_ancestors ||= categories.map(&:self_and_ancestors).flatten.uniq
|
|
1005
|
-
end
|
|
1006
|
-
|
|
1007
|
-
# Iterate through this product's categories and touch their timestamps in a batch
|
|
1008
|
-
def touch_categories
|
|
1009
|
-
if categories.any?
|
|
1010
|
-
Spree::Products::TouchCategoriesJob.
|
|
1011
|
-
set(wait: 5.seconds).
|
|
1012
|
-
perform_later(category_and_ancestors.map(&:id))
|
|
1013
|
-
end
|
|
1014
|
-
end
|
|
1015
|
-
|
|
1016
1016
|
def ensure_not_in_complete_orders
|
|
1017
1017
|
if orders.complete.any?
|
|
1018
1018
|
errors.add(:base, :cannot_destroy_if_attached_to_line_items)
|
|
@@ -42,7 +42,7 @@ module Spree
|
|
|
42
42
|
# @param line_item [Spree::LineItem]
|
|
43
43
|
# @return [BigDecimal] never positive
|
|
44
44
|
def compute_amount(line_item)
|
|
45
|
-
line_item.price * gifted_quantity_of(line_item)
|
|
45
|
+
-Spree::Money::Rounding.to_currency(line_item.price * gifted_quantity_of(line_item), line_item.currency)
|
|
46
46
|
end
|
|
47
47
|
|
|
48
48
|
# Tops the order up to the promised quantity and discounts it.
|
|
@@ -8,11 +8,8 @@ module Spree
|
|
|
8
8
|
# against the order's raw `channel_id` directly. Scope confines the
|
|
9
9
|
# existence check to the promotion's store so cross-store channel
|
|
10
10
|
# IDs can't sneak in.
|
|
11
|
-
preference :channel_ids, :array, default: [],
|
|
12
|
-
|
|
13
|
-
klass: Spree::Channel,
|
|
14
|
-
scope: ->(rule) { rule.promotion.store.channels }
|
|
15
|
-
)
|
|
11
|
+
preference :channel_ids, :array, of: :id, model: 'Spree::Channel', default: [],
|
|
12
|
+
scope: ->(rule) { rule.promotion.store.channels }
|
|
16
13
|
|
|
17
14
|
def channels
|
|
18
15
|
return Spree::Channel.none if preferred_channel_ids.blank?
|
|
@@ -9,11 +9,10 @@ module Spree
|
|
|
9
9
|
class Promotion
|
|
10
10
|
module Rules
|
|
11
11
|
class Country < Spree::PromotionRule
|
|
12
|
-
preference :country_codes, :array,
|
|
13
|
-
|
|
14
|
-
}
|
|
12
|
+
preference :country_codes, :array, of: :string, format: :iso_country, default: [],
|
|
13
|
+
parse_on_set: ->(values) { Array(values).flat_map { |value| value.to_s.split(',') }.map { |code| code.strip.upcase }.compact_blank }
|
|
15
14
|
preference :country_id, :integer # legacy single-country shortcut
|
|
16
|
-
preference :country_code, :string # legacy ISO-based shortcut
|
|
15
|
+
preference :country_code, :string, format: :iso_country # legacy ISO-based shortcut
|
|
17
16
|
|
|
18
17
|
def countries
|
|
19
18
|
isos = configured_country_codes.presence || [configured_country_code].compact_blank
|
|
@@ -9,11 +9,8 @@ module Spree
|
|
|
9
9
|
# Scoped to the promotion's own store: groups are store-owned, so an
|
|
10
10
|
# id from elsewhere must raise rather than link a promotion to an
|
|
11
11
|
# audience its store cannot see.
|
|
12
|
-
preference :customer_group_ids, :array, default: [],
|
|
13
|
-
|
|
14
|
-
klass: Spree::CustomerGroup,
|
|
15
|
-
scope: ->(rule) { rule.promotion.store.customer_groups }
|
|
16
|
-
)
|
|
12
|
+
preference :customer_group_ids, :array, of: :id, model: 'Spree::CustomerGroup', default: [],
|
|
13
|
+
scope: ->(rule) { rule.promotion.store.customer_groups }
|
|
17
14
|
|
|
18
15
|
def customer_groups
|
|
19
16
|
return Spree::CustomerGroup.none if preferred_customer_group_ids.blank?
|
|
@@ -4,9 +4,9 @@ module Spree
|
|
|
4
4
|
class Promotion
|
|
5
5
|
module Rules
|
|
6
6
|
class ItemTotal < Spree::PromotionRule
|
|
7
|
-
preference :amount_min, :
|
|
7
|
+
preference :amount_min, :money, default: 100.00
|
|
8
8
|
preference :operator_min, :string, default: '>'
|
|
9
|
-
preference :amount_max, :
|
|
9
|
+
preference :amount_max, :money, default: nil, nullable: true
|
|
10
10
|
preference :operator_max, :string, default: '<'
|
|
11
11
|
|
|
12
12
|
OPERATORS_MIN = ['gt', 'gte']
|
|
@@ -8,11 +8,8 @@ module Spree
|
|
|
8
8
|
# against the order's raw `market_id` directly. Scope confines the
|
|
9
9
|
# existence check to the promotion's store so cross-store market
|
|
10
10
|
# IDs can't sneak in.
|
|
11
|
-
preference :market_ids, :array, default: [],
|
|
12
|
-
|
|
13
|
-
klass: Spree::Market,
|
|
14
|
-
scope: ->(rule) { rule.promotion.store.markets }
|
|
15
|
-
)
|
|
11
|
+
preference :market_ids, :array, of: :id, model: 'Spree::Market', default: [],
|
|
12
|
+
scope: ->(rule) { rule.promotion.store.markets }
|
|
16
13
|
|
|
17
14
|
def markets
|
|
18
15
|
return Spree::Market.none if preferred_market_ids.blank?
|
|
@@ -3,13 +3,12 @@ module Spree
|
|
|
3
3
|
module Rules
|
|
4
4
|
class OptionValue < Spree::PromotionRule
|
|
5
5
|
MATCH_POLICIES = %w(any)
|
|
6
|
-
preference :match_policy, :string, default: MATCH_POLICIES.first
|
|
6
|
+
preference :match_policy, :string, default: MATCH_POLICIES.first, choices: MATCH_POLICIES
|
|
7
7
|
# Stored as raw Spree::OptionValue ids. Accepts prefixed IDs (`optval_…`)
|
|
8
8
|
# from API callers and decodes them on write so eligibility checks compare
|
|
9
9
|
# against stable option-value rows, not join-table ids that shift when
|
|
10
10
|
# variants are reconfigured.
|
|
11
|
-
preference :eligible_values, :array, default: []
|
|
12
|
-
parse_on_set: normalize_id_preference(klass: Spree::OptionValue)
|
|
11
|
+
preference :eligible_values, :array, of: :id, model: 'Spree::OptionValue', default: []
|
|
13
12
|
|
|
14
13
|
# @param promotable [Object]
|
|
15
14
|
# @return [Boolean]
|
|
@@ -87,6 +87,7 @@ module Spree
|
|
|
87
87
|
# `name` is whitelisted so the admin global search / command palette can
|
|
88
88
|
# filter via the `name_or_code_cont` predicate without a dedicated scope.
|
|
89
89
|
self.whitelisted_ransackable_attributes = ['name', 'path', 'promotion_category_id', 'code', 'kind', 'starts_at', 'expires_at']
|
|
90
|
+
search_by :name, :code
|
|
90
91
|
self.whitelisted_ransackable_associations = %w[coupon_codes]
|
|
91
92
|
|
|
92
93
|
def self.with_coupon_code(coupon_code)
|
|
@@ -58,7 +58,9 @@ module Spree
|
|
|
58
58
|
self.whitelisted_ransackable_attributes = %w[number status currency reference expected_at
|
|
59
59
|
cancel_by ordered_at received_at closed_short_at
|
|
60
60
|
supplier_id destination_location_id created_at]
|
|
61
|
+
search_by :number, :reference
|
|
61
62
|
self.whitelisted_ransackable_scopes = %w[open closed overdue past_cancel_by]
|
|
63
|
+
self.ransackable_scope_types = %w[open closed overdue past_cancel_by].index_with('boolean')
|
|
62
64
|
self.whitelisted_ransackable_associations = %w[supplier destination_location items]
|
|
63
65
|
|
|
64
66
|
# What the supplier will invoice for everything ordered.
|
data/app/models/spree/refund.rb
CHANGED
|
@@ -5,6 +5,9 @@ module Spree
|
|
|
5
5
|
include Spree::ActedBy
|
|
6
6
|
include Spree::HasCustomFields
|
|
7
7
|
include Spree::Metadata
|
|
8
|
+
include Spree::CanonicalDecimals
|
|
9
|
+
|
|
10
|
+
canonical_decimals :amount
|
|
8
11
|
include Spree::InstrumentsGatewayCalls
|
|
9
12
|
if defined?(Spree::Security::Refunds)
|
|
10
13
|
include Spree::Security::Refunds
|
|
@@ -56,10 +59,6 @@ module Spree
|
|
|
56
59
|
# back through a payment that may belong to several orders.
|
|
57
60
|
before_validation :assign_order_from_payment, on: :create
|
|
58
61
|
|
|
59
|
-
def amount=(amount)
|
|
60
|
-
self[:amount] = Spree::LocalizedNumber.parse(amount)
|
|
61
|
-
end
|
|
62
|
-
|
|
63
62
|
def money
|
|
64
63
|
Spree::Money.new(amount, currency: currency)
|
|
65
64
|
end
|
|
@@ -121,9 +120,7 @@ module Spree
|
|
|
121
120
|
def perform!
|
|
122
121
|
return true if transaction_id.present?
|
|
123
122
|
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
@response = process!(credit_cents)
|
|
123
|
+
@response = process!(payment.payment_method.gateway_amount(amount, currency))
|
|
127
124
|
|
|
128
125
|
self.transaction_id = @response.authorization
|
|
129
126
|
update_columns(transaction_id: transaction_id)
|
|
@@ -166,19 +163,20 @@ module Spree
|
|
|
166
163
|
end
|
|
167
164
|
|
|
168
165
|
# return a payment response object if successful or else raise an error
|
|
169
|
-
|
|
170
|
-
|
|
166
|
+
# @param credit_amount [Spree::Money, Integer] as PaymentMethod#gateway_amount returns it
|
|
167
|
+
def process!(credit_amount)
|
|
168
|
+
refund_total = calculate_refund_amount(credit_amount)
|
|
171
169
|
|
|
172
170
|
response = instrument_gateway_call(:credit, payment.payment_method) do
|
|
173
171
|
if payment.payment_method.payment_profiles_supported?
|
|
174
|
-
payment.payment_method.credit(
|
|
172
|
+
payment.payment_method.credit(refund_total, payment.source, payment.transaction_id, originator: self)
|
|
175
173
|
else
|
|
176
|
-
payment.payment_method.credit(
|
|
174
|
+
payment.payment_method.credit(refund_total, payment.transaction_id, originator: self)
|
|
177
175
|
end
|
|
178
176
|
end
|
|
179
177
|
|
|
180
178
|
if response.success?
|
|
181
|
-
track_order_as_refunded(
|
|
179
|
+
track_order_as_refunded(refund_total)
|
|
182
180
|
else
|
|
183
181
|
Rails.logger.error(I18n.t('spree.gateway_error') + " #{response.to_yaml}")
|
|
184
182
|
text = response.params['message'] || response.params['response_reason_text'] || response.message
|
|
@@ -191,12 +189,12 @@ module Spree
|
|
|
191
189
|
raise Core::GatewayError, I18n.t('spree.unable_to_connect_to_gateway')
|
|
192
190
|
end
|
|
193
191
|
|
|
194
|
-
def calculate_refund_amount(
|
|
192
|
+
def calculate_refund_amount(credit_amount)
|
|
195
193
|
# Overwrite this for more complex calculations
|
|
196
|
-
|
|
194
|
+
credit_amount
|
|
197
195
|
end
|
|
198
196
|
|
|
199
|
-
def track_order_as_refunded(
|
|
197
|
+
def track_order_as_refunded(credit_amount)
|
|
200
198
|
# You can track refunds here
|
|
201
199
|
end
|
|
202
200
|
|
data/app/models/spree/return.rb
CHANGED
|
@@ -52,6 +52,7 @@ module Spree
|
|
|
52
52
|
delegate :currency, to: :order
|
|
53
53
|
|
|
54
54
|
self.whitelisted_ransackable_attributes = %w[number status created_at]
|
|
55
|
+
search_by :number
|
|
55
56
|
self.whitelisted_ransackable_associations = %w[order reason]
|
|
56
57
|
|
|
57
58
|
# The label that currently binds the inbound parcel — bought or uploaded,
|
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
module Spree
|
|
2
2
|
module SearchProvider
|
|
3
3
|
class Base
|
|
4
|
+
# Sort keys a provider accepts beyond the product's sortable
|
|
5
|
+
# attributes. `price` also sorts descending as `-price`.
|
|
6
|
+
SORT_FIELDS = %w[price best_selling manual].freeze
|
|
7
|
+
|
|
4
8
|
attr_reader :store
|
|
5
9
|
|
|
6
10
|
# Whether this provider requires background indexing jobs.
|
data/app/models/spree/seller.rb
CHANGED
|
@@ -41,6 +41,12 @@ module Spree
|
|
|
41
41
|
has_status :pending, :invited, :canceled, :onboarding, :ready_for_review,
|
|
42
42
|
:approved, :rejected, :suspended, default: :pending
|
|
43
43
|
|
|
44
|
+
# The storefront lists approved sellers only, and a seller's contact
|
|
45
|
+
# address is back-office data.
|
|
46
|
+
self.whitelisted_ransackable_attributes = %w[status contact_email]
|
|
47
|
+
search_by :name
|
|
48
|
+
self.private_ransackable_attributes = { store: %w[status contact_email], seller: %w[status contact_email] }
|
|
49
|
+
|
|
44
50
|
# Who remits consumer tax on this seller's sales. `seller` means they are
|
|
45
51
|
# merchant of record; `platform` is the marketplace-facilitator case, and
|
|
46
52
|
# only Enterprise decides a seller belongs there — core just reads it.
|
|
@@ -196,6 +202,7 @@ module Spree
|
|
|
196
202
|
has_one_attached :logo, service: Spree.public_storage_service_name
|
|
197
203
|
has_one_attached :square_logo, service: Spree.public_storage_service_name
|
|
198
204
|
has_one_attached :cover_photo, service: Spree.public_storage_service_name
|
|
205
|
+
signed_id_attachments :logo, :square_logo, :cover_photo
|
|
199
206
|
|
|
200
207
|
#
|
|
201
208
|
# Validations
|
|
@@ -340,7 +347,7 @@ module Spree
|
|
|
340
347
|
# A seller keeps no clock of their own: a date means whatever the
|
|
341
348
|
# marketplace says it means, so their panel reads timestamps in the
|
|
342
349
|
# store's zone rather than in whichever one their browser sits in.
|
|
343
|
-
delegate :preferred_timezone, to: :store, allow_nil: true
|
|
350
|
+
delegate :preferred_timezone, :timezone, to: :store, allow_nil: true
|
|
344
351
|
|
|
345
352
|
# Sellers holding an account with one provider, for the reverse lookup a
|
|
346
353
|
# provider webhook does — it knows the account, not the seller.
|
|
@@ -60,6 +60,7 @@ module Spree
|
|
|
60
60
|
scope :owed, -> { where(status: %w[pending processing]) }
|
|
61
61
|
|
|
62
62
|
self.whitelisted_ransackable_attributes = %w[amount currency status provider reference period_start period_end created_at seller_id]
|
|
63
|
+
search_by :reference
|
|
63
64
|
# Filtered by the provider's API shorthand (`system`); the column holds
|
|
64
65
|
# its class name.
|
|
65
66
|
ransacker :provider, formatter: ->(value) { Spree::ApiTyped.class_name_for(Spree.payout_providers, value) } do |parent|
|
|
@@ -38,6 +38,7 @@ module Spree
|
|
|
38
38
|
# Private, and served only through the admin and seller branches: these
|
|
39
39
|
# are business registrations and identity documents.
|
|
40
40
|
has_one_attached :file, service: Spree.private_storage_service_name
|
|
41
|
+
signed_id_attachments :file
|
|
41
42
|
|
|
42
43
|
#
|
|
43
44
|
# Validations
|
|
@@ -112,6 +112,7 @@ module Spree
|
|
|
112
112
|
end
|
|
113
113
|
|
|
114
114
|
self.whitelisted_ransackable_attributes = %w[amount currency kind status provider reference created_at seller_id order_id payout_id]
|
|
115
|
+
search_by :reference
|
|
115
116
|
# Filtered by the provider's API shorthand (`system`); the column holds
|
|
116
117
|
# its class name.
|
|
117
118
|
ransacker :provider, formatter: ->(value) { Spree::ApiTyped.class_name_for(Spree.payout_providers, value) } do |parent|
|
|
@@ -39,6 +39,7 @@ module Spree
|
|
|
39
39
|
# merchant's account details, and is served only through an
|
|
40
40
|
# authenticated, streamed download.
|
|
41
41
|
has_one_attached :file, service: Spree.private_storage_service_name
|
|
42
|
+
signed_id_attachments :file
|
|
42
43
|
|
|
43
44
|
validates :source, inclusion: { in: SOURCES }
|
|
44
45
|
validates :owner_type, inclusion: { in: OWNER_TYPES }
|
|
@@ -121,7 +121,7 @@ module Spree
|
|
|
121
121
|
|
|
122
122
|
percent = service_row&.markup_percent || delivery_method.markup_percent || 0
|
|
123
123
|
flat = service_row&.markup_flat || delivery_method.markup_flat || 0
|
|
124
|
-
(cost * (1 + (percent / 100)) + flat)
|
|
124
|
+
Spree::Money::Rounding.to_currency(cost * (1 + (percent / 100)) + flat, currency)
|
|
125
125
|
end
|
|
126
126
|
|
|
127
127
|
# Display name for provider-priced rates: the merchant's label override,
|
|
@@ -38,7 +38,9 @@ module Spree
|
|
|
38
38
|
|
|
39
39
|
self.whitelisted_ransackable_attributes = %w[count_on_hand allocated_count reserved_count incoming_count
|
|
40
40
|
stock_location_id variant_id]
|
|
41
|
+
search_by :variant_sku, :variant_product_name
|
|
41
42
|
self.whitelisted_ransackable_scopes = %w[with_stock_status]
|
|
43
|
+
self.ransackable_scope_types = { 'with_stock_status' => { list: 'text' } }
|
|
42
44
|
self.whitelisted_ransackable_associations = %w[variant stock_location]
|
|
43
45
|
|
|
44
46
|
scope :with_active_stock_location, -> { joins(:stock_location).merge(Spree::StockLocation.active) }
|
|
@@ -66,7 +66,9 @@ module Spree
|
|
|
66
66
|
self.whitelisted_ransackable_attributes = %w[number status reference source_location_id
|
|
67
67
|
destination_location_id shipped_at received_at
|
|
68
68
|
closed_short_at created_at]
|
|
69
|
+
search_by :number, :reference
|
|
69
70
|
self.whitelisted_ransackable_scopes = %w[open closed]
|
|
71
|
+
self.ransackable_scope_types = %w[open closed].index_with('boolean')
|
|
70
72
|
self.whitelisted_ransackable_associations = %w[source_location destination_location items]
|
|
71
73
|
|
|
72
74
|
# Whether the merchant may still edit the lines. Once the box is sealed and
|