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.
Files changed (167) hide show
  1. checksums.yaml +4 -4
  2. data/app/controllers/concerns/spree/api/v3/admin/auth_cookies.rb +23 -0
  3. data/app/controllers/concerns/spree/api/v3/admin/refund_authorization.rb +26 -0
  4. data/app/controllers/concerns/spree/api/v3/admin_authentication.rb +1 -0
  5. data/app/controllers/concerns/spree/api/v3/allowed_redirect_url.rb +27 -0
  6. data/app/controllers/concerns/spree/api/v3/api_key_authentication.rb +14 -2
  7. data/app/controllers/concerns/spree/api/v3/cart_resolvable.rb +23 -4
  8. data/app/controllers/concerns/spree/api/v3/digital_assets_resource.rb +68 -0
  9. data/app/controllers/concerns/spree/api/v3/error_handler.rb +3 -0
  10. data/app/controllers/concerns/spree/api/v3/jwt_authentication.rb +27 -1
  11. data/app/controllers/concerns/spree/api/v3/orders/fulfillment_actions.rb +6 -1
  12. data/app/controllers/concerns/spree/api/v3/scoped_authorization.rb +6 -2
  13. data/app/controllers/spree/api/v3/admin/allowed_origins_controller.rb +4 -1
  14. data/app/controllers/spree/api/v3/admin/auth_controller.rb +32 -5
  15. data/app/controllers/spree/api/v3/admin/commission_lines_controller.rb +2 -2
  16. data/app/controllers/spree/api/v3/admin/companies/addresses_controller.rb +7 -1
  17. data/app/controllers/spree/api/v3/admin/custom_fields_controller.rb +27 -8
  18. data/app/controllers/spree/api/v3/admin/customer_groups_controller.rb +4 -0
  19. data/app/controllers/spree/api/v3/admin/customers/addresses_controller.rb +4 -0
  20. data/app/controllers/spree/api/v3/admin/customers/store_credits_controller.rb +1 -1
  21. data/app/controllers/spree/api/v3/admin/customers_controller.rb +14 -14
  22. data/app/controllers/spree/api/v3/admin/delivery_profiles/origin_groups_controller.rb +1 -1
  23. data/app/controllers/spree/api/v3/admin/delivery_profiles_controller.rb +1 -1
  24. data/app/controllers/spree/api/v3/admin/gift_cards_controller.rb +1 -8
  25. data/app/controllers/spree/api/v3/admin/invitation_acceptances_controller.rb +2 -0
  26. data/app/controllers/spree/api/v3/admin/me_controller.rb +2 -0
  27. data/app/controllers/spree/api/v3/admin/option_types_controller.rb +13 -0
  28. data/app/controllers/spree/api/v3/admin/orders/claims_controller.rb +2 -0
  29. data/app/controllers/spree/api/v3/admin/orders/exchanges_controller.rb +9 -0
  30. data/app/controllers/spree/api/v3/admin/orders/fulfillments_controller.rb +1 -1
  31. data/app/controllers/spree/api/v3/admin/orders/payments_controller.rb +7 -4
  32. data/app/controllers/spree/api/v3/admin/orders/returns_controller.rb +2 -0
  33. data/app/controllers/spree/api/v3/admin/orders_controller.rb +21 -7
  34. data/app/controllers/spree/api/v3/admin/package_types_controller.rb +2 -3
  35. data/app/controllers/spree/api/v3/admin/password_resets_controller.rb +7 -1
  36. data/app/controllers/spree/api/v3/admin/price_lists_controller.rb +18 -0
  37. data/app/controllers/spree/api/v3/admin/product_types_controller.rb +12 -1
  38. data/app/controllers/spree/api/v3/admin/products/digital_assets_controller.rb +5 -109
  39. data/app/controllers/spree/api/v3/admin/products/variants_controller.rb +7 -5
  40. data/app/controllers/spree/api/v3/admin/products_controller.rb +33 -6
  41. data/app/controllers/spree/api/v3/admin/sellers/payouts_controller.rb +4 -3
  42. data/app/controllers/spree/api/v3/admin/stock_transfers_controller.rb +3 -2
  43. data/app/controllers/spree/api/v3/admin/store_controller.rb +8 -1
  44. data/app/controllers/spree/api/v3/admin/tags_controller.rb +11 -9
  45. data/app/controllers/spree/api/v3/admin/variants_controller.rb +96 -1
  46. data/app/controllers/spree/api/v3/admin/webhook_endpoints_controller.rb +5 -2
  47. data/app/controllers/spree/api/v3/oauth/authorizations_controller.rb +17 -6
  48. data/app/controllers/spree/api/v3/resource_controller.rb +50 -7
  49. data/app/controllers/spree/api/v3/seller/auth_controller.rb +10 -0
  50. data/app/controllers/spree/api/v3/seller/invitation_acceptances_controller.rb +2 -0
  51. data/app/controllers/spree/api/v3/seller/me_controller.rb +4 -0
  52. data/app/controllers/spree/api/v3/seller/orders_controller.rb +11 -3
  53. data/app/controllers/spree/api/v3/seller/package_types_controller.rb +2 -2
  54. data/app/controllers/spree/api/v3/seller/password_resets_controller.rb +5 -1
  55. data/app/controllers/spree/api/v3/seller/products/digital_assets_controller.rb +67 -0
  56. data/app/controllers/spree/api/v3/seller/products_controller.rb +11 -63
  57. data/app/controllers/spree/api/v3/seller/resource_controller.rb +2 -6
  58. data/app/controllers/spree/api/v3/seller/team_controller.rb +1 -1
  59. data/app/controllers/spree/api/v3/store/carts/addresses_controller.rb +53 -0
  60. data/app/controllers/spree/api/v3/store/carts/store_credits_controller.rb +9 -1
  61. data/app/controllers/spree/api/v3/store/carts_controller.rb +17 -3
  62. data/app/controllers/spree/api/v3/store/companies/addresses_controller.rb +4 -0
  63. data/app/controllers/spree/api/v3/store/customer/addresses_controller.rb +4 -0
  64. data/app/controllers/spree/api/v3/store/customer/data_requests_controller.rb +19 -1
  65. data/app/controllers/spree/api/v3/store/customer/password_resets_controller.rb +3 -14
  66. data/app/controllers/spree/api/v3/store/customers_controller.rb +13 -1
  67. data/app/controllers/spree/api/v3/store/data_request_confirmations_controller.rb +23 -0
  68. data/app/controllers/spree/api/v3/store/data_requests_controller.rb +56 -0
  69. data/app/controllers/spree/api/v3/store/delivery_methods_controller.rb +6 -1
  70. data/app/controllers/spree/api/v3/store/newsletter_subscribers_controller.rb +2 -11
  71. data/app/controllers/spree/api/v3/store/products/filters_controller.rb +19 -4
  72. data/app/request_schemas/spree/api/v3/admin/variant_request_schema.rb +21 -0
  73. data/app/request_schemas/spree/api/v3/digital_asset_request_schema.rb +25 -0
  74. data/app/request_schemas/spree/api/v3/seller/price_request_schema.rb +20 -0
  75. data/app/request_schemas/spree/api/v3/seller/variant_request_schema.rb +19 -0
  76. data/app/request_schemas/spree/api/v3/variant_request_schema.rb +8 -13
  77. data/app/serializers/concerns/spree/api/v3/digital_asset_details.rb +70 -0
  78. data/app/serializers/spree/api/v3/address_owner_serializer.rb +27 -0
  79. data/app/serializers/spree/api/v3/address_serializer.rb +5 -1
  80. data/app/serializers/spree/api/v3/admin/address_owner_serializer.rb +10 -0
  81. data/app/serializers/spree/api/v3/admin/address_serializer.rb +1 -10
  82. data/app/serializers/spree/api/v3/admin/api_key_serializer.rb +1 -1
  83. data/app/serializers/spree/api/v3/admin/catalog_assignment_serializer.rb +1 -1
  84. data/app/serializers/spree/api/v3/admin/catalog_price_serializer.rb +1 -1
  85. data/app/serializers/spree/api/v3/admin/collection_rule_serializer.rb +1 -1
  86. data/app/serializers/spree/api/v3/admin/collection_serializer.rb +1 -1
  87. data/app/serializers/spree/api/v3/admin/commission_line_serializer.rb +2 -2
  88. data/app/serializers/spree/api/v3/admin/commission_rate_serializer.rb +1 -1
  89. data/app/serializers/spree/api/v3/admin/consent_record_serializer.rb +1 -1
  90. data/app/serializers/spree/api/v3/admin/delivery_origin_group_serializer.rb +2 -2
  91. data/app/serializers/spree/api/v3/admin/delivery_profile_serializer.rb +3 -5
  92. data/app/serializers/spree/api/v3/admin/digital_asset_serializer.rb +3 -47
  93. data/app/serializers/spree/api/v3/admin/email_template_serializer.rb +1 -1
  94. data/app/serializers/spree/api/v3/admin/newsletter_subscriber_serializer.rb +1 -0
  95. data/app/serializers/spree/api/v3/admin/order_serializer.rb +1 -1
  96. data/app/serializers/spree/api/v3/admin/package_type_serializer.rb +3 -3
  97. data/app/serializers/spree/api/v3/admin/permission_serializer.rb +1 -1
  98. data/app/serializers/spree/api/v3/admin/price_history_serializer.rb +1 -1
  99. data/app/serializers/spree/api/v3/admin/price_list_serializer.rb +9 -4
  100. data/app/serializers/spree/api/v3/admin/price_serializer.rb +1 -1
  101. data/app/serializers/spree/api/v3/admin/purchase_order_serializer.rb +1 -1
  102. data/app/serializers/spree/api/v3/admin/seller_payout_serializer.rb +2 -2
  103. data/app/serializers/spree/api/v3/admin/seller_requirement_status_serializer.rb +1 -1
  104. data/app/serializers/spree/api/v3/admin/seller_serializer.rb +3 -5
  105. data/app/serializers/spree/api/v3/admin/seller_transfer_serializer.rb +1 -1
  106. data/app/serializers/spree/api/v3/admin/shipping_label_serializer.rb +3 -3
  107. data/app/serializers/spree/api/v3/admin/stock_location_serializer.rb +1 -1
  108. data/app/serializers/spree/api/v3/admin/stock_receipt_item_serializer.rb +2 -2
  109. data/app/serializers/spree/api/v3/admin/stock_receipt_serializer.rb +1 -1
  110. data/app/serializers/spree/api/v3/admin/stock_transfer_serializer.rb +1 -1
  111. data/app/serializers/spree/api/v3/admin/store_credit_serializer.rb +3 -5
  112. data/app/serializers/spree/api/v3/admin/store_serializer.rb +1 -0
  113. data/app/serializers/spree/api/v3/admin/tax_identifier_serializer.rb +2 -2
  114. data/app/serializers/spree/api/v3/admin/tax_line_serializer.rb +1 -1
  115. data/app/serializers/spree/api/v3/admin/variant_serializer.rb +1 -1
  116. data/app/serializers/spree/api/v3/base_serializer.rb +33 -6
  117. data/app/serializers/spree/api/v3/claim_serializer.rb +1 -1
  118. data/app/serializers/spree/api/v3/collection_serializer.rb +1 -1
  119. data/app/serializers/spree/api/v3/company_invitation_serializer.rb +1 -1
  120. data/app/serializers/spree/api/v3/company_serializer.rb +1 -1
  121. data/app/serializers/spree/api/v3/data_request_confirmation_requested_event_serializer.rb +28 -0
  122. data/app/serializers/spree/api/v3/data_request_event_serializer.rb +1 -1
  123. data/app/serializers/spree/api/v3/data_request_serializer.rb +8 -4
  124. data/app/serializers/spree/api/v3/delivery_serializer.rb +1 -1
  125. data/app/serializers/spree/api/v3/delivery_zone_member_serializer.rb +1 -1
  126. data/app/serializers/spree/api/v3/discount_serializer.rb +1 -1
  127. data/app/serializers/spree/api/v3/file_upload_serializer.rb +1 -1
  128. data/app/serializers/spree/api/v3/fulfillment_serializer.rb +1 -1
  129. data/app/serializers/spree/api/v3/media_event_serializer.rb +1 -1
  130. data/app/serializers/spree/api/v3/media_serializer.rb +1 -1
  131. data/app/serializers/spree/api/v3/newsletter_subscriber_serializer.rb +5 -1
  132. data/app/serializers/spree/api/v3/option_type_serializer.rb +1 -1
  133. data/app/serializers/spree/api/v3/order_group_serializer.rb +2 -2
  134. data/app/serializers/spree/api/v3/order_serializer.rb +4 -2
  135. data/app/serializers/spree/api/v3/payment_serializer.rb +1 -1
  136. data/app/serializers/spree/api/v3/product_filter_availability_option_serializer.rb +1 -1
  137. data/app/serializers/spree/api/v3/product_filter_option_serializer.rb +1 -1
  138. data/app/serializers/spree/api/v3/seller/address_serializer.rb +3 -2
  139. data/app/serializers/spree/api/v3/seller/digital_asset_serializer.rb +16 -0
  140. data/app/serializers/spree/api/v3/seller/order_serializer.rb +3 -3
  141. data/app/serializers/spree/api/v3/seller/package_type_serializer.rb +3 -3
  142. data/app/serializers/spree/api/v3/seller/payout_serializer.rb +2 -2
  143. data/app/serializers/spree/api/v3/seller/profile_serializer.rb +2 -2
  144. data/app/serializers/spree/api/v3/seller/requirement_status_serializer.rb +1 -1
  145. data/app/serializers/spree/api/v3/seller/store_serializer.rb +18 -0
  146. data/app/serializers/spree/api/v3/seller/tax_identifier_serializer.rb +1 -1
  147. data/app/serializers/spree/api/v3/seller/variant_serializer.rb +1 -1
  148. data/app/serializers/spree/api/v3/shipping_label_event_serializer.rb +1 -1
  149. data/app/serializers/spree/api/v3/stock_movement_serializer.rb +1 -1
  150. data/app/serializers/spree/api/v3/stock_receipt_event_serializer.rb +1 -1
  151. data/app/serializers/spree/api/v3/variant_serializer.rb +2 -2
  152. data/app/services/spree/api/v3/filter_table.rb +22 -3
  153. data/app/services/spree/api/v3/filters_aggregator.rb +63 -18
  154. data/app/services/spree/api/v3/request_schema.rb +64 -17
  155. data/config/locales/en.yml +2 -0
  156. data/config/routes.rb +22 -11
  157. data/lib/spree/api/agent_write_schemas.rb +15 -3
  158. data/lib/spree/api/dependencies.rb +4 -4
  159. data/lib/spree/api/engine.rb +6 -0
  160. data/lib/spree/api/enum_types.rb +118 -0
  161. data/lib/spree/api/middleware/no_index.rb +33 -0
  162. data/lib/spree/api/openapi/filter_documentation.rb +8 -1
  163. data/lib/spree/api/openapi/schema_helper.rb +2 -1
  164. data/lib/spree/api/testing_support/v3/base.rb +18 -0
  165. data/lib/spree/api/typelizer_generation.rb +5 -0
  166. data/lib/typelizer/open_enums.rb +40 -16
  167. metadata +25 -6
@@ -7,7 +7,7 @@ module Spree
7
7
 
8
8
  typelize amount: [:string, nullable: false],
9
9
  provider_id: [:string, nullable: true], metadata: ['Record<string, unknown>', nullable: true],
10
- taxability_reason: [:string, nullable: true], country_code: [:string, nullable: true, enum: Spree::IsoData.country_codes, enum_type_name: 'CountryCode'],
10
+ taxability_reason: [:string, nullable: true, enum: Spree::TaxLine::BUILT_IN_TAXABILITY_REASONS, enum_type_name: 'TaxabilityReason'], country_code: [:string, nullable: true, enum: Spree::IsoData.country_codes, enum_type_name: 'CountryCode'],
11
11
  state_code: [:string, nullable: true], data: ['Record<string, unknown>', nullable: true],
12
12
  original_tax_line_id: [:string, nullable: true]
13
13
 
@@ -24,7 +24,7 @@ module Spree
24
24
  deleted_at: [:string, nullable: true],
25
25
  delivery_profile_id: [:string, nullable: true],
26
26
  minimum_order_quantity: ['number | null'], order_multiple: ['number | null'],
27
- purchase_unit: [:string, nullable: true, enum: Spree::Variant::PURCHASE_UNITS],
27
+ purchase_unit: [:string, nullable: true, enum: Spree::Variant::PURCHASE_UNITS, enum_type_name: 'PurchaseUnit'],
28
28
  carton_package_type_id: [:string, nullable: true],
29
29
  carton_weight: [:string, nullable: true],
30
30
  cartons_per_pallet: ['number | null'],
@@ -126,7 +126,7 @@ module Spree
126
126
  names = model.exposed_preference_names.to_a - except if names.empty?
127
127
  definitions = model.preference_definitions
128
128
  names.each do |name|
129
- typelize name => preference_type_hint(Spree::PreferenceSchema::JsonSchema.property_schema(definitions.fetch(name)))
129
+ typelize name => preference_type_hint(Spree::PreferenceSchema::JsonSchema.property_schema(definitions.fetch(name)), name)
130
130
  reader = :"preferred_#{name}"
131
131
  attribute(name) do |object|
132
132
  value = object.public_send(reader)
@@ -138,12 +138,34 @@ module Spree
138
138
  TYPELIZER_TYPES = { 'string' => :string, 'integer' => :number, 'number' => :number, 'boolean' => :boolean }.freeze
139
139
  private_constant :TYPELIZER_TYPES
140
140
 
141
- def self.preference_type_hint(schema)
141
+ # The shared name of each setting's value list, so it is defined once
142
+ # in `@spree/core` rather than inline wherever the setting appears.
143
+ PREFERENCE_LIST_NAMES = {
144
+ capture_method: 'CaptureMethod',
145
+ default_payouts_schedule_interval: 'PayoutScheduleInterval',
146
+ document_number_format: 'DocumentNumberFormat',
147
+ email_font: 'EmailFont',
148
+ inventory_provider_failure_policy: 'ProviderFailurePolicy',
149
+ pricing_provider_failure_policy: 'ProviderFailurePolicy',
150
+ storefront_access: 'StorefrontAccess',
151
+ unit_system: 'UnitSystem'
152
+ }.freeze
153
+ private_constant :PREFERENCE_LIST_NAMES
154
+
155
+ def self.preference_type_hint(schema, name)
142
156
  types = Array(schema['type'])
143
- hint = TYPELIZER_TYPES.fetch((types - ['null']).first, :unknown)
144
157
  options = {}
145
158
  options[:nullable] = true if types.include?('null')
146
- options[:enum] = schema['enum'].compact if schema['enum']
159
+ if types.include?('array')
160
+ hint = TYPELIZER_TYPES.fetch(Array(schema.dig('items', 'type')).first, :unknown)
161
+ options[:multi] = true
162
+ else
163
+ hint = TYPELIZER_TYPES.fetch((types - ['null']).first, :unknown)
164
+ end
165
+ if schema['enum']
166
+ options[:enum] = schema['enum'].compact
167
+ options[:enum_type_name] = PREFERENCE_LIST_NAMES[name.to_sym] if PREFERENCE_LIST_NAMES.key?(name.to_sym)
168
+ end
147
169
  options.empty? ? hint : [hint, options]
148
170
  end
149
171
  private_class_method :preference_type_hint
@@ -277,6 +299,10 @@ module Spree
277
299
  currency: current_currency,
278
300
  store: current_store,
279
301
  user: current_user,
302
+ company: params[:company],
303
+ market: params[:market],
304
+ channel: params[:channel],
305
+ country: params[:country],
280
306
  quantity: quantity
281
307
  )
282
308
  end
@@ -295,8 +321,9 @@ module Spree
295
321
  return Spree::QuantityRule.new if current_store.nil?
296
322
 
297
323
  Spree::Current.quantity_rules_resolver_for(
298
- company: Spree::Current.standing_company_for(current_user),
299
- user: current_user
324
+ company: params.key?(:company) ? params[:company] : Spree::Current.standing_company_for(current_user),
325
+ user: current_user,
326
+ channel: params[:channel]
300
327
  ).call(variant)
301
328
  end
302
329
 
@@ -7,7 +7,7 @@ module Spree
7
7
  class ClaimSerializer < BaseSerializer
8
8
  typelize number: :string,
9
9
  status: [:string, enum: Spree::Claim.statuses, enum_type_name: 'ClaimStatus'],
10
- resolution: [:string, nullable: true],
10
+ resolution: [:string, nullable: true, enum: Spree::Claim::RESOLUTIONS, enum_type_name: 'ClaimResolution'],
11
11
  order_id: [:string, nullable: true],
12
12
  reason_id: [:string, nullable: true],
13
13
  approved_at: [:string, nullable: true],
@@ -6,7 +6,7 @@ module Spree
6
6
  # back-office state exposed only by the Admin serializer.
7
7
  class CollectionSerializer < BaseSerializer
8
8
  typelize name: :string, permalink: :string, position: :number,
9
- sort_order: :string,
9
+ sort_order: [:string, enum: Spree::Collection::SORT_ORDERS, enum_type_name: 'CollectionSortOrder'],
10
10
  meta_title: [:string, nullable: true], meta_description: [:string, nullable: true], meta_keywords: [:string, nullable: true],
11
11
  description: :string, description_html: :string,
12
12
  image_url: [:string, nullable: true], square_image_url: [:string, nullable: true],
@@ -4,7 +4,7 @@ module Spree
4
4
  # A pending (or spent) invitation into a company node. The token is
5
5
  # deliberately absent — it travels only in the invite email.
6
6
  class CompanyInvitationSerializer < BaseSerializer
7
- typelize email: :string, company_id: :string, status: [:string, enum: %w[pending accepted revoked expired]],
7
+ typelize email: :string, company_id: :string, status: [:string, enum: %w[pending accepted revoked expired], enum_type_name: 'CompanyInvitationStatus'],
8
8
  expires_at: [:string, nullable: true]
9
9
 
10
10
  attributes :email, expires_at: :iso8601
@@ -7,7 +7,7 @@ module Spree
7
7
  # back-office fields stay in one place.
8
8
  class CompanySerializer < BaseSerializer
9
9
  typelize name: :string,
10
- kind: [:string, enum: Spree::Company::KINDS], parent_id: [:string, nullable: true],
10
+ kind: [:string, enum: Spree::Company::KINDS, enum_type_name: 'CompanyKind'], parent_id: [:string, nullable: true],
11
11
  po_number_required: :boolean,
12
12
  ancestors: 'Array<{id: string, name: string, kind: string}>'
13
13
 
@@ -0,0 +1,28 @@
1
+ module Spree
2
+ module Api
3
+ module V3
4
+ # The event shape for `data_request.confirmation_requested`: what a
5
+ # storefront that sends its own emails needs to ask the person to confirm
6
+ # a request they raised. `confirmation_token` is a live credential, so
7
+ # the event reaches only endpoints that name it.
8
+ class DataRequestConfirmationRequestedEventSerializer < DataRequestEventSerializer
9
+ typelize email: :string, confirmation_token: :string, store_id: :string,
10
+ redirect_url: [:string, optional: true]
11
+
12
+ attributes :email
13
+
14
+ attribute :confirmation_token do
15
+ params[:confirmation_token]
16
+ end
17
+
18
+ attribute :store_id do |data_request|
19
+ data_request.store.prefixed_id
20
+ end
21
+
22
+ attribute :redirect_url, if: proc { params[:redirect_url].present? } do
23
+ params[:redirect_url]
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end
@@ -11,7 +11,7 @@ module Spree
11
11
  # log. Subscribers get the fact that a request finished; fetching the
12
12
  # file stays an authenticated act by the person it belongs to.
13
13
  class DataRequestEventSerializer < BaseSerializer
14
- typelize number: :string, kind: [:string, enum: Spree::DataRequest::KINDS], status: [:string, enum: Spree::DataRequest.statuses, enum_type_name: 'DataRequestStatus'],
14
+ typelize number: :string, kind: [:string, enum: Spree::DataRequest::KINDS, enum_type_name: 'DataRequestKind'], status: [:string, enum: Spree::DataRequest.statuses, enum_type_name: 'DataRequestStatus'],
15
15
  requested_at: [:string, nullable: true],
16
16
  completed_at: [:string, nullable: true]
17
17
 
@@ -4,7 +4,7 @@ module Spree
4
4
  # Store API Data Request Serializer
5
5
  # A customer's own GDPR access or erasure request.
6
6
  class DataRequestSerializer < BaseSerializer
7
- typelize number: :string, kind: [:string, enum: Spree::DataRequest::KINDS], status: [:string, enum: Spree::DataRequest.statuses, enum_type_name: 'DataRequestStatus'],
7
+ typelize number: :string, kind: [:string, enum: Spree::DataRequest::KINDS, enum_type_name: 'DataRequestKind'], status: [:string, enum: Spree::DataRequest.statuses, enum_type_name: 'DataRequestStatus'],
8
8
  requested_at: [:string, nullable: true],
9
9
  completed_at: [:string, nullable: true],
10
10
  expires_at: [:string, nullable: true],
@@ -25,11 +25,15 @@ module Spree
25
25
  end
26
26
 
27
27
  # Present only while the file is still there — a link the storefront
28
- # can render without having to reason about expiry itself.
29
- attribute :download_url do |data_request, params|
28
+ # can render without having to reason about expiry itself — and only
29
+ # to a caller holding the confirmation token, never to the signed-in
30
+ # account, because the export covers guest orders placed at the address
31
+ # the request names.
32
+ attribute :download_url do |data_request|
33
+ next nil unless params[:download_url_visible]
30
34
  next nil unless data_request.downloadable?
31
35
 
32
- host = (params&.dig(:store) || data_request.store)&.formatted_url
36
+ host = (params[:store] || data_request.store)&.formatted_url
33
37
  next nil if host.blank?
34
38
 
35
39
  Spree::Api::DataRequestUrls.download_url(data_request, host)
@@ -10,7 +10,7 @@ module Spree
10
10
  carrier: [:string, nullable: true],
11
11
  carrier_name: [:string, nullable: true],
12
12
  service: [:string, nullable: true],
13
- status: [:string, enum: Spree::Delivery::STATUSES],
13
+ status: [:string, enum: Spree::Delivery::STATUSES, enum_type_name: 'DeliveryStatus'],
14
14
  estimated_delivery_at: [:string, nullable: true],
15
15
  delivered_at: [:string, nullable: true]
16
16
 
@@ -2,7 +2,7 @@ module Spree
2
2
  module Api
3
3
  module V3
4
4
  class DeliveryZoneMemberSerializer < BaseSerializer
5
- typelize member_type: [:string, enum: Spree::DeliveryZoneMember::MEMBER_TYPES], country_code: [:string, nullable: true, enum: Spree::IsoData.country_codes, enum_type_name: 'CountryCode'],
5
+ typelize member_type: [:string, enum: Spree::DeliveryZoneMember::MEMBER_TYPES, enum_type_name: 'DeliveryZoneMemberType'], country_code: [:string, nullable: true, enum: Spree::IsoData.country_codes, enum_type_name: 'CountryCode'],
6
6
  country_name: [:string, nullable: true],
7
7
  state_code: [:string, nullable: true], state_name: [:string, nullable: true],
8
8
  postal_code_prefix: [:string, nullable: true],
@@ -5,7 +5,7 @@ module Spree
5
5
  # line item or fulfillment. The embedded `discounts` key on Cart/Order
6
6
  # stays the applied-promotion summary — see AppliedPromotionSerializer.
7
7
  class DiscountSerializer < BaseSerializer
8
- typelize label: :string, kind: [:string, enum: Spree::Discount::KINDS], code: [:string, nullable: true],
8
+ typelize label: :string, kind: [:string, enum: Spree::Discount::KINDS, enum_type_name: 'DiscountKind'], code: [:string, nullable: true],
9
9
  value: [:string, nullable: true], value_type: [:string, nullable: true],
10
10
  promotion_id: [:string, nullable: true], line_item_id: [:string, nullable: true],
11
11
  fulfillment_id: [:string, nullable: true]
@@ -15,7 +15,7 @@ module Spree
15
15
  filename: :string,
16
16
  content_type: :string,
17
17
  byte_size: :number,
18
- visibility: [:string, enum: Spree::FileUpload::VISIBILITIES],
18
+ visibility: [:string, enum: Spree::FileUpload::VISIBILITIES, enum_type_name: 'FileUploadVisibility'],
19
19
  expires_at: :string,
20
20
  upload: [:FileUploadTarget, nullable: true]
21
21
 
@@ -4,7 +4,7 @@ module Spree
4
4
  class FulfillmentSerializer < BaseSerializer
5
5
  typelize number: :string,
6
6
  status: [:string, enum: Spree::Fulfillment.statuses, enum_type_name: 'FulfillmentStatus'],
7
- fulfillment_type: [:string, enum: %w[shipping digital pickup pickup_point]],
7
+ fulfillment_type: [:string, enum: %w[shipping digital pickup pickup_point], enum_type_name: 'FulfillmentType'],
8
8
  tracking: [:string, nullable: true], tracking_url: [:string, nullable: true],
9
9
  fulfilled_at: [:string, nullable: true], delivered_at: [:string, nullable: true], unpriced: :boolean,
10
10
  items: 'Array<{ item_id: string; variant_id: string; quantity: number }>',
@@ -7,7 +7,7 @@ module Spree
7
7
  # `type` is not exposed: the old Spree::Image/Spree::Video STI collapsed
8
8
  # into one class, so the column is a legacy vestige with no subclasses
9
9
  # — `media_type` is the discriminator (see MediaSerializer).
10
- typelize media_type: [:string, enum: Spree::Media::MEDIA_TYPES], viewable_type: :string,
10
+ typelize media_type: [:string, enum: Spree::Media::MEDIA_TYPES, enum_type_name: 'MediaType'], viewable_type: :string,
11
11
  viewable_id: :string, position: [:number, nullable: true],
12
12
  alt: [:string, nullable: true]
13
13
 
@@ -5,7 +5,7 @@ module Spree
5
5
  typelize position: :number, alt: [:string, nullable: true],
6
6
  product_id: [:string, nullable: true],
7
7
  variant_ids: [:string, multi: true],
8
- media_type: [:string, enum: Spree::Media::MEDIA_TYPES],
8
+ media_type: [:string, enum: Spree::Media::MEDIA_TYPES, enum_type_name: 'MediaType'],
9
9
  focal_point_x: [:number, nullable: true],
10
10
  focal_point_y: [:number, nullable: true],
11
11
  external_video_url: [:string, nullable: true],
@@ -16,7 +16,11 @@ module Spree
16
16
  subscriber.verified_at&.iso8601
17
17
  end
18
18
 
19
- prefixed_id_attributes :customer
19
+ # Shown to the account's owner only: for anyone else, posting an email
20
+ # must not tell them whether an account exists for it.
21
+ attribute :customer_id do |subscriber|
22
+ subscriber.customer&.prefixed_id if current_user && subscriber.customer_id == current_user.id
23
+ end
20
24
  end
21
25
  end
22
26
  end
@@ -2,7 +2,7 @@ module Spree
2
2
  module Api
3
3
  module V3
4
4
  class OptionTypeSerializer < BaseSerializer
5
- typelize name: :string, label: :string, position: :number, kind: [:string, enum: Spree::OptionType::KINDS]
5
+ typelize name: :string, label: :string, position: :number, kind: [:string, enum: Spree::OptionType::KINDS, enum_type_name: 'OptionTypeKind']
6
6
 
7
7
  attributes :name, :label, :position, :kind
8
8
  end
@@ -9,8 +9,8 @@ module Spree
9
9
  # inside it, each carrying its own items, delivery and tracking.
10
10
  class OrderGroupSerializer < BaseSerializer
11
11
  typelize number: :string, email: [:string, nullable: true], currency: [:string, enum: Spree::Money::Rounding::CURRENCY_CODES, enum_type_name: 'CurrencyCode'],
12
- fulfillment_status: [:string, nullable: true, enum: Spree::Order::FULFILLMENT_STATUSES],
13
- payment_status: [:string, nullable: true, enum: Spree::Order::PAYMENT_STATUSES],
12
+ fulfillment_status: [:string, nullable: true, enum: Spree::Order::FULFILLMENT_STATUSES, enum_type_name: 'OrderFulfillmentStatus'],
13
+ payment_status: [:string, nullable: true, enum: Spree::Order::PAYMENT_STATUSES, enum_type_name: 'OrderPaymentStatus'],
14
14
  completed_at: [:string, nullable: true],
15
15
  billing_address: { nullable: true }, shipping_address: { nullable: true }
16
16
 
@@ -11,10 +11,11 @@ module Spree
11
11
  po_document_filename: [:string, nullable: true], po_document_byte_size: ['number | null'],
12
12
  currency: [:string, enum: Spree::Money::Rounding::CURRENCY_CODES, enum_type_name: 'CurrencyCode'], locale: [:string, nullable: true, enum: Spree::Locales::ALL, enum_type_name: 'LocaleCode'], total_quantity: :number,
13
13
  coupon_code: [:string, nullable: true],
14
- fulfillment_status: [:string, nullable: true, enum: Spree::Order::FULFILLMENT_STATUSES],
15
- payment_status: [:string, nullable: true, enum: Spree::Order::PAYMENT_STATUSES],
14
+ fulfillment_status: [:string, nullable: true, enum: Spree::Order::FULFILLMENT_STATUSES, enum_type_name: 'OrderFulfillmentStatus'],
15
+ payment_status: [:string, nullable: true, enum: Spree::Order::PAYMENT_STATUSES, enum_type_name: 'OrderPaymentStatus'],
16
16
  completed_at: [:string, nullable: true],
17
17
  withdrawal_period_ends_at: [:string, nullable: true], within_withdrawal_period: :boolean,
18
+ shipping_eq_billing_address: :boolean,
18
19
  billing_address: { nullable: true }, shipping_address: { nullable: true },
19
20
  gift_card: { nullable: true }, market: { nullable: true }
20
21
 
@@ -28,6 +29,7 @@ module Spree
28
29
  end
29
30
 
30
31
  attribute :within_withdrawal_period, &:within_withdrawal_period?
32
+ attribute :shipping_eq_billing_address, &:shipping_eq_billing_address?
31
33
 
32
34
  # The checkout handle this order was born from (nil for admin drafts).
33
35
  # Lets abandonment tooling match cart.* events to the conversion.
@@ -4,7 +4,7 @@ module Spree
4
4
  class PaymentSerializer < BaseSerializer
5
5
  typelize status: [:string, enum: Spree::Payment.statuses, enum_type_name: 'PaymentStatus'],
6
6
  payment_method_id: :string, response_code: [:string, nullable: true], number: :string,
7
- source_type: [:string, nullable: true, enum: %w[credit_card store_credit payment_source]],
7
+ source_type: [:string, nullable: true, enum: %w[credit_card store_credit payment_source], enum_type_name: 'PaymentSourceType'],
8
8
  source_id: [:string, nullable: true], source: 'CreditCard | StoreCredit | PaymentSource | null'
9
9
 
10
10
  prefixed_id_attributes :payment_method
@@ -2,7 +2,7 @@ module Spree
2
2
  module Api
3
3
  module V3
4
4
  class ProductFilterAvailabilityOptionSerializer < BaseSerializer
5
- typelize id: [:string, enum: %w[in_stock out_of_stock]], count: :number
5
+ typelize id: [:string, enum: %w[in_stock out_of_stock], enum_type_name: 'StockAvailability'], count: :number
6
6
 
7
7
  attributes :id, :count
8
8
  end
@@ -6,7 +6,7 @@ module Spree
6
6
  type: "'option'",
7
7
  name: :string,
8
8
  label: :string,
9
- kind: [:string, enum: Spree::OptionType::KINDS],
9
+ kind: [:string, enum: Spree::OptionType::KINDS, enum_type_name: 'OptionTypeKind'],
10
10
  options: [:ProductFilterOptionValue, multi: true]
11
11
 
12
12
  attributes :id, :type, :name, :label, :kind
@@ -3,8 +3,9 @@ module Spree
3
3
  module V3
4
4
  module Seller
5
5
  class AddressSerializer < V3::AddressSerializer
6
- # The Store API keeps these deprecated names until 6.1.
7
- %i[state_abbr country_iso].each { |key| _attributes.delete(key) }
6
+ # The Store API keeps these deprecated names until 6.1. A seller
7
+ # keeps no address book of the buyer's, so has no owner to read.
8
+ %i[state_abbr country_iso owner].each { |key| _attributes.delete(key) }
8
9
  end
9
10
  end
10
11
  end
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Spree
4
+ module Api
5
+ module V3
6
+ module Seller
7
+ # A downloadable file on a seller's own product. Leaves out the
8
+ # provider's settings: they configure the operator's own systems, not
9
+ # the seller's listing.
10
+ class DigitalAssetSerializer < V3::DigitalAssetSerializer
11
+ include Spree::Api::V3::DigitalAssetDetails
12
+ end
13
+ end
14
+ end
15
+ end
16
+ end
@@ -23,9 +23,9 @@ module Spree
23
23
  class OrderSerializer < V3::BaseSerializer
24
24
  typelize number: :string, customer_note: [:string, nullable: true], currency: [:string, enum: Spree::Money::Rounding::CURRENCY_CODES, enum_type_name: 'CurrencyCode'],
25
25
  total_quantity: :number,
26
- fulfillment_status: [:string, nullable: true, enum: Spree::Order::FULFILLMENT_STATUSES],
27
- payment_status: [:string, nullable: true, enum: Spree::Order::PAYMENT_STATUSES],
28
- status: [:string, enum: Spree::Order::STATUSES], canceled_at: [:string, nullable: true],
26
+ fulfillment_status: [:string, nullable: true, enum: Spree::Order::FULFILLMENT_STATUSES, enum_type_name: 'OrderFulfillmentStatus'],
27
+ payment_status: [:string, nullable: true, enum: Spree::Order::PAYMENT_STATUSES, enum_type_name: 'OrderPaymentStatus'],
28
+ status: [:string, enum: Spree::Order::STATUSES, enum_type_name: 'OrderStatus'], canceled_at: [:string, nullable: true],
29
29
  cancel_reason_name: [:string, nullable: true], cancel_note: [:string, nullable: true],
30
30
  internal_note: [:string, nullable: true], internal_note_html: [:string, nullable: true]
31
31
 
@@ -15,14 +15,14 @@ module Spree
15
15
  # refuses to write them (docs/plans/6.0-seller-package-types.md).
16
16
  class PackageTypeSerializer < V3::BaseSerializer
17
17
  typelize name: :string,
18
- kind: [:string, enum: Spree::PackageType::KINDS],
18
+ kind: [:string, enum: Spree::PackageType::KINDS, enum_type_name: 'PackageTypeKind'],
19
19
  length: [:string, nullable: true],
20
20
  width: [:string, nullable: true],
21
21
  height: [:string, nullable: true],
22
- dimensions_unit: [:string, enum: Spree::Variant::DIMENSION_UNITS],
22
+ dimensions_unit: [:string, enum: Spree::Variant::DIMENSION_UNITS, enum_type_name: 'DimensionUnit'],
23
23
  weight: [:string, nullable: true],
24
24
  max_weight: [:string, nullable: true],
25
- weight_unit: [:string, enum: Spree::Variant::WEIGHT_UNITS],
25
+ weight_unit: [:string, enum: Spree::Variant::WEIGHT_UNITS, enum_type_name: 'WeightUnit'],
26
26
  volume: [:string, nullable: true],
27
27
  default: :boolean,
28
28
  editable: :boolean,
@@ -22,8 +22,8 @@ module Spree
22
22
  # How many earnings this settlement covers. Read from a count the
23
23
  # controller made for the whole page when there is one; a single
24
24
  # payout read falls back to asking for its own.
25
- attribute(:transfers_count) do |payout, params|
26
- counts = params && params[:transfer_counts]
25
+ attribute(:transfers_count) do |payout|
26
+ counts = params[:transfer_counts]
27
27
 
28
28
  counts ? counts.fetch(payout.id, 0) : payout.transfers.count
29
29
  end
@@ -8,8 +8,8 @@ module Spree
8
8
  registration_number: [:string, nullable: true],
9
9
  contact_email: [:string, nullable: true],
10
10
  billing_email: [:string, nullable: true],
11
- tax_remittance: :string,
12
- payouts_schedule_interval: [:string, nullable: true],
11
+ tax_remittance: [:string, enum: Spree::Seller::TAX_REMITTANCES, enum_type_name: 'TaxRemittance'],
12
+ payouts_schedule_interval: [:string, nullable: true, enum: Spree::Seller::PAYOUT_INTERVALS, enum_type_name: 'PayoutScheduleInterval'],
13
13
  minimum_payout_amount: [:string, nullable: true],
14
14
  holiday_mode_until: [:string, nullable: true],
15
15
  terms_accepted_at: [:string, nullable: true],
@@ -8,7 +8,7 @@ module Spree
8
8
 
9
9
  typelize id: :string, kind: [:string, comment: 'Requirement kind. Built-in: accept_terms, complete_profile, billing_address, returns_address, delivery_method, package_type, minimum_products, payout_account, required_custom_fields, policy, attestation, operator_review, document. Extensions may register more.'], name: :string,
10
10
  description: [:string, nullable: true],
11
- required: :boolean, position: :number, status: [:string, enum: Spree::SellerRequirementStatus::STATUSES],
11
+ required: :boolean, position: :number, status: [:string, enum: Spree::SellerRequirementStatus::STATUSES, enum_type_name: 'SellerRequirementState'],
12
12
  blocking: :boolean, action_url: [:string, nullable: true],
13
13
  blocker: ['{ state: string; message: string | null } | null'],
14
14
  accepts_submissions: :boolean, requires_file: :boolean,
@@ -0,0 +1,18 @@
1
+ module Spree
2
+ module Api
3
+ module V3
4
+ module Seller
5
+ # What a seller may know about the marketplace they sell on: its name,
6
+ # and the settings that shape what the panel lets them do.
7
+ class StoreSerializer < V3::BaseSerializer
8
+ typelize name: :string
9
+
10
+ attributes :name
11
+
12
+ preference_attributes Spree::Store, :seller_digital_assets_enabled, :seller_digital_asset_content_types,
13
+ :review_seller_digital_asset_changes
14
+ end
15
+ end
16
+ end
17
+ end
18
+ end
@@ -4,7 +4,7 @@ module Spree
4
4
  module Seller
5
5
  class TaxIdentifierSerializer < V3::BaseSerializer
6
6
  typelize kind: [:string, comment: 'Tax identifier kind, keyed to a registered validator. Built-in: eu_vat. Extensions may register more.'], value: :string,
7
- validation_status: [:string, nullable: true, enum: Spree::TaxIdentifier::VALIDATION_STATUSES],
7
+ validation_status: [:string, nullable: true, enum: Spree::TaxIdentifier::VALIDATION_STATUSES, enum_type_name: 'TaxIdentifierValidationStatus'],
8
8
  validated_at: [:string, nullable: true]
9
9
 
10
10
  attributes :kind, :value, :validation_status, validated_at: :iso8601
@@ -25,7 +25,7 @@ module Spree
25
25
  country_of_origin: [:string, nullable: true, enum: Spree::IsoData.country_codes, enum_type_name: 'CountryCode'],
26
26
  customs_description: [:string, nullable: true],
27
27
  minimum_order_quantity: ['number | null'], order_multiple: ['number | null'],
28
- purchase_unit: [:string, nullable: true, enum: Spree::Variant::PURCHASE_UNITS],
28
+ purchase_unit: [:string, nullable: true, enum: Spree::Variant::PURCHASE_UNITS, enum_type_name: 'PurchaseUnit'],
29
29
  metadata: 'Record<string, unknown>'
30
30
 
31
31
  attributes :metadata, :position, :cost_currency,
@@ -6,7 +6,7 @@ module Spree
6
6
  # stay behind the Admin API.
7
7
  class ShippingLabelEventSerializer < BaseSerializer
8
8
  typelize owner_id: :string,
9
- owner_type: [:string, enum: %w[fulfillment return]],
9
+ owner_type: [:string, enum: %w[fulfillment return], enum_type_name: 'ShippingLabelOwnerType'],
10
10
  status: [:string, enum: Spree::ShippingLabel.statuses, enum_type_name: 'ShippingLabelStatus'],
11
11
  carrier: [:string, nullable: true],
12
12
  service: [:string, nullable: true],
@@ -7,7 +7,7 @@ module Spree
7
7
  # `kind` is nullable for the lifetime of 6.0: a legacy row on an
8
8
  # install that has not run spree:migrate_stock_movements_to_typed_rows
9
9
  # has no kind yet.
10
- typelize quantity: :number, kind: [:string, nullable: true, enum: Spree::StockMovement::KINDS],
10
+ typelize quantity: :number, kind: [:string, nullable: true, enum: Spree::StockMovement::KINDS, enum_type_name: 'StockMovementKind'],
11
11
  reason: [:string, nullable: true], stock_level_id: [:string, nullable: true]
12
12
 
13
13
  attributes :quantity, :kind, :reason,
@@ -6,7 +6,7 @@ module Spree
6
6
  class StockReceiptEventSerializer < BaseSerializer
7
7
  typelize number: :string,
8
8
  received_at: :string,
9
- receivable_type: [:string, enum: %w[purchase_order stock_transfer]],
9
+ receivable_type: [:string, enum: %w[purchase_order stock_transfer], enum_type_name: 'StockReceiptReceivableType'],
10
10
  receivable_id: :string,
11
11
  quantity_accepted_total: :number,
12
12
  quantity_rejected_total: :number
@@ -10,11 +10,11 @@ module Spree
10
10
  purchasable: :boolean, in_stock: :boolean, backorderable: :boolean, preorder: :boolean,
11
11
  preorder_ships_at: [:string, nullable: true],
12
12
  weight: [:number, nullable: true], height: [:number, nullable: true], width: [:number, nullable: true], depth: [:number, nullable: true],
13
- weight_unit: [:string, enum: Spree::Variant::WEIGHT_UNITS], dimensions_unit: [:string, enum: Spree::Variant::DIMENSION_UNITS],
13
+ weight_unit: [:string, enum: Spree::Variant::WEIGHT_UNITS, enum_type_name: 'WeightUnit'], dimensions_unit: [:string, enum: Spree::Variant::DIMENSION_UNITS, enum_type_name: 'DimensionUnit'],
14
14
  price: 'Price',
15
15
  original_price: ['Price', nullable: true],
16
16
  minimum_order_quantity: :number, order_multiple: :number,
17
- purchase_unit: [:string, enum: Spree::Variant::PURCHASE_UNITS], units_per_carton: ['number | null'],
17
+ purchase_unit: [:string, enum: Spree::Variant::PURCHASE_UNITS, enum_type_name: 'PurchaseUnit'], units_per_carton: ['number | null'],
18
18
  seller_id: [:string, nullable: true]
19
19
 
20
20
  prefixed_id_attributes :product
@@ -25,6 +25,17 @@ module Spree
25
25
  boolean: 'boolean'
26
26
  }.freeze
27
27
 
28
+ # Columns holding a value from one of the closed code lists the API
29
+ # publishes as a shared type, matched by name (`locale`,
30
+ # `default_locale`, `cost_currency`), so clients filter with that type,
31
+ # not free text.
32
+ CODE_TYPES = {
33
+ 'locale' => 'LocaleCode',
34
+ 'currency' => 'CurrencyCode',
35
+ 'country_code' => 'CountryCode',
36
+ 'country_of_origin' => 'CountryCode'
37
+ }.freeze
38
+
28
39
  # Raised when an endpoint's table cannot be described — a column of a
29
40
  # kind the contract has no predicates for, or two models sharing a name.
30
41
  class Error < StandardError; end
@@ -124,8 +135,9 @@ module Spree
124
135
  self.class.name_for(model)
125
136
  end
126
137
 
127
- # @return [Hash{String => String, Hash}] attribute => value kind, or
128
- # `{ 'enum' => [values] }` for a status
138
+ # @return [Hash{String => String, Hash}] attribute => value kind,
139
+ # `{ 'enum' => [values] }` for a status, or `{ 'code' => type }` for
140
+ # a value from a shared code list
129
141
  def attributes
130
142
  @attributes ||= kinds.reject { |_, kind| kind == :unclassified }.compact
131
143
  end
@@ -210,6 +222,10 @@ module Spree
210
222
  # attribute the model does not have (`position` on a table without
211
223
  # one), `:unclassified` for one the contract cannot describe
212
224
  def attribute_kind(attribute)
225
+ if (code_type = code_type(attribute))
226
+ return { 'code' => code_type }
227
+ end
228
+
213
229
  aliased = model._ransack_aliases[attribute] || model.attribute_aliases[attribute]
214
230
  return attribute_kind(aliased) if aliased
215
231
  return ransacker_kind(model._ransackers[attribute]) if model._ransackers.key?(attribute)
@@ -220,7 +236,6 @@ module Spree
220
236
  if (values = inclusion_values(attribute))
221
237
  return { 'enum' => values }
222
238
  end
223
-
224
239
  column = model.columns_hash[attribute]
225
240
  return COLUMN_KINDS.fetch(column.type, :unclassified) if column
226
241
  return 'text' if translated_attribute?(attribute)
@@ -229,6 +244,10 @@ module Spree
229
244
  :unclassified
230
245
  end
231
246
 
247
+ def code_type(attribute)
248
+ CODE_TYPES.find { |name, _| attribute == name || attribute.end_with?("_#{name}") }&.last
249
+ end
250
+
232
251
  # The fixed list an inclusion validation allows, for a column that
233
252
  # holds one of a known set of values (`payment_status`, `kind`).
234
253
  def inclusion_values(attribute)