From 5f1d4c13131df6aa1c82fdea8f85297979d4e331 Mon Sep 17 00:00:00 2001 From: Sean Date: Sat, 16 May 2020 13:16:04 -0500 Subject: [PATCH] Add API endpoint for customer returns --- .../spree/api/customer_returns_controller.rb | 67 +++++++ api/app/helpers/spree/api/api_helpers.rb | 5 + .../api/customer_returns/index.json.jbuilder | 6 + .../api/customer_returns/new.json.jbuilder | 4 + .../api/customer_returns/show.json.jbuilder | 3 + api/config/routes.rb | 2 + api/openapi/solidus-api.oas.yml | 163 ++++++++++++++++++ .../api/customer_returns_controller_spec.rb | 135 +++++++++++++++ core/app/models/spree/customer_return.rb | 2 + core/app/models/spree/order.rb | 2 + 10 files changed, 389 insertions(+) create mode 100644 api/app/controllers/spree/api/customer_returns_controller.rb create mode 100644 api/app/views/spree/api/customer_returns/index.json.jbuilder create mode 100644 api/app/views/spree/api/customer_returns/new.json.jbuilder create mode 100644 api/app/views/spree/api/customer_returns/show.json.jbuilder create mode 100644 api/spec/requests/spree/api/customer_returns_controller_spec.rb diff --git a/api/app/controllers/spree/api/customer_returns_controller.rb b/api/app/controllers/spree/api/customer_returns_controller.rb new file mode 100644 index 00000000000..0f38bd689db --- /dev/null +++ b/api/app/controllers/spree/api/customer_returns_controller.rb @@ -0,0 +1,67 @@ +# frozen_string_literal: true + +module Spree + module Api + class CustomerReturnsController < Spree::Api::BaseController + before_action :load_order + around_action :lock_order, only: [:create, :update, :destroy, :cancel] + + rescue_from Spree::Order::InsufficientStock, with: :insufficient_stock_error + + def create + authorize! :create, CustomerReturn + @customer_return = CustomerReturn.create(customer_return_params) + if @customer_return.save + respond_with(@customer_return, status: 201, default_template: :show) + else + invalid_resource!(@customer_return) + end + end + + def index + authorize! :index, CustomerReturn + + @customer_returns = @order. + customer_returns. + accessible_by(current_ability, :read). + ransack(params[:q]). + result + + @customer_returns = paginate(@customer_returns) + + respond_with(@customer_returns) + end + + def new + authorize! :new, CustomerReturn + end + + def show + authorize! :show, CustomerReturn + @customer_return = @order.customer_returns.accessible_by(current_ability, :read).find(params[:id]) + respond_with(@customer_return) + end + + def update + authorize! :update, CustomerReturn + @customer_return = @order.customer_returns.accessible_by(current_ability, :update).find(params[:id]) + if @customer_return.update(customer_return_params) + respond_with(@customer_return.reload, default_template: :show) + else + invalid_resource!(@customer_return) + end + end + + private + + def load_order + @order ||= Spree::Order.find_by!(number: order_id) + authorize! :read, @order + end + + def customer_return_params + params.require(:customer_return).permit(permitted_customer_return_attributes) + end + end + end +end diff --git a/api/app/helpers/spree/api/api_helpers.rb b/api/app/helpers/spree/api/api_helpers.rb index 76d4510867a..532bf75dc9f 100644 --- a/api/app/helpers/spree/api/api_helpers.rb +++ b/api/app/helpers/spree/api/api_helpers.rb @@ -22,6 +22,7 @@ module ApiHelpers :state_attributes, :adjustment_attributes, :inventory_unit_attributes, + :customer_return_attributes, :return_authorization_attributes, :creditcard_attributes, :payment_source_attributes, @@ -117,6 +118,10 @@ def required_fields_for(model) :id, :state, :variant_id, :shipment_id ] + @@customer_return_attributes = [ + :id, :number, :stock_location_id, :created_at, :updated_at + ] + @@return_authorization_attributes = [ :id, :number, :state, :order_id, :memo, :created_at, :updated_at ] diff --git a/api/app/views/spree/api/customer_returns/index.json.jbuilder b/api/app/views/spree/api/customer_returns/index.json.jbuilder new file mode 100644 index 00000000000..db96fca4a62 --- /dev/null +++ b/api/app/views/spree/api/customer_returns/index.json.jbuilder @@ -0,0 +1,6 @@ +# frozen_string_literal: true + +json.customer_returns(@customer_returns) do |customer_return| + json.(customer_return, *customer_return_attributes) +end +json.partial! 'spree/api/shared/pagination', pagination: @customer_returns diff --git a/api/app/views/spree/api/customer_returns/new.json.jbuilder b/api/app/views/spree/api/customer_returns/new.json.jbuilder new file mode 100644 index 00000000000..335d60083af --- /dev/null +++ b/api/app/views/spree/api/customer_returns/new.json.jbuilder @@ -0,0 +1,4 @@ +# frozen_string_literal: true + +json.attributes([*customer_return_attributes]) +json.required_attributes(required_fields_for(Spree::CustomerReturn)) diff --git a/api/app/views/spree/api/customer_returns/show.json.jbuilder b/api/app/views/spree/api/customer_returns/show.json.jbuilder new file mode 100644 index 00000000000..d4fc47847a8 --- /dev/null +++ b/api/app/views/spree/api/customer_returns/show.json.jbuilder @@ -0,0 +1,3 @@ +# frozen_string_literal: true + +json.(@customer_return, *customer_return_attributes) diff --git a/api/config/routes.rb b/api/config/routes.rb index 520a02433d7..65fdb486b56 100644 --- a/api/config/routes.rb +++ b/api/config/routes.rb @@ -40,6 +40,8 @@ put :cancel end end + + resources :customer_returns, except: :destroy end resources :checkouts, only: [:update], concerns: :order_routes do diff --git a/api/openapi/solidus-api.oas.yml b/api/openapi/solidus-api.oas.yml index 02bcecbaf29..f65cca68de5 100644 --- a/api/openapi/solidus-api.oas.yml +++ b/api/openapi/solidus-api.oas.yml @@ -1475,6 +1475,60 @@ paths: - api-key: [] requestBody: $ref: '#/components/requestBodies/return-authorization-input' + '/orders/{order_id}/customer_returns/{id}': + get: + responses: + '200': + description: '' + content: + application/json: + schema: {} + '401': + $ref: '#/components/responses/invalid-api-key' + '404': + $ref: '#/components/responses/not-found' + summary: Get order customer return + description: Gets an orders customer return. + operationId: get-order-customer-return + tags: + - Customer returns + security: + - api-key: [] + parameters: + - name: order_id + in: path + required: true + description: The order number + schema: + type: string + - name: id + in: path + required: true + schema: + type: string + put: + responses: + '200': + description: '' + content: + application/json: + schema: + $ref: '#/components/schemas/customer-return' + '401': + $ref: '#/components/responses/invalid-api-key' + '404': + $ref: '#/components/responses/not-found' + '422': + $ref: '#/components/responses/unprocessable-entity' + summary: Update order customer return + description: "Updates an orders customer return." + operationId: update-order-customer-return + tags: + - Customer returns + requestBody: + $ref: '#/components/requestBodies/customer-return-input' + security: + - api-key: [] /orders: get: responses: @@ -4908,6 +4962,65 @@ paths: type: boolean security: - api-key: [] + '/orders/{order_number}/customer_returns': + get: + responses: + '200': + description: '' + content: + application/json: + schema: + allOf: + - $ref: '#/components/schemas/pagination-data' + - type: object + properties: + customer_returns: + type: array + items: + $ref: '#/components/schemas/customer-return' + '401': + $ref: '#/components/responses/invalid-api-key' + '404': + $ref: '#/components/responses/not-found' + summary: List order customer returns + description: "Lists an order's customer returns." + operationId: list-order-customer-returns + tags: + - Customer returns + parameters: + - $ref: '#/components/parameters/page' + - $ref: '#/components/parameters/per_page' + security: + - api-key: [] + parameters: + - name: order_number + in: path + schema: + type: string + required: true + post: + responses: + '200': + description: '' + content: + application/json: + schema: + $ref: '#/components/schemas/customer-return' + '401': + $ref: '#/components/responses/invalid-api-key' + '404': + $ref: '#/components/responses/not-found' + '422': + $ref: '#/components/responses/unprocessable-entity' + summary: Create order customer return + description: Creates a customer return for an order. + operationId: create-order-customer-return + tags: + - Customer returns + requestBody: + $ref: '#/components/requestBodies/customer-return-input' + security: + - api-key: [] tags: - name: Address books - name: Addresses @@ -4929,6 +5042,7 @@ tags: - name: Promotions - name: Properties - name: Return authorizations + - name: Customer returns - name: Shipments - name: States - name: Stock items @@ -5068,6 +5182,11 @@ components: application/json: schema: $ref: '#/components/schemas/return-authorization-input' + customer-return-input: + content: + application/json: + schema: + $ref: '#/components/schemas/customer-return-input' option-value-input: content: application/json: @@ -6584,3 +6703,47 @@ components: type: integer stock_item_id: type: integer + customer-return-input: + type: object + title: Customer return + properties: + number: + type: string + stock_location_id: + type: integer + return_items_attributes: + type: array + items: + type: object + properties: + inventory_unit_id: + type: integer + reception_status_event: + type: string + return_authorization_id: + type: integer + exchange_variant_id: + type: integer + preferred_reimbursement_type_id: + type: integer + resellable: + type: boolean + required: + - inventory_unit_id + required: + - stock_location_id + - return_items_attributes + x-examples: + Create New Customer Return: + stock_location_id: 1 + return_items_attributes: + - inventory_unit_id: 198 + reception_status_event: receive + customer-return: + type: object + title: Customer return + properties: + number: + type: string + stock_location_id: + type: integer diff --git a/api/spec/requests/spree/api/customer_returns_controller_spec.rb b/api/spec/requests/spree/api/customer_returns_controller_spec.rb new file mode 100644 index 00000000000..1a3b593bacb --- /dev/null +++ b/api/spec/requests/spree/api/customer_returns_controller_spec.rb @@ -0,0 +1,135 @@ +# frozen_string_literal: true + +require 'spec_helper' + +module Spree + describe Api::CustomerReturnsController, type: :request do + let!(:order) { create(:shipped_order) } + let(:attributes) { [:id, :number, :stock_location_id] } + + before do + stub_authentication! + end + + context "as a non admin" do + before do + allow_any_instance_of(Order).to receive_messages user: create(:user) + end + + it "cannot see any customer returns" do + get spree.api_order_customer_returns_path(order) + + assert_unauthorized! + end + + it "cannot see a single customer return" do + get spree.api_order_customer_return_path(order, 1) + + assert_unauthorized! + end + + it "cannot learn how to create a new customer return" do + get spree.new_api_order_customer_return_path(order) + + assert_unauthorized! + end + + it "cannot update a customer return" do + put spree.api_order_customer_return_path(order, 0) + + assert_unauthorized! + end + + it "cannot create a new customer return" do + post spree.api_order_customer_returns_path(order) + + assert_unauthorized! + end + end + + context "as an admin" do + sign_in_as_admin! + + it "can show customer return" do + customer_return = FactoryBot.create(:customer_return) + + get spree.api_order_customer_return_path(customer_return.order, customer_return.id) + + expect(response.status).to eq(200) + expect(json_response).to have_attributes(attributes) + end + + it "can get a list of customer returns" do + FactoryBot.create(:customer_return, shipped_order: order) + FactoryBot.create(:customer_return, shipped_order: order) + + get spree.api_order_customer_returns_path(order), params: { order_id: order.number } + + expect(response.status).to eq(200) + + customer_returns = json_response["customer_returns"] + + expect(customer_returns.first).to have_attributes(attributes) + expect(customer_returns.first).not_to eq(customer_returns.last) + end + + it 'can control the page size through a parameter' do + FactoryBot.create(:customer_return, shipped_order: order) + FactoryBot.create(:customer_return, shipped_order: order) + + get spree.api_order_customer_returns_path(order), params: { order_id: order.number, per_page: 1 } + + expect(json_response['count']).to eq(1) + expect(json_response['current_page']).to eq(1) + expect(json_response['pages']).to eq(2) + end + + it 'can query the results through a parameter' do + FactoryBot.create(:customer_return, shipped_order: order) + expected_result = FactoryBot.create(:customer_return, number: 'CR12', shipped_order: order) + + get spree.api_order_customer_returns_path(order), params: { q: { number_eq: 'CR12' } } + + expect(json_response['count']).to eq(1) + expect(json_response["customer_returns"].first['number']).to eq expected_result.number + end + + it "can learn how to create a new customer return" do + get spree.new_api_order_customer_return_path(order) + + expect(json_response["attributes"]).to eq(["id", "number", "stock_location_id", "created_at", "updated_at"]) + end + + it "can update a customer return" do + initial_stock_location = FactoryBot.create(:stock_location) + final_stock_location = FactoryBot.create(:stock_location) + customer_return = FactoryBot.create(:customer_return, stock_location: initial_stock_location) + + put spree.api_order_customer_return_path(customer_return.order, customer_return.id), params: { order_id: customer_return.order.number, customer_return: { stock_location_id: final_stock_location.id } } + + expect(response.status).to eq(200) + expect(json_response).to have_attributes(attributes) + expect(json_response["stock_location_id"]).to eq final_stock_location.id + end + + it "can create a new customer return" do + stock_location = FactoryBot.create(:stock_location) + unit = FactoryBot.create(:inventory_unit, state: "shipped") + cr_params = { stock_location_id: stock_location.id, + return_items_attributes: [{ + inventory_unit_id: unit.id, + reception_status_event: "receive", + }] } + + post spree.api_order_customer_returns_path(order), params: { order_id: order.number, customer_return: cr_params } + + expect(response.status).to eq(201) + expect(json_response).to have_attributes(attributes) + + customer_return = Spree::CustomerReturn.last + + expect(customer_return.return_items.first.reception_status).to eql "received" + end + end + end +end diff --git a/core/app/models/spree/customer_return.rb b/core/app/models/spree/customer_return.rb index 268f6a3eb19..8956fc57017 100644 --- a/core/app/models/spree/customer_return.rb +++ b/core/app/models/spree/customer_return.rb @@ -17,6 +17,8 @@ class CustomerReturn < Spree::Base accepts_nested_attributes_for :return_items + self.whitelisted_ransackable_attributes = ['number'] + extend DisplayMoney money_methods :pre_tax_total, :total, :total_excluding_vat, :amount deprecate display_pre_tax_total: :display_total_excluding_vat, deprecator: Spree::Deprecation diff --git a/core/app/models/spree/order.rb b/core/app/models/spree/order.rb index 4f7c88ceec6..bd5e210c56b 100644 --- a/core/app/models/spree/order.rb +++ b/core/app/models/spree/order.rb @@ -105,6 +105,8 @@ def states # Returns has_many :return_authorizations, dependent: :destroy, inverse_of: :order + has_many :return_items, through: :inventory_units + has_many :customer_returns, through: :return_items has_many :reimbursements, inverse_of: :order has_many :refunds, through: :payments