spree_api 5.4.3 → 5.5.0.rc1
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/Rakefile +19 -0
- data/app/controllers/concerns/spree/api/v3/admin/auth_cookies.rb +62 -0
- data/app/controllers/concerns/spree/api/v3/admin/subclassed_resource.rb +149 -0
- data/app/controllers/concerns/spree/api/v3/admin_authentication.rb +54 -0
- data/app/controllers/concerns/spree/api/v3/bulk_operations.rb +103 -0
- data/app/controllers/concerns/spree/api/v3/channel_resolution.rb +60 -0
- data/app/controllers/concerns/spree/api/v3/error_handler.rb +4 -0
- data/app/controllers/concerns/spree/api/v3/params_normalizer.rb +84 -0
- data/app/controllers/concerns/spree/api/v3/scoped_authorization.rb +88 -0
- data/app/controllers/concerns/spree/api/v3/store/search_provider_support.rb +35 -1
- data/app/controllers/spree/api/v3/admin/admin_users_controller.rb +97 -0
- data/app/controllers/spree/api/v3/admin/allowed_origins_controller.rb +25 -0
- data/app/controllers/spree/api/v3/admin/api_keys_controller.rb +55 -0
- data/app/controllers/spree/api/v3/admin/auth_controller.rb +134 -0
- data/app/controllers/spree/api/v3/admin/base_controller.rb +3 -17
- data/app/controllers/spree/api/v3/admin/categories_controller.rb +25 -0
- data/app/controllers/spree/api/v3/admin/channels_controller.rb +65 -0
- data/app/controllers/spree/api/v3/admin/countries_controller.rb +38 -0
- data/app/controllers/spree/api/v3/admin/coupon_codes_controller.rb +33 -0
- data/app/controllers/spree/api/v3/admin/custom_field_definitions_controller.rb +34 -0
- data/app/controllers/spree/api/v3/admin/custom_fields_controller.rb +108 -0
- data/app/controllers/spree/api/v3/admin/customer_groups_controller.rb +31 -0
- data/app/controllers/spree/api/v3/admin/customers/addresses_controller.rb +88 -0
- data/app/controllers/spree/api/v3/admin/customers/credit_cards_controller.rb +31 -0
- data/app/controllers/spree/api/v3/admin/customers/store_credits_controller.rb +93 -0
- data/app/controllers/spree/api/v3/admin/customers_controller.rb +119 -0
- data/app/controllers/spree/api/v3/admin/dashboard_controller.rb +44 -0
- data/app/controllers/spree/api/v3/admin/direct_uploads_controller.rb +40 -0
- data/app/controllers/spree/api/v3/admin/exports_controller.rb +89 -0
- data/app/controllers/spree/api/v3/admin/gift_card_batches_controller.rb +31 -0
- data/app/controllers/spree/api/v3/admin/gift_cards_controller.rb +33 -0
- data/app/controllers/spree/api/v3/admin/invitation_acceptances_controller.rb +138 -0
- data/app/controllers/spree/api/v3/admin/invitations_controller.rb +70 -0
- data/app/controllers/spree/api/v3/admin/markets_controller.rb +42 -0
- data/app/controllers/spree/api/v3/admin/me_controller.rb +69 -0
- data/app/controllers/spree/api/v3/admin/media_controller.rb +119 -0
- data/app/controllers/spree/api/v3/admin/option_types_controller.rb +34 -0
- data/app/controllers/spree/api/v3/admin/orders/adjustments_controller.rb +27 -0
- data/app/controllers/spree/api/v3/admin/orders/base_controller.rb +26 -0
- data/app/controllers/spree/api/v3/admin/orders/fulfillments_controller.rb +104 -0
- data/app/controllers/spree/api/v3/admin/orders/gift_cards_controller.rb +79 -0
- data/app/controllers/spree/api/v3/admin/orders/items_controller.rb +92 -0
- data/app/controllers/spree/api/v3/admin/orders/payments_controller.rb +90 -0
- data/app/controllers/spree/api/v3/admin/orders/refunds_controller.rb +53 -0
- data/app/controllers/spree/api/v3/admin/orders/store_credits_controller.rb +59 -0
- data/app/controllers/spree/api/v3/admin/orders_controller.rb +190 -0
- data/app/controllers/spree/api/v3/admin/payment_methods_controller.rb +73 -0
- data/app/controllers/spree/api/v3/admin/price_lists_controller.rb +156 -0
- data/app/controllers/spree/api/v3/admin/prices_controller.rb +129 -0
- data/app/controllers/spree/api/v3/admin/products/variants_controller.rb +48 -0
- data/app/controllers/spree/api/v3/admin/products_controller.rb +237 -0
- data/app/controllers/spree/api/v3/admin/promotion_actions_controller.rb +78 -0
- data/app/controllers/spree/api/v3/admin/promotion_rules_controller.rb +56 -0
- data/app/controllers/spree/api/v3/admin/promotions_controller.rb +78 -0
- data/app/controllers/spree/api/v3/admin/resource_controller.rb +29 -11
- data/app/controllers/spree/api/v3/admin/roles_controller.rb +29 -0
- data/app/controllers/spree/api/v3/admin/stock_items_controller.rb +35 -0
- data/app/controllers/spree/api/v3/admin/stock_locations_controller.rb +36 -0
- data/app/controllers/spree/api/v3/admin/stock_reservations_controller.rb +29 -0
- data/app/controllers/spree/api/v3/admin/stock_transfers_controller.rb +75 -0
- data/app/controllers/spree/api/v3/admin/store_controller.rb +53 -0
- data/app/controllers/spree/api/v3/admin/store_credit_categories_controller.rb +21 -0
- data/app/controllers/spree/api/v3/admin/tags_controller.rb +51 -0
- data/app/controllers/spree/api/v3/admin/tax_categories_controller.rb +21 -0
- data/app/controllers/spree/api/v3/admin/variants_controller.rb +33 -0
- data/app/controllers/spree/api/v3/admin/webhook_deliveries_controller.rb +49 -0
- data/app/controllers/spree/api/v3/admin/webhook_endpoints_controller.rb +75 -0
- data/app/controllers/spree/api/v3/resource_controller.rb +90 -8
- data/app/controllers/spree/api/v3/store/auth_controller.rb +8 -28
- data/app/controllers/spree/api/v3/store/base_controller.rb +6 -0
- data/app/controllers/spree/api/v3/store/carts_controller.rb +1 -0
- data/app/controllers/spree/api/v3/store/customers_controller.rb +6 -0
- data/app/controllers/spree/api/v3/store/newsletter_subscribers_controller.rb +77 -0
- data/app/controllers/spree/api/v3/store/products/filters_controller.rb +2 -2
- data/app/controllers/spree/api/v3/store/products_controller.rb +3 -3
- data/app/controllers/spree/api/v3/store/resource_controller.rb +10 -2
- data/app/jobs/spree/webhook_delivery_job.rb +5 -0
- data/app/models/spree/api_key_ability.rb +16 -0
- data/app/serializers/spree/api/v3/admin/address_serializer.rb +2 -6
- data/app/serializers/spree/api/v3/admin/adjustment_serializer.rb +3 -15
- data/app/serializers/spree/api/v3/admin/admin_user_serializer.rb +19 -3
- data/app/serializers/spree/api/v3/admin/allowed_origin_serializer.rb +2 -6
- data/app/serializers/spree/api/v3/admin/api_key_serializer.rb +42 -0
- data/app/serializers/spree/api/v3/admin/category_serializer.rb +4 -3
- data/app/serializers/spree/api/v3/admin/channel_serializer.rb +15 -0
- data/app/serializers/spree/api/v3/admin/country_serializer.rb +1 -1
- data/app/serializers/spree/api/v3/admin/coupon_code_serializer.rb +30 -0
- data/app/serializers/spree/api/v3/admin/credit_card_serializer.rb +4 -2
- data/app/serializers/spree/api/v3/admin/custom_field_definition_serializer.rb +21 -0
- data/app/serializers/spree/api/v3/admin/custom_field_serializer.rb +8 -3
- data/app/serializers/spree/api/v3/admin/customer_group_serializer.rb +27 -0
- data/app/serializers/spree/api/v3/admin/customer_serializer.rb +58 -2
- data/app/serializers/spree/api/v3/admin/dashboard_analytics_serializer.rb +143 -0
- data/app/serializers/spree/api/v3/admin/export_serializer.rb +40 -0
- data/app/serializers/spree/api/v3/admin/fulfillment_serializer.rb +2 -6
- data/app/serializers/spree/api/v3/admin/{asset_serializer.rb → gift_card_batch_serializer.rb} +1 -1
- data/app/serializers/spree/api/v3/admin/gift_card_serializer.rb +39 -4
- data/app/serializers/spree/api/v3/admin/invitation_serializer.rb +64 -0
- data/app/serializers/spree/api/v3/admin/line_item_serializer.rb +4 -16
- data/app/serializers/spree/api/v3/admin/media_serializer.rb +24 -2
- data/app/serializers/spree/api/v3/admin/option_type_serializer.rb +4 -1
- data/app/serializers/spree/api/v3/admin/option_value_serializer.rb +4 -1
- data/app/serializers/spree/api/v3/admin/order_serializer.rb +21 -6
- data/app/serializers/spree/api/v3/admin/payment_method_serializer.rb +11 -2
- data/app/serializers/spree/api/v3/admin/payment_serializer.rb +2 -6
- data/app/serializers/spree/api/v3/admin/payment_source_serializer.rb +4 -1
- data/app/serializers/spree/api/v3/admin/price_list_serializer.rb +51 -0
- data/app/serializers/spree/api/v3/admin/price_rule_serializer.rb +55 -0
- data/app/serializers/spree/api/v3/admin/price_serializer.rb +4 -0
- data/app/serializers/spree/api/v3/admin/product_publication_serializer.rb +11 -0
- data/app/serializers/spree/api/v3/admin/product_serializer.rb +34 -10
- data/app/serializers/spree/api/v3/admin/promotion_action_serializer.rb +71 -0
- data/app/serializers/spree/api/v3/admin/promotion_rule_serializer.rb +85 -0
- data/app/serializers/spree/api/v3/admin/promotion_serializer.rb +41 -0
- data/app/serializers/spree/api/v3/admin/refund_serializer.rb +4 -2
- data/app/serializers/spree/api/v3/admin/role_serializer.rb +17 -0
- data/app/serializers/spree/api/v3/admin/stock_item_serializer.rb +16 -1
- data/app/serializers/spree/api/v3/admin/stock_location_serializer.rb +11 -2
- data/app/serializers/spree/api/v3/admin/stock_reservation_serializer.rb +46 -0
- data/app/serializers/spree/api/v3/admin/stock_transfer_serializer.rb +37 -0
- data/app/serializers/spree/api/v3/admin/store_credit_category_serializer.rb +19 -0
- data/app/serializers/spree/api/v3/admin/store_credit_serializer.rb +11 -5
- data/app/serializers/spree/api/v3/admin/store_serializer.rb +55 -0
- data/app/serializers/spree/api/v3/admin/tax_category_serializer.rb +4 -2
- data/app/serializers/spree/api/v3/admin/variant_serializer.rb +37 -6
- data/app/serializers/spree/api/v3/admin/webhook_delivery_serializer.rb +45 -0
- data/app/serializers/spree/api/v3/admin/webhook_endpoint_serializer.rb +69 -0
- data/app/serializers/spree/api/v3/channel_serializer.rb +14 -0
- data/app/serializers/spree/api/v3/custom_field_serializer.rb +9 -10
- data/app/serializers/spree/api/v3/customer_serializer.rb +5 -0
- data/app/serializers/spree/api/v3/market_serializer.rb +2 -1
- data/app/serializers/spree/api/v3/media_serializer.rb +8 -6
- data/app/serializers/spree/api/v3/order_serializer.rb +6 -1
- data/app/serializers/spree/api/v3/payment_method_serializer.rb +11 -2
- data/app/serializers/spree/api/v3/product_publication_serializer.rb +22 -0
- data/app/serializers/spree/api/v3/product_serializer.rb +6 -1
- data/app/serializers/spree/api/v3/stock_reservation_serializer.rb +10 -0
- data/config/locales/en.yml +2 -0
- data/config/routes.rb +235 -1
- data/lib/spree/api/configuration.rb +2 -2
- data/lib/spree/api/dependencies.rb +25 -1
- data/lib/spree/api/openapi/path_sorter.rb +126 -0
- data/lib/spree/api/openapi/schema_helper.rb +185 -6
- metadata +96 -8
- data/app/serializers/spree/api/v3/admin/shipping_category_serializer.rb +0 -14
|
@@ -5,10 +5,11 @@ module Spree
|
|
|
5
5
|
# Admin API Category Serializer
|
|
6
6
|
# Full category data including admin-only fields
|
|
7
7
|
class CategorySerializer < V3::CategorySerializer
|
|
8
|
-
typelize lft: :number, rgt: :number
|
|
8
|
+
typelize pretty_name: :string, lft: :number, rgt: :number, sort_order: :string,
|
|
9
|
+
metadata: 'Record<string, unknown>'
|
|
9
10
|
|
|
10
|
-
#
|
|
11
|
-
attributes :lft, :rgt, created_at: :iso8601, updated_at: :iso8601
|
|
11
|
+
# Admin-only attributes
|
|
12
|
+
attributes :metadata, :pretty_name, :lft, :rgt, created_at: :iso8601, updated_at: :iso8601
|
|
12
13
|
|
|
13
14
|
# Override inherited associations to use admin serializers
|
|
14
15
|
one :parent,
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
module Spree
|
|
2
|
+
module Api
|
|
3
|
+
module V3
|
|
4
|
+
module Admin
|
|
5
|
+
class ChannelSerializer < V3::ChannelSerializer
|
|
6
|
+
typelize store_id: :string,
|
|
7
|
+
preferred_order_routing_strategy: [:string, nullable: true]
|
|
8
|
+
|
|
9
|
+
attributes :preferred_order_routing_strategy,
|
|
10
|
+
created_at: :iso8601, updated_at: :iso8601
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Spree
|
|
4
|
+
module Api
|
|
5
|
+
module V3
|
|
6
|
+
module Admin
|
|
7
|
+
# Coupon codes belong to multi-code promotions. Read-only here:
|
|
8
|
+
# codes are generated server-side based on the promotion's
|
|
9
|
+
# `code_prefix` + `number_of_codes`.
|
|
10
|
+
class CouponCodeSerializer < BaseSerializer
|
|
11
|
+
typelize code: :string,
|
|
12
|
+
state: [:string, nullable: true],
|
|
13
|
+
promotion_id: :string,
|
|
14
|
+
order_id: [:string, nullable: true]
|
|
15
|
+
|
|
16
|
+
attributes :code, :state,
|
|
17
|
+
created_at: :iso8601, updated_at: :iso8601
|
|
18
|
+
|
|
19
|
+
attribute :promotion_id do |coupon|
|
|
20
|
+
coupon.promotion&.prefixed_id
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
attribute :order_id do |coupon|
|
|
24
|
+
coupon.order&.prefixed_id
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
@@ -4,7 +4,8 @@ module Spree
|
|
|
4
4
|
module Admin
|
|
5
5
|
class CreditCardSerializer < V3::CreditCardSerializer
|
|
6
6
|
typelize customer_id: [:string, nullable: true],
|
|
7
|
-
payment_method_id: [:string, nullable: true]
|
|
7
|
+
payment_method_id: [:string, nullable: true],
|
|
8
|
+
metadata: 'Record<string, unknown>'
|
|
8
9
|
|
|
9
10
|
attribute :customer_id do |credit_card|
|
|
10
11
|
credit_card.user&.prefixed_id
|
|
@@ -14,7 +15,8 @@ module Spree
|
|
|
14
15
|
credit_card.payment_method&.prefixed_id
|
|
15
16
|
end
|
|
16
17
|
|
|
17
|
-
attributes
|
|
18
|
+
attributes :metadata,
|
|
19
|
+
created_at: :iso8601, updated_at: :iso8601
|
|
18
20
|
end
|
|
19
21
|
end
|
|
20
22
|
end
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
module Spree
|
|
2
|
+
module Api
|
|
3
|
+
module V3
|
|
4
|
+
module Admin
|
|
5
|
+
# Admin API Custom Field Definition Serializer
|
|
6
|
+
# Schema-side metadata for custom fields (per resource type).
|
|
7
|
+
class CustomFieldDefinitionSerializer < BaseSerializer
|
|
8
|
+
typelize namespace: :string,
|
|
9
|
+
key: :string,
|
|
10
|
+
label: :string,
|
|
11
|
+
field_type: Spree::Metafield::FIELD_TYPE_TOKENS,
|
|
12
|
+
resource_type: :string,
|
|
13
|
+
storefront_visible: :boolean
|
|
14
|
+
|
|
15
|
+
attributes :namespace, :key, :label, :field_type, :resource_type, :storefront_visible,
|
|
16
|
+
created_at: :iso8601, updated_at: :iso8601
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
@@ -5,12 +5,17 @@ module Spree
|
|
|
5
5
|
# Admin API Custom Field Serializer
|
|
6
6
|
# Full custom field data including admin-only fields
|
|
7
7
|
class CustomFieldSerializer < V3::CustomFieldSerializer
|
|
8
|
-
typelize storefront_visible: :boolean
|
|
8
|
+
typelize storefront_visible: :boolean,
|
|
9
|
+
custom_field_definition_id: :string
|
|
9
10
|
|
|
10
11
|
attributes created_at: :iso8601, updated_at: :iso8601
|
|
11
12
|
|
|
12
|
-
attribute :storefront_visible do |
|
|
13
|
-
|
|
13
|
+
attribute :storefront_visible do |custom_field|
|
|
14
|
+
custom_field.metafield_definition.available_on_front_end?
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
attribute :custom_field_definition_id do |custom_field|
|
|
18
|
+
custom_field.metafield_definition.prefixed_id
|
|
14
19
|
end
|
|
15
20
|
end
|
|
16
21
|
end
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
module Spree
|
|
2
|
+
module Api
|
|
3
|
+
module V3
|
|
4
|
+
module Admin
|
|
5
|
+
# Serializes Spree::CustomerGroup for the admin pickers
|
|
6
|
+
# (e.g. promotion rule, customer-group filters). Surfaces only
|
|
7
|
+
# what the admin UI needs to display + select rows.
|
|
8
|
+
class CustomerGroupSerializer < V3::BaseSerializer
|
|
9
|
+
typelize name: :string,
|
|
10
|
+
description: 'string | null',
|
|
11
|
+
customers_count: :number
|
|
12
|
+
|
|
13
|
+
attributes :name, :description, :customers_count,
|
|
14
|
+
created_at: :iso8601, updated_at: :iso8601
|
|
15
|
+
|
|
16
|
+
# Members are paginated separately via `/customers?customer_group_id_in=…`
|
|
17
|
+
# because a group can hold tens of thousands of users — embedding the
|
|
18
|
+
# whole list on every group fetch would explode the index payload.
|
|
19
|
+
# Pass `expand=customers` when you need them inline (single-record reads only).
|
|
20
|
+
many :customers,
|
|
21
|
+
resource: Spree.api.admin_customer_serializer,
|
|
22
|
+
if: proc { expand?('customers') }
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
@@ -8,10 +8,19 @@ module Spree
|
|
|
8
8
|
typelize login: [:string, nullable: true],
|
|
9
9
|
last_sign_in_at: [:string, nullable: true], current_sign_in_at: [:string, nullable: true],
|
|
10
10
|
sign_in_count: :number, failed_attempts: :number,
|
|
11
|
-
last_sign_in_ip: [:string, nullable: true], current_sign_in_ip: [:string, nullable: true]
|
|
11
|
+
last_sign_in_ip: [:string, nullable: true], current_sign_in_ip: [:string, nullable: true],
|
|
12
|
+
tags: [:string, multi: true],
|
|
13
|
+
internal_note_html: [:string, nullable: true],
|
|
14
|
+
metadata: 'Record<string, unknown>',
|
|
15
|
+
orders_count: :number,
|
|
16
|
+
total_spent: :string,
|
|
17
|
+
display_total_spent: :string,
|
|
18
|
+
last_order_completed_at: [:string, nullable: true],
|
|
19
|
+
default_billing_address_id: [:string, nullable: true],
|
|
20
|
+
default_shipping_address_id: [:string, nullable: true]
|
|
12
21
|
|
|
13
22
|
# Admin-only attributes
|
|
14
|
-
attributes :login,
|
|
23
|
+
attributes :login, :metadata,
|
|
15
24
|
last_sign_in_at: :iso8601, current_sign_in_at: :iso8601,
|
|
16
25
|
created_at: :iso8601, updated_at: :iso8601
|
|
17
26
|
|
|
@@ -31,6 +40,45 @@ module Spree
|
|
|
31
40
|
user.current_sign_in_ip
|
|
32
41
|
end
|
|
33
42
|
|
|
43
|
+
attribute :tags do |user|
|
|
44
|
+
user.tag_list.to_a
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
attribute :internal_note_html do |user|
|
|
48
|
+
user.respond_to?(:internal_note) ? user.internal_note&.body&.to_s.presence : nil
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
attribute :default_billing_address_id do |user|
|
|
52
|
+
user.bill_address&.prefixed_id
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
attribute :default_shipping_address_id do |user|
|
|
56
|
+
user.ship_address&.prefixed_id
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
# Order aggregates: prefer attributes precomputed on the scope (see
|
|
60
|
+
# CustomersController#scope) to avoid N+1 on list endpoints. Fall
|
|
61
|
+
# back to per-user queries when not preloaded (e.g. show endpoint
|
|
62
|
+
# for a freshly loaded record).
|
|
63
|
+
attribute :orders_count do |user|
|
|
64
|
+
user.attributes['orders_count']&.to_i || user.orders.complete.count
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
attribute :total_spent do |user|
|
|
68
|
+
(user.attributes['total_spent'] || user.orders.complete.sum(:total)).to_s
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
attribute :display_total_spent do |user|
|
|
72
|
+
amount = user.attributes['total_spent'] || user.orders.complete.sum(:total)
|
|
73
|
+
currency = Spree::Current.currency || Spree::Current.store&.default_currency || Spree::Config[:currency]
|
|
74
|
+
Spree::Money.new(amount, currency: currency).to_s
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
attribute :last_order_completed_at do |user|
|
|
78
|
+
value = user.attributes.key?('last_order_completed_at') ? user.attributes['last_order_completed_at'] : user.orders.complete.maximum(:completed_at)
|
|
79
|
+
value.respond_to?(:iso8601) ? value.iso8601 : value
|
|
80
|
+
end
|
|
81
|
+
|
|
34
82
|
# Override inherited associations to use admin serializers
|
|
35
83
|
many :addresses, resource: Spree.api.admin_address_serializer, if: proc { expand?('addresses') }
|
|
36
84
|
one :bill_address, key: :default_billing_address, resource: Spree.api.admin_address_serializer, if: proc { expand?('default_billing_address') }
|
|
@@ -39,6 +87,14 @@ module Spree
|
|
|
39
87
|
many :orders,
|
|
40
88
|
resource: Spree.api.admin_order_serializer,
|
|
41
89
|
if: proc { expand?('orders') }
|
|
90
|
+
|
|
91
|
+
many :store_credits,
|
|
92
|
+
resource: Spree.api.admin_store_credit_serializer,
|
|
93
|
+
if: proc { expand?('store_credits') }
|
|
94
|
+
|
|
95
|
+
many :customer_groups,
|
|
96
|
+
resource: Spree.api.admin_customer_group_serializer,
|
|
97
|
+
if: proc { expand?('customer_groups') }
|
|
42
98
|
end
|
|
43
99
|
end
|
|
44
100
|
end
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
module Spree
|
|
2
|
+
module Api
|
|
3
|
+
module V3
|
|
4
|
+
module Admin
|
|
5
|
+
class DashboardAnalyticsSerializer
|
|
6
|
+
attr_reader :store, :currency, :time_range, :params
|
|
7
|
+
|
|
8
|
+
def initialize(store:, currency:, time_range:, params: {})
|
|
9
|
+
@store = store
|
|
10
|
+
@currency = currency
|
|
11
|
+
@time_range = time_range
|
|
12
|
+
@params = params
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def to_h
|
|
16
|
+
{
|
|
17
|
+
currency: currency,
|
|
18
|
+
date_from: time_range.first.iso8601,
|
|
19
|
+
date_to: time_range.last.iso8601,
|
|
20
|
+
summary: summary,
|
|
21
|
+
chart_data: chart_data,
|
|
22
|
+
top_products: top_products
|
|
23
|
+
}
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
private
|
|
27
|
+
|
|
28
|
+
def orders
|
|
29
|
+
@orders ||= store.orders.complete.where(currency: currency, completed_at: time_range)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def prev_orders
|
|
33
|
+
@prev_orders ||= store.orders.complete.where(currency: currency, completed_at: previous_time_range)
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def previous_time_range
|
|
37
|
+
duration = time_range.last - time_range.first
|
|
38
|
+
(time_range.first - duration)..(time_range.last - duration)
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
# ---- Summary ----
|
|
42
|
+
|
|
43
|
+
def summary
|
|
44
|
+
sales = sales_total
|
|
45
|
+
count = orders_count
|
|
46
|
+
avg = count > 0 ? (sales / count).round(2) : 0.0
|
|
47
|
+
|
|
48
|
+
prev_sales = prev_orders.sum(:total).to_f
|
|
49
|
+
prev_count = prev_orders.count
|
|
50
|
+
prev_avg = prev_count > 0 ? (prev_sales / prev_count).round(2) : 0.0
|
|
51
|
+
|
|
52
|
+
{
|
|
53
|
+
sales_total: sales.round(2),
|
|
54
|
+
display_sales_total: money(sales),
|
|
55
|
+
sales_growth: growth_rate(sales, prev_sales),
|
|
56
|
+
orders_count: count,
|
|
57
|
+
orders_growth: growth_rate(count, prev_count),
|
|
58
|
+
avg_order_value: avg,
|
|
59
|
+
display_avg_order_value: money(avg),
|
|
60
|
+
avg_order_value_growth: growth_rate(avg, prev_avg)
|
|
61
|
+
}
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def sales_total
|
|
65
|
+
@sales_total ||= orders.sum(:total).to_f
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def orders_count
|
|
69
|
+
@orders_count ||= orders.count
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
# ---- Chart data ----
|
|
73
|
+
|
|
74
|
+
def chart_data
|
|
75
|
+
daily = orders
|
|
76
|
+
.select("DATE(completed_at) AS day, SUM(total) AS day_total, COUNT(*) AS day_count")
|
|
77
|
+
.group("DATE(completed_at)")
|
|
78
|
+
.order("day")
|
|
79
|
+
.index_by { |r| r.day.to_s }
|
|
80
|
+
|
|
81
|
+
(time_range.first.to_date..time_range.last.to_date).map do |date|
|
|
82
|
+
key = date.to_s
|
|
83
|
+
row = daily[key]
|
|
84
|
+
total = row&.day_total.to_f
|
|
85
|
+
count = row&.day_count.to_i || 0
|
|
86
|
+
{
|
|
87
|
+
date: key,
|
|
88
|
+
sales: total.round(2),
|
|
89
|
+
orders: count,
|
|
90
|
+
avg_order_value: count > 0 ? (total / count).round(2) : 0.0
|
|
91
|
+
}
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
# ---- Top products ----
|
|
96
|
+
|
|
97
|
+
def top_products
|
|
98
|
+
rows = Spree::LineItem
|
|
99
|
+
.joins(:variant)
|
|
100
|
+
.where(order: orders)
|
|
101
|
+
.group('spree_variants.product_id')
|
|
102
|
+
.order(Arel.sql('SUM(spree_line_items.quantity * spree_line_items.price) DESC'))
|
|
103
|
+
.limit(5)
|
|
104
|
+
.pluck(Arel.sql('spree_variants.product_id, SUM(spree_line_items.quantity), SUM(spree_line_items.quantity * spree_line_items.price)'))
|
|
105
|
+
|
|
106
|
+
product_ids = rows.map(&:first).compact
|
|
107
|
+
return [] if product_ids.empty?
|
|
108
|
+
|
|
109
|
+
products = store.products.with_deleted.includes(:primary_media).where(id: product_ids)
|
|
110
|
+
product_serializer = Spree.api.admin_product_serializer
|
|
111
|
+
|
|
112
|
+
rows.filter_map do |product_id, quantity, amount|
|
|
113
|
+
product = products.find { |p| p.id == product_id }
|
|
114
|
+
next unless product
|
|
115
|
+
|
|
116
|
+
serialized = product_serializer.new(product, params: params).to_h
|
|
117
|
+
{
|
|
118
|
+
id: serialized['id'],
|
|
119
|
+
name: serialized['name'],
|
|
120
|
+
slug: serialized['slug'],
|
|
121
|
+
image_url: serialized['thumbnail_url'],
|
|
122
|
+
price: serialized.dig('price', 'display_amount'),
|
|
123
|
+
quantity: quantity.to_i,
|
|
124
|
+
total: money(amount)
|
|
125
|
+
}
|
|
126
|
+
end
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
# ---- Helpers ----
|
|
130
|
+
|
|
131
|
+
def money(amount)
|
|
132
|
+
Spree::Money.new(amount, currency: currency).to_s
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
def growth_rate(current, previous)
|
|
136
|
+
return 0.0 if previous.zero?
|
|
137
|
+
(((current - previous) / previous.to_f) * 100).round(1)
|
|
138
|
+
end
|
|
139
|
+
end
|
|
140
|
+
end
|
|
141
|
+
end
|
|
142
|
+
end
|
|
143
|
+
end
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
module Spree
|
|
2
|
+
module Api
|
|
3
|
+
module V3
|
|
4
|
+
module Admin
|
|
5
|
+
# Admin API serializer for {Spree::Export}.
|
|
6
|
+
#
|
|
7
|
+
# `download_url` is the path to our own download endpoint, not a
|
|
8
|
+
# pre-signed ActiveStorage URL — the controller streams bytes inline
|
|
9
|
+
# so the JWT auth flow runs on every download and works through the
|
|
10
|
+
# SPA's `/api/*`-only dev proxy.
|
|
11
|
+
class ExportSerializer < V3::ExportSerializer
|
|
12
|
+
typelize done: :boolean,
|
|
13
|
+
download_url: [:string, nullable: true],
|
|
14
|
+
filename: [:string, nullable: true],
|
|
15
|
+
byte_size: [:number, nullable: true]
|
|
16
|
+
|
|
17
|
+
attribute(:done) { |export| export.done? }
|
|
18
|
+
|
|
19
|
+
# Safe-nav on `blob` — `attachment.attached?` can stay true while a
|
|
20
|
+
# background job purges the underlying blob (e.g. retention sweeps).
|
|
21
|
+
attribute :filename do |export|
|
|
22
|
+
export.attachment.blob&.filename&.to_s if export.done?
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
attribute :byte_size do |export|
|
|
26
|
+
export.attachment.blob&.byte_size if export.done?
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
attribute :download_url do |export|
|
|
30
|
+
next nil unless export.done?
|
|
31
|
+
|
|
32
|
+
Spree::Core::Engine.routes.url_helpers.download_api_v3_admin_export_path(
|
|
33
|
+
id: export.prefixed_id
|
|
34
|
+
)
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
@@ -3,19 +3,15 @@ module Spree
|
|
|
3
3
|
module V3
|
|
4
4
|
module Admin
|
|
5
5
|
class FulfillmentSerializer < V3::FulfillmentSerializer
|
|
6
|
-
typelize metadata: 'Record<string, unknown>
|
|
6
|
+
typelize metadata: 'Record<string, unknown>',
|
|
7
7
|
order_id: [:string, nullable: true],
|
|
8
8
|
stock_location_id: [:string, nullable: true],
|
|
9
9
|
adjustment_total: :string,
|
|
10
10
|
pre_tax_amount: :string
|
|
11
11
|
|
|
12
|
-
attributes :adjustment_total, :pre_tax_amount,
|
|
12
|
+
attributes :metadata, :adjustment_total, :pre_tax_amount,
|
|
13
13
|
created_at: :iso8601, updated_at: :iso8601
|
|
14
14
|
|
|
15
|
-
attribute :metadata do |shipment|
|
|
16
|
-
shipment.metadata.presence
|
|
17
|
-
end
|
|
18
|
-
|
|
19
15
|
attribute :order_id do |shipment|
|
|
20
16
|
shipment.order&.prefixed_id
|
|
21
17
|
end
|
|
@@ -2,14 +2,49 @@ module Spree
|
|
|
2
2
|
module Api
|
|
3
3
|
module V3
|
|
4
4
|
module Admin
|
|
5
|
+
# Admin serializer extends the store-facing one with operational
|
|
6
|
+
# context the admin UI needs: who the card was issued to (customer),
|
|
7
|
+
# who issued it (admin), and the orders that consumed it.
|
|
5
8
|
class GiftCardSerializer < V3::GiftCardSerializer
|
|
6
|
-
typelize
|
|
9
|
+
typelize customer_id: [:string, nullable: true],
|
|
10
|
+
created_by_id: [:string, nullable: true]
|
|
7
11
|
|
|
8
|
-
|
|
9
|
-
|
|
12
|
+
attributes created_at: :iso8601, updated_at: :iso8601
|
|
13
|
+
|
|
14
|
+
attribute :customer_id do |gift_card|
|
|
15
|
+
gift_card.user&.prefixed_id
|
|
10
16
|
end
|
|
11
17
|
|
|
12
|
-
|
|
18
|
+
attribute :created_by_id do |gift_card|
|
|
19
|
+
gift_card.created_by&.prefixed_id
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
# Customer the card was issued to. Gated behind `expand?` to keep
|
|
23
|
+
# the list payload thin — the SPA's list view passes
|
|
24
|
+
# `expand=customer,created_by` to populate the row chips.
|
|
25
|
+
one :user,
|
|
26
|
+
key: :customer,
|
|
27
|
+
resource: Spree.api.admin_customer_serializer,
|
|
28
|
+
if: proc { expand?('customer') }
|
|
29
|
+
|
|
30
|
+
# Admin who issued the card.
|
|
31
|
+
one :created_by,
|
|
32
|
+
resource: Spree.api.admin_admin_user_serializer,
|
|
33
|
+
if: proc { expand?('created_by') }
|
|
34
|
+
|
|
35
|
+
# Batch the card was issued as part of (bulk-issue flow). The
|
|
36
|
+
# `Spree::GiftCard#batch` association is keyed off
|
|
37
|
+
# `gift_card_batch_id`; we rename the JSON field to match that
|
|
38
|
+
# column for read/write symmetry.
|
|
39
|
+
one :batch,
|
|
40
|
+
key: :gift_card_batch,
|
|
41
|
+
resource: Spree.api.admin_gift_card_batch_serializer,
|
|
42
|
+
if: proc { expand?('gift_card_batch') }
|
|
43
|
+
|
|
44
|
+
# Orders that consumed the card. Detail-only — pass `expand=orders`.
|
|
45
|
+
many :orders,
|
|
46
|
+
resource: Spree.api.admin_order_serializer,
|
|
47
|
+
if: proc { expand?('orders') }
|
|
13
48
|
end
|
|
14
49
|
end
|
|
15
50
|
end
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
module Spree
|
|
2
|
+
module Api
|
|
3
|
+
module V3
|
|
4
|
+
module Admin
|
|
5
|
+
# Admin API serializer for {Spree::Invitation}. Used on the staff
|
|
6
|
+
# settings page to list pending invitations and to surface the result
|
|
7
|
+
# of `POST /admin/invitations`. Inviter and invitee are flattened to
|
|
8
|
+
# email-only — the full polymorphic identities aren't useful to the UI.
|
|
9
|
+
class InvitationSerializer < V3::BaseSerializer
|
|
10
|
+
typelize email: :string,
|
|
11
|
+
status: :string,
|
|
12
|
+
role_id: :string,
|
|
13
|
+
role_name: :string,
|
|
14
|
+
inviter_email: :string,
|
|
15
|
+
expires_at: :string,
|
|
16
|
+
acceptance_url: :string,
|
|
17
|
+
invitee_exists: :boolean,
|
|
18
|
+
store: '{ id: string; name: string }'
|
|
19
|
+
|
|
20
|
+
attributes :email, :status,
|
|
21
|
+
created_at: :iso8601, updated_at: :iso8601, expires_at: :iso8601
|
|
22
|
+
|
|
23
|
+
# `role`, `inviter` are `validates ... presence: true` on the model,
|
|
24
|
+
# so they're guaranteed non-null for any persisted invitation.
|
|
25
|
+
attribute :role_id do |invitation|
|
|
26
|
+
invitation.role.prefixed_id
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
attribute :role_name do |invitation|
|
|
30
|
+
invitation.role.name
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
attribute :inviter_email do |invitation|
|
|
34
|
+
invitation.inviter.email
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
# Absolute URL when `Spree::Config[:admin_url]` is set, otherwise
|
|
38
|
+
# the path so the SPA can prepend `window.location.origin`.
|
|
39
|
+
attribute :acceptance_url do |invitation|
|
|
40
|
+
if Spree::Config[:admin_url].present?
|
|
41
|
+
Rails.application.routes.url_helpers.admin_invitation_acceptance_url(invitation)
|
|
42
|
+
else
|
|
43
|
+
"/accept-invitation/#{invitation.prefixed_id}?token=#{invitation.token}"
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
# Drives the SPA's sign-in vs sign-up branch on the acceptance page.
|
|
48
|
+
# Looked up by email so an admin who's already on another store sees
|
|
49
|
+
# the password prompt, not a fresh account form.
|
|
50
|
+
attribute :invitee_exists do |invitation|
|
|
51
|
+
Spree.admin_user_class.exists?(email: invitation.email)
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
# Minimal store identity for the unauthenticated acceptance page's
|
|
55
|
+
# title ("Join <store>"). Full Store would over-expose internals to
|
|
56
|
+
# a public landing page; this is the smallest shape that renders.
|
|
57
|
+
attribute :store do |invitation|
|
|
58
|
+
{ id: invitation.store.prefixed_id, name: invitation.store.name }
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
end
|
|
@@ -5,16 +5,12 @@ module Spree
|
|
|
5
5
|
# Admin API Line Item Serializer
|
|
6
6
|
# Extends the store serializer with metadata visibility
|
|
7
7
|
class LineItemSerializer < V3::LineItemSerializer
|
|
8
|
-
typelize metadata: 'Record<string, unknown>
|
|
8
|
+
typelize metadata: 'Record<string, unknown>',
|
|
9
9
|
cost_price: [:string, nullable: true],
|
|
10
|
-
tax_category_id: [:string, nullable: true]
|
|
11
|
-
order_id: [:string, nullable: true]
|
|
10
|
+
tax_category_id: [:string, nullable: true]
|
|
12
11
|
|
|
13
|
-
attributes
|
|
14
|
-
|
|
15
|
-
attribute :metadata do |line_item|
|
|
16
|
-
line_item.metadata.presence
|
|
17
|
-
end
|
|
12
|
+
attributes :metadata,
|
|
13
|
+
created_at: :iso8601, updated_at: :iso8601
|
|
18
14
|
|
|
19
15
|
attribute :cost_price do |line_item|
|
|
20
16
|
line_item.cost_price&.to_s
|
|
@@ -24,18 +20,10 @@ module Spree
|
|
|
24
20
|
line_item.tax_category&.prefixed_id
|
|
25
21
|
end
|
|
26
22
|
|
|
27
|
-
attribute :order_id do |line_item|
|
|
28
|
-
line_item.order&.prefixed_id
|
|
29
|
-
end
|
|
30
|
-
|
|
31
23
|
# Override inherited associations to use admin serializers
|
|
32
24
|
many :option_values, resource: Spree.api.admin_option_value_serializer
|
|
33
25
|
many :digital_links, resource: Spree.api.admin_digital_link_serializer
|
|
34
26
|
|
|
35
|
-
one :order,
|
|
36
|
-
resource: Spree.api.admin_order_serializer,
|
|
37
|
-
if: proc { expand?('order') }
|
|
38
|
-
|
|
39
27
|
one :variant,
|
|
40
28
|
resource: Spree.api.admin_variant_serializer,
|
|
41
29
|
if: proc { expand?('variant') }
|
|
@@ -3,7 +3,9 @@ module Spree
|
|
|
3
3
|
module V3
|
|
4
4
|
module Admin
|
|
5
5
|
class MediaSerializer < V3::MediaSerializer
|
|
6
|
-
typelize viewable_type: :string, viewable_id: :string
|
|
6
|
+
typelize viewable_type: :string, viewable_id: :string,
|
|
7
|
+
metadata: 'Record<string, unknown>',
|
|
8
|
+
download_url: [:string, nullable: true]
|
|
7
9
|
|
|
8
10
|
attributes created_at: :iso8601, updated_at: :iso8601
|
|
9
11
|
|
|
@@ -11,7 +13,27 @@ module Spree
|
|
|
11
13
|
asset.viewable&.prefixed_id
|
|
12
14
|
end
|
|
13
15
|
|
|
14
|
-
|
|
16
|
+
# Forces Content-Disposition: attachment so admins downloading from
|
|
17
|
+
# cloud storage (S3) get a save-as instead of an inline view. Mirrors
|
|
18
|
+
# the host resolution from the `:cdn_image` direct route since
|
|
19
|
+
# rails_blob_url itself doesn't fall back to Spree.cdn_host or the
|
|
20
|
+
# current store's domain.
|
|
21
|
+
attribute :download_url do |asset|
|
|
22
|
+
next nil unless asset.attachment&.attached?
|
|
23
|
+
|
|
24
|
+
host = Spree.cdn_host.presence ||
|
|
25
|
+
Rails.application.routes.default_url_options[:host] ||
|
|
26
|
+
Spree::Store.current&.url_or_custom_domain
|
|
27
|
+
helpers = Rails.application.routes.url_helpers
|
|
28
|
+
|
|
29
|
+
if host.present?
|
|
30
|
+
helpers.rails_blob_url(asset.attachment.blob, disposition: 'attachment', host: host)
|
|
31
|
+
else
|
|
32
|
+
helpers.rails_blob_path(asset.attachment.blob, disposition: 'attachment')
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
attributes :metadata, :viewable_type
|
|
15
37
|
end
|
|
16
38
|
end
|
|
17
39
|
end
|