spree_api 6.0.0.rc4 → 6.0.0.rc6
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/app/controllers/concerns/spree/api/v3/admin/auth_cookies.rb +23 -0
- data/app/controllers/concerns/spree/api/v3/admin/refund_authorization.rb +26 -0
- data/app/controllers/concerns/spree/api/v3/admin_authentication.rb +1 -0
- data/app/controllers/concerns/spree/api/v3/allowed_redirect_url.rb +27 -0
- data/app/controllers/concerns/spree/api/v3/api_key_authentication.rb +14 -2
- data/app/controllers/concerns/spree/api/v3/cart_resolvable.rb +23 -4
- data/app/controllers/concerns/spree/api/v3/digital_assets_resource.rb +68 -0
- data/app/controllers/concerns/spree/api/v3/error_handler.rb +3 -0
- data/app/controllers/concerns/spree/api/v3/jwt_authentication.rb +27 -1
- data/app/controllers/concerns/spree/api/v3/orders/fulfillment_actions.rb +6 -1
- data/app/controllers/concerns/spree/api/v3/scoped_authorization.rb +6 -2
- data/app/controllers/spree/api/v3/admin/allowed_origins_controller.rb +4 -1
- data/app/controllers/spree/api/v3/admin/auth_controller.rb +32 -5
- data/app/controllers/spree/api/v3/admin/commission_lines_controller.rb +2 -2
- data/app/controllers/spree/api/v3/admin/companies/addresses_controller.rb +7 -1
- data/app/controllers/spree/api/v3/admin/custom_fields_controller.rb +27 -8
- data/app/controllers/spree/api/v3/admin/customer_groups_controller.rb +4 -0
- data/app/controllers/spree/api/v3/admin/customers/addresses_controller.rb +4 -0
- data/app/controllers/spree/api/v3/admin/customers/store_credits_controller.rb +1 -1
- data/app/controllers/spree/api/v3/admin/customers_controller.rb +14 -14
- data/app/controllers/spree/api/v3/admin/delivery_profiles/origin_groups_controller.rb +1 -1
- data/app/controllers/spree/api/v3/admin/delivery_profiles_controller.rb +1 -1
- data/app/controllers/spree/api/v3/admin/gift_cards_controller.rb +1 -8
- data/app/controllers/spree/api/v3/admin/invitation_acceptances_controller.rb +2 -0
- data/app/controllers/spree/api/v3/admin/me_controller.rb +2 -0
- data/app/controllers/spree/api/v3/admin/option_types_controller.rb +13 -0
- data/app/controllers/spree/api/v3/admin/orders/claims_controller.rb +2 -0
- data/app/controllers/spree/api/v3/admin/orders/exchanges_controller.rb +9 -0
- data/app/controllers/spree/api/v3/admin/orders/fulfillments_controller.rb +1 -1
- data/app/controllers/spree/api/v3/admin/orders/payments_controller.rb +7 -4
- data/app/controllers/spree/api/v3/admin/orders/returns_controller.rb +2 -0
- data/app/controllers/spree/api/v3/admin/orders_controller.rb +21 -7
- data/app/controllers/spree/api/v3/admin/package_types_controller.rb +2 -3
- data/app/controllers/spree/api/v3/admin/password_resets_controller.rb +7 -1
- data/app/controllers/spree/api/v3/admin/price_lists_controller.rb +18 -0
- data/app/controllers/spree/api/v3/admin/product_types_controller.rb +12 -1
- data/app/controllers/spree/api/v3/admin/products/digital_assets_controller.rb +5 -109
- data/app/controllers/spree/api/v3/admin/products/variants_controller.rb +7 -5
- data/app/controllers/spree/api/v3/admin/products_controller.rb +33 -6
- data/app/controllers/spree/api/v3/admin/sellers/payouts_controller.rb +4 -3
- data/app/controllers/spree/api/v3/admin/stock_transfers_controller.rb +3 -2
- data/app/controllers/spree/api/v3/admin/store_controller.rb +8 -1
- data/app/controllers/spree/api/v3/admin/tags_controller.rb +11 -9
- data/app/controllers/spree/api/v3/admin/variants_controller.rb +96 -1
- data/app/controllers/spree/api/v3/admin/webhook_endpoints_controller.rb +5 -2
- data/app/controllers/spree/api/v3/oauth/authorizations_controller.rb +17 -6
- data/app/controllers/spree/api/v3/resource_controller.rb +50 -7
- data/app/controllers/spree/api/v3/seller/auth_controller.rb +10 -0
- data/app/controllers/spree/api/v3/seller/invitation_acceptances_controller.rb +2 -0
- data/app/controllers/spree/api/v3/seller/me_controller.rb +4 -0
- data/app/controllers/spree/api/v3/seller/orders_controller.rb +11 -3
- data/app/controllers/spree/api/v3/seller/package_types_controller.rb +2 -2
- data/app/controllers/spree/api/v3/seller/password_resets_controller.rb +5 -1
- data/app/controllers/spree/api/v3/seller/products/digital_assets_controller.rb +67 -0
- data/app/controllers/spree/api/v3/seller/products_controller.rb +11 -63
- data/app/controllers/spree/api/v3/seller/resource_controller.rb +2 -6
- data/app/controllers/spree/api/v3/seller/team_controller.rb +1 -1
- data/app/controllers/spree/api/v3/store/carts/addresses_controller.rb +53 -0
- data/app/controllers/spree/api/v3/store/carts/store_credits_controller.rb +9 -1
- data/app/controllers/spree/api/v3/store/carts_controller.rb +17 -3
- data/app/controllers/spree/api/v3/store/companies/addresses_controller.rb +4 -0
- data/app/controllers/spree/api/v3/store/customer/addresses_controller.rb +4 -0
- data/app/controllers/spree/api/v3/store/customer/data_requests_controller.rb +19 -1
- data/app/controllers/spree/api/v3/store/customer/password_resets_controller.rb +3 -14
- data/app/controllers/spree/api/v3/store/customers_controller.rb +13 -1
- data/app/controllers/spree/api/v3/store/data_request_confirmations_controller.rb +23 -0
- data/app/controllers/spree/api/v3/store/data_requests_controller.rb +56 -0
- data/app/controllers/spree/api/v3/store/delivery_methods_controller.rb +6 -1
- data/app/controllers/spree/api/v3/store/newsletter_subscribers_controller.rb +2 -11
- data/app/controllers/spree/api/v3/store/products/filters_controller.rb +19 -4
- data/app/request_schemas/spree/api/v3/admin/variant_request_schema.rb +21 -0
- data/app/request_schemas/spree/api/v3/digital_asset_request_schema.rb +25 -0
- data/app/request_schemas/spree/api/v3/seller/price_request_schema.rb +20 -0
- data/app/request_schemas/spree/api/v3/seller/variant_request_schema.rb +19 -0
- data/app/request_schemas/spree/api/v3/variant_request_schema.rb +8 -13
- data/app/serializers/concerns/spree/api/v3/digital_asset_details.rb +70 -0
- data/app/serializers/spree/api/v3/address_owner_serializer.rb +27 -0
- data/app/serializers/spree/api/v3/address_serializer.rb +5 -1
- data/app/serializers/spree/api/v3/admin/address_owner_serializer.rb +10 -0
- data/app/serializers/spree/api/v3/admin/address_serializer.rb +1 -10
- data/app/serializers/spree/api/v3/admin/api_key_serializer.rb +1 -1
- data/app/serializers/spree/api/v3/admin/catalog_assignment_serializer.rb +1 -1
- data/app/serializers/spree/api/v3/admin/catalog_price_serializer.rb +1 -1
- data/app/serializers/spree/api/v3/admin/collection_rule_serializer.rb +1 -1
- data/app/serializers/spree/api/v3/admin/collection_serializer.rb +1 -1
- data/app/serializers/spree/api/v3/admin/commission_line_serializer.rb +2 -2
- data/app/serializers/spree/api/v3/admin/commission_rate_serializer.rb +1 -1
- data/app/serializers/spree/api/v3/admin/consent_record_serializer.rb +1 -1
- data/app/serializers/spree/api/v3/admin/delivery_origin_group_serializer.rb +2 -2
- data/app/serializers/spree/api/v3/admin/delivery_profile_serializer.rb +3 -5
- data/app/serializers/spree/api/v3/admin/digital_asset_serializer.rb +3 -47
- data/app/serializers/spree/api/v3/admin/email_template_serializer.rb +1 -1
- data/app/serializers/spree/api/v3/admin/newsletter_subscriber_serializer.rb +1 -0
- data/app/serializers/spree/api/v3/admin/order_serializer.rb +1 -1
- data/app/serializers/spree/api/v3/admin/package_type_serializer.rb +3 -3
- data/app/serializers/spree/api/v3/admin/permission_serializer.rb +1 -1
- data/app/serializers/spree/api/v3/admin/price_history_serializer.rb +1 -1
- data/app/serializers/spree/api/v3/admin/price_list_serializer.rb +9 -4
- data/app/serializers/spree/api/v3/admin/price_serializer.rb +1 -1
- data/app/serializers/spree/api/v3/admin/purchase_order_serializer.rb +1 -1
- data/app/serializers/spree/api/v3/admin/seller_payout_serializer.rb +2 -2
- data/app/serializers/spree/api/v3/admin/seller_requirement_status_serializer.rb +1 -1
- data/app/serializers/spree/api/v3/admin/seller_serializer.rb +3 -5
- data/app/serializers/spree/api/v3/admin/seller_transfer_serializer.rb +1 -1
- data/app/serializers/spree/api/v3/admin/shipping_label_serializer.rb +3 -3
- data/app/serializers/spree/api/v3/admin/stock_location_serializer.rb +1 -1
- data/app/serializers/spree/api/v3/admin/stock_receipt_item_serializer.rb +2 -2
- data/app/serializers/spree/api/v3/admin/stock_receipt_serializer.rb +1 -1
- data/app/serializers/spree/api/v3/admin/stock_transfer_serializer.rb +1 -1
- data/app/serializers/spree/api/v3/admin/store_credit_serializer.rb +3 -5
- data/app/serializers/spree/api/v3/admin/store_serializer.rb +1 -0
- data/app/serializers/spree/api/v3/admin/tax_identifier_serializer.rb +2 -2
- data/app/serializers/spree/api/v3/admin/tax_line_serializer.rb +1 -1
- data/app/serializers/spree/api/v3/admin/variant_serializer.rb +1 -1
- data/app/serializers/spree/api/v3/base_serializer.rb +33 -6
- data/app/serializers/spree/api/v3/claim_serializer.rb +1 -1
- data/app/serializers/spree/api/v3/collection_serializer.rb +1 -1
- data/app/serializers/spree/api/v3/company_invitation_serializer.rb +1 -1
- data/app/serializers/spree/api/v3/company_serializer.rb +1 -1
- data/app/serializers/spree/api/v3/data_request_confirmation_requested_event_serializer.rb +28 -0
- data/app/serializers/spree/api/v3/data_request_event_serializer.rb +1 -1
- data/app/serializers/spree/api/v3/data_request_serializer.rb +8 -4
- data/app/serializers/spree/api/v3/delivery_serializer.rb +1 -1
- data/app/serializers/spree/api/v3/delivery_zone_member_serializer.rb +1 -1
- data/app/serializers/spree/api/v3/discount_serializer.rb +1 -1
- data/app/serializers/spree/api/v3/file_upload_serializer.rb +1 -1
- data/app/serializers/spree/api/v3/fulfillment_serializer.rb +1 -1
- data/app/serializers/spree/api/v3/media_event_serializer.rb +1 -1
- data/app/serializers/spree/api/v3/media_serializer.rb +1 -1
- data/app/serializers/spree/api/v3/newsletter_subscriber_serializer.rb +5 -1
- data/app/serializers/spree/api/v3/option_type_serializer.rb +1 -1
- data/app/serializers/spree/api/v3/order_group_serializer.rb +2 -2
- data/app/serializers/spree/api/v3/order_serializer.rb +4 -2
- data/app/serializers/spree/api/v3/payment_serializer.rb +1 -1
- data/app/serializers/spree/api/v3/product_filter_availability_option_serializer.rb +1 -1
- data/app/serializers/spree/api/v3/product_filter_option_serializer.rb +1 -1
- data/app/serializers/spree/api/v3/seller/address_serializer.rb +3 -2
- data/app/serializers/spree/api/v3/seller/digital_asset_serializer.rb +16 -0
- data/app/serializers/spree/api/v3/seller/order_serializer.rb +3 -3
- data/app/serializers/spree/api/v3/seller/package_type_serializer.rb +3 -3
- data/app/serializers/spree/api/v3/seller/payout_serializer.rb +2 -2
- data/app/serializers/spree/api/v3/seller/profile_serializer.rb +2 -2
- data/app/serializers/spree/api/v3/seller/requirement_status_serializer.rb +1 -1
- data/app/serializers/spree/api/v3/seller/store_serializer.rb +18 -0
- data/app/serializers/spree/api/v3/seller/tax_identifier_serializer.rb +1 -1
- data/app/serializers/spree/api/v3/seller/variant_serializer.rb +1 -1
- data/app/serializers/spree/api/v3/shipping_label_event_serializer.rb +1 -1
- data/app/serializers/spree/api/v3/stock_movement_serializer.rb +1 -1
- data/app/serializers/spree/api/v3/stock_receipt_event_serializer.rb +1 -1
- data/app/serializers/spree/api/v3/variant_serializer.rb +2 -2
- data/app/services/spree/api/v3/filter_table.rb +22 -3
- data/app/services/spree/api/v3/filters_aggregator.rb +63 -18
- data/app/services/spree/api/v3/request_schema.rb +64 -17
- data/config/locales/en.yml +2 -0
- data/config/routes.rb +22 -11
- data/lib/spree/api/agent_write_schemas.rb +15 -3
- data/lib/spree/api/dependencies.rb +4 -4
- data/lib/spree/api/engine.rb +6 -0
- data/lib/spree/api/enum_types.rb +118 -0
- data/lib/spree/api/middleware/no_index.rb +33 -0
- data/lib/spree/api/openapi/filter_documentation.rb +8 -1
- data/lib/spree/api/openapi/schema_helper.rb +2 -1
- data/lib/spree/api/testing_support/v3/base.rb +18 -0
- data/lib/spree/api/typelizer_generation.rb +5 -0
- data/lib/typelizer/open_enums.rb +40 -16
- metadata +25 -6
|
@@ -9,7 +9,7 @@ module Spree
|
|
|
9
9
|
body.attribute :name, :string, example: 'My Store'
|
|
10
10
|
body.attribute :admin_locale, :string, example: 'en'
|
|
11
11
|
body.attribute :timezone, :string, example: 'Europe/Berlin'
|
|
12
|
-
body.attribute :weight_unit, :
|
|
12
|
+
body.attribute :weight_unit, :weight_unit
|
|
13
13
|
body.attribute :unit_system, :string, choices: Spree::Store::UNIT_SYSTEMS
|
|
14
14
|
body.attribute :storefront_access, :string,
|
|
15
15
|
choices: Spree::Channel::Gating::STOREFRONT_ACCESS,
|
|
@@ -65,6 +65,13 @@ module Spree
|
|
|
65
65
|
description: 'Admits a seller as soon as they finish onboarding, with no operator review.'
|
|
66
66
|
body.attribute :auto_approve_seller_products, :boolean,
|
|
67
67
|
description: 'Puts a seller product on sale the moment it is submitted, with no review.'
|
|
68
|
+
body.attribute :seller_digital_assets_enabled, :boolean,
|
|
69
|
+
description: 'Lets sellers attach downloadable files to their own products.'
|
|
70
|
+
body.attribute :seller_digital_asset_content_types, :array, of: :string,
|
|
71
|
+
example: %w[application/pdf application/zip],
|
|
72
|
+
description: "The content types a seller's file may have, checked against the file's own signature where it has one. An entry names one type or, with `*` as the subtype, a whole family. The operator's own uploads are not restricted."
|
|
73
|
+
body.attribute :review_seller_digital_asset_changes, :boolean,
|
|
74
|
+
description: 'Sends a seller product already on sale back to review when the seller adds or replaces a file on it.'
|
|
68
75
|
body.attribute :send_seller_transactional_emails, :boolean,
|
|
69
76
|
description: 'When false, transactional seller emails (approval, payouts and so on) are not sent.'
|
|
70
77
|
body.attribute :default_commission_tax_rate, :decimal,
|
|
@@ -63,22 +63,24 @@ module Spree
|
|
|
63
63
|
false
|
|
64
64
|
end
|
|
65
65
|
|
|
66
|
-
# Tagging filter.
|
|
67
|
-
#
|
|
68
|
-
#
|
|
69
|
-
#
|
|
70
|
-
# taggables like the customer/user type carry no store, so their tags
|
|
71
|
-
# are not store-scoped here.
|
|
66
|
+
# Tagging filter. A taggable declaring `acts_as_taggable_tenant :store_id`
|
|
67
|
+
# stores the owning store on each tagging, so its vocabulary is bounded
|
|
68
|
+
# to the current store with a single indexed `tenant` filter. Taggables
|
|
69
|
+
# without a store tenant are not store-scoped here.
|
|
72
70
|
def taggings_conditions
|
|
73
71
|
conditions = { taggable_type: taggable_type, context: 'tags' }
|
|
74
72
|
conditions[:tenant] = current_store.id.to_s if store_tenanted_taggable?
|
|
75
73
|
conditions
|
|
76
74
|
end
|
|
77
75
|
|
|
78
|
-
# True for taggables whose taggings carry a store `tenant`
|
|
79
|
-
# (`acts_as_taggable_tenant :store_id`), i.e. store-owned records.
|
|
80
76
|
def store_tenanted_taggable?
|
|
81
|
-
|
|
77
|
+
taggable_class.try(:tenant_column).to_s == 'store_id'
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
# `taggable_type` is already matched against `Spree.taggable_types`,
|
|
81
|
+
# so only a registered class name is ever constantized.
|
|
82
|
+
def taggable_class
|
|
83
|
+
taggable_type.constantize
|
|
82
84
|
end
|
|
83
85
|
|
|
84
86
|
# Per-type scope check for API-key principals: a key listing product
|
|
@@ -5,6 +5,30 @@ module Spree
|
|
|
5
5
|
class VariantsController < ResourceController
|
|
6
6
|
scoped_resource :products
|
|
7
7
|
|
|
8
|
+
SALES_CONTEXT = Spree::Api::V3::RequestSchema.shape('VariantSalesContextParams') do |query|
|
|
9
|
+
query.attribute :order_id, :id,
|
|
10
|
+
description: 'A draft order to sell to: lists only what its buyer can be sold on its channel, ' \
|
|
11
|
+
'priced in its market. Needs `read_orders`, and cannot be combined with the fields below.'
|
|
12
|
+
query.attribute :market_id, :id,
|
|
13
|
+
description: 'Prices in this market and lists only variants with a price in its currency.'
|
|
14
|
+
query.attribute :channel_id, :id,
|
|
15
|
+
description: 'Lists only variants published on this channel. When a market, customer, company or ' \
|
|
16
|
+
"currency is given without it, the store default channel applies; an order uses its own."
|
|
17
|
+
query.attribute :customer_id, :id,
|
|
18
|
+
description: "Narrows to the customer's catalogs and prices as they would pay. Needs `read_customers`."
|
|
19
|
+
query.attribute :company_id, :id,
|
|
20
|
+
description: "Narrows to the company's catalogs and prices under its agreement. Needs `read_customers`."
|
|
21
|
+
query.attribute :currency, :currency,
|
|
22
|
+
description: 'Lists only variants with a price in this currency. ' \
|
|
23
|
+
"With `market_id` it must be the market's currency."
|
|
24
|
+
end
|
|
25
|
+
SALES_CONTEXT_PARAMS = (SALES_CONTEXT.fields.keys - [:order_id]).freeze
|
|
26
|
+
|
|
27
|
+
# A draft order's item picker asks with the order's market, channel and
|
|
28
|
+
# buyer, so it lists only what that buyer can be sold there, priced as
|
|
29
|
+
# they would pay.
|
|
30
|
+
before_action :load_sales_context, only: :index
|
|
31
|
+
|
|
8
32
|
protected
|
|
9
33
|
|
|
10
34
|
def model_class
|
|
@@ -19,7 +43,21 @@ module Spree
|
|
|
19
43
|
# relation from scratch dropped `scope_includes`, so every listed
|
|
20
44
|
# variant fell back to per-row price and stock queries.
|
|
21
45
|
def scope
|
|
22
|
-
super.eligible.accessible_by(current_ability, ability_action_for_request)
|
|
46
|
+
variants = super.eligible.accessible_by(current_ability, ability_action_for_request)
|
|
47
|
+
@sales_context ? variants.where(id: @sales_context.sellable_variants.select(:id)) : variants
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def serializer_params
|
|
51
|
+
return super unless @sales_context
|
|
52
|
+
|
|
53
|
+
super.merge(
|
|
54
|
+
currency: @sales_context.currency,
|
|
55
|
+
user: @sales_context.customer,
|
|
56
|
+
company: @sales_context.company,
|
|
57
|
+
market: @sales_context.market,
|
|
58
|
+
channel: @sales_context.channel,
|
|
59
|
+
country: @sales_context.country
|
|
60
|
+
)
|
|
23
61
|
end
|
|
24
62
|
|
|
25
63
|
# `active_stock_reservations` rides along with the stock levels:
|
|
@@ -33,6 +71,63 @@ module Spree
|
|
|
33
71
|
{ primary_media: [{ attachment_attachment: :blob }, { poster_attachment: :blob }] }
|
|
34
72
|
]
|
|
35
73
|
end
|
|
74
|
+
|
|
75
|
+
private
|
|
76
|
+
|
|
77
|
+
def load_sales_context
|
|
78
|
+
requested = SALES_CONTEXT_PARAMS.select { |key| params[key].present? }
|
|
79
|
+
return if requested.empty? && params[:order_id].blank?
|
|
80
|
+
|
|
81
|
+
permission = required_sales_context_permission
|
|
82
|
+
return render_missing_permission(permission) if permission && !holds_permission?(permission)
|
|
83
|
+
|
|
84
|
+
if params[:order_id].present?
|
|
85
|
+
return refuse_order_with_context(requested) if requested.any?
|
|
86
|
+
|
|
87
|
+
@sales_context = Spree::SalesContext.from_order(context_order)
|
|
88
|
+
else
|
|
89
|
+
@sales_context = requested_sales_context
|
|
90
|
+
render_errors(@sales_context.errors) unless @sales_context.valid?
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
# An order already fixes its market, channel, buyer and currency, so
|
|
95
|
+
# naming them too could only disagree with it.
|
|
96
|
+
def refuse_order_with_context(requested)
|
|
97
|
+
render_error(
|
|
98
|
+
code: Spree::Api::V3::ErrorHandler::ERROR_CODES[:parameter_invalid],
|
|
99
|
+
message: I18n.t('spree.api.errors.order_sales_context_exclusive', params: requested.join(', ')),
|
|
100
|
+
status: :unprocessable_content,
|
|
101
|
+
details: { order_id: requested.map(&:to_s) }
|
|
102
|
+
)
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
def required_sales_context_permission
|
|
106
|
+
return 'read_orders' if params[:order_id].present?
|
|
107
|
+
|
|
108
|
+
'read_customers' if params[:customer_id].present? || params[:company_id].present?
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
def requested_sales_context
|
|
112
|
+
Spree::SalesContext.new(
|
|
113
|
+
store: current_store,
|
|
114
|
+
market: find_requested(current_store.markets, :market_id),
|
|
115
|
+
channel: find_requested(current_store.channels, :channel_id),
|
|
116
|
+
customer: find_requested(
|
|
117
|
+
Spree.customer_class.for_store(current_store).accessible_by(current_ability, :show), :customer_id
|
|
118
|
+
),
|
|
119
|
+
company: find_requested(current_store.companies, :company_id),
|
|
120
|
+
currency: params[:currency]
|
|
121
|
+
)
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
def find_requested(relation, key)
|
|
125
|
+
relation.find_by_prefix_id!(params[key]) if params[key].present?
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
def context_order
|
|
129
|
+
current_store.orders.accessible_by(current_ability, :show).find_by_prefix_id!(params[:order_id])
|
|
130
|
+
end
|
|
36
131
|
end
|
|
37
132
|
end
|
|
38
133
|
end
|
|
@@ -21,7 +21,7 @@ module Spree
|
|
|
21
21
|
description: 'Defaults to true on create. Prefer the disable and enable endpoints to pause one.'
|
|
22
22
|
body.attribute :subscriptions, :array, of: :string,
|
|
23
23
|
example: %w[order.placed order.canceled],
|
|
24
|
-
description: 'Event names this endpoint receives. An empty list or `["*"]` subscribes to every event; otherwise list exact names (`order.completed`) or patterns (`order
|
|
24
|
+
description: 'Event names this endpoint receives. An empty list or `["*"]` subscribes to every event; otherwise list exact names (`order.completed`) or patterns with `*` in place of a segment (`order.*`, `*.created`). At most 200 entries of up to 100 characters.'
|
|
25
25
|
end
|
|
26
26
|
|
|
27
27
|
request_params 'WebhookEndpointDisableParams', action: :disable do |body|
|
|
@@ -112,8 +112,11 @@ module Spree
|
|
|
112
112
|
)
|
|
113
113
|
end
|
|
114
114
|
|
|
115
|
+
# Checked after the model's normalization, so what the gate sees is
|
|
116
|
+
# exactly what would be stored.
|
|
115
117
|
def requested_credential_events
|
|
116
|
-
Spree::
|
|
118
|
+
subscriptions = Spree::WebhookEndpoint.normalize_value_for(:subscriptions, permitted_params[:subscriptions])
|
|
119
|
+
Spree::Events.catalog.credential_events(subscriptions)
|
|
117
120
|
end
|
|
118
121
|
|
|
119
122
|
def repointed_credential_events
|
|
@@ -44,15 +44,26 @@ module Spree
|
|
|
44
44
|
# and the merchant would approve on the wrong one.
|
|
45
45
|
def consent_url
|
|
46
46
|
query = params.permit(*FORWARDED_PARAMETERS).to_h.compact_blank.to_query
|
|
47
|
-
base = Spree::Stores::DashboardUrl.call(store:
|
|
47
|
+
base = Spree::Stores::DashboardUrl.call(store: current_store)
|
|
48
48
|
|
|
49
|
-
"#{base}/#{
|
|
49
|
+
"#{base}/#{current_store.prefixed_id}/oauth/authorize?#{query}"
|
|
50
50
|
end
|
|
51
51
|
|
|
52
|
-
#
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
52
|
+
# The client's store wins over the hostname's, and is looked up
|
|
53
|
+
# before the hostname is resolved at all: resolving it assigns the
|
|
54
|
+
# default store to Spree::Current, and an app scoping queries by
|
|
55
|
+
# that store would then never find another store's client.
|
|
56
|
+
#
|
|
57
|
+
# @return [Spree::Store, nil]
|
|
58
|
+
def current_store
|
|
59
|
+
@current_store ||= client_application&.store || super
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
# @return [Spree::OauthApplication, nil]
|
|
63
|
+
def client_application
|
|
64
|
+
return if params[:client_id].blank?
|
|
65
|
+
|
|
66
|
+
Spree::OauthApplication.find_by(uid: params[:client_id])
|
|
56
67
|
end
|
|
57
68
|
end
|
|
58
69
|
end
|
|
@@ -248,8 +248,11 @@ module Spree
|
|
|
248
248
|
end
|
|
249
249
|
|
|
250
250
|
# Finds a single resource within scope using prefixed ID
|
|
251
|
+
# Loaded through the lazy-preload relation, as the collection is, so
|
|
252
|
+
# the serializer's walk over the record's children batches their
|
|
253
|
+
# associations instead of querying once per child.
|
|
251
254
|
def find_resource
|
|
252
|
-
scope.find_by_prefix_id!(params[:id])
|
|
255
|
+
scope.preload_associations_lazily.find_by_prefix_id!(params[:id])
|
|
253
256
|
end
|
|
254
257
|
|
|
255
258
|
# Per-record authorization hook. A no-op in the shared base: the Store
|
|
@@ -481,13 +484,23 @@ module Spree
|
|
|
481
484
|
base_scope = if @parent.present?
|
|
482
485
|
@parent.send(parent_association)
|
|
483
486
|
else
|
|
484
|
-
|
|
487
|
+
store_scope
|
|
485
488
|
end
|
|
486
489
|
base_scope = base_scope.includes(scope_includes) if scope_includes.any?
|
|
487
490
|
base_scope = base_scope.preload_associations_lazily
|
|
488
491
|
model_class.include?(Spree::TranslatableResource) ? base_scope.i18n : base_scope
|
|
489
492
|
end
|
|
490
493
|
|
|
494
|
+
# The store's rows of this model when the resource has no parent. A
|
|
495
|
+
# model the store reaches through another record rather than an
|
|
496
|
+
# association of its own — a commission line through its order —
|
|
497
|
+
# names that path here, since `for_store` answers nothing for it.
|
|
498
|
+
#
|
|
499
|
+
# @return [ActiveRecord::Relation]
|
|
500
|
+
def store_scope
|
|
501
|
+
model_class.for_store(current_store)
|
|
502
|
+
end
|
|
503
|
+
|
|
491
504
|
# Action names treated as reads. Override in subclasses with custom
|
|
492
505
|
# read-only member/collection actions (e.g. add `analytics`, `types`)
|
|
493
506
|
# so they map to the `:show` ability instead of a write.
|
|
@@ -559,7 +572,17 @@ module Spree
|
|
|
559
572
|
# declared on the model. Override +resource_permitted_attributes+ rather
|
|
560
573
|
# than this, so the extension union is not lost.
|
|
561
574
|
def permitted_attributes
|
|
562
|
-
resource_permitted_attributes + model_additional_permitted_attributes
|
|
575
|
+
with_nested_additional_permitted_attributes(resource_permitted_attributes) + model_additional_permitted_attributes
|
|
576
|
+
end
|
|
577
|
+
|
|
578
|
+
# Nested lists this resource writes, keyed by param name, mapped to the
|
|
579
|
+
# model each entry becomes — +{ media: Spree::Media }+ lets an
|
|
580
|
+
# extension's +Spree::Media.additional_permitted_attributes+ ride the
|
|
581
|
+
# product's +media+ entries, not only the media endpoint.
|
|
582
|
+
#
|
|
583
|
+
# @return [Hash{Symbol => Class}]
|
|
584
|
+
def nested_permitted_models
|
|
585
|
+
{}
|
|
563
586
|
end
|
|
564
587
|
|
|
565
588
|
# The resource's writable attributes: its `request_params`
|
|
@@ -580,13 +603,33 @@ module Spree
|
|
|
580
603
|
def model_additional_permitted_attributes
|
|
581
604
|
return [] unless respond_to?(:model_class, true)
|
|
582
605
|
|
|
583
|
-
|
|
584
|
-
return [] unless model.respond_to?(:additional_permitted_attributes)
|
|
585
|
-
|
|
586
|
-
Array(model.additional_permitted_attributes)
|
|
606
|
+
additional_permitted_attributes_for(model_class)
|
|
587
607
|
rescue NotImplementedError
|
|
588
608
|
[]
|
|
589
609
|
end
|
|
610
|
+
|
|
611
|
+
# What extensions declared writable on +model+. `Spree.base_class` is
|
|
612
|
+
# host-overridable, so the class_attribute may not be there at all.
|
|
613
|
+
#
|
|
614
|
+
# @param model [Class]
|
|
615
|
+
# @return [Array]
|
|
616
|
+
def additional_permitted_attributes_for(model)
|
|
617
|
+
Array(model.try(:additional_permitted_attributes))
|
|
618
|
+
end
|
|
619
|
+
|
|
620
|
+
def with_nested_additional_permitted_attributes(attributes)
|
|
621
|
+
return attributes if nested_permitted_models.empty?
|
|
622
|
+
|
|
623
|
+
attributes.map do |attribute|
|
|
624
|
+
next attribute unless attribute.is_a?(Hash)
|
|
625
|
+
|
|
626
|
+
attribute.to_h do |name, nested|
|
|
627
|
+
model = nested_permitted_models[name.to_sym]
|
|
628
|
+
additional = model ? additional_permitted_attributes_for(model) : []
|
|
629
|
+
[name, additional.any? && nested.is_a?(Array) ? nested + additional : nested]
|
|
630
|
+
end
|
|
631
|
+
end
|
|
632
|
+
end
|
|
590
633
|
end
|
|
591
634
|
end
|
|
592
635
|
end
|
|
@@ -52,6 +52,8 @@ module Spree
|
|
|
52
52
|
return render_authentication_failed(result.error) unless result.success?
|
|
53
53
|
|
|
54
54
|
user = result.value
|
|
55
|
+
return render_account_deactivated if account_deactivated?(user)
|
|
56
|
+
|
|
55
57
|
# A store's own staff share this user class, so authenticating is
|
|
56
58
|
# not enough: without a role on some seller there is no panel to
|
|
57
59
|
# sign in to, and minting a token would hand out an audience the
|
|
@@ -101,6 +103,14 @@ module Spree
|
|
|
101
103
|
end
|
|
102
104
|
|
|
103
105
|
user = refresh_token.user
|
|
106
|
+
# Deactivating deletes the account's refresh tokens; this catches a
|
|
107
|
+
# refresh already under way when that happened.
|
|
108
|
+
if account_deactivated?(user)
|
|
109
|
+
refresh_token.destroy
|
|
110
|
+
clear_refresh_cookie
|
|
111
|
+
return render_account_deactivated
|
|
112
|
+
end
|
|
113
|
+
|
|
104
114
|
# Membership can be revoked while a session is live; the refresh is
|
|
105
115
|
# where that takes effect.
|
|
106
116
|
unless user.seller_member?
|
|
@@ -46,6 +46,8 @@ module Spree
|
|
|
46
46
|
|
|
47
47
|
user = resolve_or_create_invitee(@invitation)
|
|
48
48
|
return if performed?
|
|
49
|
+
# Checked before accepting: a deactivated account gains no role.
|
|
50
|
+
return render_account_deactivated if account_deactivated?(user)
|
|
49
51
|
|
|
50
52
|
@invitation.invitee = user
|
|
51
53
|
result = Spree.invitation_accept_workflow.call(invitation: @invitation)
|
|
@@ -58,6 +58,10 @@ module Spree
|
|
|
58
58
|
{
|
|
59
59
|
user: Spree.api.seller_account_serializer.new(current_user, params: serializer_params).to_h,
|
|
60
60
|
sellers: serialized_sellers,
|
|
61
|
+
# The marketplace's own settings that shape what the panel lets a
|
|
62
|
+
# seller do, read before any seller is chosen since there is one
|
|
63
|
+
# store per installation.
|
|
64
|
+
store: Spree.api.seller_store_serializer.new(serializer_params[:store], params: serializer_params).to_h,
|
|
61
65
|
# Both shapes, exactly as the admin `/me`: the panel's `<Can>`
|
|
62
66
|
# reads CanCanCan rules, the key gate reads keys. Sending only
|
|
63
67
|
# keys left `<Can>` answering false for everything on the seller
|
|
@@ -84,7 +84,7 @@ module Spree
|
|
|
84
84
|
)
|
|
85
85
|
|
|
86
86
|
if result.success?
|
|
87
|
-
render json: serialize_resource(
|
|
87
|
+
render json: serialize_resource(reloaded_resource)
|
|
88
88
|
else
|
|
89
89
|
render_service_error(@resource.errors.presence || result.error)
|
|
90
90
|
end
|
|
@@ -115,7 +115,7 @@ module Spree
|
|
|
115
115
|
next render_validation_error(invalid.errors) if invalid
|
|
116
116
|
|
|
117
117
|
if @resource.update(addresses)
|
|
118
|
-
render json: serialize_resource(
|
|
118
|
+
render json: serialize_resource(reloaded_resource)
|
|
119
119
|
else
|
|
120
120
|
render_validation_error(@resource.errors)
|
|
121
121
|
end
|
|
@@ -143,12 +143,20 @@ module Spree
|
|
|
143
143
|
super.not_drafts
|
|
144
144
|
end
|
|
145
145
|
|
|
146
|
+
# Both addresses are named because they are two associations on one
|
|
147
|
+
# table, so a row that reads them separately costs a lookup each.
|
|
146
148
|
def collection_includes
|
|
147
|
-
[:line_items, :customer, :channel]
|
|
149
|
+
[:line_items, :customer, :channel, :bill_address, :ship_address]
|
|
148
150
|
end
|
|
149
151
|
|
|
150
152
|
private
|
|
151
153
|
|
|
154
|
+
# Read back as #show reads it, lazily preloading, so its
|
|
155
|
+
# fulfillments' rates and methods load once rather than per row.
|
|
156
|
+
def reloaded_resource
|
|
157
|
+
scope.preload_associations_lazily.find(@resource.id)
|
|
158
|
+
end
|
|
159
|
+
|
|
152
160
|
# Either address, as a correction rather than a replacement: the
|
|
153
161
|
# sent fields are laid over what the order already holds, so a
|
|
154
162
|
# request naming one line does not blank out the country and
|
|
@@ -28,12 +28,12 @@ module Spree
|
|
|
28
28
|
body.attribute :length, :number, nullable: true
|
|
29
29
|
body.attribute :width, :number, nullable: true
|
|
30
30
|
body.attribute :height, :number, nullable: true
|
|
31
|
-
body.attribute :dimensions_unit, :
|
|
31
|
+
body.attribute :dimensions_unit, :dimension_unit, nullable: true
|
|
32
32
|
body.attribute :weight, :number,
|
|
33
33
|
nullable: true,
|
|
34
34
|
description: "The empty package's own weight, added to every quote."
|
|
35
35
|
body.attribute :max_weight, :number, nullable: true
|
|
36
|
-
body.attribute :weight_unit, :
|
|
36
|
+
body.attribute :weight_unit, :weight_unit, nullable: true
|
|
37
37
|
body.attribute :default, :boolean,
|
|
38
38
|
description: "Makes this the box this seller's parcels are quoted with. One default per seller, so it never displaces the marketplace's."
|
|
39
39
|
body.attribute :metadata, :hash, nullable: true
|
|
@@ -43,7 +43,8 @@ module Spree
|
|
|
43
43
|
# admin who runs no seller has no panel to be sent to, and mailing
|
|
44
44
|
# them a seller reset link would be an invitation to a session they
|
|
45
45
|
# cannot hold.
|
|
46
|
-
|
|
46
|
+
# A deactivated account is treated as no account: no email is sent.
|
|
47
|
+
if user&.seller_member? && !account_deactivated?(user)
|
|
47
48
|
user.publish_event(RESET_REQUESTED_EVENT, event_payload(user))
|
|
48
49
|
end
|
|
49
50
|
|
|
@@ -60,6 +61,9 @@ module Spree
|
|
|
60
61
|
# it can be revoked between the email being sent and the link being
|
|
61
62
|
# opened, and signing in is what the success path does.
|
|
62
63
|
return render_token_invalid unless user&.seller_member?
|
|
64
|
+
# A link sent before the account was switched off must not change
|
|
65
|
+
# its password or sign it in.
|
|
66
|
+
return render_account_deactivated if account_deactivated?(user)
|
|
63
67
|
|
|
64
68
|
unless user.update(password: params[:password], password_confirmation: params[:password_confirmation])
|
|
65
69
|
return render_errors(user.errors)
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
module Spree
|
|
2
|
+
module Api
|
|
3
|
+
module V3
|
|
4
|
+
module Seller
|
|
5
|
+
module Products
|
|
6
|
+
# The downloadable files on one of this seller's own products.
|
|
7
|
+
#
|
|
8
|
+
# Uploaded files only: a provider is the operator's own code
|
|
9
|
+
# reaching into their internal systems, so a seller can neither pick
|
|
10
|
+
# one nor read its settings. An asset the operator set up with one
|
|
11
|
+
# is still listed, since it is part of what the seller's product
|
|
12
|
+
# delivers.
|
|
13
|
+
#
|
|
14
|
+
# The marketplace's rules for a seller's file — whether sellers may
|
|
15
|
+
# upload at all, which content types, and whether an approved
|
|
16
|
+
# listing goes back to review — apply because the workflow is told
|
|
17
|
+
# who is writing.
|
|
18
|
+
class DigitalAssetsController < Seller::ResourceController
|
|
19
|
+
include Spree::Api::V3::DigitalAssetsResource
|
|
20
|
+
|
|
21
|
+
scoped_resource :products
|
|
22
|
+
|
|
23
|
+
request_params :digital_asset do |body|
|
|
24
|
+
body.include_attributes Spree::Api::V3::DigitalAssetRequestSchema
|
|
25
|
+
body.require_attributes :signed_id, on: :create
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
# DELETE /api/v3/seller/products/:product_id/digital_assets/:id
|
|
29
|
+
#
|
|
30
|
+
# Removing a file is covered by the same switch as adding one: a
|
|
31
|
+
# marketplace that keeps digital files to itself keeps the ones it
|
|
32
|
+
# attached to a seller's product.
|
|
33
|
+
def destroy
|
|
34
|
+
unless current_store.preferred_seller_digital_assets_enabled
|
|
35
|
+
return render_validation_error(I18n.t('spree.digital_assets_sellers_disabled'))
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
super
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
protected
|
|
42
|
+
|
|
43
|
+
def serializer_class
|
|
44
|
+
Spree.api.seller_digital_asset_serializer
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def create_workflow_arguments
|
|
48
|
+
super.merge(seller: current_seller, submitted_by: current_user)
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def update_workflow_arguments
|
|
52
|
+
super.merge(seller: current_seller, submitted_by: current_user)
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
# Rooted in the seller's own catalog, so another seller's product
|
|
56
|
+
# is a 404. Changing a product's files is a change to the product,
|
|
57
|
+
# so writes need `:update` on it, not just `:show`.
|
|
58
|
+
def set_parent
|
|
59
|
+
@parent = current_seller.products.find_by_prefix_id!(params[:product_id])
|
|
60
|
+
authorize_parent!(@parent)
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
end
|
|
@@ -41,19 +41,6 @@ module Spree
|
|
|
41
41
|
'archived' => -> { Spree.product_archive_workflow }
|
|
42
42
|
}.freeze
|
|
43
43
|
|
|
44
|
-
# The operator's price shape, except `currency` may be left out: the
|
|
45
|
-
# store's currency is assumed (see `default_price_currencies`).
|
|
46
|
-
PRICE = Spree::Api::V3::RequestSchema.new do |schema|
|
|
47
|
-
schema.attribute :currency, :currency,
|
|
48
|
-
example: 'USD',
|
|
49
|
-
description: "Omit to price in the store's currency. A seller has no currency of their own."
|
|
50
|
-
schema.attribute :amount, :decimal,
|
|
51
|
-
required: true,
|
|
52
|
-
example: '24.00',
|
|
53
|
-
description: 'A canonical decimal string (`"1234.56"`: period decimal, no grouping). The API does not parse localized input.'
|
|
54
|
-
schema.attribute :compare_at_amount, :decimal, nullable: true, example: '30.00'
|
|
55
|
-
end
|
|
56
|
-
|
|
57
44
|
scoped_resource :products
|
|
58
45
|
|
|
59
46
|
request_params :product do |body|
|
|
@@ -69,7 +56,7 @@ module Spree
|
|
|
69
56
|
description: "The marketplace's product type this product is listed against, from the product types endpoint. Null detaches it. Saving with a type seeds its option types onto the product."
|
|
70
57
|
body.attribute :delivery_profile_id, :id,
|
|
71
58
|
description: "Which of the marketplace's delivery profiles the product ships under. A new product sent without one lands on the profile marked default."
|
|
72
|
-
body.attribute :prices, :array, of:
|
|
59
|
+
body.attribute :prices, :array, of: Spree::Api::V3::Seller::PriceRequestSchema,
|
|
73
60
|
description: "Shorthand for a simple (no-options) product: per-currency prices that forward to the product's sole variant."
|
|
74
61
|
# `external_url` stays out for the same reason it does on the
|
|
75
62
|
# operator's endpoint: it would make the server fetch a URL the
|
|
@@ -83,56 +70,11 @@ module Spree
|
|
|
83
70
|
media_item.attribute :variant_ids, :array, of: :id,
|
|
84
71
|
description: 'Replaces the variants this media is linked to. An empty list clears them.'
|
|
85
72
|
end
|
|
86
|
-
# Narrower than the operator's variant set: `seller_id` would let a
|
|
87
|
-
# payload hand a variant to somebody else, and
|
|
88
|
-
# `tax_category_id`/`delivery_profile_id` are marketplace
|
|
89
|
-
# configuration.
|
|
90
73
|
body.attribute :variants, :array,
|
|
91
|
-
as: '
|
|
74
|
+
as: 'ProductVariantParams',
|
|
92
75
|
description: 'The variants, as a whole: one absent from the list is removed. Pass `id` to update an existing variant.' do |variant|
|
|
76
|
+
variant.extends Spree::Api::V3::Seller::VariantRequestSchema
|
|
93
77
|
variant.attribute :id, :id
|
|
94
|
-
variant.attribute :sku, :string
|
|
95
|
-
variant.attribute :barcode, :string
|
|
96
|
-
variant.attribute :cost_price, :decimal, nullable: true
|
|
97
|
-
variant.attribute :cost_currency, :currency
|
|
98
|
-
variant.attribute :weight, :number, nullable: true
|
|
99
|
-
variant.attribute :height, :number, nullable: true
|
|
100
|
-
variant.attribute :width, :number, nullable: true
|
|
101
|
-
variant.attribute :depth, :number, nullable: true
|
|
102
|
-
variant.attribute :weight_unit, :string, choices: Spree::Variant::WEIGHT_UNITS
|
|
103
|
-
variant.attribute :dimensions_unit, :string, choices: Spree::Variant::DIMENSION_UNITS
|
|
104
|
-
variant.attribute :hs_code, :string
|
|
105
|
-
variant.attribute :customs_description, :string
|
|
106
|
-
variant.attribute :country_of_origin, :country
|
|
107
|
-
variant.attribute :minimum_order_quantity, :integer, nullable: true
|
|
108
|
-
variant.attribute :order_multiple, :integer, nullable: true
|
|
109
|
-
variant.attribute :purchase_unit, :string
|
|
110
|
-
variant.attribute :units_per_carton, :integer,
|
|
111
|
-
nullable: true,
|
|
112
|
-
description: 'How many units fill one carton.'
|
|
113
|
-
variant.attribute :carton_package_type_id, :id,
|
|
114
|
-
nullable: true,
|
|
115
|
-
description: 'The carton this variant is packed into: a package type of kind `carton`.'
|
|
116
|
-
variant.attribute :carton_weight, :number,
|
|
117
|
-
nullable: true,
|
|
118
|
-
description: "What one packed carton weighs, in the variant's weight unit."
|
|
119
|
-
variant.attribute :cartons_per_pallet, :integer,
|
|
120
|
-
nullable: true,
|
|
121
|
-
description: 'How many cartons stack on one pallet.'
|
|
122
|
-
variant.attribute :track_inventory, :boolean
|
|
123
|
-
variant.attribute :preorderable, :boolean
|
|
124
|
-
variant.attribute :preorder_ships_at, :datetime,
|
|
125
|
-
nullable: true,
|
|
126
|
-
description: 'The "ships by" date shown to customers while the variant is a pre-order. Null clears it.'
|
|
127
|
-
variant.attribute :backorder_limit, :integer,
|
|
128
|
-
nullable: true,
|
|
129
|
-
description: 'Maximum units sellable beyond available stock, capping backorders and pre-orders. Null is unlimited.'
|
|
130
|
-
variant.attribute :position, :integer
|
|
131
|
-
variant.attribute :options, :array, of: Spree::Api::V3::VariantOptionRequestSchema,
|
|
132
|
-
description: 'One pair per option type the variant takes part in (size, color).'
|
|
133
|
-
variant.attribute :prices, :array, of: PRICE, description: 'Per-currency prices. Upserted by currency.'
|
|
134
|
-
variant.attribute :stock_levels, :array, of: Spree::Api::V3::StockLevelRequestSchema,
|
|
135
|
-
description: "Stock per location, at this seller's own locations only: a location belonging to anyone else is ignored."
|
|
136
78
|
end
|
|
137
79
|
end
|
|
138
80
|
|
|
@@ -200,8 +142,10 @@ module Spree
|
|
|
200
142
|
submitted_by: try_spree_current_user)
|
|
201
143
|
|
|
202
144
|
# A product Propose refused is untouched; one it accepted is in
|
|
203
|
-
# review or already through it.
|
|
204
|
-
|
|
145
|
+
# review or already through it. The saved value, not the
|
|
146
|
+
# attribute: a refused update leaves the new status unsaved on
|
|
147
|
+
# the record, and a rolled-back one restores what was stored.
|
|
148
|
+
product.status_in_database != was
|
|
205
149
|
}
|
|
206
150
|
end
|
|
207
151
|
|
|
@@ -270,6 +214,10 @@ module Spree
|
|
|
270
214
|
Spree.product_update_workflow
|
|
271
215
|
end
|
|
272
216
|
|
|
217
|
+
def nested_permitted_models
|
|
218
|
+
{ media: Spree::Media, variants: Spree::Variant }
|
|
219
|
+
end
|
|
220
|
+
|
|
273
221
|
# The workflow builds its own product, which would leave a seller's
|
|
274
222
|
# listing unowned. Handing it one built through `current_seller.products`
|
|
275
223
|
# keeps the seller on the record, whatever the payload claims.
|
|
@@ -69,12 +69,8 @@ module Spree
|
|
|
69
69
|
# What extensions opened to sellers, never the operator-level list:
|
|
70
70
|
# an attribute opened to operators must not become seller-writable
|
|
71
71
|
# by accident.
|
|
72
|
-
def
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
Array(model_class.try(:additional_seller_permitted_attributes))
|
|
76
|
-
rescue NotImplementedError
|
|
77
|
-
[]
|
|
72
|
+
def additional_permitted_attributes_for(model)
|
|
73
|
+
Array(model.try(:additional_seller_permitted_attributes))
|
|
78
74
|
end
|
|
79
75
|
|
|
80
76
|
def authorize_resource!(resource = @resource, action = action_name.to_sym)
|