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
|
@@ -59,14 +59,7 @@ module Spree
|
|
|
59
59
|
return unless params.key?(:customer_id)
|
|
60
60
|
return @customer = nil if params[:customer_id].blank?
|
|
61
61
|
|
|
62
|
-
unless holds_permission?('read_customers')
|
|
63
|
-
return render_error(
|
|
64
|
-
code: Spree::Api::V3::ErrorHandler::ERROR_CODES[:access_denied],
|
|
65
|
-
message: 'Missing permission: read_customers',
|
|
66
|
-
status: :forbidden,
|
|
67
|
-
details: { required_permission: 'read_customers' }
|
|
68
|
-
)
|
|
69
|
-
end
|
|
62
|
+
return render_missing_permission('read_customers') unless holds_permission?('read_customers')
|
|
70
63
|
|
|
71
64
|
@customer = Spree.customer_class.
|
|
72
65
|
accessible_by(current_ability, :show).
|
|
@@ -37,6 +37,8 @@ module Spree
|
|
|
37
37
|
|
|
38
38
|
user = resolve_or_create_invitee(@invitation)
|
|
39
39
|
return if performed?
|
|
40
|
+
# Checked before accepting: a deactivated account gains no role.
|
|
41
|
+
return render_account_deactivated if account_deactivated?(user)
|
|
40
42
|
|
|
41
43
|
@invitation.invitee = user
|
|
42
44
|
result = Spree.invitation_accept_workflow.call(invitation: @invitation)
|
|
@@ -103,6 +103,8 @@ module Spree
|
|
|
103
103
|
# role would both disclose more than was handed over and mislead a
|
|
104
104
|
# client that reads /me to decide what to attempt.
|
|
105
105
|
def me_response
|
|
106
|
+
ActiveRecord::Associations::Preloader.new(records: [current_user], associations: { spree_roles: :resource }).call
|
|
107
|
+
|
|
106
108
|
{
|
|
107
109
|
user: admin_user_serializer.new(current_user, params: serializer_params).to_h,
|
|
108
110
|
permissions: serialize_permissions(current_ability),
|
|
@@ -42,6 +42,19 @@ module Spree
|
|
|
42
42
|
[:option_values]
|
|
43
43
|
end
|
|
44
44
|
|
|
45
|
+
# Values created or changed by this write were never loaded with
|
|
46
|
+
# their swatches, so fetch every value's image in one query before
|
|
47
|
+
# rendering.
|
|
48
|
+
def serialize_resource(resource)
|
|
49
|
+
if expand_list.include?('option_values')
|
|
50
|
+
ActiveRecord::Associations::Preloader.new(
|
|
51
|
+
records: resource.option_values.to_a,
|
|
52
|
+
associations: { image_attachment: :blob }
|
|
53
|
+
).call
|
|
54
|
+
end
|
|
55
|
+
super
|
|
56
|
+
end
|
|
57
|
+
|
|
45
58
|
def permitted_params
|
|
46
59
|
params.permit(*permitted_attributes)
|
|
47
60
|
end
|
|
@@ -6,6 +6,7 @@ module Spree
|
|
|
6
6
|
# Claims on a completed order.
|
|
7
7
|
class ClaimsController < BaseController
|
|
8
8
|
include Spree::Api::V3::Orders::ClaimActions
|
|
9
|
+
include Spree::Api::V3::Admin::RefundAuthorization
|
|
9
10
|
|
|
10
11
|
# Claims are a subject of the `orders` catalog resource, so
|
|
11
12
|
# `read_orders`/`write_orders` gate these endpoints. `:claims`
|
|
@@ -51,6 +52,7 @@ module Spree
|
|
|
51
52
|
end
|
|
52
53
|
|
|
53
54
|
before_action :set_resource, only: [:show, :update, :approve, :resolve, :deny, :cancel]
|
|
55
|
+
before_action :require_refund_permission!, only: :resolve, if: -> { params[:resolution].to_s.include?('refund') }
|
|
54
56
|
|
|
55
57
|
# PATCH /api/v3/admin/orders/:order_id/claims/:id
|
|
56
58
|
#
|
|
@@ -7,6 +7,7 @@ module Spree
|
|
|
7
7
|
# member action, because each carries different arguments.
|
|
8
8
|
class ExchangesController < BaseController
|
|
9
9
|
include Spree::Api::V3::Orders::ExchangeActions
|
|
10
|
+
include Spree::Api::V3::Admin::RefundAuthorization
|
|
10
11
|
|
|
11
12
|
# Exchanges are a subject of the `orders` catalog resource, so
|
|
12
13
|
# `read_orders`/`write_orders` gate these endpoints. `:exchanges`
|
|
@@ -49,6 +50,14 @@ module Spree
|
|
|
49
50
|
end
|
|
50
51
|
|
|
51
52
|
before_action :set_resource, only: [:show, :update, :approve, :receive, :fulfill, :cancel]
|
|
53
|
+
# Fulfilling settles the difference on the units that actually came
|
|
54
|
+
# back, a credit to the customer when the replacements cost less.
|
|
55
|
+
# That figure is only known once the tax rows are rewritten for
|
|
56
|
+
# those units, inside the fulfillment itself, so the gate cannot be
|
|
57
|
+
# narrowed on the announced difference: a line received at zero
|
|
58
|
+
# units would offset a credit still paid on another. Every
|
|
59
|
+
# fulfillment settles money, so all of them take the refund authority.
|
|
60
|
+
before_action :require_refund_permission!, only: :fulfill
|
|
52
61
|
|
|
53
62
|
# PATCH /api/v3/admin/orders/:order_id/exchanges/:id
|
|
54
63
|
#
|
|
@@ -186,7 +186,7 @@ module Spree
|
|
|
186
186
|
def delivery_method_for_create
|
|
187
187
|
return if create_params[:delivery_method_id].blank?
|
|
188
188
|
|
|
189
|
-
|
|
189
|
+
current_store.delivery_methods.accessible_by(current_ability, :show).find_by_prefix_id!(create_params[:delivery_method_id])
|
|
190
190
|
end
|
|
191
191
|
|
|
192
192
|
def source_fulfillment_for_create
|
|
@@ -28,10 +28,13 @@ module Spree
|
|
|
28
28
|
def create
|
|
29
29
|
with_order_lock do
|
|
30
30
|
payment_method = current_store.payment_methods.accessible_by(current_ability, :show).find_by_prefix_id!(params[:payment_method_id])
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
)
|
|
31
|
+
# The default is what the order still owes, which is nothing
|
|
32
|
+
# once store credit covers it — a zero payment is refused the
|
|
33
|
+
# same way a zero amount is.
|
|
34
|
+
amount = money_param(:amount, @parent.currency) || @parent.order_total_after_store_credit
|
|
35
|
+
raise Spree::Money::InvalidFormat.new('must be greater than 0', field: :amount) unless amount.positive?
|
|
36
|
+
|
|
37
|
+
@resource = @parent.payments.build(amount: amount, payment_method: payment_method)
|
|
35
38
|
|
|
36
39
|
if params[:source_id].present? && payment_method.source_required?
|
|
37
40
|
@resource.source = find_source!(payment_method, params[:source_id])
|
|
@@ -10,6 +10,7 @@ module Spree
|
|
|
10
10
|
# an amount.
|
|
11
11
|
class ReturnsController < BaseController
|
|
12
12
|
include Spree::Api::V3::Orders::ReturnActions
|
|
13
|
+
include Spree::Api::V3::Admin::RefundAuthorization
|
|
13
14
|
|
|
14
15
|
# Returns are a subject of the `orders` catalog resource (see
|
|
15
16
|
# Spree::PermissionConfiguration), so `read_orders`/`write_orders`
|
|
@@ -57,6 +58,7 @@ module Spree
|
|
|
57
58
|
end
|
|
58
59
|
|
|
59
60
|
before_action :set_resource, only: [:show, :update, :approve, :receive, :refund, :cancel]
|
|
61
|
+
before_action :require_refund_permission!, only: :refund
|
|
60
62
|
|
|
61
63
|
# PATCH /api/v3/admin/orders/:order_id/returns/:id
|
|
62
64
|
#
|
|
@@ -4,6 +4,7 @@ module Spree
|
|
|
4
4
|
module Admin
|
|
5
5
|
class OrdersController < ResourceController
|
|
6
6
|
include Spree::Api::V3::OrderLock
|
|
7
|
+
include Spree::Api::V3::Admin::RefundAuthorization
|
|
7
8
|
|
|
8
9
|
scoped_resource :orders
|
|
9
10
|
|
|
@@ -17,10 +18,12 @@ module Spree
|
|
|
17
18
|
request_params :order do |body|
|
|
18
19
|
body.external_references
|
|
19
20
|
body.attribute :email, :string, example: 'customer@example.com'
|
|
20
|
-
body.attribute :customer_id, :id
|
|
21
|
+
body.attribute :customer_id, :id,
|
|
22
|
+
nullable: true,
|
|
23
|
+
description: 'Null detaches the customer, leaving a guest order under its `email`.'
|
|
21
24
|
body.attribute :use_customer_default_address, :boolean,
|
|
22
25
|
only: :create,
|
|
23
|
-
description: "
|
|
26
|
+
description: "Copies the saved default billing and shipping addresses onto the order. Sent with `company_id`, it takes that company's defaults, inherited from its parent nodes when the company holds none of its own; otherwise it takes the customer's."
|
|
24
27
|
body.attribute :currency, :currency, example: 'USD'
|
|
25
28
|
body.attribute :market_id, :id
|
|
26
29
|
body.attribute :channel_id, :id, description: 'Defaults to the store primary channel when omitted.'
|
|
@@ -35,10 +38,10 @@ module Spree
|
|
|
35
38
|
description: "The `signed_id` of the buyer's purchase order, from `files.upload()` with `visibility: 'private'`. Null removes the document the order carries."
|
|
36
39
|
body.attribute :shipping_address_id, :id,
|
|
37
40
|
only: :create,
|
|
38
|
-
description:
|
|
41
|
+
description: "An existing address from the customer's address book, or from the address book of the company the order is for."
|
|
39
42
|
body.attribute :billing_address_id, :id,
|
|
40
43
|
only: :create,
|
|
41
|
-
description:
|
|
44
|
+
description: "An existing address from the customer's address book, or from the address book of the company the order is for."
|
|
42
45
|
body.attribute :preferred_stock_location_id, :id,
|
|
43
46
|
description: "Stock location to prefer for fulfillment. Order Routing's built-in preferred-location rule ranks it first; routing falls back to the next rule when the location can't cover the cart."
|
|
44
47
|
body.attribute :company_id, :id,
|
|
@@ -51,6 +54,8 @@ module Spree
|
|
|
51
54
|
body.attribute :tags, :array, of: :string
|
|
52
55
|
body.attribute :shipping_address, :object, shape: ADDRESS
|
|
53
56
|
body.attribute :billing_address, :object, shape: ADDRESS
|
|
57
|
+
body.attribute :use_shipping, :boolean,
|
|
58
|
+
description: 'When true, copies the shipping address to the billing address.'
|
|
54
59
|
body.attribute :items, :array,
|
|
55
60
|
description: 'Line items to upsert, keyed by variant. An entry sets that variant\'s quantity, creating the line item when the order has none; `quantity: 0` removes it. Variants you do not send are left alone.' do |item|
|
|
56
61
|
item.attribute :variant_id, :id, required: true
|
|
@@ -82,6 +87,7 @@ module Spree
|
|
|
82
87
|
|
|
83
88
|
skip_before_action :set_resource, only: [:index, :create]
|
|
84
89
|
before_action :set_resource, only: [:show, :update, :destroy, :complete, :cancel, :approve, :resend_confirmation, :resend_digital_links, :po_document]
|
|
90
|
+
before_action :require_refund_permission!, only: :cancel, if: -> { params[:refund_payments].to_b }
|
|
85
91
|
|
|
86
92
|
# POST /api/v3/admin/orders
|
|
87
93
|
def create
|
|
@@ -227,7 +233,9 @@ module Spree
|
|
|
227
233
|
# `fields` list names an order's attributes, and narrowing a group by
|
|
228
234
|
# it can drop the very keys that say it is one.
|
|
229
235
|
def serialize_completion(record)
|
|
230
|
-
|
|
236
|
+
# Read back lazily preloading, as #show is, so its fulfillments and
|
|
237
|
+
# their rows load once for all of them rather than per fulfillment.
|
|
238
|
+
return serialize_resource(Spree::Order.preload_associations_lazily.find(record.id)) unless record.is_a?(Spree::OrderGroup)
|
|
231
239
|
|
|
232
240
|
Spree.api.admin_order_group_serializer.new(completed_group(record), params: serializer_params).to_h
|
|
233
241
|
end
|
|
@@ -239,7 +247,7 @@ module Spree
|
|
|
239
247
|
:customer, :ship_address, :bill_address,
|
|
240
248
|
{ payments: %i[payment_method source] },
|
|
241
249
|
{ orders: [:seller, :market, :gift_card, :customer, { line_items: { variant: :prices } }] }
|
|
242
|
-
).find(group.id)
|
|
250
|
+
).preload_associations_lazily.find(group.id)
|
|
243
251
|
end
|
|
244
252
|
|
|
245
253
|
# Override scope — Order uses SingleStoreResource (for_store).
|
|
@@ -294,8 +302,12 @@ module Spree
|
|
|
294
302
|
# are loaded with their selected rate because the freight summary
|
|
295
303
|
# reads it — otherwise a page of orders costs a query per
|
|
296
304
|
# fulfillment for a field that is nil on every parcel order.
|
|
305
|
+
# Both addresses are named because they are two associations on
|
|
306
|
+
# one table, so a row that reads them separately costs a lookup
|
|
307
|
+
# each.
|
|
297
308
|
[:customer, :channel, :seller, :external_references, :cancel_reason,
|
|
298
309
|
:market, { fulfillments: %i[selected_delivery_rate external_references] },
|
|
310
|
+
:bill_address, :ship_address,
|
|
299
311
|
{ line_items: [:external_references, { variant: :prices }, { price_list: :catalog }] },
|
|
300
312
|
{ po_document_attachment: :blob }]
|
|
301
313
|
end
|
|
@@ -343,7 +355,9 @@ module Spree
|
|
|
343
355
|
# Named rather than read from the action, since an upsert reaches
|
|
344
356
|
# update from the create action.
|
|
345
357
|
def order_update_params
|
|
346
|
-
resolve_company_param(normalize_params(params.permit(*request_params_schema.permit_list(:update))))
|
|
358
|
+
permitted = resolve_company_param(normalize_params(params.permit(*request_params_schema.permit_list(:update))))
|
|
359
|
+
permitted[:customer_id] = resolve_customer&.id if permitted.key?(:customer_id)
|
|
360
|
+
permitted
|
|
347
361
|
end
|
|
348
362
|
|
|
349
363
|
# An incidental lookup like any other: resolved through the store so
|
|
@@ -27,9 +27,8 @@ module Spree
|
|
|
27
27
|
body.attribute :length, :number, nullable: true
|
|
28
28
|
body.attribute :width, :number, nullable: true
|
|
29
29
|
body.attribute :height, :number, nullable: true
|
|
30
|
-
body.attribute :dimensions_unit, :
|
|
30
|
+
body.attribute :dimensions_unit, :dimension_unit,
|
|
31
31
|
nullable: true,
|
|
32
|
-
choices: Spree::Variant::DIMENSION_UNITS,
|
|
33
32
|
example: 'cm',
|
|
34
33
|
description: "Unit the three dimensions are in; falls back to the store's unit system."
|
|
35
34
|
body.attribute :weight, :number,
|
|
@@ -38,7 +37,7 @@ module Spree
|
|
|
38
37
|
body.attribute :max_weight, :number,
|
|
39
38
|
nullable: true,
|
|
40
39
|
description: 'What the package can hold, not the same as its own weight.'
|
|
41
|
-
body.attribute :weight_unit, :
|
|
40
|
+
body.attribute :weight_unit, :weight_unit, nullable: true, example: 'kg'
|
|
42
41
|
body.attribute :default, :boolean,
|
|
43
42
|
description: 'The box every parcel quote is built on. Setting this demotes whichever row held it.'
|
|
44
43
|
body.attribute :metadata, :hash
|
|
@@ -36,7 +36,9 @@ module Spree
|
|
|
36
36
|
def create
|
|
37
37
|
user = Spree.admin_user_class.find_by(email: params[:email])
|
|
38
38
|
|
|
39
|
-
|
|
39
|
+
# A deactivated account is treated as no account: no email is sent,
|
|
40
|
+
# and the response is the same 202.
|
|
41
|
+
if user && !account_deactivated?(user)
|
|
40
42
|
event_name = 'admin_user.password_reset_requested'
|
|
41
43
|
payload = user.event_payload_for(
|
|
42
44
|
event_name,
|
|
@@ -62,6 +64,10 @@ module Spree
|
|
|
62
64
|
)
|
|
63
65
|
end
|
|
64
66
|
|
|
67
|
+
# A link sent before the account was switched off must not change
|
|
68
|
+
# its password or sign it in.
|
|
69
|
+
return render_account_deactivated if account_deactivated?(user)
|
|
70
|
+
|
|
65
71
|
if user.update(password: params[:password], password_confirmation: params[:password_confirmation])
|
|
66
72
|
user.publish_event('admin_user.password_reset')
|
|
67
73
|
# A password reset must kill every existing session — a stolen
|
|
@@ -123,8 +123,26 @@ module Spree
|
|
|
123
123
|
[:price_adjustment_tiers]
|
|
124
124
|
end
|
|
125
125
|
|
|
126
|
+
def serializer_params
|
|
127
|
+
return super unless action_name == 'index'
|
|
128
|
+
|
|
129
|
+
super.merge(page_counts)
|
|
130
|
+
end
|
|
131
|
+
|
|
126
132
|
private
|
|
127
133
|
|
|
134
|
+
# One grouped count per figure for the whole page rather than two
|
|
135
|
+
# queries per list.
|
|
136
|
+
def page_counts
|
|
137
|
+
@page_counts ||= begin
|
|
138
|
+
price_list_ids = collection.map(&:id)
|
|
139
|
+
{
|
|
140
|
+
products_counts: Spree::PriceList.products_count_by_list(price_list_ids),
|
|
141
|
+
prices_counts: Spree::PriceList.prices_count_by_list(price_list_ids)
|
|
142
|
+
}
|
|
143
|
+
end
|
|
144
|
+
end
|
|
145
|
+
|
|
128
146
|
# Loads the record without the action-derived authorization
|
|
129
147
|
# `set_resource` runs (which would check `:activate` /
|
|
130
148
|
# `:deactivate` — actions that abilities don't grant). The
|
|
@@ -76,9 +76,21 @@ module Spree
|
|
|
76
76
|
def permitted_params
|
|
77
77
|
attrs = params.permit(*permitted_attributes)
|
|
78
78
|
attrs = scope_delivery_profile(attrs)
|
|
79
|
+
attrs = scope_option_type_ids(attrs)
|
|
79
80
|
scope_category_ids(attrs)
|
|
80
81
|
end
|
|
81
82
|
|
|
83
|
+
# `option_type_ids=` resolves prefixed ids with no store scoping, so a
|
|
84
|
+
# type could otherwise seed another store's option types onto every
|
|
85
|
+
# product it is applied to.
|
|
86
|
+
def scope_option_type_ids(attrs)
|
|
87
|
+
return attrs if attrs[:option_type_ids].blank?
|
|
88
|
+
|
|
89
|
+
option_type_ids = decode_ids(attrs[:option_type_ids], Spree::OptionType)
|
|
90
|
+
attrs[:option_type_ids] = Spree::OptionType.for_store(current_store).where(id: option_type_ids).pluck(:id)
|
|
91
|
+
attrs
|
|
92
|
+
end
|
|
93
|
+
|
|
82
94
|
# The template profile must belong to this store — a cross-store id
|
|
83
95
|
# 404s instead of silently linking.
|
|
84
96
|
def scope_delivery_profile(attrs)
|
|
@@ -93,7 +105,6 @@ module Spree
|
|
|
93
105
|
# `category_ids=` resolves prefixed ids with no store scoping, so a
|
|
94
106
|
# type in this store could otherwise be pointed at another store's
|
|
95
107
|
# categories. Mirrors ProductsController#apply_categories.
|
|
96
|
-
# (OptionType is global — nothing to scope there.)
|
|
97
108
|
def scope_category_ids(attrs)
|
|
98
109
|
return attrs if attrs[:category_ids].blank?
|
|
99
110
|
|
|
@@ -4,61 +4,19 @@ module Spree
|
|
|
4
4
|
module Admin
|
|
5
5
|
module Products
|
|
6
6
|
class DigitalAssetsController < ResourceController
|
|
7
|
+
include Spree::Api::V3::DigitalAssetsResource
|
|
8
|
+
|
|
7
9
|
scoped_resource :products
|
|
8
10
|
|
|
9
11
|
request_params :digital_asset do |body|
|
|
10
|
-
|
|
11
|
-
# separately (+attach_uploaded_file+).
|
|
12
|
-
body.attribute :signed_id, :string,
|
|
13
|
-
permit: false,
|
|
14
|
-
description: "A `signed_id` from `files.upload()` with `visibility: 'private'`. Required for an uploaded file, omitted for a provider-backed asset. On update it replaces the file for everyone who already bought it."
|
|
15
|
-
# Resolved through the parent rather than assigned, so an asset
|
|
16
|
-
# can't be pointed at a variant of another product or store.
|
|
17
|
-
body.attribute :variant_id, :id,
|
|
18
|
-
permit: false,
|
|
19
|
-
description: "Defaults to the product's default variant when omitted."
|
|
12
|
+
body.include_attributes Spree::Api::V3::DigitalAssetRequestSchema
|
|
20
13
|
# The model validates it names a registered provider.
|
|
21
14
|
body.attribute :provider_type, :string,
|
|
22
|
-
description: 'A provider shorthand from the providers endpoint. Omit for an uploaded file
|
|
15
|
+
description: 'A provider shorthand from the providers endpoint. Omit for an uploaded file; a provider-backed asset sends no `signed_id`.'
|
|
23
16
|
# Its keys are declared by the provider, so it lands verbatim
|
|
24
17
|
# under one key in the asset's metadata.
|
|
25
18
|
body.attribute :provider_settings, :hash,
|
|
26
19
|
description: "Values for the provider's settings schema, keyed by field name."
|
|
27
|
-
body.attribute :authorized_clicks, :integer,
|
|
28
|
-
nullable: true,
|
|
29
|
-
description: "Null means the store's download settings apply."
|
|
30
|
-
body.attribute :authorized_days, :integer,
|
|
31
|
-
nullable: true,
|
|
32
|
-
description: "Null means the store's download settings apply."
|
|
33
|
-
end
|
|
34
|
-
|
|
35
|
-
# PATCH /api/v3/admin/products/:product_id/digital_assets/:id
|
|
36
|
-
#
|
|
37
|
-
# Replacing the file keeps every already-issued link working, since
|
|
38
|
-
# links resolve through the asset rather than the blob.
|
|
39
|
-
def update
|
|
40
|
-
return render_private_storage_error unless uploaded_blob_private?
|
|
41
|
-
|
|
42
|
-
# The asset already exists, so attaching a replacement blob writes
|
|
43
|
-
# to the database immediately, while the update below renders a 422
|
|
44
|
-
# without raising when it fails. Wrap both so a failed update rolls
|
|
45
|
-
# the new attachment back rather than leaving the file swapped but
|
|
46
|
-
# the record unchanged.
|
|
47
|
-
model_class.transaction do
|
|
48
|
-
@resource.variant = requested_variant if params[:variant_id].present?
|
|
49
|
-
attach_uploaded_file(@resource)
|
|
50
|
-
|
|
51
|
-
super
|
|
52
|
-
|
|
53
|
-
raise ActiveRecord::Rollback if @resource.errors.any?
|
|
54
|
-
end
|
|
55
|
-
end
|
|
56
|
-
|
|
57
|
-
# POST /api/v3/admin/products/:product_id/digital_assets
|
|
58
|
-
def create
|
|
59
|
-
return render_private_storage_error unless uploaded_blob_private?
|
|
60
|
-
|
|
61
|
-
super
|
|
62
20
|
end
|
|
63
21
|
|
|
64
22
|
# GET /api/v3/admin/products/:product_id/digital_assets/providers
|
|
@@ -83,75 +41,13 @@ module Spree
|
|
|
83
41
|
|
|
84
42
|
protected
|
|
85
43
|
|
|
86
|
-
def model_class
|
|
87
|
-
Spree::DigitalAsset
|
|
88
|
-
end
|
|
89
|
-
|
|
90
44
|
def serializer_class
|
|
91
45
|
Spree.api.admin_digital_asset_serializer
|
|
92
46
|
end
|
|
93
47
|
|
|
94
48
|
def set_parent
|
|
95
49
|
@parent = current_store.products.find_by_prefix_id!(params[:product_id])
|
|
96
|
-
|
|
97
|
-
end
|
|
98
|
-
|
|
99
|
-
# The parent association reaches assets through `variants`, which
|
|
100
|
-
# carries an ORDER BY on the variants table. Combined with the
|
|
101
|
-
# collection's DISTINCT, PostgreSQL rejects the query outright
|
|
102
|
-
# (ordering columns must appear in the select list), so order by
|
|
103
|
-
# the assets' own table instead. SQLite accepts either form, so
|
|
104
|
-
# this only fails on a real deployment.
|
|
105
|
-
def scope
|
|
106
|
-
super.reorder(created_at: :asc)
|
|
107
|
-
end
|
|
108
|
-
|
|
109
|
-
def scope_includes
|
|
110
|
-
[:variant, { attachment_attachment: :blob }]
|
|
111
|
-
end
|
|
112
|
-
|
|
113
|
-
def permitted_params
|
|
114
|
-
params.permit(*declared_permitted_attributes)
|
|
115
|
-
end
|
|
116
|
-
|
|
117
|
-
private
|
|
118
|
-
|
|
119
|
-
# A product without explicit variants keeps its file on the default
|
|
120
|
-
# variant, mirroring how the rest of the admin treats such products.
|
|
121
|
-
def build_resource
|
|
122
|
-
digital_asset = super
|
|
123
|
-
digital_asset.variant = requested_variant || @parent.default_variant
|
|
124
|
-
attach_uploaded_file(digital_asset)
|
|
125
|
-
digital_asset
|
|
126
|
-
end
|
|
127
|
-
|
|
128
|
-
# Scoped to the parent, not merely to the store: an asset reached
|
|
129
|
-
# through a product's route has no business pointing at another
|
|
130
|
-
# product's variant. An id from anywhere else is a 404.
|
|
131
|
-
def requested_variant
|
|
132
|
-
return if params[:variant_id].blank?
|
|
133
|
-
|
|
134
|
-
@parent.variants.find_by_prefix_id!(params[:variant_id])
|
|
135
|
-
end
|
|
136
|
-
|
|
137
|
-
def attach_uploaded_file(digital_asset)
|
|
138
|
-
return if params[:signed_id].blank?
|
|
139
|
-
|
|
140
|
-
digital_asset.attachment.attach(params[:signed_id])
|
|
141
|
-
end
|
|
142
|
-
|
|
143
|
-
# Attaching never moves a blob between storage services, so a file
|
|
144
|
-
# uploaded to the public bucket would stay publicly readable while
|
|
145
|
-
# looking attached. Refuse it rather than storing it wrongly.
|
|
146
|
-
def uploaded_blob_private?
|
|
147
|
-
return true if params[:signed_id].blank?
|
|
148
|
-
|
|
149
|
-
blob = ActiveStorage::Blob.find_signed(params[:signed_id])
|
|
150
|
-
blob.present? && blob.service_name.to_s == Spree.private_storage_service_name.to_s
|
|
151
|
-
end
|
|
152
|
-
|
|
153
|
-
def render_private_storage_error
|
|
154
|
-
render_validation_error(I18n.t('spree.digital_assets_attachment_must_be_private'))
|
|
50
|
+
authorize_parent!(@parent)
|
|
155
51
|
end
|
|
156
52
|
end
|
|
157
53
|
end
|
|
@@ -7,11 +7,10 @@ module Spree
|
|
|
7
7
|
scoped_resource :products
|
|
8
8
|
|
|
9
9
|
request_params :variant do |body|
|
|
10
|
+
body.extends Spree::Api::V3::Admin::VariantRequestSchema
|
|
10
11
|
body.external_references
|
|
11
|
-
body.
|
|
12
|
-
body.
|
|
13
|
-
required: true, on: :create,
|
|
14
|
-
description: 'One pair per option type the variant takes part in (size, color). Option types and values are created when missing. On update, omit to leave the option values untouched.'
|
|
12
|
+
body.require_attributes :options, on: :create
|
|
13
|
+
body.describe_attribute :options, 'One pair per option type the variant takes part in (size, color). Option types and values are created when missing. On update, omit to leave the option values untouched.'
|
|
15
14
|
end
|
|
16
15
|
|
|
17
16
|
protected
|
|
@@ -29,8 +28,11 @@ module Spree
|
|
|
29
28
|
authorize!(:show, @parent)
|
|
30
29
|
end
|
|
31
30
|
|
|
31
|
+
# `active_stock_reservations` is what lets the quantifier count
|
|
32
|
+
# held stock in memory; without it the serializer's availability
|
|
33
|
+
# column sums reservations once per variant.
|
|
32
34
|
def scope_includes
|
|
33
|
-
[:prices, stock_levels: :stock_location]
|
|
35
|
+
[:prices, { stock_levels: [:stock_location, :active_stock_reservations] }]
|
|
34
36
|
end
|
|
35
37
|
|
|
36
38
|
def create_workflow
|
|
@@ -77,10 +77,8 @@ module Spree
|
|
|
77
77
|
body.attribute :variants, :array,
|
|
78
78
|
as: 'ProductVariantParams',
|
|
79
79
|
description: 'Every purchasable attribute (sku, prices, stock, weight, dimensions) lives on variants. An entry without `options` upserts onto the default variant of a simple product; pass `id` to update an existing variant.' do |variant|
|
|
80
|
+
variant.extends Spree::Api::V3::Admin::VariantRequestSchema
|
|
80
81
|
variant.attribute :id, :id
|
|
81
|
-
variant.include_attributes Spree::Api::V3::VariantRequestSchema
|
|
82
|
-
variant.attribute :options, :array, of: Spree::Api::V3::VariantOptionRequestSchema,
|
|
83
|
-
description: 'One pair per option type the variant takes part in (size, color). Option types and values are created when missing.'
|
|
84
82
|
end
|
|
85
83
|
end
|
|
86
84
|
|
|
@@ -221,7 +219,7 @@ module Spree
|
|
|
221
219
|
|
|
222
220
|
channels = scoped_channels
|
|
223
221
|
product_ids = bulk_collection.distinct.ids
|
|
224
|
-
channels.
|
|
222
|
+
channels.add_products(product_ids)
|
|
225
223
|
|
|
226
224
|
render json: { product_count: product_ids.size, channel_count: channels.size }
|
|
227
225
|
end
|
|
@@ -233,7 +231,7 @@ module Spree
|
|
|
233
231
|
|
|
234
232
|
channels = scoped_channels
|
|
235
233
|
product_ids = bulk_collection.distinct.ids
|
|
236
|
-
removed = channels.
|
|
234
|
+
removed = channels.remove_products(product_ids)
|
|
237
235
|
|
|
238
236
|
render json: { product_count: product_ids.size, channel_count: channels.size, removed: removed }
|
|
239
237
|
end
|
|
@@ -291,6 +289,18 @@ module Spree
|
|
|
291
289
|
Spree.product_update_workflow
|
|
292
290
|
end
|
|
293
291
|
|
|
292
|
+
def nested_permitted_models
|
|
293
|
+
{ media: Spree::Media, variants: Spree::Variant }
|
|
294
|
+
end
|
|
295
|
+
|
|
296
|
+
# A written product is serialized from a fresh read: lazy preloading
|
|
297
|
+
# does not reach records a write built, so the instance the workflow
|
|
298
|
+
# returned would query once per variant.
|
|
299
|
+
def serialize_resource(resource)
|
|
300
|
+
resource = scope.where(id: resource.id).preload_associations_lazily.take! if resource.lazy_preload_context.nil?
|
|
301
|
+
super
|
|
302
|
+
end
|
|
303
|
+
|
|
294
304
|
# Use SearchProvider::Database for collection to handle price/best_selling
|
|
295
305
|
# sorting correctly (counts before sorting, avoiding PG/Mobility issues).
|
|
296
306
|
def collection
|
|
@@ -321,7 +331,24 @@ module Spree
|
|
|
321
331
|
end
|
|
322
332
|
|
|
323
333
|
def permitted_params
|
|
324
|
-
params.permit(*permitted_attributes).tap
|
|
334
|
+
params.permit(*permitted_attributes).tap do |attrs|
|
|
335
|
+
attrs.delete(:status) if review_status?(attrs[:status])
|
|
336
|
+
scope_store_references(attrs)
|
|
337
|
+
end
|
|
338
|
+
end
|
|
339
|
+
|
|
340
|
+
# Read through the store here because the model only checks that the
|
|
341
|
+
# record exists: another store's type or tax category would otherwise
|
|
342
|
+
# be accepted, and the type would seed its option types onto the
|
|
343
|
+
# product.
|
|
344
|
+
STORE_REFERENCES = { product_type_id: :product_types, tax_category_id: :tax_categories }.freeze
|
|
345
|
+
|
|
346
|
+
def scope_store_references(attrs)
|
|
347
|
+
STORE_REFERENCES.each do |key, association|
|
|
348
|
+
next if attrs[key].blank?
|
|
349
|
+
|
|
350
|
+
attrs[key] = current_store.public_send(association).find_by_param!(attrs[key]).id
|
|
351
|
+
end
|
|
325
352
|
end
|
|
326
353
|
|
|
327
354
|
# A review status is an outcome, not a value to assign: `proposed`
|
|
@@ -30,7 +30,8 @@ module Spree
|
|
|
30
30
|
# because another went through: the payout row exists and is
|
|
31
31
|
# failed or unresolved, and an operator reading "settled" would
|
|
32
32
|
# not know to go looking for it.
|
|
33
|
-
|
|
33
|
+
transfer_counts = Spree::SellerTransfer.where(payout_id: settled.map(&:id)).group(:payout_id).count
|
|
34
|
+
body = { data: settled.map { |payout| serialize_payout(payout, transfer_counts) } }
|
|
34
35
|
body[:meta] = { failures: failures } if failures.any?
|
|
35
36
|
|
|
36
37
|
render json: body, status: :created
|
|
@@ -96,8 +97,8 @@ module Spree
|
|
|
96
97
|
@seller.payable_currencies
|
|
97
98
|
end
|
|
98
99
|
|
|
99
|
-
def serialize_payout(payout)
|
|
100
|
-
Spree.api.admin_seller_payout_serializer.new(payout, params: { store: current_store }).to_h
|
|
100
|
+
def serialize_payout(payout, transfer_counts)
|
|
101
|
+
Spree.api.admin_seller_payout_serializer.new(payout, params: { store: current_store, transfer_counts: transfer_counts }).to_h
|
|
101
102
|
end
|
|
102
103
|
end
|
|
103
104
|
end
|
|
@@ -202,9 +202,10 @@ module Spree
|
|
|
202
202
|
end
|
|
203
203
|
|
|
204
204
|
# The listing and the detail page both read a transfer's totals off
|
|
205
|
-
# its lines.
|
|
205
|
+
# its lines. Both warehouses come from one table, so their external
|
|
206
|
+
# references are loaded here rather than once per warehouse.
|
|
206
207
|
def scope_includes
|
|
207
|
-
[line_includes]
|
|
208
|
+
[{ source_location: :external_references, destination_location: :external_references }, line_includes]
|
|
208
209
|
end
|
|
209
210
|
end
|
|
210
211
|
end
|