Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(revshare): missing bits to complete the feature #3094

Merged
merged 20 commits into from
Jan 23, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
b5fa3f3
extract named scopes for invoice self_billed and non_self_billed
ancorcruz Jan 22, 2025
1ee62c5
Customer overdue balance ignores self billed invoices
ancorcruz Jan 22, 2025
3b01f51
Add self_billed filter to invoices query
ancorcruz Jan 22, 2025
554ea46
Allow graphql requests to filter self billed invoices
ancorcruz Jan 22, 2025
24b11dd
Allow API v1 requests to filter invoices by self billed
ancorcruz Jan 22, 2025
bc114e3
Add account_type filter to customers query object
ancorcruz Jan 22, 2025
77c190f
Add account type filter to customers resolver
ancorcruz Jan 22, 2025
0670875
Remove aggregate_failures from new tests
ancorcruz Jan 22, 2025
d6b479a
Add filter by account_type to GET API/V1/customers endpoint
ancorcruz Jan 22, 2025
02a5eac
Update self_billed translations for PDF invoice
ancorcruz Jan 22, 2025
0e0f692
Filter credit notes by self billed invoice
ancorcruz Jan 22, 2025
21fbdba
Add filter for self billed credit notes to API/v1 endpoint
ancorcruz Jan 22, 2025
ea6e9d7
Add self_billed filter to credit notes resolver
ancorcruz Jan 22, 2025
31467b5
Add account_type to Customer type within the customer portal scope
ancorcruz Jan 22, 2025
7574546
Add self_billed to credit note serializer (from invoice)
ancorcruz Jan 22, 2025
178886f
Add self_billed to fee serializer
ancorcruz Jan 22, 2025
b15b962
Render one_off self billed invoices
ancorcruz Jan 22, 2025
438efb6
update graphql schemas
ancorcruz Jan 22, 2025
090a34a
fix linter
ancorcruz Jan 22, 2025
4eb2941
Fix document name test for self billed invoices
ancorcruz Jan 23, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions app/contracts/queries/customers_query_filters_contract.rb
Original file line number Diff line number Diff line change
@@ -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
13 changes: 7 additions & 6 deletions app/controllers/api/v1/credit_notes_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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),
Expand Down
3 changes: 2 additions & 1 deletion app/controllers/api/v1/customers_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,8 @@ def index
pagination: {
page: params[:page],
limit: params[:per_page] || PER_PAGE
}
},
filters: params.slice(:account_type)
)

if result.success?
Expand Down
2 changes: 1 addition & 1 deletion app/controllers/api/v1/fees_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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),
Expand Down
13 changes: 7 additions & 6 deletions app/controllers/api/v1/invoices_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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]))
}
)

Expand Down
12 changes: 8 additions & 4 deletions app/graphql/resolvers/credit_notes_resolver.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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

Expand All @@ -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],
Expand Down
14 changes: 9 additions & 5 deletions app/graphql/resolvers/customers_resolver.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
21 changes: 12 additions & 9 deletions app/graphql/resolvers/invoices_resolver.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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,
Expand All @@ -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:
}
)

Expand Down
1 change: 1 addition & 0 deletions app/graphql/types/customer_portal/customers/object.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion app/models/customer.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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!
Expand Down
9 changes: 6 additions & 3 deletions app/models/invoice.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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}
Expand Down Expand Up @@ -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,
Expand All @@ -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)
Expand All @@ -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

Expand Down
9 changes: 9 additions & 0 deletions app/queries/credit_notes_query.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
12 changes: 12 additions & 0 deletions app/queries/customers_query.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
5 changes: 5 additions & 0 deletions app/queries/invoices_query.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -130,6 +131,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
Expand Down
3 changes: 2 additions & 1 deletion app/serializers/v1/credit_note_serializer.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
3 changes: 2 additions & 1 deletion app/serializers/v1/fee_serializer.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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?
Expand Down
47 changes: 47 additions & 0 deletions app/views/templates/invoices/v4/_one_off.slim
Original file line number Diff line number Diff line change
@@ -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)
Loading
Loading