diff --git a/app/controllers/api/v1/customers_controller.rb b/app/controllers/api/v1/customers_controller.rb index 24bd1db8ee..86bf05fc65 100644 --- a/app/controllers/api/v1/customers_controller.rb +++ b/app/controllers/api/v1/customers_controller.rb @@ -6,11 +6,17 @@ module Api module V1 class CustomersController < Api::V1::BaseController include AddressTransformation + include ExtraFields skip_authorization_check only: :index before_action :authorize_action, only: [:show, :update, :destroy] + # Query parameters + before_action only: [:index] do + @extra_customer_fields = extra_fields :customer, [:balance] + end + def index @pagy, customers = pagy(search_customers, pagy_options) @@ -51,7 +57,11 @@ module Api private def customer - @customer ||= Customer.find(params[:id]) + @customer ||= if action_name == "show" + CustomersWithBalance.new(Customer.where(id: params[:id])).query.first! + else + Customer.find(params[:id]) + end end def authorize_action @@ -61,6 +71,11 @@ module Api def search_customers customers = visible_customers.includes(:bill_address, :ship_address) customers = customers.where(enterprise_id: params[:enterprise_id]) if params[:enterprise_id] + + if @extra_customer_fields.include?(:balance) + customers = CustomersWithBalance.new(customers).query + end + customers.ransack(params[:q]).result.order(:id) end diff --git a/app/json_schemas/customer_schema.rb b/app/json_schemas/customer_schema.rb index ecced41793..1156ef8327 100644 --- a/app/json_schemas/customer_schema.rb +++ b/app/json_schemas/customer_schema.rb @@ -61,4 +61,9 @@ class CustomerSchema < JsonApiSchema def self.relationships [:enterprise] end + + # Optional attributes included with eg: CustomerSchema.schema(extra_fields: :balance) + def self.balance + { balance: { type: :number, format: :double } } + end end diff --git a/app/serializers/api/v1/customer_serializer.rb b/app/serializers/api/v1/customer_serializer.rb index b5e59648b5..d558728d75 100644 --- a/app/serializers/api/v1/customer_serializer.rb +++ b/app/serializers/api/v1/customer_serializer.rb @@ -16,6 +16,10 @@ module Api address(object.shipping_address) end + attribute :balance, if: proc { |record| + record.respond_to?(:balance_value) + }, &:balance_value + belongs_to :enterprise, links: { related: ->(object) { url_helpers.api_v1_enterprise_url(id: object.enterprise_id) diff --git a/spec/requests/api/v1/customers_spec.rb b/spec/requests/api/v1/customers_spec.rb index 047c5e0438..3a4b7b38bb 100644 --- a/spec/requests/api/v1/customers_spec.rb +++ b/spec/requests/api/v1/customers_spec.rb @@ -26,10 +26,13 @@ describe "Customers", type: :request do get "List customers" do tags "Customers" parameter name: :enterprise_id, in: :query, type: :string + parameter name: "extra_fields[customer]", in: :query, type: :string, example: :balance, + description: "Add extra fields to each customer" produces "application/json" response "200", "Customers list" do param(:enterprise_id) { enterprise1.id } + param("extra_fields[customer]") { :balance } schema "$ref": "#/components/schemas/customers_collection" run_test! @@ -105,6 +108,33 @@ describe "Customers", type: :request do end end + describe "query parameters" do + describe "extra_fields[customer]" do + context "with balance" do + it "adds balance to each customer" do + get "/api/v1/customers", params: { extra_fields: { customer: :balance } } + balances = json_response[:data].map{ |c| c[:attributes][:balance] } + expect(balances.all?{ |b| b.is_a? Numeric }).to eq(true) + end + end + + context "with unknown field" do + it "returns unprocessable entity" do + get "/api/v1/customers", params: { extra_fields: { customer: :unknown } } + expect([response.status, json_error_detail]).to eq [422, "Unsupported fields: unknown"] + end + end + + context "when not recevied" do + it "does not add balances" do + get "/api/v1/customers" + balances = json_response[:data].map{ |c| c[:attributes][:balance] } + expect([response.status, balances.compact]).to eq [200, []] + end + end + end + end + post "Create customer" do tags "Customers" consumes "application/json" @@ -191,7 +221,10 @@ describe "Customers", type: :request do response "200", "Customer" do param(:id) { customer1.id } - schema "$ref": "#/components/schemas/customer" + schema CustomerSchema.schema( + require_all: true, + extra_fields: { name: :balance, required: true } + ) run_test! do date_time_string = diff --git a/spec/swagger_helper.rb b/spec/swagger_helper.rb index ad4cd3c34e..bebca37075 100644 --- a/spec/swagger_helper.rb +++ b/spec/swagger_helper.rb @@ -27,8 +27,9 @@ RSpec.configure do |config| components: { schemas: { error_response: ErrorsSchema.schema, + # only customer#show is with extra_fields: {name: :balance, required: true} customer: CustomerSchema.schema(require_all: true), - customers_collection: CustomerSchema.collection(require_all: true) + customers_collection: CustomerSchema.collection(require_all: true, extra_fields: :balance) }, securitySchemes: { api_key_header: { diff --git a/swagger/v1/swagger.yaml b/swagger/v1/swagger.yaml index 2909306e6e..0f4cec8aff 100644 --- a/swagger/v1/swagger.yaml +++ b/swagger/v1/swagger.yaml @@ -210,6 +210,9 @@ components: country: code: AU name: Australia + balance: + type: number + format: double required: - id - enterprise_id @@ -307,6 +310,12 @@ paths: in: query schema: type: string + - name: extra_fields[customer] + in: query + example: balance + description: Add extra fields to each customer + schema: + type: string responses: '200': description: Customers list @@ -406,7 +415,120 @@ paths: content: application/json: schema: - "$ref": "#/components/schemas/customer" + type: object + properties: + data: + type: object + properties: + id: + type: string + example: '1' + type: + type: string + example: customer + attributes: + type: object + properties: + id: + type: integer + example: 1 + enterprise_id: + type: integer + example: 2 + first_name: + type: string + nullable: true + example: Alice + last_name: + type: string + nullable: true + example: Springs + code: + type: string + nullable: true + example: BUYER1 + email: + type: string + example: alice@example.com + allow_charges: + type: boolean + example: false + tags: + type: array + items: + type: string + example: + - staff + - discount + terms_and_conditions_accepted_at: + type: string + format: date-time + nullable: true + example: '2022-03-12T15:55:00.000+11:00' + billing_address: + type: object + nullable: true + example: + shipping_address: + type: object + nullable: true + example: + phone: 0404 333 222 111 + latitude: -37.8173751 + longitude: 144.964803195704 + first_name: Alice + last_name: Springs + street_address_1: 1 Flinders Street + street_address_2: '' + postal_code: '1234' + locality: Melbourne + region: + code: Vic + name: Victoria + country: + code: AU + name: Australia + balance: + type: number + format: double + required: + - id + - enterprise_id + - first_name + - last_name + - code + - email + - allow_charges + - tags + - terms_and_conditions_accepted_at + - billing_address + - shipping_address + - balance + relationships: + type: object + properties: + enterprise: + type: object + properties: + data: + type: object + properties: + id: + type: string + type: + type: string + example: enterprise + links: + type: object + properties: + related: + type: string + meta: + type: object + links: + type: object + required: + - data '404': description: Not found content: