diff --git a/app/contracts/queries/customers_query_filters_contract.rb b/app/contracts/queries/customers_query_filters_contract.rb new file mode 100644 index 00000000000..4651291f93a --- /dev/null +++ b/app/contracts/queries/customers_query_filters_contract.rb @@ -0,0 +1,13 @@ +# frozen_string_literal: true + +module Queries + class CustomersQueryFiltersContract < Dry::Validation::Contract + params do + required(:filters).hash do + optional(:account_type).array(:string, included_in?: Customer::ACCOUNT_TYPES.values) + end + + optional(:search_term).maybe(:string) + end + end +end diff --git a/app/controllers/api/v1/credit_notes_controller.rb b/app/controllers/api/v1/credit_notes_controller.rb index ae053331f07..d0ce4d067f0 100644 --- a/app/controllers/api/v1/credit_notes_controller.rb +++ b/app/controllers/api/v1/credit_notes_controller.rb @@ -101,23 +101,24 @@ def index }, search_term: params[:search_term], filters: { + amount_from: params[:amount_from], + amount_to: params[:amount_to], + credit_status: params[:credit_status], currency: params[:currency], customer_external_id: params[:external_customer_id], - reason: params[:reason], - credit_status: params[:credit_status], - refund_status: params[:refund_status], invoice_number: params[:invoice_number], issuing_date_from: (Date.strptime(params[:issuing_date_from]) if valid_date?(params[:issuing_date_from])), issuing_date_to: (Date.strptime(params[:issuing_date_to]) if valid_date?(params[:issuing_date_to])), - amount_from: params[:amount_from], - amount_to: params[:amount_to] + reason: params[:reason], + refund_status: params[:refund_status], + self_billed: params[:self_billed] } ) if result.success? render( json: ::CollectionSerializer.new( - result.credit_notes.includes(:items, :applied_taxes), + result.credit_notes.includes(:items, :applied_taxes, :invoice), ::V1::CreditNoteSerializer, collection_name: "credit_notes", meta: pagination_metadata(result.credit_notes), diff --git a/app/controllers/api/v1/customers_controller.rb b/app/controllers/api/v1/customers_controller.rb index aec41e90e3c..0fa495e3074 100644 --- a/app/controllers/api/v1/customers_controller.rb +++ b/app/controllers/api/v1/customers_controller.rb @@ -41,7 +41,8 @@ def index pagination: { page: params[:page], limit: params[:per_page] || PER_PAGE - } + }, + filters: params.slice(:account_type) ) if result.success? diff --git a/app/controllers/api/v1/fees_controller.rb b/app/controllers/api/v1/fees_controller.rb index 8c7d5a8bfb8..d32710f449d 100644 --- a/app/controllers/api/v1/fees_controller.rb +++ b/app/controllers/api/v1/fees_controller.rb @@ -37,7 +37,7 @@ def index if result.success? render( json: ::CollectionSerializer.new( - result.fees.includes(:applied_taxes), + result.fees.includes(:applied_taxes, :invoice), ::V1::FeeSerializer, collection_name: 'fees', meta: pagination_metadata(result.fees), diff --git a/app/controllers/api/v1/invoices_controller.rb b/app/controllers/api/v1/invoices_controller.rb index d21526ed87a..2381f0fdd97 100644 --- a/app/controllers/api/v1/invoices_controller.rb +++ b/app/controllers/api/v1/invoices_controller.rb @@ -53,17 +53,18 @@ def index filters: { amount_from: params[:amount_from], amount_to: params[:amount_to], - payment_status: (params[:payment_status] if valid_payment_status?(params[:payment_status])), - payment_dispute_lost: params[:payment_dispute_lost], - payment_overdue: (params[:payment_overdue] if %w[true false].include?(params[:payment_overdue])), - partially_paid: (params[:partially_paid] if %w[true false].include?(params[:partially_paid])), - status: (params[:status] if valid_status?(params[:status])), currency: params[:currency], customer_external_id: params[:external_customer_id], invoice_type: params[:invoice_type], issuing_date_from: (Date.strptime(params[:issuing_date_from]) if valid_date?(params[:issuing_date_from])), issuing_date_to: (Date.strptime(params[:issuing_date_to]) if valid_date?(params[:issuing_date_to])), - metadata: params[:metadata]&.permit!.to_h + metadata: params[:metadata]&.permit!.to_h, + partially_paid: (params[:partially_paid] if %w[true false].include?(params[:partially_paid])), + payment_dispute_lost: params[:payment_dispute_lost], + payment_overdue: (params[:payment_overdue] if %w[true false].include?(params[:payment_overdue])), + payment_status: (params[:payment_status] if valid_payment_status?(params[:payment_status])), + self_billed: (params[:self_billed] if %w[true false].include?(params[:self_billed])), + status: (params[:status] if valid_status?(params[:status])) } ) diff --git a/app/graphql/resolvers/credit_notes_resolver.rb b/app/graphql/resolvers/credit_notes_resolver.rb index 6797bf9f671..f20d625f0ad 100644 --- a/app/graphql/resolvers/credit_notes_resolver.rb +++ b/app/graphql/resolvers/credit_notes_resolver.rb @@ -9,6 +9,11 @@ class CreditNotesResolver < Resolvers::BaseResolver description 'Query credit notes' + argument :search_term, String, required: false + + argument :limit, Integer, required: false + argument :page, Integer, required: false + argument :amount_from, Integer, required: false argument :amount_to, Integer, required: false argument :credit_status, [Types::CreditNotes::CreditStatusTypeEnum], required: false @@ -18,11 +23,9 @@ class CreditNotesResolver < Resolvers::BaseResolver 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 + argument :self_billed, Boolean, required: false type Types::CreditNotes::Object.collection_type, null: false @@ -41,7 +44,8 @@ def resolve(args) issuing_date_from: args[:issuing_date_from], issuing_date_to: args[:issuing_date_to], reason: args[:reason], - refund_status: args[:refund_status] + refund_status: args[:refund_status], + self_billed: args[:self_billed] }, pagination: { page: args[:page], diff --git a/app/graphql/resolvers/customers_resolver.rb b/app/graphql/resolvers/customers_resolver.rb index 3762cfbbb44..2162aa07ff3 100644 --- a/app/graphql/resolvers/customers_resolver.rb +++ b/app/graphql/resolvers/customers_resolver.rb @@ -11,18 +11,22 @@ class CustomersResolver < Resolvers::BaseResolver argument :limit, Integer, required: false argument :page, Integer, required: false + argument :search_term, String, required: false + argument :account_type, [Types::Customers::AccountTypeEnum], required: false + type Types::Customers::Object.collection_type, null: false - def resolve(page: nil, limit: nil, search_term: nil) + def resolve(**args) result = CustomersQuery.call( organization: current_organization, - search_term:, + search_term: args[:search_term], pagination: { - page:, - limit: - } + page: args[:page], + limit: args[:limit] + }, + filters: args.slice(:account_type) ) result.customers diff --git a/app/graphql/resolvers/invoices_resolver.rb b/app/graphql/resolvers/invoices_resolver.rb index a6a235e1d4a..09712c352eb 100644 --- a/app/graphql/resolvers/invoices_resolver.rb +++ b/app/graphql/resolvers/invoices_resolver.rb @@ -23,6 +23,7 @@ class InvoicesResolver < Resolvers::BaseResolver argument :payment_overdue, Boolean, required: false argument :payment_status, [Types::Invoices::PaymentStatusTypeEnum], required: false argument :search_term, String, required: false + argument :self_billed, Boolean, required: false argument :status, [Types::Invoices::StatusTypeEnum], required: false type Types::Invoices::Object.collection_type, null: false @@ -36,13 +37,14 @@ def resolve( # rubocop:disable Metrics/ParameterLists invoice_type: nil, issuing_date_from: nil, issuing_date_to: nil, - page: nil, limit: nil, + page: nil, + payment_dispute_lost: nil, + payment_overdue: nil, payment_status: nil, - status: nil, search_term: nil, - payment_dispute_lost: nil, - payment_overdue: nil + self_billed: nil, + status: nil ) result = InvoicesQuery.call( organization: current_organization, @@ -51,16 +53,17 @@ def resolve( # rubocop:disable Metrics/ParameterLists filters: { amount_from:, amount_to:, - payment_status:, - payment_dispute_lost:, - payment_overdue:, - status:, currency:, customer_external_id:, customer_id:, invoice_type:, issuing_date_from:, - issuing_date_to: + issuing_date_to:, + payment_dispute_lost:, + payment_overdue:, + payment_status:, + self_billed:, + status: } ) diff --git a/app/graphql/types/customer_portal/customers/object.rb b/app/graphql/types/customer_portal/customers/object.rb index ef8a1d226c0..0fd902778b1 100644 --- a/app/graphql/types/customer_portal/customers/object.rb +++ b/app/graphql/types/customer_portal/customers/object.rb @@ -8,6 +8,7 @@ class Object < Types::BaseObject field :id, ID, null: false + field :account_type, Types::Customers::AccountTypeEnum, null: false field :applicable_timezone, Types::TimezoneEnum, null: false field :currency, Types::CurrencyEnum, null: true field :customer_type, Types::Customers::CustomerTypeEnum diff --git a/app/models/customer.rb b/app/models/customer.rb index b54c4a7de79..9dd353ee424 100644 --- a/app/models/customer.rb +++ b/app/models/customer.rb @@ -205,7 +205,7 @@ def empty_billing_and_shipping_address? end def overdue_balance_cents - invoices.payment_overdue.where(currency:).sum(:total_amount_cents) + invoices.non_self_billed.payment_overdue.where(currency:).sum(:total_amount_cents) end def reset_dunning_campaign! diff --git a/app/models/invoice.rb b/app/models/invoice.rb index d47b7c2c401..bacbc7b397f 100644 --- a/app/models/invoice.rb +++ b/app/models/invoice.rb @@ -122,6 +122,9 @@ class Invoice < ApplicationRecord .distinct } + scope :self_billed, -> { where(self_billed: true) } + scope :non_self_billed, -> { where(self_billed: false) } + validates :issuing_date, :currency, presence: true validates :timezone, timezone: true, allow_nil: true validates :total_amount_cents, numericality: {greater_than_or_equal_to: 0} @@ -402,7 +405,7 @@ def generate_organization_sequential_id "date_trunc('month', created_at::timestamptz AT TIME ZONE ?)::date = ?", timezone, Time.now.in_time_zone(timezone).beginning_of_month.to_date - ).where(self_billed: false) + ).non_self_billed result = Invoice.with_advisory_lock( organization_id, @@ -415,7 +418,7 @@ def generate_organization_sequential_id else organization .invoices - .where(self_billed: false) + .non_self_billed .where.not(organization_sequential_id: 0) .order(organization_sequential_id: :desc) .limit(1) @@ -437,7 +440,7 @@ def generate_organization_sequential_id end def switched_from_customer_numbering? - last_invoice = organization.invoices.where(self_billed: false).order(created_at: :desc).with_generated_number.first + last_invoice = organization.invoices.non_self_billed.order(created_at: :desc).with_generated_number.first return false unless last_invoice diff --git a/app/queries/credit_notes_query.rb b/app/queries/credit_notes_query.rb index 6bc46427fa4..f95521df01c 100644 --- a/app/queries/credit_notes_query.rb +++ b/app/queries/credit_notes_query.rb @@ -15,6 +15,7 @@ def call credit_notes = with_invoice_number(credit_notes) if filters.invoice_number.present? credit_notes = with_issuing_date_range(credit_notes) if filters.issuing_date_from || filters.issuing_date_to credit_notes = with_amount_range(credit_notes) if filters.amount_from.present? || filters.amount_to.present? + credit_notes = with_self_billed_invoice(credit_notes) unless filters.self_billed.nil? result.credit_notes = credit_notes result @@ -93,6 +94,14 @@ def with_amount_range(scope) scope end + def with_self_billed_invoice(scope) + scope + .joins(:invoice) + .where(invoices: { + self_billed: ActiveModel::Type::Boolean.new.cast(filters.self_billed) + }) + end + def issuing_date_from @issuing_date_from ||= parse_datetime_filter(:issuing_date_from) end diff --git a/app/queries/customers_query.rb b/app/queries/customers_query.rb index 8b0b89c6042..bf3b5246677 100644 --- a/app/queries/customers_query.rb +++ b/app/queries/customers_query.rb @@ -2,16 +2,24 @@ class CustomersQuery < BaseQuery def call + return result unless validate_filters.success? + customers = base_scope.result customers = paginate(customers) customers = apply_consistent_ordering(customers) + customers = with_account_type(customers) if filters.account_type.present? + result.customers = customers result end private + def filters_contract + @filters_contract ||= Queries::CustomersQueryFiltersContract.new + end + def base_scope Customer.where(organization:).ransack(search_params) end @@ -29,4 +37,8 @@ def search_params email_cont: search_term } end + + def with_account_type(scope) + scope.where(account_type: filters.account_type) + end end diff --git a/app/queries/invoices_query.rb b/app/queries/invoices_query.rb index 3ba8ec27839..decd94b1e5a 100644 --- a/app/queries/invoices_query.rb +++ b/app/queries/invoices_query.rb @@ -21,6 +21,7 @@ def call invoices = with_amount_range(invoices) if filters.amount_from.present? || filters.amount_to.present? invoices = with_metadata(invoices) if filters.metadata.present? invoices = with_partially_paid(invoices) unless filters.partially_paid.nil? + invoices = with_self_billed(invoices) unless filters.self_billed.nil? result.invoices = invoices result @@ -141,6 +142,10 @@ def with_metadata(scope) scope.where(id: subquery.select(:id)) end + def with_self_billed(scope) + scope.where(self_billed: ActiveModel::Type::Boolean.new.cast(filters.self_billed)) + end + def issuing_date_from @issuing_date_from ||= parse_datetime_filter(:issuing_date_from) end diff --git a/app/serializers/v1/credit_note_serializer.rb b/app/serializers/v1/credit_note_serializer.rb index 621e92425a4..faaedaabb45 100644 --- a/app/serializers/v1/credit_note_serializer.rb +++ b/app/serializers/v1/credit_note_serializer.rb @@ -25,7 +25,8 @@ def serialize taxes_rate: model.taxes_rate, created_at: model.created_at.iso8601, updated_at: model.updated_at.iso8601, - file_url: model.file_url + file_url: model.file_url, + self_billed: model.invoice.self_billed } payload.merge!(customer) if include?(:customer) diff --git a/app/serializers/v1/fee_serializer.rb b/app/serializers/v1/fee_serializer.rb index 5284554c3a0..54519435230 100644 --- a/app/serializers/v1/fee_serializer.rb +++ b/app/serializers/v1/fee_serializer.rb @@ -49,7 +49,8 @@ def serialize succeeded_at: model.succeeded_at&.iso8601, failed_at: model.failed_at&.iso8601, refunded_at: model.refunded_at&.iso8601, - amount_details: model.amount_details + amount_details: model.amount_details, + self_billed: model.invoice&.self_billed } payload.merge!(date_boundaries) if model.charge? || model.subscription? diff --git a/app/views/templates/invoices/v4/_one_off.slim b/app/views/templates/invoices/v4/_one_off.slim new file mode 100644 index 00000000000..4d83d646390 --- /dev/null +++ b/app/views/templates/invoices/v4/_one_off.slim @@ -0,0 +1,47 @@ +table.invoice-resume-table width="100%" + tr + td.body-2 = I18n.t('invoice.item') + td.body-2 = I18n.t('invoice.units') + td.body-2 = I18n.t('invoice.unit_price') + td.body-2 = I18n.t('invoice.tax_rate') + td.body-2 = I18n.t('invoice.amount') + - if one_off? + - fees.each do |fee| + tr + td + .body-1 = fee.invoice_name + .body-3 = fee.description + td.body-2 = fee.units + td.body-2 = MoneyHelper.format(fee.unit_amount) + td.body-2 == TaxHelper.applied_taxes(fee) + td.body-2 = MoneyHelper.format(fee.amount) + +table.total-table width="100%" + tr + td.body-2 + td.body-2 = I18n.t('invoice.sub_total_without_tax') + td.body-2 = MoneyHelper.format(sub_total_excluding_taxes_amount) + - if applied_taxes.present? + - applied_taxes.order(tax_rate: :desc).each do |applied_tax| + tr + - if applied_tax.applied_on_whole_invoice? + td.body-2 + td.body-2 = I18n.t('invoice.tax_name_only.' + applied_tax.tax_code) + td.body-2 + - else + td.body-2 + td.body-2 = I18n.t('invoice.tax_name', name: applied_tax.tax_name, rate: applied_tax.tax_rate, amount: MoneyHelper.format(applied_tax.taxable_amount)) + td.body-2 = MoneyHelper.format(applied_tax.amount) + - else + tr + td.body-2 + td.body-2 = I18n.t('invoice.tax_name_with_details', name: 'Tax', rate: 0) + td.body-2 = MoneyHelper.format(0.to_money(currency)) + tr + td.body-2 + td.body-2 = I18n.t('invoice.sub_total_with_tax') + td.body-2 = MoneyHelper.format(sub_total_including_taxes_amount) + tr + td.body-2 + td.body-1 = I18n.t('invoice.total_due') + td.body-1 = MoneyHelper.format(total_amount) diff --git a/app/views/templates/invoices/v4/one_off.slim b/app/views/templates/invoices/v4/one_off.slim index dd63a93aae8..a207b278377 100644 --- a/app/views/templates/invoices/v4/one_off.slim +++ b/app/views/templates/invoices/v4/one_off.slim @@ -415,53 +415,7 @@ html .body-1 = I18n.t('invoice.due_date', date: I18n.l(payment_due_date, format: :default)) .invoice-resume.mb-24.overflow-auto - table.invoice-resume-table width="100%" - tr - td.body-2 = I18n.t('invoice.item') - td.body-2 = I18n.t('invoice.units') - td.body-2 = I18n.t('invoice.unit_price') - td.body-2 = I18n.t('invoice.tax_rate') - td.body-2 = I18n.t('invoice.amount') - - if one_off? - - fees.each do |fee| - tr - td - .body-1 = fee.invoice_name - .body-3 = fee.description - td.body-2 = fee.units - td.body-2 = MoneyHelper.format(fee.unit_amount) - td.body-2 == TaxHelper.applied_taxes(fee) - td.body-2 = MoneyHelper.format(fee.amount) - - table.total-table width="100%" - tr - td.body-2 - td.body-2 = I18n.t('invoice.sub_total_without_tax') - td.body-2 = MoneyHelper.format(sub_total_excluding_taxes_amount) - - if applied_taxes.present? - - applied_taxes.order(tax_rate: :desc).each do |applied_tax| - tr - - if applied_tax.applied_on_whole_invoice? - td.body-2 - td.body-2 = I18n.t('invoice.tax_name_only.' + applied_tax.tax_code) - td.body-2 - - else - td.body-2 - td.body-2 = I18n.t('invoice.tax_name', name: applied_tax.tax_name, rate: applied_tax.tax_rate, amount: MoneyHelper.format(applied_tax.taxable_amount)) - td.body-2 = MoneyHelper.format(applied_tax.amount) - - else - tr - td.body-2 - td.body-2 = I18n.t('invoice.tax_name_with_details', name: 'Tax', rate: 0) - td.body-2 = MoneyHelper.format(0.to_money(currency)) - tr - td.body-2 - td.body-2 = I18n.t('invoice.sub_total_with_tax') - td.body-2 = MoneyHelper.format(sub_total_including_taxes_amount) - tr - td.body-2 - td.body-1 = I18n.t('invoice.total_due') - td.body-1 = MoneyHelper.format(total_amount) + == SlimHelper.render('templates/invoices/v4/_one_off', self) == SlimHelper.render('templates/invoices/v4/_eu_tax_management', self) diff --git a/app/views/templates/invoices/v4/self_billed.slim b/app/views/templates/invoices/v4/self_billed.slim index 7edd7276e38..2001115edf6 100644 --- a/app/views/templates/invoices/v4/self_billed.slim +++ b/app/views/templates/invoices/v4/self_billed.slim @@ -451,7 +451,9 @@ html .body-1 = I18n.t('invoice.due_date', date: I18n.l(payment_due_date, format: :default)) .invoice-resume.mb-24.overflow-auto - - if credit? + - if one_off? + == SlimHelper.render('templates/invoices/v4/_one_off', self) + - elsif credit? == SlimHelper.render('templates/invoices/v4/_credit', self) - elsif progressive_billing? == SlimHelper.render('templates/invoices/v4/_progressive_billing_details', self) diff --git a/config/locales/de/invoice.yml b/config/locales/de/invoice.yml index 495c2fee35e..dd67579817a 100644 --- a/config/locales/de/invoice.yml +++ b/config/locales/de/invoice.yml @@ -62,8 +62,8 @@ de: reached_usage_threshold: Diese progressive Rechnung wird erstellt, da Ihre kumulierte Nutzung %{usage_amount} erreicht hat und den Schwellenwert von %{threshold_amount} überschritten hat. see_breakdown: Siehe Aufschlüsselung für Gesamtübersicht self_billed: - document_name: Selbstabrechnungsrechnung - footer: Selbstabgerechnete Rechnung. Der Partner hat zugestimmt, für diese Transaktion keine eigene Rechnung auszustellen. + document_name: Gutschriftrechnung + footer: Diese Gutschriftrechnung wurde vom Kunden im Namen des Partners mit dessen Zustimmung erstellt. Der Partner hat zugestimmt, keine eigene Rechnung für diese Transaktion auszustellen. sub_total: Zwischensumme sub_total_with_tax: Zwischensumme (inkl. Steuern) sub_total_without_tax: Zwischensumme (ohne Steuern) diff --git a/config/locales/en/invoice.yml b/config/locales/en/invoice.yml index f5854989fd3..f148becdfbe 100644 --- a/config/locales/en/invoice.yml +++ b/config/locales/en/invoice.yml @@ -62,8 +62,8 @@ en: reached_usage_threshold: This progressive billing is generated because your cumulative usage has reached %{usage_amount}, exceeding the %{threshold_amount} threshold. see_breakdown: See breakdown for total unit self_billed: - document_name: Self billing invoice - footer: Self-billed invoice. The partner has agreed not to issue their own invoice for this transaction. + document_name: Self-billing invoice + footer: This self-billing invoice was issued by the client on behalf of the partner, with their consent. The partner has agreed not to issue their own invoice for this transaction. sub_total: Subtotal sub_total_with_tax: Subtotal (incl. tax) sub_total_without_tax: Subtotal (excl. tax) diff --git a/config/locales/es/invoice.yml b/config/locales/es/invoice.yml index 38dc7d685c6..0ab2263b74a 100644 --- a/config/locales/es/invoice.yml +++ b/config/locales/es/invoice.yml @@ -61,8 +61,8 @@ es: reached_usage_threshold: Esta facturación progresiva se genera porque su uso acumulado ha alcanzado los %{usage_amount}, superando el umbral de %{threshold_amount}. see_breakdown: Consulte el desglose a continuación self_billed: - document_name: Factura de autofacturación - footer: Factura autofacturada. El socio ha aceptado no emitir su propia factura para esta transacción. + document_name: Factura de autoliquidación + footer: Esta factura de autoliquidación ha sido emitida por el cliente en nombre del socio, con su consentimiento. El socio ha aceptado no emitir su propia factura para esta transacción. sub_total: Subtotal sub_total_with_tax: Subtotal (impuestos incl.) sub_total_without_tax: Subtotal (impuestos excl.) diff --git a/config/locales/fr/invoice.yml b/config/locales/fr/invoice.yml index 4502fdf695f..f353c537a16 100644 --- a/config/locales/fr/invoice.yml +++ b/config/locales/fr/invoice.yml @@ -63,7 +63,7 @@ fr: see_breakdown: Consultez le détail ci-après self_billed: document_name: Facture d'autofacturation - footer: Facture autofacturée. Le partenaire a accepté de ne pas émettre sa propre facture pour cette transaction. + footer: Cette facture d'autofacturation a été émise par le client au nom du partenaire, avec son consentement. Le partenaire a accepté de ne pas établir sa propre facture pour cette transaction. sub_total: Sous total sub_total_with_tax: Sous total (TTC) sub_total_without_tax: Sous total (HT) diff --git a/config/locales/it/invoice.yml b/config/locales/it/invoice.yml index 83919fec276..d45eec4c34c 100644 --- a/config/locales/it/invoice.yml +++ b/config/locales/it/invoice.yml @@ -63,7 +63,7 @@ it: see_breakdown: Vedere la ripartizione per l'unità totale self_billed: document_name: Fattura di autofatturazione - footer: Fattura autofatturata. Il partner ha accettato di non emettere la propria fattura per questa transazione. + footer: Questa fattura di autofatturazione è stata emessa dal cliente per conto del partner, con il suo consenso. Il partner ha accettato di non emettere una propria fattura per questa transazione. sub_total: Subtotale sub_total_with_tax: Subtotale (incl. tasse) sub_total_without_tax: Subtotale (escl. tasse) diff --git a/config/locales/nb/invoice.yml b/config/locales/nb/invoice.yml index ca31eb30185..8d1619de12e 100644 --- a/config/locales/nb/invoice.yml +++ b/config/locales/nb/invoice.yml @@ -62,8 +62,8 @@ nb: reached_usage_threshold: Denne progressive faktureringen er generert fordi din akkumulerte bruk har nådd %{usage_amount}, og overskredet terskelen på %{threshold_amount}. see_breakdown: Se oversikt for antall enheter self_billed: - document_name: Selvfakturering - footer: Selvfakturert faktura. Partneren har gått med på å ikke utstede sin egen faktura for denne transaksjonen. + document_name: Egenfakturering + footer: Denne egenfakturaen ble utstedt av kunden på vegne av partneren, med deres samtykke. Partneren har gått med på å ikke utstede sin egen faktura for denne transaksjonen. sub_total: Sub total sub_total_with_tax: Sub total (inkl. MVA) sub_total_without_tax: Sub total (ekskl. MVA) diff --git a/config/locales/sv/invoice.yml b/config/locales/sv/invoice.yml index eafaff67f5a..daac8672230 100644 --- a/config/locales/sv/invoice.yml +++ b/config/locales/sv/invoice.yml @@ -61,8 +61,8 @@ sv: reached_usage_threshold: Denna progressiva fakturering skapas eftersom din ackumulerade användning har nått %{usage_amount} och överstiger %{threshold_amount} tröskeln. see_breakdown: Se uppdelning nedan self_billed: - document_name: Självfaktura - footer: Självfakturerad faktura. Partnern har gått med på att inte utfärda sin egen faktura för denna transaktion. + document_name: Självfakturering + footer: Denna självfaktura har utfärdats av kunden för partnerns räkning, med deras samtycke. Partnern har gått med på att inte utfärda en egen faktura för denna transaktion. sub_total: Delsumma sub_total_with_tax: Delsumma (inkl. moms) sub_total_without_tax: Delsumma (exkl. moms) diff --git a/schema.graphql b/schema.graphql index 48faf07705c..1d14da0ca0d 100644 --- a/schema.graphql +++ b/schema.graphql @@ -3391,6 +3391,7 @@ input CustomerMetadataInput { } type CustomerPortalCustomer { + accountType: CustomerAccountTypeEnum! addressLine1: String addressLine2: String applicableTimezone: TimezoneEnum! @@ -6593,6 +6594,7 @@ type Query { reason: [CreditNoteReasonEnum!] refundStatus: [CreditNoteRefundStatusEnum!] searchTerm: String + selfBilled: Boolean ): CreditNoteCollection! """ @@ -6678,7 +6680,7 @@ type Query { """ Query customers of an organization """ - customers(limit: Int, page: Int, searchTerm: String): CustomerCollection! + customers(accountType: [CustomerAccountTypeEnum!], limit: Int, page: Int, searchTerm: String): CustomerCollection! """ Query a single dunning campaign of an organization @@ -6849,6 +6851,7 @@ type Query { paymentOverdue: Boolean paymentStatus: [InvoicePaymentStatusTypeEnum!] searchTerm: String + selfBilled: Boolean status: [InvoiceStatusTypeEnum!] ): InvoiceCollection! diff --git a/schema.json b/schema.json index 359765a635b..3163e532dbd 100644 --- a/schema.json +++ b/schema.json @@ -13905,6 +13905,22 @@ "interfaces": [], "possibleTypes": null, "fields": [ + { + "name": "accountType", + "description": null, + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "ENUM", + "name": "CustomerAccountTypeEnum", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null, + "args": [] + }, { "name": "addressLine1", "description": null, @@ -31617,6 +31633,42 @@ "isDeprecated": false, "deprecationReason": null, "args": [ + { + "name": "searchTerm", + "description": null, + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "defaultValue": null, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "limit", + "description": null, + "type": { + "kind": "SCALAR", + "name": "Int", + "ofType": null + }, + "defaultValue": null, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "page", + "description": null, + "type": { + "kind": "SCALAR", + "name": "Int", + "ofType": null + }, + "defaultValue": null, + "isDeprecated": false, + "deprecationReason": null + }, { "name": "amountFrom", "description": null, @@ -31733,30 +31785,6 @@ "isDeprecated": false, "deprecationReason": null }, - { - "name": "limit", - "description": null, - "type": { - "kind": "SCALAR", - "name": "Int", - "ofType": null - }, - "defaultValue": null, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "page", - "description": null, - "type": { - "kind": "SCALAR", - "name": "Int", - "ofType": null - }, - "defaultValue": null, - "isDeprecated": false, - "deprecationReason": null - }, { "name": "reason", "description": null, @@ -31798,11 +31826,11 @@ "deprecationReason": null }, { - "name": "searchTerm", + "name": "selfBilled", "description": null, "type": { "kind": "SCALAR", - "name": "String", + "name": "Boolean", "ofType": null }, "defaultValue": null, @@ -32411,6 +32439,26 @@ "defaultValue": null, "isDeprecated": false, "deprecationReason": null + }, + { + "name": "accountType", + "description": null, + "type": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "ENUM", + "name": "CustomerAccountTypeEnum", + "ofType": null + } + } + }, + "defaultValue": null, + "isDeprecated": false, + "deprecationReason": null } ] }, @@ -33501,6 +33549,18 @@ "isDeprecated": false, "deprecationReason": null }, + { + "name": "selfBilled", + "description": null, + "type": { + "kind": "SCALAR", + "name": "Boolean", + "ofType": null + }, + "defaultValue": null, + "isDeprecated": false, + "deprecationReason": null + }, { "name": "status", "description": null, diff --git a/spec/contracts/queries/customers_query_filters_contract_spec.rb b/spec/contracts/queries/customers_query_filters_contract_spec.rb new file mode 100644 index 00000000000..d911f0f5cc0 --- /dev/null +++ b/spec/contracts/queries/customers_query_filters_contract_spec.rb @@ -0,0 +1,49 @@ +# frozen_string_literal: true + +require "rails_helper" + +RSpec.describe Queries::CustomersQueryFiltersContract, type: :contract do + subject(:result) { described_class.new.call(filters:, search_term:) } + + let(:filters) { {} } + let(:search_term) { nil } + + context "when filtering by account type" do + let(:filters) { {account_type: %w[customer partner]} } + + it "is valid" do + expect(result.success?).to be(true) + end + end + + context "when search_term is provided and valid" do + let(:search_term) { "valid_search_term" } + + it "is valid" do + expect(result.success?).to be(true) + end + end + + context "when search_term is invalid" do + let(:search_term) { 12345 } + + it "is invalid" do + expect(result.success?).to be(false) + expect(result.errors.to_h).to include(search_term: ["must be a string"]) + end + end + + context "when filters are invalid" do + shared_examples "an invalid filter" do |filter, value, error_message| + let(:filters) { {filter => value} } + + it "is invalid when #{filter} is set to #{value.inspect}" do + expect(result.success?).to be(false) + expect(result.errors.to_h).to include(filters: {filter => error_message}) + end + end + + it_behaves_like "an invalid filter", :account_type, nil, ["must be an array"] + it_behaves_like "an invalid filter", :account_type, %w[random], {0 => ["must be one of: customer, partner"]} + end +end diff --git a/spec/graphql/resolvers/credit_notes_resolver_spec.rb b/spec/graphql/resolvers/credit_notes_resolver_spec.rb index c02a5d7c7f8..5df3d579038 100644 --- a/spec/graphql/resolvers/credit_notes_resolver_spec.rb +++ b/spec/graphql/resolvers/credit_notes_resolver_spec.rb @@ -213,4 +213,40 @@ expect(response_collection.pluck('id')).to contain_exactly credit_note.id end end + + context "when filtering by self billed invoice" do + let(:self_billed_credit_note) do + invoice = create(:invoice, :self_billed, customer:, organization:) + + create(:credit_note, invoice:, customer:) + end + + let(:non_self_billed_credit_note) do + create(:credit_note, customer:) + end + + let(:query) do + <<~GQL + query { + creditNotes(limit: 5, selfBilled: true) { + collection { id } + metadata { currentPage, totalCount } + } + } + GQL + end + + before do + self_billed_credit_note + non_self_billed_credit_note + end + + it "returns all credit notes from self billed invoices" do + expect(response_collection.count).to eq(1) + expect(response_collection.first["id"]).to eq(self_billed_credit_note.id) + + expect(result["data"]["creditNotes"]["metadata"]["currentPage"]).to eq(1) + expect(result["data"]["creditNotes"]["metadata"]["totalCount"]).to eq(1) + end + end end diff --git a/spec/graphql/resolvers/customers_resolver_spec.rb b/spec/graphql/resolvers/customers_resolver_spec.rb index f383d0d9990..5e2f0888c07 100644 --- a/spec/graphql/resolvers/customers_resolver_spec.rb +++ b/spec/graphql/resolvers/customers_resolver_spec.rb @@ -53,4 +53,43 @@ ) end end + + context "when filtering by partner account type" do + let(:customer) { create(:customer, organization:) } + let(:partner) { create(:customer, organization:, account_type: "partner") } + + let(:query) do + <<~GQL + query($accountType: [CustomerAccountTypeEnum!]) { + customers(limit: 5, accountType: $accountType) { + collection { id } + metadata { currentPage, totalCount } + } + } + GQL + end + + before do + customer + partner + end + + it "returns all customers with account_type partner" do + result = execute_graphql( + current_user: membership.user, + current_organization: organization, + permissions: required_permission, + query:, + variables: {accountType: "partner"} + ) + + invoices_response = result["data"]["customers"] + + expect(invoices_response["collection"].count).to eq(1) + expect(invoices_response["collection"].first["id"]).to eq(partner.id) + + expect(invoices_response["metadata"]["currentPage"]).to eq(1) + expect(invoices_response["metadata"]["totalCount"]).to eq(1) + end + end end diff --git a/spec/graphql/resolvers/invoices_resolver_spec.rb b/spec/graphql/resolvers/invoices_resolver_spec.rb index 63aec0e7f54..d49992d53cf 100644 --- a/spec/graphql/resolvers/invoices_resolver_spec.rb +++ b/spec/graphql/resolvers/invoices_resolver_spec.rb @@ -403,4 +403,58 @@ expect(collection.pluck('id')).to match_array invoices[1..3].pluck(:id) end end + + context "when filtering by self billed" do + let(:invoice_third) do + create( + :invoice, + customer: customer_second, + status: :draft, + organization: + ) + end + + let(:invoice_fourth) do + create( + :invoice, + :self_billed, + customer: customer_second, + status: :finalized, + organization: + ) + end + + let(:query) do + <<~GQL + query { + invoices(limit: 5, selfBilled: true) { + collection { id } + metadata { currentPage, totalCount } + } + } + GQL + end + + before do + invoice_third + invoice_fourth + end + + it "returns all self billed invoices" do + result = execute_graphql( + current_user: membership.user, + current_organization: organization, + permissions: required_permission, + query: + ) + + invoices_response = result["data"]["invoices"] + + expect(invoices_response["collection"].count).to eq(1) + expect(invoices_response["collection"].first["id"]).to eq(invoice_fourth.id) + + expect(invoices_response["metadata"]["currentPage"]).to eq(1) + expect(invoices_response["metadata"]["totalCount"]).to eq(1) + end + end end diff --git a/spec/graphql/types/customer_portal/customers/object_spec.rb b/spec/graphql/types/customer_portal/customers/object_spec.rb index 4ce8b4d647a..b8b7c885dfc 100644 --- a/spec/graphql/types/customer_portal/customers/object_spec.rb +++ b/spec/graphql/types/customer_portal/customers/object_spec.rb @@ -7,6 +7,7 @@ it { is_expected.to have_field(:id).of_type("ID!") } + it { is_expected.to have_field(:account_type).of_type("CustomerAccountTypeEnum!") } it { is_expected.to have_field(:applicable_timezone).of_type("TimezoneEnum!") } it { is_expected.to have_field(:currency).of_type("CurrencyEnum") } it { is_expected.to have_field(:customer_type).of_type("CustomerTypeEnum") } diff --git a/spec/models/customer_spec.rb b/spec/models/customer_spec.rb index a74c3d078f7..008cbb3d7ca 100644 --- a/spec/models/customer_spec.rb +++ b/spec/models/customer_spec.rb @@ -668,6 +668,17 @@ expect(customer.overdue_balance_cents).to eq 2_00 end end + + context "when invoices are self billed" do + before do + create(:invoice, customer: customer, payment_overdue: true, currency: "USD", total_amount_cents: 2_00) + create(:invoice, :self_billed, customer: customer, payment_overdue: true, currency: "USD", total_amount_cents: 3_00) + end + + it "ignores self billed invoices" do + expect(customer.overdue_balance_cents).to eq 2_00 + end + end end describe "#reset_dunning_campaign!" do diff --git a/spec/models/invoice_spec.rb b/spec/models/invoice_spec.rb index 5d4d9b9c078..e07c3ca1864 100644 --- a/spec/models/invoice_spec.rb +++ b/spec/models/invoice_spec.rb @@ -830,7 +830,7 @@ let(:invoice) { create(:invoice, :self_billed, customer:, organization:) } it 'returns the correct name for EU country' do - expect(invoice.document_invoice_name).to eq("Self billing invoice") + expect(invoice.document_invoice_name).to eq("Self-billing invoice") end end diff --git a/spec/queries/credit_notes_query_spec.rb b/spec/queries/credit_notes_query_spec.rb index fabd5550f99..247ad83ea4e 100644 --- a/spec/queries/credit_notes_query_spec.rb +++ b/spec/queries/credit_notes_query_spec.rb @@ -7,6 +7,8 @@ described_class.call(organization:, search_term:, pagination:, filters:) end + let(:returned_ids) { result.credit_notes.pluck(:id) } + let(:organization) { customer.organization } let(:customer) { create(:customer) } @@ -314,6 +316,52 @@ end end + context "when filtering by self_billed" do + let(:credit_note_first) do + invoice = create(:invoice, :self_billed, organization:, customer:) + + create(:credit_note, customer:, invoice:) + end + + let(:credit_note_second) do + invoice = create(:invoice, organization:, customer:) + + create(:credit_note, customer:, invoice:) + end + + before do + credit_note_first + credit_note_second + end + + context "when self_billed is true" do + let(:filters) { {self_billed: true} } + + it "returns only credit notes from self billed invoices" do + expect(returned_ids).to include(credit_note_first.id) + expect(returned_ids).not_to include(credit_note_second.id) + end + end + + context "when self_billed is false" do + let(:filters) { {self_billed: false} } + + it "returns only credit notes from non self billed invoices" do + expect(returned_ids).not_to include(credit_note_first.id) + expect(returned_ids).to include(credit_note_second.id) + end + end + + context "when self_billed is nil" do + let(:filters) { {self_billed: nil} } + + it "returns all credit notes" do + expect(returned_ids).to include(credit_note_first.id) + expect(returned_ids).to include(credit_note_second.id) + end + end + end + context "when search term filter applied" do context "with term matching credit note by id" do let(:search_term) { matching_credit_note.id.first(10) } diff --git a/spec/queries/customers_query_spec.rb b/spec/queries/customers_query_spec.rb index 9062c4b08fb..fd6e7028e35 100644 --- a/spec/queries/customers_query_spec.rb +++ b/spec/queries/customers_query_spec.rb @@ -21,7 +21,17 @@ create(:customer, organization:, name: "abcde", firstname: "Jane", lastname: "Smith", legal_name: "other name", external_id: "22", email: "2@example.com") end let(:customer_third) do - create(:customer, organization:, name: "presuv", firstname: "Mary", lastname: "Johnson", legal_name: "Company name", external_id: "33", email: "3@example.com") + create( + :customer, + organization:, + account_type: "partner", + email: "3@example.com", + external_id: "33", + firstname: "Mary", + lastname: "Johnson", + legal_name: "Company name", + name: "presuv" + ) end before do @@ -38,6 +48,25 @@ expect(returned_ids).to include(customer_third.id) end + context "when filtering by partner account_type" do + let(:filters) { {account_type: %w[partner]} } + + it "returns partner accounts" do + expect(returned_ids.count).to eq(1) + expect(returned_ids).to eq [customer_third.id] + end + end + + context "when filtering by customer account_type" do + let(:filters) { {account_type: %w[customer]} } + + it "returns customer accounts" do + expect(returned_ids.count).to eq(2) + expect(returned_ids).to include customer_first.id + expect(returned_ids).to include customer_second.id + end + end + context "when customers have the same values for the ordering criteria" do let(:customer_second) do create( @@ -121,4 +150,13 @@ expect(returned_ids).to include(customer_third.id) end end + + context "when filters validation fails" do + let(:filters) { {account_type: %w[random]} } + + it "captures all validation errors" do + expect(result).not_to be_success + expect(result.error.messages[:filters][:account_type]).to eq({0 => ["must be one of: customer, partner"]}) + end + end end diff --git a/spec/queries/invoices_query_spec.rb b/spec/queries/invoices_query_spec.rb index 10504949ee1..49be589dcdd 100644 --- a/spec/queries/invoices_query_spec.rb +++ b/spec/queries/invoices_query_spec.rb @@ -727,4 +727,62 @@ expect(result.invoices.pluck(:id)).to contain_exactly invoice.id end end + + context "when filtering by self_billed" do + let(:invoice_first) do + create( + :invoice, + :self_billed, + organization:, + status: "finalized", + payment_status: "succeeded", + customer: customer_first, + number: "1111111111", + issuing_date: 1.week.ago, + total_amount_cents: 2000, + total_paid_amount_cents: 2000 + ) + end + let(:invoice_second) do + create( + :invoice, + organization:, + status: "finalized", + payment_status: "pending", + customer: customer_second, + number: "2222222222", + issuing_date: 2.weeks.ago, + total_amount_cents: 2000, + total_paid_amount_cents: 1500, + self_billed: false + ) + end + + context "when self_billed is true" do + let(:filters) { {self_billed: true} } + + it "returns only self billed invoices" do + expect(returned_ids).to include(invoice_first.id) + expect(returned_ids).not_to include(invoice_second.id) + end + + context "when self_billed is false" do + let(:filters) { {self_billed: false} } + + it "returns only non self billed invoices" do + expect(returned_ids).not_to include(invoice_first.id) + expect(returned_ids).to include(invoice_second.id) + end + end + + context "when self_billed is nil" do + let(:filters) { {self_billed: nil} } + + it "returns all invoices" do + expect(returned_ids).to include(invoice_first.id) + expect(returned_ids).to include(invoice_second.id) + end + end + end + end end diff --git a/spec/requests/api/v1/credit_notes_controller_spec.rb b/spec/requests/api/v1/credit_notes_controller_spec.rb index 2ca331c5141..6ae05cfba90 100644 --- a/spec/requests/api/v1/credit_notes_controller_spec.rb +++ b/spec/requests/api/v1/credit_notes_controller_spec.rb @@ -47,7 +47,8 @@ balance_amount_cents: credit_note.balance_amount_cents, created_at: credit_note.created_at.iso8601, updated_at: credit_note.updated_at.iso8601, - applied_taxes: [] + applied_taxes: [], + self_billed: invoice.self_billed ) expect(json[:credit_note][:items].count).to eq(2) @@ -394,6 +395,56 @@ end end + context "with self billed invoice filter" do + let(:params) { {self_billed: true} } + + let(:self_billed_credit_note) do + invoice = create(:invoice, :self_billed, customer:, organization:) + + create(:credit_note, invoice:, customer:) + end + + let(:non_self_billed_credit_note) do + create(:credit_note, customer:) + end + + before do + self_billed_credit_note + non_self_billed_credit_note + end + + it "returns self billed credit_notes" do + subject + + expect(response).to have_http_status(:success) + expect(json[:credit_notes].count).to eq(1) + expect(json[:credit_notes].first[:lago_id]).to eq(self_billed_credit_note.id) + end + + context "when self billed is false" do + let(:params) { {self_billed: false} } + + it "returns non self billed credit_notes" do + subject + + expect(response).to have_http_status(:success) + expect(json[:credit_notes].count).to eq(1) + expect(json[:credit_notes].first[:lago_id]).to eq(non_self_billed_credit_note.id) + end + end + + context "when self billed is nil" do + let(:params) { {self_billed: nil} } + + it "returns all credit_notes" do + subject + + expect(response).to have_http_status(:success) + expect(json[:credit_notes].count).to eq(2) + end + end + end + context 'with search term' do let(:params) { {search_term: matching_credit_note.invoice.number} } let!(:matching_credit_note) { create(:credit_note, customer:) } diff --git a/spec/requests/api/v1/customers_controller_spec.rb b/spec/requests/api/v1/customers_controller_spec.rb index 2233ed12d2a..b4384fa354f 100644 --- a/spec/requests/api/v1/customers_controller_spec.rb +++ b/spec/requests/api/v1/customers_controller_spec.rb @@ -334,8 +334,9 @@ end describe 'GET /api/v1/customers' do - subject { get_with_token(organization, '/api/v1/customers') } + subject { get_with_token(organization, '/api/v1/customers', params) } + let(:params) { {} } let(:organization) { create(:organization) } before { create_pair(:customer, organization:) } @@ -351,6 +352,24 @@ expect(json[:customers][0][:taxes]).not_to be_nil end end + + context "with account_type filters" do + let(:params) { {account_type: %w[partner]} } + + let(:partner) do + create(:customer, organization:, account_type: "partner") + end + + before { partner } + + it "returns partner customers" do + subject + + expect(response).to have_http_status(:success) + expect(json[:customers].count).to eq(1) + expect(json[:customers].first[:lago_id]).to eq(partner.id) + end + end end describe 'GET /api/v1/customers/:customer_id' do diff --git a/spec/requests/api/v1/fees_controller_spec.rb b/spec/requests/api/v1/fees_controller_spec.rb index 055bb6583c2..ac9fb9d6cc3 100644 --- a/spec/requests/api/v1/fees_controller_spec.rb +++ b/spec/requests/api/v1/fees_controller_spec.rb @@ -28,7 +28,8 @@ taxes_amount_cents: fee.taxes_amount_cents, units: fee.units.to_s, events_count: fee.events_count, - applied_taxes: [] + applied_taxes: [], + self_billed: nil ) expect(json[:fee][:item]).to include( type: fee.fee_type, @@ -55,7 +56,8 @@ taxes_amount_cents: fee.taxes_amount_cents, units: fee.units.to_s, events_count: fee.events_count, - applied_taxes: [] + applied_taxes: [], + self_billed: invoice.self_billed ) expect(json[:fee][:item]).to include( type: fee.fee_type, diff --git a/spec/requests/api/v1/invoices_controller_spec.rb b/spec/requests/api/v1/invoices_controller_spec.rb index 7e2afc667c4..b73d8400bdf 100644 --- a/spec/requests/api/v1/invoices_controller_spec.rb +++ b/spec/requests/api/v1/invoices_controller_spec.rb @@ -552,6 +552,54 @@ expect(json[:invoices].pluck(:lago_id)).to contain_exactly matching_invoice.id end end + + context "with self billed filters" do + let(:params) { {self_billed: true} } + + let(:self_billed_invoice) do + create(:invoice, :self_billed, customer:, organization:) + end + + let(:non_self_billed_invoice) do + create(:invoice, customer:, organization:) + end + + before do + self_billed_invoice + non_self_billed_invoice + end + + it "returns self billed invoices" do + subject + + expect(response).to have_http_status(:success) + expect(json[:invoices].count).to eq(1) + expect(json[:invoices].first[:lago_id]).to eq(self_billed_invoice.id) + end + + context "when self billed is false" do + let(:params) { {self_billed: false} } + + it "returns non self billed invoices" do + subject + + expect(response).to have_http_status(:success) + expect(json[:invoices].count).to eq(1) + expect(json[:invoices].first[:lago_id]).to eq(non_self_billed_invoice.id) + end + end + + context "when self billed is nil" do + let(:params) { {self_billed: nil} } + + it "returns all invoices" do + subject + + expect(response).to have_http_status(:success) + expect(json[:invoices].count).to eq(2) + end + end + end end describe 'PUT /api/v1/invoices/:id/refresh' do diff --git a/spec/serializers/v1/credit_note_serializer_spec.rb b/spec/serializers/v1/credit_note_serializer_spec.rb index 3c11566daad..97628a01e40 100644 --- a/spec/serializers/v1/credit_note_serializer_spec.rb +++ b/spec/serializers/v1/credit_note_serializer_spec.rb @@ -46,7 +46,8 @@ 'lago_id' => error_detail.id, 'error_code' => error_detail.error_code, 'details' => error_detail.details - }] + }], + "self_billed" => credit_note.invoice.self_billed ) expect(result['credit_note'].keys).to include('customer', 'items') diff --git a/spec/serializers/v1/fee_serializer_spec.rb b/spec/serializers/v1/fee_serializer_spec.rb index 7c4f89fa85f..1ddb6e9c7ea 100644 --- a/spec/serializers/v1/fee_serializer_spec.rb +++ b/spec/serializers/v1/fee_serializer_spec.rb @@ -51,7 +51,8 @@ 'succeeded_at' => fee.succeeded_at&.iso8601, 'failed_at' => fee.failed_at&.iso8601, 'refunded_at' => fee.refunded_at&.iso8601, - 'amount_details' => fee.amount_details + 'amount_details' => fee.amount_details, + 'self_billed' => fee.invoice.self_billed ) expect(result['fee']['item']).to include( 'type' => fee.fee_type, @@ -71,6 +72,17 @@ end end + context "when fee is not attached to an invoice" do + let(:fee) { create(:fee, invoice: nil) } + + it "serialize self_billed as false" do + expect(result["fee"]).to include( + "lago_invoice_id" => nil, + "self_billed" => false + ) + end + end + context 'when fee is charge' do let(:charge) { charge_filter.charge } let(:charge_filter) { create(:charge_filter) }