From b5c72f0bfc652570a30bce0e1c49a0da688437ca Mon Sep 17 00:00:00 2001 From: Oleksandr Chebotarov Date: Fri, 20 Dec 2024 12:12:29 +0200 Subject: [PATCH] Add new filters options to graphql and export of credit notes --- .../data_exports/credit_notes/create.rb | 34 ++ .../resolvers/credit_notes_resolver.rb | 37 +- .../data_exports/credit_notes/create_input.rb | 15 + .../credit_notes/export_type_enum.rb | 14 + .../credit_notes/filters_input.rb | 25 + .../data_exports/invoices/export_type_enum.rb | 2 + app/graphql/types/mutation_type.rb | 1 + schema.graphql | 84 ++- schema.json | 512 +++++++++++++++++- .../data_exports/credit_notes/create_spec.rb | 61 +++ .../data_exports/invoices/create_spec.rb | 1 - .../resolvers/credit_notes_resolver_spec.rb | 227 +++++--- .../credit_notes/filters_input_spec.rb | 20 + 13 files changed, 924 insertions(+), 109 deletions(-) create mode 100644 app/graphql/mutations/data_exports/credit_notes/create.rb create mode 100644 app/graphql/types/data_exports/credit_notes/create_input.rb create mode 100644 app/graphql/types/data_exports/credit_notes/export_type_enum.rb create mode 100644 app/graphql/types/data_exports/credit_notes/filters_input.rb create mode 100644 spec/graphql/mutations/data_exports/credit_notes/create_spec.rb create mode 100644 spec/graphql/types/data_exports/credit_notes/filters_input_spec.rb diff --git a/app/graphql/mutations/data_exports/credit_notes/create.rb b/app/graphql/mutations/data_exports/credit_notes/create.rb new file mode 100644 index 00000000000..67739b2a93b --- /dev/null +++ b/app/graphql/mutations/data_exports/credit_notes/create.rb @@ -0,0 +1,34 @@ +# frozen_string_literal: true + +module Mutations + module DataExports + module CreditNotes + class Create < BaseMutation + include AuthenticableApiUser + include RequiredOrganization + + REQUIRED_PERMISSION = 'credit_notes:export' + + graphql_name 'CreateCreditNotesDataExport' + description 'Request data export of credit notes' + + input_object_class Types::DataExports::CreditNotes::CreateInput + + type Types::DataExports::Object + + def resolve(format:, filters:, resource_type:) + result = ::DataExports::CreateService + .call( + organization: current_organization, + user: context[:current_user], + format:, + resource_type:, + resource_query: filters + ) + + result.success? ? result.data_export : result_error(result) + end + end + end + end +end diff --git a/app/graphql/resolvers/credit_notes_resolver.rb b/app/graphql/resolvers/credit_notes_resolver.rb index 5d7e2d97067..6797bf9f671 100644 --- a/app/graphql/resolvers/credit_notes_resolver.rb +++ b/app/graphql/resolvers/credit_notes_resolver.rb @@ -9,28 +9,43 @@ class CreditNotesResolver < Resolvers::BaseResolver description 'Query credit notes' - argument :customer_id, ID, required: false + argument :amount_from, Integer, required: false + argument :amount_to, Integer, required: false + argument :credit_status, [Types::CreditNotes::CreditStatusTypeEnum], required: false + argument :currency, Types::CurrencyEnum, required: false + argument :customer_external_id, String, required: false + argument :customer_id, ID, required: false, description: 'Uniq ID of the customer' + argument :invoice_number, String, required: false + argument :issuing_date_from, GraphQL::Types::ISO8601Date, required: false + argument :issuing_date_to, GraphQL::Types::ISO8601Date, required: false argument :limit, Integer, required: false argument :page, Integer, required: false + argument :reason, [Types::CreditNotes::ReasonTypeEnum], required: false + argument :refund_status, [Types::CreditNotes::RefundStatusTypeEnum], required: false argument :search_term, String, required: false type Types::CreditNotes::Object.collection_type, null: false - def resolve( - page: nil, - limit: nil, - search_term: nil, - customer_id: nil - ) + def resolve(args) result = CreditNotesQuery.call( organization: current_organization, - search_term:, + search_term: args[:search_term], filters: { - customer_id: + amount_from: args[:amount_from], + amount_to: args[:amount_to], + credit_status: args[:credit_status], + currency: args[:currency], + customer_external_id: args[:customer_external_id], + customer_id: args[:customer_id], + invoice_number: args[:invoice_number], + issuing_date_from: args[:issuing_date_from], + issuing_date_to: args[:issuing_date_to], + reason: args[:reason], + refund_status: args[:refund_status] }, pagination: { - page:, - limit: + page: args[:page], + limit: args[:limit] } ) diff --git a/app/graphql/types/data_exports/credit_notes/create_input.rb b/app/graphql/types/data_exports/credit_notes/create_input.rb new file mode 100644 index 00000000000..dd592d5eacb --- /dev/null +++ b/app/graphql/types/data_exports/credit_notes/create_input.rb @@ -0,0 +1,15 @@ +# frozen_string_literal: true + +module Types + module DataExports + module CreditNotes + class CreateInput < Types::BaseInputObject + graphql_name 'CreateDataExportsCreditNotesInput' + + argument :filters, Types::DataExports::CreditNotes::FiltersInput + argument :format, Types::DataExports::FormatTypeEnum + argument :resource_type, Types::DataExports::CreditNotes::ExportTypeEnum + end + end + end +end diff --git a/app/graphql/types/data_exports/credit_notes/export_type_enum.rb b/app/graphql/types/data_exports/credit_notes/export_type_enum.rb new file mode 100644 index 00000000000..21693944df9 --- /dev/null +++ b/app/graphql/types/data_exports/credit_notes/export_type_enum.rb @@ -0,0 +1,14 @@ +# frozen_string_literal: true + +module Types + module DataExports + module CreditNotes + class ExportTypeEnum < Types::BaseEnum + graphql_name 'CreditNoteExportTypeEnum' + + value 'credit_notes' + value 'credit_note_items' + end + end + end +end diff --git a/app/graphql/types/data_exports/credit_notes/filters_input.rb b/app/graphql/types/data_exports/credit_notes/filters_input.rb new file mode 100644 index 00000000000..ac19097d3ed --- /dev/null +++ b/app/graphql/types/data_exports/credit_notes/filters_input.rb @@ -0,0 +1,25 @@ +# frozen_string_literal: true + +module Types + module DataExports + module CreditNotes + class FiltersInput < BaseInputObject + graphql_name 'DataExportCreditNoteFiltersInput' + description 'Export credit notes search query and filters input argument' + + argument :amount_from, Integer, required: false + argument :amount_to, Integer, required: false + argument :credit_status, [Types::CreditNotes::CreditStatusTypeEnum], required: false + argument :currency, Types::CurrencyEnum, required: false + argument :customer_external_id, String, required: false + argument :customer_id, ID, required: false, description: 'Uniq ID of the customer' + argument :invoice_number, String, required: false + argument :issuing_date_from, GraphQL::Types::ISO8601Date, required: false + argument :issuing_date_to, GraphQL::Types::ISO8601Date, required: false + argument :reason, [Types::CreditNotes::ReasonTypeEnum], required: false + argument :refund_status, [Types::CreditNotes::RefundStatusTypeEnum], required: false + argument :search_term, String, required: false + end + end + end +end diff --git a/app/graphql/types/data_exports/invoices/export_type_enum.rb b/app/graphql/types/data_exports/invoices/export_type_enum.rb index 6e2df6de2aa..95df71e1c5a 100644 --- a/app/graphql/types/data_exports/invoices/export_type_enum.rb +++ b/app/graphql/types/data_exports/invoices/export_type_enum.rb @@ -4,6 +4,8 @@ module Types module DataExports module Invoices class ExportTypeEnum < Types::BaseEnum + graphql_name 'InvoiceExportTypeEnum' + value 'invoices' value 'invoice_fees' end diff --git a/app/graphql/types/mutation_type.rb b/app/graphql/types/mutation_type.rb index bcff7f12e6e..fd2a72bc3f5 100644 --- a/app/graphql/types/mutation_type.rb +++ b/app/graphql/types/mutation_type.rb @@ -28,6 +28,7 @@ class MutationType < Types::BaseObject field :generate_customer_portal_url, mutation: Mutations::CustomerPortal::GenerateUrl field :update_customer_portal_customer, mutation: Mutations::CustomerPortal::UpdateCustomer + field :create_credit_notes_data_export, mutation: Mutations::DataExports::CreditNotes::Create field :create_invoices_data_export, mutation: Mutations::DataExports::Invoices::Create field :create_subscription, mutation: Mutations::Subscriptions::Create diff --git a/schema.graphql b/schema.graphql index de53ad5dd75..34131b0ee54 100644 --- a/schema.graphql +++ b/schema.graphql @@ -2011,6 +2011,19 @@ input CreateCustomerWalletTransactionInput { walletId: ID! } +""" +Autogenerated input type of CreateCreditNotesDataExport +""" +input CreateDataExportsCreditNotesInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + filters: DataExportCreditNoteFiltersInput! + format: DataExportFormatTypeEnum! + resourceType: CreditNoteExportTypeEnum! +} + """ Autogenerated input type of CreateInvoicesDataExport """ @@ -2021,7 +2034,7 @@ input CreateDataExportsInvoicesInput { clientMutationId: String filters: DataExportInvoiceFiltersInput! format: DataExportFormatTypeEnum! - resourceType: ExportTypeEnum! + resourceType: InvoiceExportTypeEnum! } """ @@ -2374,6 +2387,11 @@ type CreditNoteEstimate { taxesRate: Float! } +enum CreditNoteExportTypeEnum { + credit_note_items + credit_notes +} + type CreditNoteItem { amountCents: BigInt! amountCurrency: CurrencyEnum! @@ -3444,6 +3462,28 @@ type DataExport { status: DataExportStatusEnum! } +""" +Export credit notes search query and filters input argument +""" +input DataExportCreditNoteFiltersInput { + amountFrom: Int + amountTo: Int + creditStatus: [CreditNoteCreditStatusEnum!] + currency: CurrencyEnum + customerExternalId: String + + """ + Uniq ID of the customer + """ + customerId: ID + invoiceNumber: String + issuingDateFrom: ISO8601Date + issuingDateTo: ISO8601Date + reason: [CreditNoteReasonEnum!] + refundStatus: [CreditNoteRefundStatusEnum!] + searchTerm: String +} + enum DataExportFormatTypeEnum { csv } @@ -3933,11 +3973,6 @@ type EventCollection { metadata: CollectionMetadata! } -enum ExportTypeEnum { - invoice_fees - invoices -} - type Fee implements InvoiceItem { adjustedFee: Boolean! adjustedFeeType: AdjustedFeeTypeEnum @@ -4517,6 +4552,11 @@ type InvoiceCustomSectionCollection { metadata: CollectionMetadata! } +enum InvoiceExportTypeEnum { + invoice_fees + invoices +} + """ Invoice Item """ @@ -4898,6 +4938,16 @@ type Mutation { input: CreateCreditNoteInput! ): CreditNote + """ + Request data export of credit notes + """ + createCreditNotesDataExport( + """ + Parameters for CreateCreditNotesDataExport + """ + input: CreateDataExportsCreditNotesInput! + ): DataExport + """ Creates a new customer """ @@ -6156,6 +6206,7 @@ type Permissions { couponsUpdate: Boolean! couponsView: Boolean! creditNotesCreate: Boolean! + creditNotesExport: Boolean! creditNotesUpdate: Boolean! creditNotesView: Boolean! creditNotesVoid: Boolean! @@ -6439,7 +6490,26 @@ type Query { """ Query credit notes """ - creditNotes(customerId: ID, limit: Int, page: Int, searchTerm: String): CreditNoteCollection! + creditNotes( + amountFrom: Int + amountTo: Int + creditStatus: [CreditNoteCreditStatusEnum!] + currency: CurrencyEnum + customerExternalId: String + + """ + Uniq ID of the customer + """ + customerId: ID + invoiceNumber: String + issuingDateFrom: ISO8601Date + issuingDateTo: ISO8601Date + limit: Int + page: Int + reason: [CreditNoteReasonEnum!] + refundStatus: [CreditNoteRefundStatusEnum!] + searchTerm: String + ): CreditNoteCollection! """ Retrieves currently connected user diff --git a/schema.json b/schema.json index 0044043c0e3..5f0dde520b9 100644 --- a/schema.json +++ b/schema.json @@ -7595,6 +7595,77 @@ ], "enumValues": null }, + { + "kind": "INPUT_OBJECT", + "name": "CreateDataExportsCreditNotesInput", + "description": "Autogenerated input type of CreateCreditNotesDataExport", + "interfaces": null, + "possibleTypes": null, + "fields": null, + "inputFields": [ + { + "name": "filters", + "description": null, + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "INPUT_OBJECT", + "name": "DataExportCreditNoteFiltersInput", + "ofType": null + } + }, + "defaultValue": null, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "format", + "description": null, + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "ENUM", + "name": "DataExportFormatTypeEnum", + "ofType": null + } + }, + "defaultValue": null, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "resourceType", + "description": null, + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "ENUM", + "name": "CreditNoteExportTypeEnum", + "ofType": null + } + }, + "defaultValue": null, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "clientMutationId", + "description": "A unique identifier for the client performing the mutation.", + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "defaultValue": null, + "isDeprecated": false, + "deprecationReason": null + } + ], + "enumValues": null + }, { "kind": "INPUT_OBJECT", "name": "CreateDataExportsInvoicesInput", @@ -7643,7 +7714,7 @@ "name": null, "ofType": { "kind": "ENUM", - "name": "ExportTypeEnum", + "name": "InvoiceExportTypeEnum", "ofType": null } }, @@ -10433,6 +10504,29 @@ "inputFields": null, "enumValues": null }, + { + "kind": "ENUM", + "name": "CreditNoteExportTypeEnum", + "description": null, + "interfaces": null, + "possibleTypes": null, + "fields": null, + "inputFields": null, + "enumValues": [ + { + "name": "credit_notes", + "description": null, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "credit_note_items", + "description": null, + "isDeprecated": false, + "deprecationReason": null + } + ] + }, { "kind": "OBJECT", "name": "CreditNoteItem", @@ -14594,6 +14688,185 @@ "inputFields": null, "enumValues": null }, + { + "kind": "INPUT_OBJECT", + "name": "DataExportCreditNoteFiltersInput", + "description": "Export credit notes search query and filters input argument", + "interfaces": null, + "possibleTypes": null, + "fields": null, + "inputFields": [ + { + "name": "amountFrom", + "description": null, + "type": { + "kind": "SCALAR", + "name": "Int", + "ofType": null + }, + "defaultValue": null, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "amountTo", + "description": null, + "type": { + "kind": "SCALAR", + "name": "Int", + "ofType": null + }, + "defaultValue": null, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "creditStatus", + "description": null, + "type": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "ENUM", + "name": "CreditNoteCreditStatusEnum", + "ofType": null + } + } + }, + "defaultValue": null, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "currency", + "description": null, + "type": { + "kind": "ENUM", + "name": "CurrencyEnum", + "ofType": null + }, + "defaultValue": null, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "customerExternalId", + "description": null, + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "defaultValue": null, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "customerId", + "description": "Uniq ID of the customer", + "type": { + "kind": "SCALAR", + "name": "ID", + "ofType": null + }, + "defaultValue": null, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "invoiceNumber", + "description": null, + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "defaultValue": null, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "issuingDateFrom", + "description": null, + "type": { + "kind": "SCALAR", + "name": "ISO8601Date", + "ofType": null + }, + "defaultValue": null, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "issuingDateTo", + "description": null, + "type": { + "kind": "SCALAR", + "name": "ISO8601Date", + "ofType": null + }, + "defaultValue": null, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "reason", + "description": null, + "type": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "ENUM", + "name": "CreditNoteReasonEnum", + "ofType": null + } + } + }, + "defaultValue": null, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "refundStatus", + "description": null, + "type": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "ENUM", + "name": "CreditNoteRefundStatusEnum", + "ofType": null + } + } + }, + "defaultValue": null, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "searchTerm", + "description": null, + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "defaultValue": null, + "isDeprecated": false, + "deprecationReason": null + } + ], + "enumValues": null + }, { "kind": "ENUM", "name": "DataExportFormatTypeEnum", @@ -16749,29 +17022,6 @@ "inputFields": null, "enumValues": null }, - { - "kind": "ENUM", - "name": "ExportTypeEnum", - "description": null, - "interfaces": null, - "possibleTypes": null, - "fields": null, - "inputFields": null, - "enumValues": [ - { - "name": "invoices", - "description": null, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "invoice_fees", - "description": null, - "isDeprecated": false, - "deprecationReason": null - } - ] - }, { "kind": "OBJECT", "name": "Fee", @@ -21437,6 +21687,29 @@ "inputFields": null, "enumValues": null }, + { + "kind": "ENUM", + "name": "InvoiceExportTypeEnum", + "description": null, + "interfaces": null, + "possibleTypes": null, + "fields": null, + "inputFields": null, + "enumValues": [ + { + "name": "invoices", + "description": null, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "invoice_fees", + "description": null, + "isDeprecated": false, + "deprecationReason": null + } + ] + }, { "kind": "INTERFACE", "name": "InvoiceItem", @@ -23388,6 +23661,35 @@ } ] }, + { + "name": "createCreditNotesDataExport", + "description": "Request data export of credit notes", + "type": { + "kind": "OBJECT", + "name": "DataExport", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null, + "args": [ + { + "name": "input", + "description": "Parameters for CreateCreditNotesDataExport", + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "INPUT_OBJECT", + "name": "CreateDataExportsCreditNotesInput", + "ofType": null + } + }, + "defaultValue": null, + "isDeprecated": false, + "deprecationReason": null + } + ] + }, { "name": "createCustomer", "description": "Creates a new customer", @@ -28077,6 +28379,22 @@ "deprecationReason": null, "args": [] }, + { + "name": "creditNotesExport", + "description": null, + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "Boolean", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null, + "args": [] + }, { "name": "creditNotesUpdate", "description": null, @@ -30797,8 +31115,76 @@ "deprecationReason": null, "args": [ { - "name": "customerId", + "name": "amountFrom", + "description": null, + "type": { + "kind": "SCALAR", + "name": "Int", + "ofType": null + }, + "defaultValue": null, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "amountTo", "description": null, + "type": { + "kind": "SCALAR", + "name": "Int", + "ofType": null + }, + "defaultValue": null, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "creditStatus", + "description": null, + "type": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "ENUM", + "name": "CreditNoteCreditStatusEnum", + "ofType": null + } + } + }, + "defaultValue": null, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "currency", + "description": null, + "type": { + "kind": "ENUM", + "name": "CurrencyEnum", + "ofType": null + }, + "defaultValue": null, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "customerExternalId", + "description": null, + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "defaultValue": null, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "customerId", + "description": "Uniq ID of the customer", "type": { "kind": "SCALAR", "name": "ID", @@ -30808,6 +31194,42 @@ "isDeprecated": false, "deprecationReason": null }, + { + "name": "invoiceNumber", + "description": null, + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "defaultValue": null, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "issuingDateFrom", + "description": null, + "type": { + "kind": "SCALAR", + "name": "ISO8601Date", + "ofType": null + }, + "defaultValue": null, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "issuingDateTo", + "description": null, + "type": { + "kind": "SCALAR", + "name": "ISO8601Date", + "ofType": null + }, + "defaultValue": null, + "isDeprecated": false, + "deprecationReason": null + }, { "name": "limit", "description": null, @@ -30832,6 +31254,46 @@ "isDeprecated": false, "deprecationReason": null }, + { + "name": "reason", + "description": null, + "type": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "ENUM", + "name": "CreditNoteReasonEnum", + "ofType": null + } + } + }, + "defaultValue": null, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "refundStatus", + "description": null, + "type": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "ENUM", + "name": "CreditNoteRefundStatusEnum", + "ofType": null + } + } + }, + "defaultValue": null, + "isDeprecated": false, + "deprecationReason": null + }, { "name": "searchTerm", "description": null, diff --git a/spec/graphql/mutations/data_exports/credit_notes/create_spec.rb b/spec/graphql/mutations/data_exports/credit_notes/create_spec.rb new file mode 100644 index 00000000000..93fbebf29a7 --- /dev/null +++ b/spec/graphql/mutations/data_exports/credit_notes/create_spec.rb @@ -0,0 +1,61 @@ +# frozen_string_literal: true + +require "rails_helper" + +RSpec.describe Mutations::DataExports::CreditNotes::Create, type: :graphql do + subject(:result) do + execute_graphql( + current_user: membership.user, + current_organization: organization, + permissions: required_permission, + query: mutation, + variables: + ) + end + + let(:required_permission) { "credit_notes:export" } + let(:membership) { create(:membership) } + let(:organization) { membership.organization } + + let(:mutation) do + <<-GQL + mutation($input: CreateDataExportsCreditNotesInput!) { + createCreditNotesDataExport(input: $input) { + id, + status, + } + } + GQL + end + + let(:variables) do + { + input: { + format: "csv", + resourceType: "credit_notes", + filters: { + amountFrom: 2000, + amountTo: 5000, + creditStatus: %w[available consumed], + currency: "USD", + customerExternalId: "abc123", + issuingDateFrom: "2024-05-23", + issuingDateTo: "2024-07-01", + reason: %w[duplicated_charge product_unsatisfactory], + refundStatus: %w[pending succeeded], + searchTerm: "abc" + } + } + } + end + + it_behaves_like "requires current user" + it_behaves_like "requires current organization" + it_behaves_like "requires permission", "credit_notes:export" + + it "creates data export" do + result_data = result["data"]["createCreditNotesDataExport"] + + expect(result_data).to include("id" => String, "status" => "pending") + end +end diff --git a/spec/graphql/mutations/data_exports/invoices/create_spec.rb b/spec/graphql/mutations/data_exports/invoices/create_spec.rb index 525a128dd4b..ead616cabf7 100644 --- a/spec/graphql/mutations/data_exports/invoices/create_spec.rb +++ b/spec/graphql/mutations/data_exports/invoices/create_spec.rb @@ -6,7 +6,6 @@ let(:required_permission) { 'invoices:export' } let(:membership) { create(:membership) } let(:organization) { membership.organization } - let(:currency) { 'EUR' } let(:mutation) do <<-GQL diff --git a/spec/graphql/resolvers/credit_notes_resolver_spec.rb b/spec/graphql/resolvers/credit_notes_resolver_spec.rb index c1bd9f0c6e6..c02a5d7c7f8 100644 --- a/spec/graphql/resolvers/credit_notes_resolver_spec.rb +++ b/spec/graphql/resolvers/credit_notes_resolver_spec.rb @@ -3,11 +3,20 @@ require 'rails_helper' RSpec.describe Resolvers::CreditNotesResolver, type: :graphql do + subject(:result) do + execute_graphql( + current_user: membership.user, + current_organization: organization, + permissions: required_permission, + query: + ) + end + let(:required_permission) { 'credit_notes:view' } let(:query) do <<~GQL query { - creditNotes(customerId: "#{customer_id}", searchTerm: "#{search_term}", limit: 5) { + creditNotes(#{[arguments, "limit: 5"].join(", ")}) { collection { id number } metadata { currentPage, totalCount } } @@ -18,102 +27,190 @@ let(:membership) { create(:membership) } let(:organization) { membership.organization } let(:customer) { create(:customer, organization:) } - let(:subscription) { create(:subscription, customer:, organization:) } - let(:credit_note) { create(:credit_note, customer:) } - let(:customer_id) { nil } - let(:search_term) { nil } - - before do - subscription - credit_note - create(:credit_note, :draft, organization:, customer:) - end + let(:arguments) { "" } + + let(:response_collection) { result['data']['creditNotes']['collection'] } + + before { create(:credit_note, :draft, customer:) } it_behaves_like 'requires current user' it_behaves_like 'requires current organization' it_behaves_like 'requires permission', 'credit_notes:view' - it 'returns a list of finalized credit_notes' do - result = execute_graphql( - current_user: membership.user, - current_organization: organization, - permissions: required_permission, - query: - ) + context 'with no arguments' do + let!(:credit_note) { create(:credit_note, customer:) } + + it 'returns finalized credit_notes' do + expect(response_collection.pluck('id')).to contain_exactly credit_note.id - credit_notes_response = result['data']['creditNotes'] + expect(result['data']['creditNotes']['metadata']['currentPage']).to eq(1) + expect(result['data']['creditNotes']['metadata']['totalCount']).to eq(1) + end + end + + context 'with currency' do + let(:arguments) { "currency: #{credit_note.currency.upcase}" } + let!(:credit_note) { create(:credit_note, customer:, total_amount_currency: "USD") } + + before { create(:credit_note, customer:, total_amount_currency: "EUR") } + + it 'returns finalized credit_notes matching currency' do + expect(response_collection.pluck('id')).to contain_exactly credit_note.id + end + end + + context 'with customer_external_id' do + let(:arguments) { "customerExternalId: #{customer.external_id.inspect}" } + let!(:credit_note) { create(:credit_note, customer:) } - aggregate_failures do - expect(credit_notes_response['collection'].count).to eq(1) - expect(credit_notes_response['collection'].first['id']).to eq(credit_note.id) + before do + another_customer = create(:customer, organization:) + create(:credit_note, customer: another_customer) + end - expect(credit_notes_response['metadata']['currentPage']).to eq(1) - expect(credit_notes_response['metadata']['totalCount']).to eq(1) + it 'returns finalized credit_notes with matching customer external id' do + expect(response_collection.pluck('id')).to contain_exactly credit_note.id end end context 'with customer_id' do - let(:customer_id) { customer.id } - - it 'returns a list of finalized credit_notes for a customer' do - result = execute_graphql( - current_user: membership.user, - current_organization: organization, - permissions: required_permission, - query: + let(:arguments) { "customerId: #{customer.id.inspect}" } + let!(:credit_note) { create(:credit_note, customer:) } + + before do + another_customer = create(:customer, organization:) + create(:credit_note, customer: another_customer) + end + + it 'returns finalized credit_notes with matching customer id' do + expect(response_collection.pluck('id')).to contain_exactly credit_note.id + end + end + + context 'with reason' do + let(:arguments) { "reason: [#{matching_reasons.map(&:to_s).join(", ")}]" } + let(:matching_reasons) { CreditNote::REASON.sample(2) } + + let!(:credit_notes) do + matching_reasons.map { |reason| create(:credit_note, reason:, customer:) } + end + + before do + create( + :credit_note, + reason: CreditNote::REASON.excluding(matching_reasons).sample, + customer: ) + end - credit_notes_response = result['data']['creditNotes'] + it 'returns finalized credit_notes with matching reasons' do + expect(response_collection.pluck('id')).to match_array credit_notes.pluck(:id) + end + end - aggregate_failures do - expect(credit_notes_response['collection'].count).to eq(1) - expect(credit_notes_response['collection'].first['id']).to eq(credit_note.id) + context 'with credit_status' do + let(:arguments) { "creditStatus: [#{matching_credit_statuses.map(&:to_s).join(", ")}]" } + let(:matching_credit_statuses) { CreditNote::CREDIT_STATUS.sample(2) } - expect(credit_notes_response['metadata']['currentPage']).to eq(1) - expect(credit_notes_response['metadata']['totalCount']).to eq(1) + let!(:credit_notes) do + matching_credit_statuses.map do |credit_status| + create(:credit_note, credit_status:, customer:) end end + + before do + create( + :credit_note, + credit_status: CreditNote::CREDIT_STATUS.excluding(matching_credit_statuses).sample, + customer: + ) + end + + it 'returns finalized credit_notes with matching credit statuses' do + expect(response_collection.pluck('id')).to match_array credit_notes.pluck(:id) + end end - context 'with search_terms' do - let(:search_term) { "yolo" } + context 'with refund_status' do + let(:arguments) { "refundStatus: [#{matching_refund_statuses.map(&:to_s).join(", ")}]" } + let(:matching_refund_statuses) { CreditNote::REFUND_STATUS.sample(2) } - it 'returns a list of finalized credit_notes matching the terms' do - create(:credit_note, number: 'yolo', customer: create(:customer, organization:)) + let!(:credit_notes) do + matching_refund_statuses.map do |refund_status| + create(:credit_note, refund_status:, customer:) + end + end - result = execute_graphql( - current_user: membership.user, - current_organization: organization, - permissions: required_permission, - query: + before do + create( + :credit_note, + refund_status: CreditNote::REFUND_STATUS.excluding(matching_refund_statuses).sample, + customer: ) + end - credit_notes_response = result['data']['creditNotes'] + it 'returns finalized credit_notes with matching refund statuses' do + expect(response_collection.pluck('id')).to match_array credit_notes.pluck(:id) + end + end - aggregate_failures do - expect(credit_notes_response['collection'].count).to eq(1) - expect(credit_notes_response['collection'].first['number']).to eq("yolo") + context 'with invoice_number' do + let(:arguments) { "invoiceNumber: #{credit_note.invoice.number.inspect}" } + let!(:credit_note) { create(:credit_note, customer:) } - expect(credit_notes_response['metadata']['currentPage']).to eq(1) - expect(credit_notes_response['metadata']['totalCount']).to eq(1) - end + before { create(:credit_note, customer:) } + + it 'returns finalized credit_notes matching invoice number' do + expect(response_collection.pluck('id')).to contain_exactly credit_note.id end end - context 'when customer does not exists' do - let(:customer_id) { 'unknown' } + context 'with both issuing_date_from and issuing_date_to' do + let(:arguments) do + [ + "issuingDateFrom: #{credit_notes.second.issuing_date.to_s.inspect}", + "issuingDateTo: #{credit_notes.fourth.issuing_date.to_s.inspect}" + ].join(", ") + end - it 'returns an error' do - result = execute_graphql( - current_user: membership.user, - current_organization: organization, - permissions: required_permission, - query: - ) + let!(:credit_notes) do + (1..5).to_a.map do |i| + create(:credit_note, issuing_date: i.days.ago, customer:) + end.reverse # from oldest to newest + end + + it 'returns finalized credit notes that were issued between provided dates' do + expect(response_collection.pluck('id')).to match_array credit_notes[1..3].pluck(:id) + end + end + + context 'with both amount_from and amount_to' do + let(:arguments) do + [ + "amountFrom: #{credit_notes.second.total_amount_cents.inspect}", + "amountTo: #{credit_notes.fourth.total_amount_cents.inspect}" + ].join(", ") + end + + let!(:credit_notes) do + (1..5).to_a.map do |i| + create(:credit_note, total_amount_cents: i.succ * 1_000, customer:) + end # from smallest to biggest + end + + it 'returns finalized credit notes total cents amount in provided range' do + expect(response_collection.pluck('id')).to match_array credit_notes[1..3].pluck(:id) + end + end + + context 'with search_term' do + let(:arguments) { "searchTerm: #{credit_note.number.inspect}" } + let!(:credit_note) { create(:credit_note, customer:) } - credit_notes_response = result['data']['creditNotes'] + before { create(:credit_note, customer:) } - expect(credit_notes_response['collection']).to be_empty + it 'returns finalized credit_notes matching the terms' do + expect(response_collection.pluck('id')).to contain_exactly credit_note.id end end end diff --git a/spec/graphql/types/data_exports/credit_notes/filters_input_spec.rb b/spec/graphql/types/data_exports/credit_notes/filters_input_spec.rb new file mode 100644 index 00000000000..14ab5bc91c2 --- /dev/null +++ b/spec/graphql/types/data_exports/credit_notes/filters_input_spec.rb @@ -0,0 +1,20 @@ +# frozen_string_literal: true + +require 'rails_helper' + +RSpec.describe Types::DataExports::CreditNotes::FiltersInput do + subject { described_class } + + it { is_expected.to accept_argument(:amount_from).of_type('Int') } + it { is_expected.to accept_argument(:amount_to).of_type('Int') } + it { is_expected.to accept_argument(:credit_status).of_type('[CreditNoteCreditStatusEnum!]') } + it { is_expected.to accept_argument(:currency).of_type('CurrencyEnum') } + it { is_expected.to accept_argument(:customer_external_id).of_type('String') } + it { is_expected.to accept_argument(:customer_id).of_type('ID') } + it { is_expected.to accept_argument(:invoice_number).of_type('String') } + it { is_expected.to accept_argument(:issuing_date_from).of_type('ISO8601Date') } + it { is_expected.to accept_argument(:issuing_date_to).of_type('ISO8601Date') } + it { is_expected.to accept_argument(:reason).of_type('[CreditNoteReasonEnum!]') } + it { is_expected.to accept_argument(:refund_status).of_type('[CreditNoteRefundStatusEnum!]') } + it { is_expected.to accept_argument(:search_term).of_type('String') } +end